Aturan

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

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

  1. Ambil sekumpulan file sumber .cpp (input).
  2. Jalankan g++ pada file sumber (tindakan).
  3. Menampilkan penyedia DefaultInfo dengan output yang dapat dieksekusi dan file lain agar tersedia saat 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 untuk aturan ini. Sebagai penulis aturan, Anda tidak hanya harus mempertimbangkan input yang diberikan pengguna ke suatu 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). Anda juga dapat mempelajari makro untuk memahami perbedaan antara aturan dan makro. Untuk memulai, tinjau terlebih dahulu Tutorial Aturan. Kemudian, gunakan halaman ini sebagai referensi.

Beberapa aturan sudah ada di Bazel. Aturan native ini, seperti genrule dan filegroup, memberikan beberapa dukungan inti. Dengan menentukan aturan Anda sendiri, Anda dapat menambahkan dukungan untuk bahasa dan alat yang tidak didukung secara native oleh Bazel.

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 dapat memutuskan atribut yang didukung dan cara aturan tersebut menghasilkan outputnya.

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

Pembuatan aturan

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

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

Tindakan ini menentukan jenis aturan bernama example_library.

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

Instansiasi 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 berupa penentuan target. Tindakan ini disebut membuat instance aturan. Tindakan ini menentukan nama untuk target baru dan nilai 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 penerapan target, atau dapat merujuk ke target lain, sehingga membuat grafik dependensi.

Atribut khusus aturan, seperti srcs atau deps, ditentukan dengan meneruskan peta dari nama atribut ke skema (dibuat menggunakan modul attr) ke parameter attrs dari rule. Atribut umum, seperti name dan visibility, secara implisit ditambahkan ke semua aturan. Atribut tambahan ditambahkan secara implisit ke aturan yang dapat dieksekusi 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. Biasanya, skema atribut menentukan ekstensi file yang diharapkan untuk jenis file sumber yang diproses oleh aturan. Aturan untuk bahasa dengan file header biasanya menentukan atribut hdrs terpisah untuk header yang diproses oleh target dan konsumennya.
  • deps menentukan dependensi kode untuk target. Skema atribut harus menentukan penyedia mana yang harus menyediakan dependensi tersebut. (Misalnya, cc_library menyediakan CcInfo.)
  • data menentukan file yang akan tersedia saat runtime untuk semua file yang dapat dieksekusi yang bergantung pada target. Hal ini 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. Atribut apa pun yang menentukan label input (yang ditentukan dengan attr.label_list, attr.label, atau attr.label_keyed_string_dict) menentukan dependensi jenis tertentu antara target dan target yang labelnya (atau objek Label yang sesuai) tercantum dalam atribut tersebut saat target ditentukan. Repositori, dan mungkin jalur, untuk label ini diselesaikan 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 my_target, sehingga other_target dianalisis terlebih dahulu. Terjadi error jika ada siklus dalam grafik dependensi target.

Atribut pribadi dan dependensi implisit

Atribut dependensi dengan nilai default membuat dependensi implisit. Hal ini implisit karena merupakan bagian dari grafik target yang tidak ditentukan pengguna dalam file BUILD. Dependensi implisit berguna untuk mengodekan secara permanen hubungan antara aturan dan alat (dependensi waktu build, seperti kompiler), karena sebagian besar waktu pengguna tidak tertarik untuk menentukan alat yang digunakan aturan. Di dalam fungsi penerapan aturan, hal ini diperlakukan sama seperti dependensi lainnya.

Jika ingin memberikan dependensi implisit tanpa mengizinkan pengguna mengganti nilai tersebut, Anda dapat membuat atribut pribadi dengan memberikan nama yang diawali dengan garis bawah (_). Atribut pribadi harus memiliki nilai default. Umumnya, atribut pribadi hanya masuk akal untuk digunakan pada 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 kompiler, meskipun pengguna tidak meneruskan labelnya sebagai input. Karena _compiler adalah atribut pribadi, maka ctx.attr._compiler 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 mengganti kompiler lain jika perlu, tetapi tidak memerlukan pengetahuan tentang label kompiler.

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

Atribut output

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

  • Tindakan ini menentukan target file output, bukan merujuk ke target yang ditentukan di tempat lain.
  • Target file output bergantung pada target aturan yang di-instantiate, bukan sebaliknya.

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

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

