Aspek

Laporkan masalah Lihat sumber Per Malam · 7,2 · 7,1 · 7,0 · 6,5 · 6,4

Halaman ini menjelaskan dasar-dasar dan manfaat penggunaan aspek serta memberikan contoh sederhana dan lanjutan.

Aspek memungkinkan penambahan grafik dependensi build dengan informasi tambahan tindakan dan tindakan. Beberapa skenario umum ketika aspek dapat berguna:

  • IDE yang mengintegrasikan Bazel dapat menggunakan aspek untuk mengumpulkan informasi tentang proyek.
  • Alat pembuatan kode dapat memanfaatkan aspek untuk dijalankan pada input mereka agnostik target lebih lanjut. Sebagai contoh, file BUILD dapat menentukan hierarki dari library protobuf definisi, dan aturan spesifik per bahasa dapat menggunakan aspek untuk melampirkan tindakan yang menghasilkan kode dukungan protobuf untuk bahasa tertentu.

Dasar-dasar aspek

File BUILD memberikan deskripsi kode sumber project: sumber apa adalah bagian dari project, artefak (target) apa yang harus dibuat file-file itu, apa dependensi di antara file-file itu, dll. Bazel menggunakan informasi ini untuk menjalankan build, yaitu, ia mencari tahu rangkaian tindakan yang diperlukan untuk menghasilkan artefak (seperti menjalankan compiler atau penaut) dan menjalankan tindakan-tindakan tersebut. Bazel mencapai hal ini dengan membangun ketergantungan grafik antar-target dan mengunjungi grafik ini untuk mengumpulkan tindakan tersebut.

Pertimbangkan file BUILD berikut:

java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)

File BUILD ini menentukan grafik dependensi yang ditampilkan dalam gambar berikut:

Buat grafik

Gambar 1. BUILD grafik dependensi file.

Bazel menganalisis grafik dependensi ini dengan memanggil fungsi implementasi dari aturan yang sesuai (dalam hal ini "java_library") untuk setiap pada contoh di atas. Fungsi implementasi aturan menghasilkan tindakan yang artefak build, seperti file .jar, dan meneruskan informasi, seperti lokasi dan nama artefak itu, pada dependensi kebalikan dari target-target itu dalam penyedia.

Aspek serupa dengan aturan karena memiliki fungsi implementasi yang akan menghasilkan tindakan dan menghasilkan penyedia. Namun, kekuatan mereka berasal dari bagaimana grafik dependensi dibuat untuk mereka. Aspek memiliki implementasi dan daftar semua atribut yang disebarkan olehnya. Pertimbangkan aspek A yang menyebar di sepanjang atribut bernama "deps". Aspek ini dapat diterapkan pada target X, yang menghasilkan node aplikasi aspek A(X). Selama penerapannya, aspek A diterapkan secara rekursif ke semua target yang dirujuk X dalam "dependensi"-nya (semua atribut dalam daftar penyebaran A).

Jadi, satu tindakan penerapan aspek A ke target X akan menghasilkan "grafik bayangan" pengguna grafik dependensi target asli yang ditampilkan di gambar berikut:

Membuat Grafik dengan Aspek

Gambar 2. Buat grafik dengan aspek.

Satu-satunya tepi yang dibayangi adalah tepi di sepanjang atribut dalam kumpulan propagasi, sehingga tepi runtime_deps tidak dibayangi dalam contoh. Fungsi implementasi aspek kemudian dipanggil pada semua node dalam grafik bayangan yang serupa dengan bagaimana penerapan aturan dipanggil pada node dari grafik asli.

Contoh sederhana

Contoh ini menunjukkan cara mencetak file sumber secara rekursif untuk dan semua dependensinya yang memiliki atribut deps. Menunjukkan implementasi aspek, definisi aspek, dan cara memanggil aspek dari baris perintah Bazel.

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

Mari kita bagi contoh menjadi bagian-bagiannya dan periksa satu per satu.

Definisi aspek

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

Definisi aspek mirip dengan definisi aturan, dan ditentukan menggunakan fungsi aspect.

Sama seperti aturan, aspek memiliki fungsi implementasi yang dalam hal ini _print_aspect_impl.

attr_aspects adalah daftar atribut aturan yang digunakan untuk menyebarkan aspek. Dalam hal ini, aspek akan diterapkan di sepanjang atribut deps elemen aturan tempatnya diterapkan.

Argumen umum lainnya untuk attr_aspects adalah ['*'] yang akan menyebarkan aspek untuk semua atribut dari sebuah aturan.

Implementasi Aspect

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

Fungsi penerapan aspek mirip dengan implementasi aturan fungsi-fungsi lainnya. Properti tersebut menampilkan penyedia, dapat menghasilkan actions, dan mengambil dua argumen:

  • target: target aspek yang sedang diterapkan.
  • ctx: Objek ctx yang dapat digunakan untuk mengakses atribut serta menghasilkan output dan tindakan.

Fungsi penerapan dapat mengakses atribut aturan target melalui ctx.rule.attr Alat ini dapat memeriksa penyedia yang disediakan oleh target tempatnya diterapkan (melalui argumen target).

Aspek diperlukan untuk menampilkan daftar penyedia. Dalam contoh ini, aspek tidak menyediakan apa pun, sehingga mengembalikan daftar kosong.

Memanggil aspek menggunakan command line

