Aspek

Laporkan masalah Lihat sumber Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

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

  • IDE yang mengintegrasikan Bazel dapat menggunakan aspek untuk mengumpulkan informasi tentang project.
  • Alat pembuatan kode dapat memanfaatkan aspek untuk mengeksekusi inputnya dengan cara agnostik target. Sebagai contoh, 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 apa yang merupakan bagian dari project, artefak (target) apa yang harus dibangun dari file tersebut, apa saja dependensi antar-file tersebut, dll. Bazel menggunakan informasi ini untuk melakukan build, yaitu, Bazel menentukan serangkaian tindakan yang diperlukan untuk menghasilkan artefak (seperti menjalankan compiler atau linker) dan menjalankan tindakan tersebut. Bazel melakukannya dengan membuat grafik dependensi 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 ditunjukkan dalam gambar berikut:

Buat grafik

Gambar 1. Grafik dependensi file BUILD.

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

Aspek serupa dengan aturan karena memiliki fungsi penerapan yang menghasilkan tindakan dan menampilkan penyedia. Namun, kekuatannya berasal dari cara grafik dependensi dibuat untuknya. Aspek memiliki penerapan dan daftar semua atribut yang dipropagasinya. Pertimbangkan aspek A yang dipropagasi di sepanjang atribut bernama "deps". Aspek ini dapat diterapkan ke target X, sehingga menghasilkan node penerapan aspek A(X). Selama penerapannya, aspek A diterapkan secara rekursif ke semua target yang dirujuk X dalam atribut "deps" (semua atribut dalam daftar propagasi A).

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

Membangun Grafik dengan Aspek

Gambar 2. Membangun grafik dengan aspek.

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

Contoh sederhana

Contoh ini menunjukkan cara mencetak file sumber secara rekursif untuk aturan dan semua dependensinya yang memiliki atribut deps. Bagian 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 menjadi beberapa bagian dan pelajari setiap bagian 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 penerapan yang dalam hal ini adalah _print_aspect_impl.

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

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

Implementasi 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 ini menampilkan penyedia, dapat membuat tindakan, dan menggunakan dua argumen:

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

Fungsi penerapan dapat mengakses atribut aturan target melalui ctx.rule.attr. Objek ini dapat memeriksa penyedia yang disediakan oleh target tempat objek 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 command line

Cara paling sederhana untuk menerapkan aspek adalah dari command line menggunakan argumen --aspects. 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 rekursif 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 menurut ekstensi. Contoh 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 disebarkan melalui atribut deps.

attrs menentukan serangkaian atribut untuk aspek. Atribut aspek publik menentukan parameter dan hanya dapat berupa jenis bool, int, atau string. Untuk aspek yang disebarkan aturan, parameter int dan string harus memiliki values yang ditentukan. Contoh ini memiliki parameter bernama extension yang dapat memiliki '*', 'h', atau 'cc' sebagai nilai.

Untuk aspek yang disebarkan aturan, nilai parameter diambil dari aturan yang meminta aspek, menggunakan atribut aturan yang memiliki nama dan jenis yang sama. (lihat definisi file_count_rule).

Untuk aspek command line, nilai parameter dapat diteruskan menggunakan flag --aspects_parameters. Batasan values parameter int dan string dapat dihilangkan.

Aspek juga diizinkan untuk memiliki atribut pribadi berjenis 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 Anda dapat meneruskan alat ke aspek:

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

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

Dalam contoh ini, FileCountInfo ditentukan sebagai penyedia yang memiliki satu kolom count. Sebaiknya tentukan kolom penyedia secara eksplisit menggunakan atribut fields.

Kumpulan penyedia untuk aplikasi aspek A(X) adalah gabungan 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. Terjadi error jika target dan aspek yang diterapkan padanya masing-masing memberikan penyedia dengan jenis yang sama, dengan pengecualian OutputGroupInfo (yang digabungkan, selama aturan dan aspek menentukan grup output yang berbeda) dan InstrumentedFilesInfo (yang diambil dari aspek). Artinya, penerapan aspek mungkin tidak 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 kembali, nilai atribut yang digunakan untuk propagasi aspek (dari daftar attr_aspects) diganti dengan hasil penerapan aspek padanya. Misalnya, jika target X memiliki Y dan Z dalam dependensinya, ctx.rule.attr.deps untuk A(X) adalah [A(Y), A(Z)]. Dalam contoh ini, ctx.rule.attr.deps adalah objek Target yang merupakan hasil penerapan aspek ke 'deps' target asli tempat aspek telah diterapkan.

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

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 memberikan nilai default (*). Perhatikan bahwa nilai default yang bukan 'cc', 'h', atau '*' akan menjadi error karena batasan 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',
)

Contoh ini menunjukkan cara meneruskan parameter extension ke 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 dengan sendirinya, dan semua target dapat diakses secara rekursif melalui deps.

Referensi