Fungsi penerapan

Setiap aturan memerlukan fungsi implementation. Fungsi ini dieksekusi secara ketat dalam fase analisis dan mengubah grafik target yang dihasilkan dalam fase pemuatan menjadi grafik tindakan yang akan dilakukan selama fase eksekusi. Oleh karena itu, fungsi penerapan tidak dapat membaca atau menulis file.

Fungsi penerapan aturan biasanya bersifat pribadi (diberi nama dengan garis bawah di depannya). Secara konvensional, namanya sama dengan aturan, tetapi dengan akhiran _impl.

Fungsi penerapan hanya menggunakan satu parameter: konteks aturan, yang secara konvensional diberi nama ctx. Fungsi ini menampilkan daftar penyedia.

Target

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

ctx.attr memiliki kolom yang sesuai dengan nama setiap atribut dependensi, yang berisi objek Target yang merepresentasikan setiap dependensi langsung menggunakan atribut tersebut. Untuk atribut label_list, ini adalah daftar Targets. Untuk atribut label, nilainya adalah Target atau None tunggal.

Daftar objek penyedia ditampilkan oleh fungsi penerapan target:

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

Provider tersebut 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 menggunakan atribut hdrs dan menyediakannya ke tindakan kompilasi target dan konsumennya, aturan tersebut dapat mengumpulkannya seperti ini:

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

Ada gaya struct lama, yang sangat tidak disarankan dan aturan harus dimigrasikan dari gaya tersebut.

File

File direpresentasikan oleh objek File. Karena Bazel tidak melakukan I/O file selama fase analisis, objek ini tidak dapat digunakan untuk membaca atau menulis konten file secara langsung. Sebaliknya, nilai tersebut diteruskan ke fungsi pemancaran tindakan (lihat ctx.actions) untuk membuat bagian grafik tindakan.

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

Untuk setiap atribut dependensi, kolom ctx.files yang sesuai berisi daftar output default semua dependensi yang menggunakan 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 menetapkan allow_single_file = True. ctx.executable berperilaku sama seperti ctx.file, tetapi hanya berisi kolom untuk atribut dependensi yang spesifikasinya menetapkan executable = True.

Mendeklarasikan output

Selama fase analisis, fungsi penerapan 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 ctx.outputs yang sesuai.

Tindakan

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

Fungsi serbaguna yang membuat tindakan ditentukan di ctx.actions:

ctx.actions.args dapat digunakan untuk mengakumulasi argumen tindakan secara efisien. Menghindari perataan depsets 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 depset file input dan menghasilkan 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 akan diekstrak (sebelum menjalankan unzip). Tindakan yang membuat sejumlah file secara internal dapat membungkusnya dalam satu file (seperti zip, tar, atau format arsip lainnya).

Tindakan harus mencantumkan semua inputnya. Mencantumkan input yang tidak digunakan diizinkan, tetapi tidak efisien.

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

Tindakan dapat dibandingkan dengan fungsi murni: Tindakan hanya boleh bergantung pada input yang diberikan, dan menghindari akses ke 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 memutuskan tindakan mana yang akan dieksekusi. Error akan terjadi jika ada siklus dalam grafik dependensi. Membuat tindakan tidak menjamin bahwa tindakan tersebut akan dieksekusi, hal ini bergantung pada apakah outputnya diperlukan untuk build.

Penyedia

Penyedia adalah informasi yang diekspos oleh suatu aturan ke aturan lain yang bergantung padanya. Data ini dapat mencakup file output, library, parameter yang akan diteruskan di command line alat, atau hal lain yang harus diketahui konsumen target.

Karena fungsi penerapan aturan hanya dapat membaca penyedia dari dependensi langsung target yang di-instance, aturan perlu 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 penyedia yang ditampilkan oleh fungsi penerapan.

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

Output default

Output default target adalah output yang diminta secara default saat target diminta untuk build di command line. Misalnya, target java_library //pkg:foo memiliki foo.jar sebagai output default, sehingga akan dibangun 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 penerapan aturan atau parameter files tidak ditentukan, DefaultInfo.files secara default akan menampilkan semua output yang telah dideklarasikan sebelumnya (umumnya, 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 dihapus. Jika output hanya digunakan oleh konsumen target, tindakan tersebut tidak akan dilakukan saat target dibangun secara terpisah. Hal ini membuat proses pen-debug-an menjadi lebih sulit karena membangun ulang target yang gagal saja tidak akan mereproduksi kegagalan.

