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

Aturan

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

Aturan menentukan serangkaian tindakan yang dilakukan Bazel pada input untuk menghasilkan sekumpulan output, yang dirujuk dalam penyedia yang ditampilkan oleh fungsi implementasi aturan. Misalnya, aturan biner C++ mungkin:

  1. Ambil sekumpulan file sumber .cpp (input).
  2. Jalankan g++ di file sumber (tindakan).
  3. Tampilkan penyedia DefaultInfo dengan output yang dapat dieksekusi dan file lainnya agar tersedia pada runtime.
  4. Menampilkan penyedia CcInfo dengan informasi khusus C++ yang dikumpulkan dari target dan dependensinya.

Dari perspektif Bazel, g++ dan library C++ standar juga merupakan input dari aturan ini. Sebagai penulis aturan, Anda tidak hanya harus mempertimbangkan input yang diberikan pengguna pada aturan, tetapi juga semua alat dan library yang diperlukan untuk menjalankan tindakan.

Sebelum membuat atau mengubah aturan apa pun, pastikan Anda memahami fase build Bazel. Penting untuk memahami tiga fase build (pemuatan, analisis, dan eksekusi). Hal ini juga berguna untuk mempelajari makro untuk memahami perbedaan antara aturan dan makro. Untuk memulai, tinjau Tutorial Aturan terlebih dahulu. Lalu, gunakan halaman ini sebagai referensi.

Beberapa aturan telah disertakan dalam Bazel. Aturan native ini, seperti cc_library dan java_binary, memberikan beberapa dukungan inti untuk bahasa tertentu. Dengan menentukan aturan Anda sendiri, Anda dapat menambahkan dukungan yang serupa untuk bahasa dan alat yang tidak didukung oleh Bazel secara native.

Bazel menyediakan model ekstensibilitas untuk menulis aturan menggunakan bahasa Starlark. Aturan ini ditulis dalam file .bzl, yang dapat dimuat langsung dari file BUILD.

Saat menentukan aturan Anda sendiri, Anda harus memutuskan atribut apa yang didukungnya dan cara atribut tersebut menghasilkan output-nya.

Fungsi implementation aturan menentukan perilaku yang tepat selama fase analisis. Fungsi ini tidak menjalankan perintah eksternal. Sebaliknya, metode ini mendaftarkan tindakan yang akan digunakan nanti selama fase eksekusi untuk membuat output aturan, jika diperlukan.

Pembuatan aturan

Dalam file .bzl, gunakan fungsi aturan untuk menentukan aturan baru, dan simpan hasilnya dalam variabel global. Panggilan ke rule menentukan atribut dan fungsi implementasi:

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "deps": attr.label_list(),
        ...
    },
)

Ini mendefinisikan jenis aturan bernama example_library.

Panggilan ke rule juga harus menentukan apakah aturan akan membuat output yang dapat dieksekusi (dengan executable=True), atau secara khusus pengujian yang dapat dieksekusi (dengan test=True). Jika aturan yang terakhir, aturan adalah aturan pengujian, dan nama aturan harus diakhiri dengan _test.

Pembuatan instance target

Aturan dapat dimuat dan dipanggil dalam file BUILD:

load('//some/pkg:rules.bzl', 'example_library')

example_library(
    name = "example_target",
    deps = [":another_target"],
    ...
)

Setiap panggilan ke aturan build tidak menampilkan nilai, tetapi memiliki efek samping penentuan target. Tindakan ini disebut membuat instance aturan. Ini menentukan nama untuk target dan nilai baru untuk atribut target.

Aturan juga dapat dipanggil dari fungsi Starlark dan dimuat dalam file .bzl. Fungsi Starlark yang memanggil aturan disebut makro Starlark. Makro Starlark pada akhirnya harus dipanggil dari file BUILD, dan hanya dapat dipanggil selama fase pemuatan, saat file BUILD dievaluasi untuk membuat instance target.

Atribut

Atribut adalah argumen aturan. Atribut dapat memberikan nilai tertentu ke implementasi target, atau dapat merujuk ke target lainnya, sehingga membuat grafik dependensi.

Atribut khusus aturan, seperti srcs atau deps, ditentukan dengan meneruskan peta dari nama atribut ke skema (yang dibuat menggunakan modul attr) ke parameter attrs rule. Atribut umum, seperti name dan visibility, ditambahkan secara implisit ke semua aturan. Atribut tambahan secara implisit ditambahkan ke aturan eksekusi dan pengujian secara khusus. Atribut yang ditambahkan secara implisit ke aturan tidak dapat disertakan dalam kamus yang diteruskan ke attrs.

Atribut dependensi

Aturan yang memproses kode sumber biasanya menentukan atribut berikut untuk menangani berbagai jenis dependensi:

  • srcs menentukan file sumber yang diproses oleh tindakan target. Sering kali, skema atribut menentukan ekstensi file mana yang diharapkan untuk jenis file sumber yang diproses oleh aturan. Aturan untuk bahasa dengan file header umumnya menentukan atribut hdrs terpisah untuk header yang diproses oleh target dan konsumennya.
  • deps menentukan dependensi kode untuk target. Skema atribut harus menentukan penyedia yang harus disediakan oleh dependensi tersebut. (Misalnya, cc_library menyediakan CcInfo.)
  • data menentukan file yang akan tersedia saat runtime ke file yang dapat dieksekusi yang bergantung pada target. Tindakan tersebut akan memungkinkan file arbitrer ditentukan.
example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = [".example"]),
        "hdrs": attr.label_list(allow_files = [".header"]),
        "deps": attr.label_list(providers = [ExampleInfo]),
        "data": attr.label_list(allow_files = True),
        ...
    },
)

Berikut adalah contoh atribut dependensi. Setiap atribut yang menentukan label input (yang ditentukan dengan attr.label_list, attr.label, atau attr.label_keyed_string_dict) menentukan dependensi dari jenis tertentu antara target dan target yang labelnya (atau objek Label yang sesuai) dicantumkan dalam atribut tersebut saat target ditentukan. Repositori, dan mungkin jalur, untuk label ini diselesaikan secara relatif terhadap target yang ditentukan.

example_library(
    name = "my_target",
    deps = [":other_target"],
)

example_library(
    name = "other_target",
    ...
)

Dalam contoh ini, other_target adalah dependensi dari my_target, sehingga other_target dianalisis terlebih dahulu. Error ini terjadi jika ada siklus dalam grafik dependensi target.

Atribut pribadi dan dependensi implisit

