BazelCon 2022 akan hadir pada 16-17 November ke New York dan online.
Daftar sekarang.

Aspek

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

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

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

  • IDE yang mengintegrasikan Bazel dapat menggunakan aspek-aspek untuk mengumpulkan informasi tentang project.
  • Alat pembuatan kode dapat memanfaatkan aspek untuk dieksekusi pada input mereka dengan cara yang bersifat target-agnostik. Misalnya, file BUILD dapat menentukan hierarki definisi library protobuf, dan aturan khusus 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: file sumber yang merupakan bagian dari project, artefak apa (target) yang harus di-build dari file tersebut, dependensi antara file tersebut, dll. Bazel menggunakan informasi ini untuk melakukan build, yaitu menentukan kumpulan tindakan yang diperlukan untuk menghasilkan artefak (seperti menjalankan compiler atau linker) dan menjalankan tindakan tersebut. Bazel melakukannya dengan membuat grafik dependensi antartarget 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. Grafik dependensi file BUILD.

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

Aspeknya mirip dengan aturan karena memiliki fungsi implementasi yang menghasilkan tindakan dan menampilkan penyedia. Namun, kecanggihannya berasal dari cara grafik dependensi dibuat untuk mereka. Aspek memiliki implementasi dan daftar semua atribut yang disebarkan. Pertimbangkan aspek A yang menyebar bersama dengan atribut bernama "deps". Aspek ini dapat diterapkan ke target X, menghasilkan node aplikasi aspek A(X). Selama penerapannya, aspek A diterapkan secara rekursif pada semua target yang dirujuk X dalam atribut "deps" (semua atribut dalam daftar propagasi A).

Dengan demikian, satu tindakan penerapan aspek A ke target X akan menghasilkan "grafik bayangan" dari grafik dependensi asli dari target yang ditampilkan dalam gambar berikut:

Membuat Grafik dengan Aspek

Gambar 2. Buat grafik dengan aspek.

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

Contoh sederhana

Contoh ini menunjukkan cara mencetak file sumber secara berulang untuk aturan dan semua dependensinya yang memiliki atribut deps. Hal ini menunjukkan implementasi aspek, definisi aspek, dan cara memanggil aspek dari command line 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 tersebut 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.

Seperti halnya aturan, aspek memiliki fungsi implementasi yang dalam hal ini adalah _print_aspect_impl.

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

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

Penerapan aspek

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 fungsi penerapan aturan. Fungsi tersebut menampilkan penyedia, dapat menghasilkan tindakan, dan mengambil dua argumen:

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

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

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

Memanggil aspek menggunakan baris perintah

Cara termudah untuk menerapkan aspek adalah dari command line menggunakan argumen --aspects. Dengan asumsi bahwa 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 dalam target, yang berpotensi memfilternya berdasarkan ekstensi. Bagian ini menunjukkan cara menggunakan penyedia untuk menampilkan nilai, cara menggunakan parameter untuk meneruskan argumen ke dalam implementasi 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 disebarluaskan melalui atribut deps.

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

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

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

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

Penerapan aspek

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 akan menampilkan struct penyedia yang dapat diakses oleh dependensinya.

Dalam contoh ini, FileCountInfo ditentukan sebagai penyedia yang memiliki satu kolom count. Praktik terbaiknya adalah menentukan kolom penyedia secara eksplisit menggunakan atribut fields.

Kumpulan penyedia untuk aplikasi aspek A(X) adalah gabungan dari penyedia yang berasal dari penerapan aturan untuk target X dan dari penerapan aspek A. Penyedia yang diterapkan oleh penerapan aturan dibuat dan dibekukan sebelum aspek diterapkan dan tidak dapat diubah dari aspek. Akan terjadi error jika target dan aspek yang diterapkan padanya menyediakan penyedia dengan jenis yang sama, dengan pengecualian OutputGroupInfo (yang digabungkan, asalkan aturan dan aspek menentukan grup output yang berbeda) dan InstrumentedFilesInfo (yang diambil dari aspek). Artinya, implementasi aspek mungkin tidak akan pernah menampilkan DefaultInfo.

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

Untuk penyedia yang ditampilkan, nilai atribut yang selama aspek tersebut disebarkan (dari daftar attr_aspects) akan diganti dengan hasil penerapan aspek tersebut. 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 merupakan hasil penerapan aspek ke 'deps' dari target asli tempat aspek tersebut telah diterapkan.

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

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 = '*'),
    },
)

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

Definisi aturan menunjukkan cara menentukan parameter (extension) dan memberinya nilai default (*). Perhatikan bahwa memiliki nilai default yang bukan salah satu dari 'cc', 'h', atau '*' akan menjadi error karena pembatasan yang diterapkan 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',
)

Hal 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 di-build, aspek kita akan dievaluasi sendiri, dan semua target dapat diakses secara rekursif melalui deps.