Runfiles

Runfile adalah sekumpulan file yang digunakan oleh target saat runtime (bukan saat build). Selama fase eksekusi, Bazel membuat pohon direktori yang berisi symlink yang mengarah ke runfile. Langkah ini menyiapkan lingkungan untuk biner sehingga dapat mengakses file run selama runtime.

File yang dapat dijalankan dapat ditambahkan secara manual selama pembuatan aturan. Objek runfiles dapat dibuat oleh metode runfiles pada konteks aturan, ctx.runfiles dan diteruskan ke parameter runfiles pada DefaultInfo. Output yang dapat dieksekusi dari aturan yang dapat dieksekusi ditambahkan secara implisit ke file yang dapat dijalankan.

Beberapa aturan menentukan atribut, yang umumnya bernama data, yang outputnya ditambahkan ke file run target. Runfile juga harus digabungkan dari data, serta dari atribut apa pun yang dapat menyediakan kode untuk eksekusi akhir, umumnya 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 instansiasi penyedia dengan logika pra-pemrosesan dan validasi kustom. Hal ini dapat digunakan untuk memastikan bahwa semua instance penyedia memenuhi invarian tertentu, atau untuk memberi pengguna API yang lebih bersih 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 yang ditampilkan biasa saat init tidak digunakan, dan "konstruktor mentah".

Dalam hal ini, saat simbol penyedia dipanggil, bukan langsung menampilkan instance baru, simbol tersebut akan meneruskan argumen ke callback init. Nilai yang ditampilkan callback harus berupa dict yang memetakan nama kolom (string) ke nilai; nilai ini digunakan untuk melakukan inisialisasi kolom instance baru. Perhatikan bahwa callback dapat memiliki tanda tangan apa pun, dan jika argumen tidak cocok dengan tanda tangan, error dilaporkan seolah-olah callback dipanggil secara langsung.

Sebaliknya, konstruktor mentah akan melewati callback init.

Contoh berikut menggunakan init untuk memproses dan memvalidasi argumennya:

# //pkg:exampleinfo.bzl

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

# 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(
    fields = ["files_to_link", "headers"],
    init = _exampleinfo_init,
)

Kemudian, penerapan aturan 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, exampleinfo.bzl 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 ke variabel yang namanya diawali dengan garis bawah (_new_exampleinfo di atas), sehingga kode pengguna tidak dapat memuatnya dan membuat instance penyedia arbitrer.

Penggunaan lain untuk init adalah untuk mencegah pengguna memanggil simbol penyedia sama sekali, dan memaksa mereka menggunakan fungsi pabrik sebagai gantinya:

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 yang dapat dieksekusi khusus yang targetnya juga dapat dipanggil oleh perintah bazel test. Aturan yang dapat dieksekusi dan pengujian dibuat dengan menetapkan argumen executable atau test masing-masing 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 diakhiri dengan _test berdasarkan konvensi, tetapi hal ini tidak wajib.) Aturan non-pengujian tidak boleh memiliki akhiran ini.

Kedua jenis aturan harus menghasilkan file output yang dapat dieksekusi (yang mungkin atau mungkin tidak dideklarasikan sebelumnya) yang akan dipanggil oleh perintah run atau test. Untuk memberi tahu Bazel output aturan mana 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). executable juga ditambahkan secara implisit ke runfiles:

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

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

Sebagai perilaku lama, aturan yang dapat dieksekusi memiliki output khusus ctx.outputs.executable yang telah dideklarasikan sebelumnya. File ini berfungsi sebagai dapat dieksekusi default jika Anda tidak menentukannya menggunakan DefaultInfo; file ini tidak boleh digunakan jika tidak. 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 atribut yang ditambahkan untuk semua aturan. Default atribut yang ditambahkan secara implisit tidak dapat diubah, meskipun hal ini dapat diatasi dengan membungkus aturan pribadi dalam makro Starlark yang mengubah default:

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

_example_test = rule(
 ...
)

Lokasi file yang dapat dijalankan

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

# Given launcher_path and runfile_file:
runfiles_root = launcher_path.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 di direktori runfiles sesuai dengan File.short_path.

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

Topik lanjutan

Meminta file output

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

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

