Dependensi

A target bergantung pada target B jika B diperlukan oleh A pada waktu build atau eksekusi. Hubungan bergantung pada menyebabkan Directed Acyclic Graph (DAG) melebihi target, dan disebut grafik dependensi.

Dependensi direct target adalah target lain yang dapat dijangkau oleh jalur dengan panjang 1 dalam grafik dependensi. Dependensi transitif target adalah target yang bergantung pada jalur dengan panjang berapa pun melalui grafik.

Bahkan, dalam konteks build, ada dua grafik dependensi, grafik dependensi aktual dan grafik dependensi yang dideklarasikan. Sering kali, kedua grafik tersebut sangat mirip sehingga perbedaan ini tidak perlu dibuat, namun berguna untuk diskusi di bawah ini.

Dependensi aktual dan yang dideklarasikan

X target sebenarnya bergantung pada Y target jika Y harus ada, dibuat, dan terbaru agar X dapat dibuat dengan benar. Build dapat berarti dihasilkan, diproses, dikompilasi, ditautkan, diarsipkan, dikompresi, dieksekusi, atau jenis tugas lain yang secara rutin terjadi selama build.

X target memiliki dependensi yang dideklarasikan pada Y target jika ada tepi dependensi dari X hingga Y dalam paket X.

Untuk build yang benar, grafik dependensi aktual A harus berupa subgrafik dari grafik dependensi yang dideklarasikan D. Artinya, setiap pasangan node yang terhubung langsung x --> y di A juga harus terhubung langsung di D. Dapat dikatakan bahwa D adalah penaksiran berlebihan A.

Penulis file BUILD harus secara eksplisit mendeklarasikan semua dependensi langsung yang sebenarnya untuk setiap aturan ke sistem build, dan tidak lebih.

Kegagalan dalam mengamati prinsip ini akan menyebabkan perilaku yang tidak ditentukan: build mungkin gagal, tetapi lebih buruk lagi, build mungkin bergantung pada beberapa operasi sebelumnya, atau pada dependensi transitif yang dinyatakan yang dimiliki target. Bazel akan memeriksa dependensi yang hilang dan melaporkan error, tetapi pemeriksaan ini tidak mungkin selesai dalam semua kasus.

Anda tidak perlu (dan tidak boleh) berupaya mencantumkan semua yang diimpor secara tidak langsung, meskipun diperlukan oleh A pada waktu eksekusi.

Selama build X target, alat build akan memeriksa seluruh penutupan transitif dependensi X untuk memastikan bahwa setiap perubahan dalam target tersebut ditampilkan dalam hasil akhir, mem-build ulang intermediate sesuai kebutuhan.

Sifat transitif dependensi menyebabkan kesalahan umum. Terkadang, kode dalam satu file dapat menggunakan kode yang disediakan oleh dependensi tidak langsung — edge transitif tetapi bukan langsung dalam grafik dependensi yang dideklarasikan. Dependensi tidak langsung tidak muncul di file BUILD. Karena aturan tidak bergantung secara langsung pada penyedia, tidak ada cara untuk melacak perubahan, seperti yang ditunjukkan pada contoh linimasa berikut:

1. Dependensi yang dideklarasikan cocok dengan dependensi yang sebenarnya

Pada awalnya, semuanya berfungsi. Kode dalam paket a menggunakan kode dalam paket b. Kode dalam paket b menggunakan kode dalam paket c, sehingga a secara transitif bergantung pada c.

a/BUILD b/BUILD
rule(
    name = "a",
    srcs = "a.in",
    deps = "//b:b",
)
      
rule(
    name = "b",
    srcs = "b.in",
    deps = "//c:c",
)
      
a / a.in b / b.in
import b;
b.foo();
    
import c;
function foo() {
  c.bar();
}
      
Mendeklarasikan grafik dependensi dengan panah yang menghubungkan a, b, dan c
Grafik dependensi yang dideklarasikan
Grafik dependensi aktual yang cocok dengan grafik dependensi
                  yang dideklarasikan dengan panah yang menghubungkan a, b, dan c
Grafik dependensi Aktual

Dependensi yang dinyatakan lebih mendekati dependensi yang sebenarnya. Semuanya baik-baik saja.

2. Menambahkan dependensi yang tidak dideklarasikan

Bahaya laten diperkenalkan saat seseorang menambahkan kode ke a yang membuat dependensi sebenarnya langsung pada c, tetapi lupa mendeklarasikannya dalam file build a/BUILD.

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
Mendeklarasikan grafik dependensi dengan panah yang menghubungkan a, b, dan c
Grafik dependensi yang dideklarasikan
Grafik dependensi aktual dengan panah yang menghubungkan a, b, dan c. Panah sekarang juga menghubungkan A ke C. Ini tidak cocok dengan grafik dependensi yang dideklarasikan
Grafik dependensi Aktual

Dependensi yang dideklarasikan tidak lagi mendekati dependensi sebenarnya. Tindakan ini dapat berjalan dengan baik, karena penutupan transitif kedua grafik sama, tetapi menyamarkan masalah: a memiliki dependensi aktual tetapi tidak dinyatakan pada c.

3. Perbedaan antara grafik dependensi yang dinyatakan dan yang sebenarnya

Bahaya terungkap saat seseorang memfaktorkan ulang b sehingga tidak lagi bergantung pada c, yang secara tidak sengaja merusak a tanpa kegagalan yang terjadi sendiri.

  b/BUILD
 
rule(
    name = "b",
    srcs = "b.in",
    deps = "//d:d",
)
      
  b / b.in
 
      import d;
      function foo() {
        d.baz();
      }
      