Cara paling sederhana untuk menerapkan aspek adalah dari baris perintah menggunakan --aspects argumen. Dengan asumsi aspek di atas ditentukan dalam file bernama print.bzl ini:

bazel build //MyExample:example --aspects print.bzl%print_aspect

akan menerapkan print_aspect ke example target dan semua aturan target yang dapat diakses secara berulang melalui atribut deps.

Flag --aspects mengambil satu argumen, yang merupakan spesifikasi aspek dalam format <extension file label>%<aspect top-level name>.

Contoh lanjutan

Contoh berikut menunjukkan penggunaan aspek dari aturan target yang menghitung file di target, yang berpotensi memfilternya berdasarkan ekstensi. Bagian ini menunjukkan cara menggunakan penyedia untuk menampilkan nilai, cara menggunakan parameter untuk meneruskan menjadi penerapan aspek, dan cara memanggil aspek dari aturan.

File file_count.bzl:

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

File BUILD.bazel:

load('//:file_count.bzl', 'file_count_rule')

cc_library(
    name = 'lib',
    srcs = [
        'lib.h',
        'lib.cc',
    ],
)

cc_binary(
    name = 'app',
    srcs = [
        'app.h',
        'app.cc',
        'main.cc',
    ],
    deps = ['lib'],
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

Definisi aspek

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

Contoh ini menunjukkan cara aspek disebarkan melalui atribut deps.

attrs menentukan kumpulan atribut untuk aspek. Atribut aspek publik adalah jenis string dan disebut parameter. Parameter harus memiliki values yang ditentukan pada iklan tersebut. Contoh ini memiliki parameter yang disebut extension yang diizinkan memiliki '*', 'h', atau 'cc' sebagai nilai.

Nilai parameter untuk aspek diambil dari atribut string dengan atribut nama aturan yang meminta aspek (lihat definisi file_count_rule). Aspek dengan parameter tidak dapat digunakan melalui baris perintah karena tidak ada sintaksis untuk menentukan parameter.

Aspek juga diizinkan memiliki atribut pribadi jenis label atau label_list. Atribut label pribadi dapat digunakan untuk menentukan dependensi pada alat atau pustaka yang dibutuhkan untuk tindakan yang dihasilkan oleh aspek. Tidak ada atribut pribadi yang ditentukan dalam contoh ini, namun cuplikan kode berikut menunjukkan cara meneruskan alat ke suatu aspek:

...
    attrs = {
        '_protoc' : attr.label(
            default = Label('//tools:protoc'),
            executable = True,
            cfg = "exec"
        )
    }
...

Implementasi Aspect

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

Sama seperti fungsi penerapan aturan, fungsi penerapan aspek mengembalikan struct penyedia yang dapat diakses oleh dependensinya.

Dalam contoh ini, FileCountInfo didefinisikan sebagai penyedia yang memiliki satu bidang count. Praktik terbaiknya adalah secara eksplisit menentukan {i>field<i} menggunakan atribut fields.

Kumpulan penyedia untuk aplikasi aspek A(X) adalah gabungan penyedia yang berasal dari penerapan aturan untuk target X dan dari implementasi aspek A. Penyedia yang diterapkan oleh penerapan aturan dibuat dan dibekukan sebelum aspek diterapkan dan tidak dapat dimodifikasi dari aspek. Akan terjadi error jika target dan aspek yang diterapkan masing-masing penyedia dengan jenis yang sama, dengan pengecualian OutputGroupInfo (yang digabungkan, asalkan dan aspek menentukan kelompok output yang berbeda) dan InstrumentedFilesInfo (yang diambil dari aspek). Ini berarti bahwa implementasi aspek dapat jangan pernah menampilkan DefaultInfo.

Parameter dan atribut privat diteruskan dalam atribut atribut ctx. Contoh ini mereferensikan parameter extension dan menentukan file apa yang dihitung.

Untuk penyedia yang kembali, nilai-nilai atribut yang aspek yang disebarkan (dari daftar attr_aspects) diganti dengan hasil penerapan aspek pada mereka. Misalnya, jika target X memiliki Y dan Z dalam dependensinya, ctx.rule.attr.deps untuk A(X) akan menjadi [A(Y), A(Z)]. Dalam contoh ini, ctx.rule.attr.deps adalah objek Target yang hasil penerapan aspek ke kolom 'deps' dari target asli yang aspek telah diterapkan.

Pada contoh, aspek mengakses penyedia FileCountInfo dari elemen dependensi target untuk mengakumulasi jumlah total transitif file.

Memanggil aspek dari aturan

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

Penerapan aturan menunjukkan cara mengakses FileCountInfo melalui ctx.attr.deps.

Definisi aturan menunjukkan cara menentukan parameter (extension) dan beri nilai default (*). Perhatikan bahwa memiliki nilai {i>default<i} yang bukan salah satu dari 'cc', 'h', atau '*' adalah sebuah kesalahan karena batasan yang ditempatkan pada parameter dalam definisi aspek.

Memanggil aspek melalui aturan target

load('//:file_count.bzl', 'file_count_rule')

cc_binary(
    name = 'app',
...
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

Contoh ini menunjukkan cara meneruskan parameter extension ke dalam aspek melalui aturan. Karena parameter extension memiliki nilai default dalam penerapan aturan, extension akan dianggap sebagai parameter opsional.

Saat target file_count dibuat, aspek kita akan dievaluasi untuk itu sendiri, dan semua target yang dapat diakses secara rekursif melalui deps.