Selain output default, ada grup output, yaitu kumpulan file output yang dapat diminta bersama. Hal ini dapat diminta dengan --output_groups. Misalnya, jika target //pkg:mytarget 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 di 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 kebanyakan penyedia, OutputGroupInfo dapat ditampilkan oleh aspek dan target aturan yang menerapkan aspek tersebut, selama keduanya 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 itu.

Konfigurasi

Bayangkan Anda ingin membuat biner C++ untuk arsitektur yang berbeda. Build dapat menjadi rumit dan melibatkan beberapa langkah. Beberapa biner perantara, seperti compiler dan generator kode, harus berjalan di platform eksekusi (yang bisa berupa host Anda, atau executor jarak jauh). Beberapa biner seperti output akhir harus dibangun untuk arsitektur target.

Oleh karena itu, Bazel memiliki konsep "konfigurasi" dan transisi. Target paling atas (yang diminta di command line) dibuat dalam konfigurasi "target", sementara alat yang harus dijalankan di 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 hal ini terjadi, model akan dianalisis dan berpotensi dibangun beberapa kali.

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

Untuk setiap atribut dependensi, Anda dapat menggunakan cfg untuk memutuskan apakah dependensi harus dibangun dalam konfigurasi yang sama atau bertransisi ke konfigurasi exec. Jika atribut dependensi memiliki tanda executable = True, cfg harus ditetapkan secara eksplisit. Tindakan ini dilakukan untuk mencegah pembuatan alat secara tidak sengaja untuk konfigurasi yang salah. Lihat contoh

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

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

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

cfg = "target" sebenarnya tidak melakukan apa pun: ini murni nilai praktis untuk membantu desainer aturan menyatakan maksud mereka secara eksplisit. Jika executable = False, yang berarti cfg bersifat opsional, tetapkan hanya jika benar-benar membantu keterbacaan.

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

Catatan: Secara historis, Bazel tidak memiliki konsep platform eksekusi, dan semua tindakan build dianggap berjalan di mesin host. Versi Bazel sebelum 6.0 membuat konfigurasi "host" yang berbeda untuk merepresentasikan hal ini. Jika Anda melihat referensi ke "host" dalam kode atau dokumentasi lama, itulah yang dimaksud. Sebaiknya gunakan Bazel 6.0 atau yang lebih baru untuk menghindari overhead konseptual tambahan ini.

Fragmen konfigurasi

Aturan dapat mengakses fragmen konfigurasi seperti cpp dan java. 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
    ...
)

Biasanya, jalur relatif file di hierarki runfiles sama dengan jalur relatif file tersebut di hierarki sumber atau hierarki output yang dihasilkan. Jika perlu berbeda karena alasan tertentu, Anda dapat menentukan argumen root_symlinks atau symlinks. root_symlinks adalah dictionary yang memetakan jalur ke file, dengan jalur yang bersifat relatif terhadap root direktori runfiles. Kamus symlinks sama, tetapi jalur secara implisit diberi awalan dengan nama ruang kerja utama (bukan nama repositori yang berisi target saat ini).

    ...
    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 agar tidak memetakan dua file yang berbeda ke jalur yang sama di pohon runfile. Tindakan ini akan menyebabkan build gagal dengan error yang menjelaskan konflik. Untuk memperbaikinya, Anda harus mengubah argumen ctx.runfiles untuk menghapus tabrakan. Pemeriksaan ini akan dilakukan untuk semua target yang menggunakan aturan Anda, 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 runfile alat dan semua dependensinya.

Cakupan kode

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

Jika penerapan aturan menambahkan instrumentasi cakupan pada waktu build, penerapan tersebut harus memperhitungkannya dalam fungsi penerapannya. ctx.coverage_instrumented menampilkan True dalam mode cakupan jika sumber target harus diinstrumentasi:

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

Logika yang harus selalu aktif dalam mode cakupan (terlepas dari apakah sumber target diinstrumentasikan secara khusus atau tidak) dapat dikondisikan pada 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 sumber dependensi harus diinstrumentasi:

# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if 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 mungkin 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, InstrumentedFilesInfo default akan dibuat dengan setiap atribut dependensi non-alat yang tidak menetapkan cfg ke "exec" dalam skema atribut. di dependency_attributes. (Ini bukan perilaku yang ideal, karena menempatkan atribut seperti srcs di dependency_attributes, bukan source_attributes, tetapi menghindari kebutuhan akan konfigurasi cakupan eksplisit untuk semua aturan dalam rantai dependensi.)