Atribut dependensi dengan nilai default membuat dependensi implisit. Ini implisit karena merupakan bagian dari grafik target yang tidak ditentukan pengguna dalam file BUILD. Dependensi implisit berguna untuk melakukan hard-coding hubungan antara aturan dan alat (dependensi waktu build, seperti compiler), karena sering kali pengguna tidak tertarik untuk menentukan alat apa yang digunakan aturan. Di dalam fungsi penerapan aturan, fungsi ini diperlakukan sama seperti dependensi lainnya.

Jika ingin memberikan dependensi implisit tanpa mengizinkan pengguna mengganti nilai tersebut, Anda dapat membuat atribut menjadi pribadi dengan memberinya nama yang dimulai dengan garis bawah (_). Atribut pribadi harus memiliki nilai default. Secara umum, sebaiknya gunakan atribut pribadi untuk dependensi implisit.

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        ...
        "_compiler": attr.label(
            default = Label("//tools:example_compiler"),
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

Dalam contoh ini, setiap target jenis example_library memiliki dependensi implisit pada compiler //tools:example_compiler. Hal ini memungkinkan fungsi penerapan example_library untuk menghasilkan tindakan yang memanggil compiler, meskipun pengguna tidak meneruskan labelnya sebagai input. Karena _compiler adalah atribut pribadi, atribut tersebut mengikuti ctx.attr._compiler yang akan selalu mengarah ke //tools:example_compiler di semua target jenis aturan ini. Atau, Anda dapat memberi nama atribut compiler tanpa garis bawah dan mempertahankan nilai default. Hal ini memungkinkan pengguna untuk mengganti compiler yang berbeda jika diperlukan, tetapi tidak memerlukan kesadaran akan label compiler.

Dependensi implisit umumnya digunakan untuk alat yang berada di repositori yang sama dengan penerapan aturan. Jika alat berasal dari platform eksekusi atau repositori yang berbeda, aturan harus mendapatkan alat tersebut dari toolchain.

Atribut output

Atribut output, seperti attr.output dan attr.output_list, deklarasikan file output yang dihasilkan target. Hal ini berbeda dengan atribut dependensi dalam dua hal:

  • Fungsi ini menentukan target file output, bukan merujuk ke target yang ditentukan di tempat lain.
  • Target file output bergantung pada target aturan yang dibuat instance, bukan sebaliknya.

Biasanya, atribut output hanya digunakan saat aturan perlu membuat output dengan nama buatan pengguna yang tidak dapat didasarkan pada nama target. Jika aturan memiliki satu atribut output, biasanya aturan tersebut bernama out atau outs.

Atribut output adalah cara yang lebih disukai untuk membuat output yang dideklarasikan sebelumnya, yang dapat secara khusus bergantung pada atau diminta pada command line.

Fungsi implementasi

Setiap aturan memerlukan fungsi implementation. Fungsi ini dijalankan secara ketat dalam fase analisis dan mengubah grafik target yang dihasilkan dalam fase pemuatan menjadi grafik tindakan yang akan dilakukan selama fase eksekusi. Dengan demikian, fungsi implementasi tidak dapat benar-benar membaca atau menulis file.

Fungsi penerapan aturan biasanya bersifat pribadi (diberi nama dengan garis bawah). Secara konvensional, fungsi tersebut diberi nama yang sama dengan aturannya, tetapi diakhiri dengan _impl.

Fungsi implementasi mengambil tepat satu parameter: konteks aturan, yang secara konvensional dinamai ctx. Fungsi tersebut menampilkan daftar penyedia.

Targets

Dependensi direpresentasikan pada waktu analisis sebagai objek Target. Objek ini berisi penyedia yang dihasilkan saat fungsi penerapan target dieksekusi.

ctx.attr memiliki kolom yang sesuai dengan nama setiap atribut dependensi, berisi objek Target yang mewakili setiap dependensi langsung melalui atribut tersebut. Untuk atribut label_list, ini adalah daftar Targets. Untuk atribut label, ini adalah satu Target atau None.

Daftar objek penyedia ditampilkan oleh fungsi implementasi target:

return [ExampleInfo(headers = depset(...))]

Keduanya dapat diakses menggunakan notasi indeks ([]), dengan jenis penyedia sebagai kunci. Ini dapat berupa penyedia kustom yang ditentukan di Starlark atau penyedia untuk aturan native yang tersedia sebagai variabel global Starlark.

Misalnya, jika aturan mengambil file header melalui atribut hdrs dan memberinya tindakan kompilasi target dan konsumennya, aturan tersebut dapat mengumpulkannya seperti berikut:

def _example_library_impl(ctx):
    ...
    transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]

Untuk gaya lama yang menampilkan struct dari fungsi penerapan target, bukan daftar objek penyedia:

return struct(example_info = struct(headers = depset(...)))

Penyedia dapat diambil dari kolom terkait pada objek Target:

transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]

Gaya ini sangat tidak disarankan dan aturan harus dialihkan darinya.

Files

File diwakili oleh objek File. Karena Bazel tidak menjalankan I/O file selama fase analisis, objek ini tidak dapat digunakan untuk membaca atau menulis konten file secara langsung. Sebaliknya, fungsi ini akan diteruskan ke fungsi yang mengeluarkan tindakan (lihat ctx.actions) untuk membuat grafik tindakan.

File dapat berupa file sumber atau file yang dihasilkan. Setiap file yang dihasilkan harus berupa output dari satu tindakan. File sumber tidak boleh berupa output tindakan apa pun.

Untuk setiap atribut dependensi, kolom ctx.files yang sesuai akan berisi daftar output default dari semua dependensi melalui atribut tersebut:

def _example_library_impl(ctx):
    ...
    headers = depset(ctx.files.hdrs, transitive=transitive_headers)
    srcs = ctx.files.srcs
    ...

ctx.file berisi satu File atau None untuk atribut dependensi yang spesifikasinya ditetapkan allow_single_file=True. ctx.executable berperilaku sama seperti ctx.file, tetapi hanya berisi kolom untuk atribut dependensi yang spesifikasinya ditetapkan executable=True.

Mendeklarasikan output

Selama fase analisis, fungsi implementasi aturan dapat membuat output. Karena semua label harus diketahui selama fase pemuatan, output tambahan ini tidak memiliki label. Objek File untuk output dapat dibuat menggunakan ctx.actions.declare_file dan ctx.actions.declare_directory. Sering kali, nama output didasarkan pada nama target, ctx.label.name:

def _example_library_impl(ctx):
  ...
  output_file = ctx.actions.declare_file(ctx.label.name + ".output")
  ...