Mendeklarasikan grafik dependensi dengan panah yang menghubungkan a dan b.
                  b tidak lagi terhubung ke c, yang memutus koneksi a ke c
Grafik dependensi yang dideklarasikan
Grafik dependensi aktual yang menunjukkan koneksi ke b dan c,
                  tetapi b tidak lagi terhubung ke c
Grafik dependensi Aktual

Grafik dependensi yang dideklarasikan sekarang merupakan perkiraan yang kurang dari dependensi yang sebenarnya, bahkan saat ditutup secara transitif; build kemungkinan akan gagal.

Masalah ini dapat dihindari dengan memastikan bahwa dependensi sebenarnya dari a ke c yang diperkenalkan di Langkah 2 dideklarasikan dengan benar dalam file BUILD.

Jenis-jenis dependensi

Sebagian besar aturan build memiliki tiga atribut untuk menentukan berbagai jenis dependensi umum: srcs, deps, dan data. Hal tersebut dijelaskan di bawah. Untuk mengetahui detail selengkapnya, lihat Atribut yang umum untuk semua aturan.

Banyak aturan juga memiliki atribut tambahan untuk jenis dependensi khusus aturan, misalnya, compiler atau resources. Hal ini dijelaskan dalam Build Encyclopedia.

srcs dependensi

File yang digunakan langsung oleh aturan atau aturan yang menghasilkan file sumber.

deps dependensi

Aturan yang mengarah ke modul yang dikompilasi secara terpisah yang menyediakan file header, simbol, library, data, dll.

data dependensi

Target build mungkin memerlukan beberapa file data agar dapat berjalan dengan benar. File data ini bukan kode sumber: file ini tidak memengaruhi cara target dibuat. Misalnya, pengujian unit mungkin membandingkan output fungsi dengan isi file. Saat mem-build pengujian unit, Anda tidak memerlukan file tersebut, tetapi akan memerlukannya saat menjalankan pengujian. Hal yang sama berlaku untuk alat yang diluncurkan selama eksekusi.

Sistem build menjalankan pengujian di direktori terisolasi tempat hanya file yang tercantum sebagai data yang tersedia. Oleh karena itu, jika biner/library/pengujian memerlukan beberapa file untuk dijalankan, tentukan file tersebut (atau aturan build yang berisi file tersebut) di data. Contoh:

# I need a config file from a directory named env:
java_binary(
    name = "setenv",
    ...
    data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
    name = "regtest",
    srcs = ["regtest.sh"],
    data = [
        "//data:file1.txt",
        "//data:file2.txt",
        ...
    ],
)

File ini tersedia menggunakan jalur relatif path/to/data/file. Dalam pengujian, Anda dapat merujuk ke file ini dengan menggabungkan jalur direktori sumber pengujian dan jalur relatif ruang kerja, misalnya, ${TEST_SRCDIR}/workspace/path/to/data/file.

Menggunakan label untuk mereferensikan direktori

Saat memeriksa file BUILD, Anda mungkin melihat bahwa beberapa label data mengacu ke direktori. Label ini diakhiri dengan /. atau / seperti contoh berikut, yang tidak boleh digunakan:

Tidak direkomendasikandata = ["//data/regression:unittest/."]

Tidak direkomendasikandata = ["testdata/."]

Tidak direkomendasikandata = ["testdata/"]

Hal ini tampaknya praktis, terutama untuk pengujian karena memungkinkan pengujian untuk menggunakan semua file data dalam direktori.

Tapi cobalah untuk tidak melakukannya. Untuk memastikan rebuild inkremental yang benar (dan eksekusi ulang pengujian) setelah perubahan, sistem build harus mengetahui kumpulan file lengkap yang merupakan input untuk build (atau pengujian). Saat Anda menentukan direktori, sistem build akan melakukan build ulang hanya saat direktori itu sendiri berubah (karena penambahan atau penghapusan file), tetapi tidak akan dapat mendeteksi pengeditan pada setiap file karena perubahan tersebut tidak memengaruhi direktori yang dicakup. Daripada menetapkan direktori sebagai input untuk sistem build, sebaiknya enumerasi kumpulan file yang terdapat di dalamnya, baik secara eksplisit maupun menggunakan fungsi glob(). (Gunakan ** untuk memaksa glob() menjadi rekursif.)

Direkomendasikandata = glob(["testdata/**"])

Sayangnya, ada beberapa skenario di mana label direktori harus digunakan. Misalnya, jika direktori testdata berisi file yang namanya tidak sesuai dengan sintaksis label, enumerasi file yang eksplisit atau penggunaan fungsi glob() akan menghasilkan error label yang tidak valid. Dalam hal ini, Anda harus menggunakan label direktori, tetapi waspadalah terhadap risiko terkait pembuatan ulang yang salah seperti yang dijelaskan di atas.

Jika Anda harus menggunakan label direktori, ingat bahwa Anda tidak dapat merujuk ke paket induk dengan jalur ../ relatif; sebagai gantinya, gunakan jalur absolut seperti //data/regression:unittest/..

Aturan eksternal apa pun, seperti pengujian, yang perlu menggunakan beberapa file harus secara eksplisit mendeklarasikan dependensinya pada semuanya. Anda dapat menggunakan filegroup() untuk mengelompokkan file dalam file BUILD:

filegroup(
        name = 'my_data',
        srcs = glob(['my_unittest_data/*'])
)

Selanjutnya, Anda dapat mereferensikan label my_data sebagai dependensi data dalam pengujian Anda.

BANGUN file Visibilitas