Menguji aturan

Aturan pengujian memerlukan penyiapan tambahan untuk membuat laporan cakupan. Aturan itu sendiri harus menambahkan atribut implisit berikut:

my_test = rule(
    ...,
    attrs = {
        ...,
        # Implicit dependencies used by Bazel to generate coverage reports.
        "_lcov_merger": attr.label(
            default = configuration_field(fragment = "coverage", name = "output_generator"),
            executable = True,
            cfg = config.exec(exec_group = "test"),
        ),
        "_collect_cc_coverage": attr.label(
            default = "@bazel_tools//tools/test:collect_cc_coverage",
            executable = True,
            cfg = config.exec(exec_group = "test"),
        )
    },
    test = True,
)

Dengan menggunakan configuration_field, dependensi pada alat penggabungan LCOV Java dapat dihindari selama cakupan tidak diminta.

Saat dijalankan, pengujian akan memancarkan informasi cakupan dalam bentuk satu atau beberapa file LCOV dengan nama unik ke direktori yang ditentukan oleh variabel lingkungan COVERAGE_DIR. Kemudian, Bazel akan menggabungkan file ini menjadi satu file LCOV menggunakan alat _lcov_merger. Jika ada, alat ini juga akan mengumpulkan cakupan C/C++ menggunakan alat _collect_cc_coverage.

Cakupan dasar pengukuran

Karena cakupan hanya dikumpulkan untuk kode yang ada di pohon dependensi pengujian, laporan cakupan dapat menyesatkan karena tidak selalu mencakup semua kode yang cocok dengan tanda --instrumentation_filter.

Oleh karena itu, Bazel memungkinkan aturan untuk menentukan file cakupan dasar menggunakan atribut baseline_coverage_files dari ctx.instrumented_files_info). File ini harus dibuat dalam format LCOV oleh tindakan yang ditentukan pengguna dan seharusnya mencantumkan semua baris, cabang, fungsi, dan/atau blok dalam file sumber target (sesuai dengan parameter sources_attributes dan extensions). Untuk file sumber dalam target yang diinstrumentasi untuk cakupan, Bazel menggabungkan cakupan dasar ke dalam laporan cakupan gabungan yang dihasilkan dengan --combined_report dan dengan demikian memastikan bahwa file yang tidak diuji tetap muncul sebagai tidak tercakup.

Jika aturan tidak menyediakan file cakupan dasar, Bazel akan membuat informasi cakupan sintetis yang hanya menyebutkan jalur file sumber, tetapi tidak berisi informasi apa pun tentang isinya.

Tindakan Validasi

Terkadang Anda perlu memvalidasi sesuatu tentang build, dan informasi yang diperlukan untuk melakukan validasi tersebut hanya tersedia di artefak (file sumber atau file yang dihasilkan). Karena informasi ini ada 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, dan 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 tindakan yang tidak diperlukan untuk membangun artefak ke dalam tindakan terpisah. Misalnya, jika satu tindakan yang melakukan kompilasi dan linting dapat dipisahkan menjadi tindakan kompilasi dan tindakan linting, maka tindakan linting dapat dijalankan sebagai tindakan validasi dan dijalankan secara paralel dengan tindakan lain.

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

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

  1. Tindakan validasi berada di jalur kritis build. Karena Bazel menganggap output kosong diperlukan untuk menjalankan tindakan kompilasi, Bazel 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 berjalan, bukan tindakan kompilasi, maka output kosong dari tindakan validasi juga perlu ditambahkan ke tindakan tersebut (misalnya, output jar sumber java_library). Hal ini juga menjadi masalah jika tindakan baru yang mungkin berjalan, bukan tindakan kompilasi, ditambahkan nanti, dan output validasi kosong tidak sengaja dihilangkan.

Solusi untuk masalah ini adalah menggunakan Grup Output Validasi.

Grup Output Validasi

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

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

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

Tindakan validasi target tidak dijalankan dalam tiga kasus:

  • Saat target bergantung pada alat
  • Saat target bergantung sebagai dependensi implisit (misalnya, atribut yang dimulai dengan "_")
  • Saat target dibuat dalam konfigurasi exec.

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

Menggunakan Grup Output Validasi