Untuk output yang dideklarasikan sebelumnya, seperti yang dibuat untuk atribut output, objek File dapat diambil dari kolom yang sesuai ctx.outputs.

Tindakan

Tindakan menjelaskan cara menghasilkan sekumpulan output dari sekumpulan input, misalnya "jalankan gcc di hello.c dan dapatkan hello.o". Saat tindakan dibuat, Bazel tidak akan langsung menjalankan perintah. Hal ini mendaftarkannya dalam grafik dependensi, karena suatu tindakan dapat bergantung pada output tindakan lain. Misalnya, di C, penaut harus dipanggil setelah compiler.

Fungsi tujuan umum yang membuat tindakan ditentukan di ctx.actions:

ctx.actions.args dapat digunakan untuk mengakumulasi argumen untuk tindakan secara efisien. Hal ini untuk menghindari depset yang meratakan hingga waktu eksekusi:

def _example_library_impl(ctx):
    ...

    transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
    headers = depset(ctx.files.hdrs, transitive=transitive_headers)
    srcs = ctx.files.srcs
    inputs = depset(srcs, transitive=[headers])
    output_file = ctx.actions.declare_file(ctx.label.name + ".output")

    args = ctx.actions.args()
    args.add_joined("-h", headers, join_with=",")
    args.add_joined("-s", srcs, join_with=",")
    args.add("-o", output_file)

    ctx.actions.run(
        mnemonic = "ExampleCompile",
        executable = ctx.executable._compiler,
        arguments = [args],
        inputs = inputs,
        outputs = [output_file],
    )
    ...

Tindakan mengambil daftar atau rangkaian file input dan membuat daftar file output (yang tidak kosong). Kumpulan file input dan output harus diketahui selama fase analisis. Hal ini mungkin bergantung pada nilai atribut, termasuk penyedia dari dependensi, tetapi tidak dapat bergantung pada hasil eksekusi. Misalnya, jika tindakan Anda menjalankan perintah unzip, Anda harus menentukan file mana yang ingin di-inflate (sebelum menjalankan unzip). Tindakan yang membuat sejumlah variabel file secara internal dapat menggabungkan beberapa file dalam satu file (seperti zip, tar, atau format arsip lainnya).

Tindakan harus mencantumkan semua inputnya. Input listingan yang tidak digunakan akan diizinkan, tetapi tidak efisien.

Tindakan harus membuat semua output-nya. Mereka dapat menulis file lain, tetapi item apa pun yang tidak ada dalam output tidak akan tersedia bagi konsumen. Semua output yang dideklarasikan harus ditulis oleh beberapa tindakan.

Tindakan sebanding dengan fungsi murni: Tindakan hanya boleh bergantung pada input yang disediakan, dan tidak mengakses informasi komputer, nama pengguna, jam, jaringan, atau perangkat I/O (kecuali untuk membaca input dan menulis output). Hal ini penting karena output akan di-cache dan digunakan kembali.

Dependensi diselesaikan oleh Bazel, yang akan menentukan tindakan mana yang dijalankan. Error ini terjadi jika ada siklus dalam grafik dependensi. Membuat suatu tindakan tidak menjamin bahwa tindakan akan dijalankan, yang bergantung pada apakah outputnya diperlukan untuk build.

Penyedia

Penyedia adalah bagian informasi yang diekspos oleh aturan ke aturan lain yang bergantung pada aturan tersebut. Data ini dapat mencakup file output, library, parameter yang akan diteruskan pada command line alat, atau hal lainnya yang harus diketahui konsumen.

Karena fungsi penerapan aturan hanya dapat membaca penyedia dari dependensi langsung target yang dibuat instance-nya, aturan harus meneruskan informasi apa pun dari dependensi target yang perlu diketahui oleh konsumen target, umumnya dengan mengakumulasikannya ke dalam depset.

Penyedia target ditentukan oleh daftar objek Provider yang ditampilkan oleh fungsi implementasi.

Fungsi implementasi lama juga dapat ditulis dalam gaya lama, di mana fungsi implementasi menampilkan struct, bukan daftar objek penyedia. Gaya ini sangat tidak disarankan dan aturan harus dialihkan darinya.

Output default

Output default target adalah output yang diminta secara default saat target diminta untuk dibuat pada command line. Misalnya, target java_library //pkg:foo memiliki foo.jar sebagai output default, sehingga akan dibuat oleh perintah bazel build //pkg:foo.

Output default ditentukan oleh parameter files dari DefaultInfo:

def _example_library_impl(ctx):
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        ...
    ]

Jika DefaultInfo tidak ditampilkan oleh implementasi aturan atau parameter files tidak ditentukan, DefaultInfo.files akan ditetapkan secara default ke semua output yang dideklarasikan sebelumnya (umumnya, output yang dibuat oleh atribut output).

Aturan yang melakukan tindakan harus memberikan output default, meskipun output tersebut tidak diharapkan untuk digunakan secara langsung. Tindakan yang tidak ada dalam grafik output yang diminta akan dipangkas. Jika output hanya digunakan oleh konsumen target, tindakan tersebut tidak akan dilakukan saat target di-build secara terpisah. Hal ini menyebabkan proses debug lebih sulit karena mem-build ulang hanya target yang gagal tidak akan mereproduksi kegagalan.

Runfile

Runfile adalah kumpulan file yang digunakan oleh target pada waktu proses (bukan waktu build). Selama fase eksekusi, Bazel membuat hierarki direktori yang berisi symlink yang mengarah ke runfile. Ini memindahkan lingkungan untuk biner sehingga dapat mengakses runfile selama waktu proses.

Runfile dapat ditambahkan secara manual selama pembuatan aturan. Objek runfiles dapat dibuat dengan metode runfiles pada konteks aturan, ctx.runfiles dan diteruskan ke parameter runfiles pada DefaultInfo. Output yang dapat dieksekusi dari aturan yang dapat dieksekusi secara implisit ditambahkan ke runfile.

Beberapa aturan menentukan atribut, biasanya bernama data, yang outputnya ditambahkan ke target' runfile. Runfile juga harus digabungkan dari data, serta dari atribut apa pun yang mungkin memberikan kode untuk eksekusi akhir, biasanya srcs (yang mungkin berisi target filegroup dengan data terkait) dan deps.