Grup Output Validasi diberi nama _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 salah satu output implisit target bergantung secara langsung atau tidak langsung.

Biasanya, output tindakan validasi hanya boleh masuk ke grup output validasi, dan tidak boleh ditambahkan ke input tindakan lain, karena hal ini dapat mengurangi keuntungan paralelisme. Namun, perhatikan bahwa Bazel tidak memiliki pemeriksaan khusus untuk menerapkan hal ini. 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)

Bendera Tindakan Validasi

Menjalankan tindakan validasi dikontrol oleh tanda baris perintah --run_validations, yang secara default bernilai benar (true).

Fitur yang tidak digunakan lagi

Output yang tidak digunakan lagi dan dideklarasikan sebelumnya

Ada dua cara yang tidak digunakan lagi untuk menggunakan output yang telah dideklarasikan sebelumnya:

  • Parameter outputs dari rule menentukan pemetaan antara nama atribut output dan template string untuk membuat label output yang telah dideklarasikan sebelumnya. Lebih baik 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 telah dideklarasikan sebelumnya.

  • Untuk aturan yang dapat dieksekusi, ctx.outputs.executable merujuk ke output yang dapat dieksekusi yang telah dideklarasikan sebelumnya dengan nama yang sama seperti target aturan. Sebaiknya deklarasikan output secara eksplisit, misalnya dengan ctx.actions.declare_file(ctx.label.name), dan pastikan perintah yang membuat file yang dapat dieksekusi 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, yang sebagian besar dipertahankan karena alasan lama. Rekomendasi berikut membantu mengurangi kompleksitas:

  • Hindari penggunaan mode collect_data dan collect_default dari ctx.runfiles. Mode ini secara implisit mengumpulkan file yang dapat dieksekusi di seluruh 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 file run "default" dan "data" dipertahankan karena alasan lama. Misalnya, beberapa aturan menempatkan output defaultnya di data_runfiles, tetapi tidak di default_runfiles. Daripada menggunakan data_runfiles, aturan keduanya harus menyertakan output default dan menggabungkan default_runfiles dari atribut yang menyediakan runfile (sering kali data).

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

Bermigrasi dari penyedia lama

Sebelumnya, penyedia Bazel adalah kolom sederhana pada objek Target. Atribut tersebut diakses menggunakan operator titik, dan dibuat dengan menempatkan kolom dalam struct yang ditampilkan oleh fungsi penerapan aturan, bukan daftar objek penyedia:

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

Penyedia tersebut dapat diambil dari kolom yang sesuai pada objek Target:

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

Gaya ini tidak digunakan lagi dan tidak boleh digunakan dalam kode baru; lihat informasi berikut yang dapat membantu Anda melakukan migrasi. Mekanisme penyedia baru menghindari konflik nama. API 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 sebagai 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 (dan dengan demikian tidak membuat penyedia lama yang sesuai):

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

  • Kolom output_groups mengambil nilai struct dan sesuai dengan OutputGroupInfo.

Dalam deklarasi aturan provides, dan dalam deklarasi atribut dependensi providers, penyedia lama diteruskan sebagai string dan penyedia modern diteruskan oleh simbol Info-nya. Pastikan untuk mengubah dari string menjadi simbol saat melakukan migrasi. Untuk set aturan yang kompleks atau besar yang sulit diperbarui secara atomik, Anda mungkin akan lebih mudah jika mengikuti urutan langkah berikut:

  1. Ubah aturan yang menghasilkan penyedia lama untuk menghasilkan penyedia lama dan modern, menggunakan sintaksis sebelumnya. Untuk aturan yang menyatakan bahwa aturan tersebut mengembalikan penyedia lama, perbarui pernyataan tersebut untuk menyertakan penyedia lama dan modern.

  2. Ubah aturan yang menggunakan penyedia lama agar menggunakan penyedia modern. Jika ada deklarasi atribut yang memerlukan penyedia lama, perbarui juga deklarasi tersebut agar memerlukan penyedia modern. Secara opsional, Anda dapat menjalin pekerjaan ini dengan langkah 1 dengan membuat konsumen menerima atau mewajibkan salah satu penyedia: Uji keberadaan penyedia lama menggunakan hasattr(target, 'foo'), atau penyedia baru menggunakan FooInfo in target.

  3. Hapus sepenuhnya penyedia lama dari semua aturan.