def _example_library_impl(ctx):
    ...
    runfiles = ctx.runfiles(files = ctx.files.data)
    transitive_runfiles = []
    for runfiles_attr in (
        ctx.attr.srcs,
        ctx.attr.hdrs,
        ctx.attr.deps,
        ctx.attr.data,
    ):
        for target in runfiles_attr:
            transitive_runfiles.append(target[DefaultInfo].default_runfiles)
    runfiles = runfiles.merge_all(transitive_runfiles)
    return [
        DefaultInfo(..., runfiles = runfiles),
        ...
    ]

Penyedia kustom

Penyedia dapat ditentukan menggunakan fungsi provider untuk menyampaikan informasi khusus aturan:

ExampleInfo = provider(
    "Info needed to compile/link Example code.",
    fields={
        "headers": "depset of header Files from transitive dependencies.",
        "files_to_link": "depset of Files from compilation.",
    })

Fungsi penerapan aturan kemudian dapat membuat dan menampilkan instance penyedia:

def _example_library_impl(ctx):
  ...
  return [
      ...
      ExampleInfo(
          headers = headers,
          files_to_link = depset(
              [output_file],
              transitive = [
                  dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
              ],
          ),
      )
  ]
Inisialisasi kustom penyedia

Anda dapat melindungi pembuatan instance penyedia dengan logika pra-pemrosesan dan validasi kustom. Hal ini dapat digunakan untuk memastikan bahwa semua instance penyedia mematuhi invarian tertentu, atau memberikan API yang lebih bersih kepada pengguna untuk mendapatkan instance.

Hal ini dilakukan dengan meneruskan callback init ke fungsi provider. Jika callback ini diberikan, jenis nilai yang ditampilkan provider() akan berubah menjadi tuple dua nilai: simbol penyedia yang merupakan nilai return biasa bila init tidak digunakan, dan "konstruktor mentah"

Dalam hal ini, saat simbol penyedia dipanggil, bukannya langsung menampilkan instance baru, argumen akan diteruskan ke callback init. Nilai yang ditampilkan callback harus berupa nama kolom (string) pemetaan dict ke nilai; hal ini digunakan untuk menginisialisasi kolom instance baru. Perhatikan bahwa callback mungkin memiliki tanda tangan, dan jika argumen tidak cocok dengan tanda tangan, error akan dilaporkan seolah-olah callback dipanggil secara langsung.

Sebaliknya, konstruktor mentah akan mengabaikan callback init.

Contoh berikut menggunakan init untuk melakukan pra-proses dan memvalidasi argumennya:

# //pkg:exampleinfo.bzl

_core_headers = [...]  # private constant representing standard library files

# It's possible to define an init accepting positional arguments, but
# keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
    if not files_to_link and not allow_empty_files_to_link:
        fail("files_to_link may not be empty")
    all_headers = depset(_core_headers, transitive = headers)
    return {'files_to_link': files_to_link, 'headers': all_headers}

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init)

export ExampleInfo

Penerapan aturan kemudian dapat membuat instance penyedia sebagai berikut:

    ExampleInfo(
        files_to_link=my_files_to_link,  # may not be empty
        headers = my_headers,  # will automatically include the core headers
    )

Konstruktor mentah dapat digunakan untuk menentukan fungsi factory publik alternatif yang tidak melalui logika init. Misalnya, dalam exampleinfo.bzl, kita dapat menentukan:

def make_barebones_exampleinfo(headers):
    """Returns an ExampleInfo with no files_to_link and only the specified headers."""
    return _new_exampleinfo(files_to_link = depset(), headers = all_headers)

Biasanya, konstruktor mentah terikat dengan variabel yang namanya diawali dengan garis bawah (_new_exampleinfo di atas) sehingga kode pengguna tidak dapat memuatnya dan menghasilkan instance penyedia arbitrer.

Penggunaan lain untuk init adalah dengan hanya mencegah pengguna memanggil simbol penyedia secara paksa, dan memaksanya untuk menggunakan fungsi factory:

def _exampleinfo_init_banned(*args, **kwargs):
    fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init_banned)

def make_exampleinfo(...):
    ...
    return _new_exampleinfo(...)

Aturan yang dapat dieksekusi dan aturan pengujian

Aturan yang dapat dieksekusi menentukan target yang dapat dipanggil oleh perintah bazel run. Aturan pengujian adalah jenis aturan khusus yang dapat dieksekusi yang targetnya juga dapat dipanggil oleh perintah bazel test. Aturan yang dapat dieksekusi dan pengujian dibuat dengan menyetel argumen executable atau test yang terkait ke True dalam panggilan ke rule:

example_binary = rule(
   implementation = _example_binary_impl,
   executable = True,
   ...
)

example_test = rule(
   implementation = _example_binary_impl,
   test = True,
   ...
)

Aturan pengujian harus memiliki nama yang diakhiri dengan _test. (Nama target pengujian juga sering berakhiran _test berdasarkan konvensi, tetapi ini tidak diwajibkan.) Aturan non-pengujian tidak boleh memiliki akhiran ini.

Kedua jenis aturan harus menghasilkan file output yang dapat dieksekusi (yang mungkin atau mungkin tidak dinyatakan sebelumnya) yang akan dipanggil oleh perintah run atau test. Untuk memberi tahu Bazel bahwa output aturan yang akan digunakan sebagai file yang dapat dieksekusi ini, teruskan sebagai argumen executable dari penyedia DefaultInfo yang ditampilkan. executable tersebut ditambahkan ke output default aturan (sehingga Anda tidak perlu meneruskannya ke executable dan files). Nilai ini juga ditambahkan secara implisit ke runfile:

def _example_binary_impl(ctx):
    executable = ctx.actions.declare_file(ctx.label.name)
    ...
    return [
        DefaultInfo(executable = executable, ...),
        ...
    ]

Tindakan yang menghasilkan file ini harus menetapkan bit yang dapat dieksekusi pada file. Untuk tindakan ctx.actions.run atau ctx.actions.run_shell, ini harus dilakukan oleh alat dasar yang dipanggil oleh tindakan. Untuk tindakan ctx.actions.write, teruskan is_executable=True.

Sebagai perilaku lama, aturan yang dapat dieksekusi memiliki output mendeklarasikan ctx.outputs.executable khusus. File ini berfungsi sebagai file default yang dapat dieksekusi jika Anda tidak menentukannya menggunakan DefaultInfo; file tersebut tidak boleh digunakan. Mekanisme output ini tidak digunakan lagi karena tidak mendukung penyesuaian nama file yang dapat dieksekusi pada waktu analisis.

Lihat contoh aturan yang dapat dieksekusi dan aturan pengujian.

Aturan yang dapat dieksekusi dan aturan pengujian memiliki atribut tambahan yang ditentukan secara implisit, selain yang ditambahkan untuk semua aturan. Default atribut yang ditambahkan secara implisit tidak dapat diubah, meskipun hal ini dapat diatasi dengan menggabungkan aturan pribadi dalam makro Starlark yang mengubah default:

def example_test(size="small", **kwargs):
  _example_test(size=size, **kwargs)

_example_test = rule(
 ...
)

Lokasi runfiles

Saat target yang dapat dieksekusi dijalankan dengan bazel run (atau test), root direktori runfiles berdekatan dengan file yang dapat dieksekusi. Jalurnya sebagai berikut:

# Given executable_file and runfile_file:
runfiles_root = executable_file.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
    runfiles_root, workspace_name, runfile_path)

Jalur ke File pada direktori runfiles akan sesuai dengan File.short_path.

Biner yang dieksekusi secara langsung oleh bazel berdekatan dengan root direktori runfiles. Namun, biner yang disebut dari runfile tidak dapat membuat asumsi yang sama. Untuk mengurangi hal ini, setiap biner harus menyediakan cara untuk menerima root runfile-nya sebagai parameter menggunakan argumen/flag command line atau lingkungan. Hal ini memungkinkan biner meneruskan root runfile kanonis yang benar ke biner yang dipanggilnya. Jika hal tersebut tidak ditetapkan, biner dapat menebak bahwa itu adalah biner pertama yang dipanggil dan mencari direktori runfile yang berdekatan.

Topik lanjutan

Meminta file output

Satu target dapat memiliki beberapa file output. Saat perintah bazel build dijalankan, beberapa output dari target yang diberikan ke perintah tersebut dianggap diminta. Bazel hanya membuat file yang diminta tersebut dan file yang menjadi dependensinya secara langsung atau tidak langsung. (Dalam hal grafik tindakan, Bazel hanya menjalankan tindakan yang dapat dijangkau sebagai dependensi transitif dari file yang diminta.)

Selain output default, semua output yang dideklarasikan sebelumnya dapat diminta secara eksplisit di command line. Aturan dapat menentukan output yang dideklarasikan sebelumnya melalui atribut output. Dalam hal ini, pengguna secara eksplisit memilih label untuk output saat membuat instance aturan. Untuk mendapatkan objek File bagi atribut output, gunakan atribut yang sesuai dari ctx.outputs. Aturan juga dapatsecara implisit menentukan output yang dideklarasikan sebelumnya berdasarkan nama target, tetapi fitur ini tidak digunakan lagi.

Selain output default, ada grup output, yang merupakan kumpulan file output yang dapat diminta secara bersamaan. Ini dapat diminta dengan --output_groups. Misalnya, jika //pkg:mytarget target adalah jenis aturan yang memiliki grup output debug_files, file ini dapat dibuat dengan menjalankan bazel build //pkg:mytarget --output_groups=debug_files. Karena output yang tidak dideklarasikan sebelumnya tidak memiliki label, output tersebut hanya dapat diminta dengan muncul dalam output default atau grup output.

Grup output dapat ditentukan dengan penyedia OutputGroupInfo. Perhatikan bahwa tidak seperti banyak penyedia bawaan, OutputGroupInfo dapat mengambil parameter dengan nama arbitrer untuk menentukan grup output dengan nama tersebut:

def _example_library_impl(ctx):
    ...
    debug_file = ctx.actions.declare_file(name + ".pdb")
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        OutputGroupInfo(
            debug_files = depset([debug_file]),
            all_files = depset([output_file, debug_file]),
        ),
        ...
    ]

Selain itu, tidak seperti sebagian besar penyedia, OutputGroupInfo dapat ditampilkan oleh aspek dan target aturan yang menerapkan aspek tersebut, selama tidak menentukan grup output yang sama. Dalam hal ini, penyedia yang dihasilkan akan digabungkan.

Perhatikan bahwa OutputGroupInfo umumnya tidak boleh digunakan untuk menyampaikan jenis file tertentu dari target ke tindakan konsumennya. Tentukan penyedia khusus aturan untuk melakukannya.

Konfigurasi

Bayangkan Anda ingin mem-build biner C++ untuk arsitektur yang berbeda. Build dapat menjadi kompleks dan melibatkan beberapa langkah. Beberapa biner perantara, seperti compiler dan generator kode, harus berjalan pada platform eksekusi (yang dapat berupa host Anda, atau eksekutor jarak jauh). Beberapa biner seperti output akhir harus dibuat untuk arsitektur target.

Untuk alasan ini, Bazel memiliki konsep "konfigurasi" dan transisi. Target teratas (yang diminta pada command line) dibuat dalam konfigurasi "target" sedangkan alat yang seharusnya berjalan pada platform eksekusi dibuat dalam konfigurasi "exec". Aturan dapat menghasilkan tindakan yang berbeda berdasarkan konfigurasi, misalnya untuk mengubah arsitektur CPU yang diteruskan ke compiler. Dalam beberapa kasus, library yang sama mungkin diperlukan untuk konfigurasi yang berbeda. Jika ini terjadi, pengujian akan dianalisis dan berpotensi dibuat beberapa kali.

Secara default, Bazel membuat dependensi target dalam konfigurasi yang sama dengan target itu sendiri, dengan kata lain tanpa transisi. Jika dependensi adalah alat yang diperlukan untuk membantu mem-build target, atribut yang sesuai harus menentukan transisi ke konfigurasi eksekutif. Hal ini menyebabkan alat dan semua dependensinya di-build untuk platform eksekusi.

Untuk setiap atribut dependensi, Anda dapat menggunakan cfg untuk menentukan apakah dependensi harus dibuat dalam konfigurasi yang sama atau bertransisi ke konfigurasi eksekutif. Jika atribut dependensi memiliki flag executable=True, cfg harus ditetapkan secara eksplisit. Langkah ini bertujuan agar Anda tidak sengaja membuat alat untuk konfigurasi yang salah. Lihat contoh

Secara umum, sumber, library dependen, dan file yang dapat dieksekusi yang diperlukan pada runtime dapat menggunakan konfigurasi yang sama.

Alat yang dijalankan sebagai bagian dari build (seperti compiler atau generator kode) harus di-build untuk konfigurasi exec. Dalam hal ini, tentukan cfg="exec" dalam atribut.

Jika tidak, file yang dapat dieksekusi yang digunakan saat runtime (seperti bagian dari pengujian) harus di-build untuk konfigurasi target. Dalam hal ini, tentukan cfg="target" dalam atribut.

cfg="target" sebenarnya tidak melakukan apa pun: hal ini murni nilai kemudahan untuk membantu desainer aturan eksplisit tentang niatnya. Jika executable=False, yang berarti cfg bersifat opsional, hanya setel atribut ini jika benar-benar membantu meningkatkan keterbacaan.

Anda juga dapat menggunakan cfg=my_transition untuk menggunakan transisi yang ditentukan pengguna, yang memungkinkan penulis aturan mendapatkan banyak fleksibilitas dalam mengubah konfigurasi, dengan kekurangan membuat grafik build lebih besar dan kurang mudah dipahami.

Catatan: Secara historis, Bazel tidak memiliki konsep platform eksekusi, dan sebagai gantinya, semua tindakan build dianggap berjalan di mesin host. Karena itu, ada satu konfigurasi "host" dan "host" transisi yang dapat digunakan untuk membuat dependensi dalam konfigurasi host. Banyak aturan masih menggunakan transisi "host" untuk alat mereka, tetapi saat ini tidak digunakan lagi dan dimigrasikan untuk menggunakan "exec" jika memungkinkan.

Ada banyak perbedaan antara konfigurasi "host" dan "exec"

  • "host" adalah terminal, "exec" Bukan't: Setelah dependensi berada dalam konfigurasi "host" , transisi lagi tidak diizinkan. Anda dapat terus melakukan transisi konfigurasi lebih lanjut setelah Anda berada dalam konfigurasi "exec".
  • &}}
  • "host" menganggap Anda menjalankan alat di mesin yang sama dengan Bazel, atau di mesin yang hampir serupa. Hal ini tidak lagi benar: Anda dapat menjalankan tindakan build pada mesin lokal Anda, atau pada eksekutor jarak jauh, dan tidak ada jaminan bahwa eksekutor jarak jauh adalah CPU dan OS yang sama dengan mesin lokal Anda.

Konfigurasi "exec" dan "host" menerapkan perubahan opsi yang sama, (misalnya, menyetel --compilation_mode dari --host_compilation_mode, setel --cpu dari --host_cpu, dll.). Perbedaannya adalah konfigurasi "host" dimulai dengan nilai default dari semua flag lainnya, sedangkan konfigurasi "exec" dimulai dengan nilai flag saat ini, berdasarkan konfigurasi target.

Fragmen konfigurasi

Aturan dapat mengakses fragmen konfigurasi seperti cpp, java, dan jvm. Namun, semua fragmen yang diperlukan harus dideklarasikan untuk menghindari error akses:

def _impl(ctx):
    # Using ctx.fragments.cpp leads to an error since it was not declared.
    x = ctx.fragments.java
    ...

my_rule = rule(
    implementation = _impl,
    fragments = ["java"],      # Required fragments of the target configuration
    host_fragments = ["java"], # Required fragments of the host configuration
    ...
)

ctx.fragments hanya menyediakan fragmen konfigurasi untuk konfigurasi target. Jika Anda ingin mengakses fragmen untuk konfigurasi host, gunakan ctx.host_fragments.

Biasanya, jalur relatif file dalam hierarki runfile sama dengan jalur relatif file tersebut di pohon sumber atau pohon output yang dihasilkan. Jika alasan ini harus berbeda karena beberapa alasan, Anda dapat menentukan argumen root_symlinks atau symlinks. root_symlinks adalah jalur pemetaan kamus ke file, yang jalurnya relatif terhadap root direktori runfiles. Kamus symlinks sama, tetapi jalur diawali secara implisit dengan nama ruang kerja.

    ...
    runfiles = ctx.runfiles(
        root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
        symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
    )
    # Creates something like:
    # sometarget.runfiles/
    #     some/
    #         path/
    #             here.foo -> some_data_file2
    #     <workspace_name>/
    #         some/
    #             path/
    #                 here.bar -> some_data_file3

Jika symlinks atau root_symlinks digunakan, berhati-hatilah untuk tidak memetakan dua file yang berbeda ke jalur yang sama di hierarki runfiles. Tindakan ini akan menyebabkan build gagal dengan error yang mendeskripsikan konflik. Untuk memperbaikinya, Anda harus mengubah argumen ctx.runfiles untuk menghilangkan konflik. Pemeriksaan ini akan dilakukan untuk setiap target yang menggunakan aturan, serta target apa pun yang bergantung pada target tersebut. Hal ini sangat berisiko jika alat Anda kemungkinan akan digunakan secara transitif oleh alat lain; nama symlink harus unik di seluruh file run alat dan semua dependensinya.

Cakupan kode

Saat perintah coverage dijalankan, build mungkin perlu menambahkan instrumentasi cakupan untuk target tertentu. Build ini juga mengumpulkan daftar file sumber yang diinstrumentasikan. Subkumpulan yang dipertimbangkan dikontrol oleh flag --instrumentation_filter. Target pengujian dikecualikan, kecuali jika --instrument_test_targets ditentukan.

Jika implementasi aturan menambahkan instrumentasi cakupan pada waktu build, implementasi tersebut harus memperhitungkan hal itu di fungsi implementasinya. ctx.coverage_instrumented akan menampilkan true dalam mode cakupan jika sumber target diinstrumentasikan:

# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
  # Do something to turn on coverage for this compile action

Logika yang selalu perlu diaktifkan dalam mode cakupan (baik sumber target yang secara khusus diinstrumentasikan atau tidak) dapat dikondisikan di ctx.configuration.coverage_enabled.

Jika aturan secara langsung menyertakan sumber dari dependensinya sebelum kompilasi (seperti file header), aturan tersebut mungkin juga perlu mengaktifkan instrumentasi waktu kompilasi jika dependensi' sumber harus diinstrumentasikan:

# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
    (ctx.coverage_instrumented() or
     any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
    # Do something to turn on coverage for this compile action

Aturan juga harus memberikan informasi tentang atribut mana yang relevan untuk cakupan dengan penyedia InstrumentedFilesInfo, yang dibuat menggunakan coverage_common.instrumented_files_info. Parameter dependency_attributes dari instrumented_files_info harus mencantumkan semua atribut dependensi runtime, termasuk dependensi kode seperti deps dan dependensi data seperti data. Parameter source_attributes harus mencantumkan atribut file sumber aturan jika instrumentasi cakupan dapat ditambahkan:

def _example_library_impl(ctx):
    ...
    return [
        ...
        coverage_common.instrumented_files_info(
            ctx,
            dependency_attributes = ["deps", "data"],
            # Omitted if coverage is not supported for this rule:
            source_attributes = ["srcs", "hdrs"],
        )
        ...
    ]

Jika InstrumentedFilesInfo tidak ditampilkan, atribut default akan dibuat dengan setiap atribut dependensi non-alat yang tidak menetapkan cfg ke "host" atau "exec" dalam skema atribut) di dependency_attributes. (Ini bukan perilaku yang ideal, karena menempatkan atribut seperti srcs di dependency_attributes dan bukan source_attributes, tetapi menghindari kebutuhan akan konfigurasi cakupan eksploit untuk semua aturan dalam rantai dependensi.)

Tindakan Validasi

Terkadang Anda perlu memvalidasi sesuatu terkait build, dan informasi yang diperlukan untuk melakukan validasi tersebut hanya tersedia dalam artefak (file sumber atau file yang dihasilkan). Karena informasi ini berada dalam artefak, aturan tidak dapat melakukan validasi ini pada waktu analisis karena aturan tidak dapat membaca file. Sebagai gantinya, tindakan harus melakukan validasi ini pada waktu eksekusi. Jika validasi gagal, tindakan akan gagal, sehingga build juga akan gagal.

Contoh validasi yang mungkin dijalankan adalah analisis statis, linting, pemeriksaan dependensi dan konsistensi, serta pemeriksaan gaya.

Tindakan validasi juga dapat membantu meningkatkan performa build dengan memindahkan bagian dari tindakan yang tidak diperlukan untuk mem-build artefak menjadi tindakan terpisah. Misalnya, jika satu tindakan yang melakukan kompilasi dan linting dapat dipisahkan menjadi tindakan kompilasi dan tindakan lint, tindakan lint dapat dijalankan sebagai tindakan validasi dan berjalan secara paralel dengan tindakan lainnya.

"Tindakan validasi" sering kali tidak menghasilkan apa pun yang digunakan di tempat lain dalam build, karena hanya perlu menegaskan sesuatu tentang input mereka. Namun, hal ini akan menimbulkan masalah: Jika tindakan validasi tidak menghasilkan apa pun yang digunakan di tempat lain dalam build, bagaimana cara aturan menjalankan tindakan tersebut? Secara historis, pendekatannya adalah membuat tindakan validasi menghasilkan file kosong, dan menambahkan output tersebut secara input ke input dari beberapa tindakan penting lainnya dalam build:

Hal ini berfungsi, karena Bazel akan selalu menjalankan tindakan validasi saat tindakan kompilasi dijalankan, tetapi memiliki kelemahan yang signifikan:

  1. Tindakan validasi berada di jalur penting build. Karena Bazel menganggap output kosong diperlukan untuk menjalankan tindakan kompilasi, tindakan tersebut akan menjalankan tindakan validasi terlebih dahulu, meskipun tindakan kompilasi akan mengabaikan input. Hal ini mengurangi paralelisme dan memperlambat build.

  2. Jika tindakan lain dalam build mungkin dijalankan alih-alih tindakan kompilasi, output kosong dari tindakan validasi juga harus ditambahkan ke tindakan tersebut (misalnya, output jar sumber java_library). Hal ini juga menjadi masalah jika tindakan baru yang mungkin berjalan dan bukan tindakan kompilasi ditambahkan nanti, dan output validasi kosong tidak sengaja ditinggalkan.

Solusi untuk masalah ini adalah dengan menggunakan Grup Output Validasi.

Grup Output Validasi

Grup Output Validasi adalah grup output yang dirancang untuk menampung output tindakan validasi yang tidak digunakan, sehingga tidak perlu ditambahkan secara buatan ke input tindakan lainnya.

Grup ini khusus karena outputnya selalu diminta, terlepas dari nilai flag --output_groups, dan terlepas dari cara dependensi bergantung (misalnya, pada command line, sebagai dependensi, atau melalui output implisit dari target). Perhatikan bahwa caching dan inkrementalitas normal tetap berlaku: jika input ke tindakan validasi tidak berubah dan tindakan validasi sebelumnya berhasil, tindakan validasi tidak akan dijalankan.

Penggunaan grup output ini masih memerlukan tindakan validasi untuk menghasilkan file tertentu, bahkan file kosong. Hal ini mungkin memerlukan penggabungan beberapa alat yang biasanya tidak membuat output sehingga file dibuat.

Tindakan validasi target tidak dijalankan dalam tiga kasus:

  • Jika target diandalkan sebagai alat
  • Jika target diandalkan sebagai dependensi implisit (misalnya, atribut yang dimulai dengan "_")
  • Saat target dibuat di konfigurasi host atau eksekutif.

Diasumsikan bahwa target ini memiliki build dan pengujian terpisah sendiri yang akan mengungkap kegagalan validasi.

Menggunakan Grup Output Validasi

Grup Output Validasi bernama _validation dan digunakan seperti grup output lainnya:

def _rule_with_validation_impl(ctx):

  ctx.actions.write(ctx.outputs.main, "main output\n")

  ctx.actions.write(ctx.outputs.implicit, "implicit output\n")

  validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
  ctx.actions.run(
      outputs = [validation_output],
      executable = ctx.executable._validation_tool,
      arguments = [validation_output.path])

  return [
    DefaultInfo(files = depset([ctx.outputs.main])),
    OutputGroupInfo(_validation = depset([validation_output])),
  ]


rule_with_validation = rule(
  implementation = _rule_with_validation_impl,
  outputs = {
    "main": "%{name}.main",
    "implicit": "%{name}.implicit",
  },
  attrs = {
    "_validation_tool": attr.label(
        default = Label("//validation_actions:validation_tool"),
        executable = True,
        cfg = "exec"),
  }
)

Perhatikan bahwa file output validasi tidak ditambahkan ke DefaultInfo atau input ke tindakan lainnya. Tindakan validasi untuk target jenis aturan ini akan tetap berjalan jika target bergantung pada label, atau output implisit target bergantung pada secara langsung atau tidak langsung.

Biasanya output tindakan validasi hanya boleh masuk ke grup output validasi, dan tidak ditambahkan ke input tindakan lain, karena dapat mengalahkan peningkatan paralelisme. Namun, perlu diperhatikan bahwa saat ini Bazel tidak memiliki pemeriksaan khusus untuk menerapkannya. Oleh karena itu, Anda harus menguji bahwa output tindakan validasi tidak ditambahkan ke input tindakan apa pun dalam pengujian untuk aturan Starlark. Contoh:

load("@bazel_skylib//lib:unittest.bzl", "analysistest")

def _validation_outputs_test_impl(ctx):
  env = analysistest.begin(ctx)

  actions = analysistest.target_actions(env)
  target = analysistest.target_under_test(env)
  validation_outputs = target.output_groups._validation.to_list()
  for action in actions:
    for validation_output in validation_outputs:
      if validation_output in action.inputs.to_list():
        analysistest.fail(env,
            "%s is a validation action output, but is an input to action %s" % (
                validation_output, action))

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

Tanda Tindakan Validasi

Menjalankan tindakan validasi dikontrol oleh tanda command line --run_validations, yang setelan defaultnya adalah true.

Fitur yang tidak digunakan lagi

Output yang dideklarasikan sebelumnya tidak digunakan lagi

Ada dua cara untuk menghentikan penggunaan output yang telah dideklarasikan sebelumnya:

  • Parameter outputs dari rule menentukan pemetaan antara nama atribut output dan template string untuk menghasilkan label output yang dideklarasikan sebelumnya. Memilih menggunakan output yang tidak dideklarasikan sebelumnya dan menambahkan output secara eksplisit ke DefaultInfo.files. Gunakan label target aturan sebagai input untuk aturan yang menggunakan output, bukan label output yang dideklarasikan sebelumnya.

  • Untuk aturan yang dapat dieksekusi, ctx.outputs.executable mengacu pada output yang dapat dieksekusi yang telah dideklarasikan sebelumnya dengan nama yang sama dengan target aturan. Memilih untuk mendeklarasikan output secara eksplisit, misalnya dengan ctx.actions.declare_file(ctx.label.name), dan memastikan bahwa perintah yang menghasilkan file yang dapat dieksekusi akan menetapkan izinnya untuk mengizinkan eksekusi. Teruskan output yang dapat dieksekusi secara eksplisit ke parameter executable dari DefaultInfo.

Fitur runfile yang harus dihindari

Jenis ctx.runfiles dan runfiles memiliki serangkaian fitur yang kompleks, banyak di antaranya yang disimpan karena alasan lama. Rekomendasi berikut membantu mengurangi kerumitan:

  • Hindari penggunaan mode collect_data dan collect_default dari ctx.runfiles. Mode ini secara implisit mengumpulkan runfile di tepi dependensi hardcode tertentu dengan cara yang membingungkan. Sebagai gantinya, tambahkan file menggunakan parameter files atau transitive_files dari ctx.runfiles, atau dengan menggabungkan runfile dari dependensi dengan runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles).

  • Hindari penggunaan data_runfiles dan default_runfiles dari konstruktor DefaultInfo. Sebagai gantinya, tentukan DefaultInfo(runfiles = ...). Perbedaan antara "default" dan "data" runfile dipertahankan karena alasan lama. Misalnya, beberapa aturan menempatkan output default-nya di data_runfiles, tetapi tidak di default_runfiles. Daripada menggunakan data_runfiles, aturan harus menyertakan output default dan menggabungkannya dalam default_runfiles dari atribut yang menyediakan runfile (sering kali berupa data).

  • Saat mengambil runfiles dari DefaultInfo (umumnya hanya untuk menggabungkan file run antara aturan saat ini dan dependensinya), gunakan DefaultInfo.default_runfiles, bukan DefaultInfo.data_runfiles.

Bermigrasi dari penyedia lama

Secara historis, penyedia Bazel adalah kolom sederhana pada objek Target. Aturan tersebut diakses menggunakan operator titik, dan dibuat dengan menempatkan kolom dalam struct yang ditampilkan oleh fungsi penerapan aturan.

Gaya ini tidak digunakan lagi dan tidak boleh digunakan dalam kode baru; lihat di bawah untuk informasi yang dapat membantu Anda melakukan migrasi. Mekanisme penyedia baru akan menghindari konflik nama. Library ini juga mendukung penyembunyian data, dengan mewajibkan kode apa pun yang mengakses instance penyedia untuk mengambilnya menggunakan simbol penyedia.

Untuk saat ini, penyedia lama masih didukung. Aturan dapat menampilkan penyedia lama dan modern seperti berikut:

def _old_rule_impl(ctx):
  ...
  legacy_data = struct(x="foo", ...)
  modern_data = MyInfo(y="bar", ...)
  # When any legacy providers are returned, the top-level returned value is a
  # struct.
  return struct(
      # One key = value entry for each legacy provider.
      legacy_info = legacy_data,
      ...
      # Additional modern providers:
      providers = [modern_data, ...])

Jika dep adalah objek Target yang dihasilkan untuk instance aturan ini, penyedia dan kontennya dapat diambil sebagai dep.legacy_info.x dan dep[MyInfo].y.

Selain providers, struct yang ditampilkan juga dapat mengambil beberapa kolom lain yang memiliki arti khusus (sehingga tidak membuat penyedia lama yang sesuai):

  • Kolom files, runfiles, data_runfiles, default_runfiles, dan executable sesuai dengan kolom bernama DefaultInfo yang sama. Anda tidak diizinkan untuk menentukan kolom ini sekaligus menampilkan penyedia DefaultInfo.

  • Kolom output_groups mengambil nilai struct dan sesuai dengan OutputGroupInfo.

Dalam deklarasi aturan provides, dan dalam providers deklarasi atribut dependensi, penyedia lama diteruskan sebagai string dan penyedia modern diteruskan oleh simbol *Info. Pastikan untuk mengubah dari string ke simbol saat bermigrasi. Untuk kumpulan aturan yang kompleks atau besar, yang sulit memperbarui semua aturan secara atomik, Anda mungkin memiliki waktu yang lebih mudah jika mengikuti urutan langkah-langkah berikut:

  1. Ubah aturan yang menghasilkan penyedia lama untuk menghasilkan penyedia lama dan modern, menggunakan sintaksis di atas. Untuk aturan yang mendeklarasikan bahwa penyedia lama ditampilkan, perbarui deklarasi tersebut untuk menyertakan penyedia lama dan modern.

  2. Ubah aturan yang menggunakan penyedia lama untuk menggunakan penyedia modern. Jika ada deklarasi atribut yang memerlukan penyedia lama, update juga penyedia tersebut sebagai penyedia modern. Secara opsional, Anda dapat menghentikan pekerjaan ini dengan langkah 1 dengan meminta konsumen menerima/mewajibkan penyedia: Uji keberadaan penyedia lama menggunakan hasattr(target, 'foo'), atau penyedia baru menggunakan FooInfo in target.

  3. Hapus penyedia lama sepenuhnya dari semua aturan.