Tutorial Bazel: Mem-build Project Java

Laporkan masalah Lihat sumber

Tutorial ini membahas dasar-dasar pembuatan aplikasi Java dengan Bazel. Anda akan menyiapkan ruang kerja dan membuat project Java sederhana yang menggambarkan konsep utama Bazel, seperti target dan file BUILD.

Estimasi waktu penyelesaian: 30 menit.

Yang akan Anda pelajari

Dalam tutorial ini, Anda akan mempelajari cara:

  • Membuat target
  • Memvisualisasikan dependensi project
  • Memisahkan project menjadi beberapa target dan paket
  • Mengontrol visibilitas target di seluruh paket
  • Mereferensikan target melalui label
  • Men-deploy target

Sebelum memulai

Instal Bazel

Untuk mempersiapkan tutorial, Instal Bazel terlebih dahulu jika Anda belum menginstalnya.

Menginstal JDK

  1. Instal Java JDK (versi yang lebih disukai adalah 11, namun versi antara 8 dan 15 didukung).

  2. Setel variabel lingkungan JAVA_HOME agar mengarah ke JDK.

    • Di Linux/macOS:

      export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
      
    • Di Windows:

      1. Buka Control Panel.
      2. Buka "Sistem dan Keamanan" > "Sistem" > "Setelan Sistem Lanjutan" > tab "Lanjutan" > "Variabel Lingkungan..." .
      3. Pada daftar "Variabel pengguna" (yang ada di atas), klik "Baru...".
      4. Di kolom "Nama variabel", masukkan JAVA_HOME.
      5. Klik "Jelajahi Direktori...".
      6. Buka direktori JDK (misalnya C:\Program Files\Java\jdk1.8.0_152).
      7. Klik "Oke" di semua jendela dialog.

Mendapatkan project contoh

Ambil project contoh dari repositori GitHub Bazel:

git clone https://github.com/bazelbuild/examples

Project contoh untuk tutorial ini berada di direktori examples/java-tutorial dan disusun sebagai berikut:

java-tutorial
├── BUILD
├── src
│   └── main
│       └── java
│           └── com
│               └── example
│                   ├── cmdline
│                   │   ├── BUILD
│                   │   └── Runner.java
│                   ├── Greeting.java
│                   └── ProjectRunner.java
└── MODULE.bazel

Mem-build dengan Bazel

Menyiapkan ruang kerja

Sebelum dapat membuat project, Anda perlu menyiapkan ruang kerjanya. Ruang kerja adalah direktori yang menyimpan file sumber project dan output build Bazel. File ini juga berisi file yang dikenali Bazel sebagai file khusus:

  • File MODULE.bazel, yang mengidentifikasi direktori dan kontennya sebagai ruang kerja Bazel dan berada di root struktur direktori project,

  • Satu atau beberapa file BUILD, yang memberi tahu Bazel cara mem-build berbagai bagian project. (Direktori dalam ruang kerja yang berisi file BUILD adalah paket. Anda akan mempelajari paket nanti dalam tutorial ini.)

Untuk menetapkan direktori sebagai ruang kerja Bazel, buat file kosong bernama MODULE.bazel di direktori tersebut.

Saat Bazel mem-build project, semua input dan dependensi harus berada di ruang kerja yang sama. File yang berada di ruang kerja yang berbeda tidak saling terkait, kecuali jika ditautkan, yang berada di luar cakupan tutorial ini.

Memahami file BUILD

File BUILD berisi beberapa jenis petunjuk untuk Bazel. Jenis yang paling penting adalah aturan build, yang memberi tahu Bazel cara mem-build output yang diinginkan, seperti library atau biner yang dapat dieksekusi. Setiap instance aturan build dalam file BUILD disebut target dan mengarah ke kumpulan file sumber dan dependensi tertentu. Target juga dapat mengarah ke target lain.

Lihat file java-tutorial/BUILD:

java_binary(
    name = "ProjectRunner",
    srcs = glob(["src/main/java/com/example/*.java"]),
)

Dalam contoh ini, target ProjectRunner membuat instance aturan java_binary bawaan Bazel. Aturan ini memberi tahu Bazel untuk membuat file .jar dan skrip shell wrapper (keduanya diberi nama sesuai target).

Atribut dalam target secara eksplisit menyatakan dependensi dan opsi. Meskipun atribut name bersifat wajib, banyak atribut yang bersifat opsional. Misalnya, dalam target aturan ProjectRunner, name adalah nama target, srcs menentukan file sumber yang digunakan Bazel untuk mem-build target, dan main_class menentukan class yang berisi metode utama. (Anda mungkin telah memperhatikan bahwa contoh kami menggunakan glob untuk meneruskan kumpulan file sumber ke Bazel, bukan mencantumkannya satu per satu.)

Mem-build project

Untuk membuat project contoh, buka direktori java-tutorial lalu jalankan:

bazel build //:ProjectRunner

Pada label target, bagian // adalah lokasi file BUILD yang relatif terhadap root ruang kerja (dalam hal ini, root itu sendiri), dan ProjectRunner adalah nama target dalam file BUILD. (Anda akan mempelajari label target secara lebih mendetail di akhir tutorial ini.)

Bazel menghasilkan output yang mirip dengan berikut ini:

   INFO: Found 1 target...
   Target //:ProjectRunner up-to-date:
      bazel-bin/ProjectRunner.jar
      bazel-bin/ProjectRunner
   INFO: Elapsed time: 1.021s, Critical Path: 0.83s

Selamat, Anda baru saja mem-build target Bazel pertama Anda. Bazel menempatkan output build di direktori bazel-bin di root ruang kerja. Jelajahi kontennya untuk mendapatkan ide struktur output Bazel.

Sekarang, uji biner yang baru Anda build:

bazel-bin/ProjectRunner

Meninjau grafik dependensi

Bazel mewajibkan dependensi build untuk dideklarasikan secara eksplisit dalam file BUILD. Bazel menggunakan pernyataan tersebut untuk membuat grafik dependensi project, yang memungkinkan build inkremental yang akurat.

Untuk memvisualisasikan dependensi project contoh, Anda dapat membuat representasi teks dari grafik dependensi dengan menjalankan perintah ini di root ruang kerja:

bazel query  --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph

Perintah di atas memberi tahu Bazel untuk mencari semua dependensi untuk target //:ProjectRunner (tidak termasuk dependensi host dan implisit) dan memformat output sebagai grafik.

Kemudian, tempelkan teks ke GraphViz.

Seperti yang dapat Anda lihat, project memiliki satu target yang mem-build dua file sumber tanpa dependensi tambahan:

Grafik dependensi target 'ProjectRunner'

Setelah menyiapkan ruang kerja, membangun project, dan memeriksa dependensinya, Anda dapat menambahkan beberapa kompleksitas.

Meningkatkan kualitas build Bazel

Meskipun satu target sudah cukup untuk project kecil, sebaiknya bagi project yang lebih besar menjadi beberapa target dan paket untuk memungkinkan build inkremental yang cepat (yaitu, hanya membangun ulang yang berubah) dan mempercepat build dengan mem-build beberapa bagian project sekaligus.

Menentukan beberapa target build

Anda dapat membagi build contoh project menjadi dua target. Ganti konten file java-tutorial/BUILD dengan kode berikut:

java_binary(
    name = "ProjectRunner",
    srcs = ["src/main/java/com/example/ProjectRunner.java"],
    main_class = "com.example.ProjectRunner",
    deps = [":greeter"],
)

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
)

Dengan konfigurasi ini, Bazel akan mem-build library greeter terlebih dahulu, lalu biner ProjectRunner. Atribut deps di java_binary memberi tahu Bazel bahwa library greeter diperlukan untuk mem-build biner ProjectRunner.

Untuk mem-build project versi baru ini, jalankan perintah berikut:

bazel build //:ProjectRunner

Bazel menghasilkan output yang mirip dengan berikut ini:

INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
  bazel-bin/ProjectRunner.jar
  bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s

Sekarang, uji biner yang baru Anda build:

bazel-bin/ProjectRunner

Jika sekarang Anda mengubah ProjectRunner.java dan mem-build ulang project, Bazel hanya akan mengompilasi ulang file tersebut.

Dengan melihat grafik dependensi, Anda dapat melihat bahwa ProjectRunner bergantung pada input yang sama seperti sebelumnya, tetapi struktur build-nya berbeda:

Grafik dependensi target 'ProjectRunner' setelah menambahkan dependensi

Sekarang Anda telah membangun proyek dengan dua target. Target ProjectRunner mem-build satu file sumber dan bergantung pada satu target lain (:greeter), yang mem-build satu file sumber tambahan.

Menggunakan beberapa paket

Sekarang, mari kita bagi project menjadi beberapa paket. Jika Anda melihat direktori src/main/java/com/example/cmdline, Anda dapat melihat bahwa direktori tersebut juga berisi file BUILD, ditambah beberapa file sumber. Oleh karena itu, bagi Bazel, ruang kerja kini berisi dua paket, //src/main/java/com/example/cmdline dan // (karena ada file BUILD di root ruang kerja).

Lihat file src/main/java/com/example/cmdline/BUILD:

java_binary(
    name = "runner",
    srcs = ["Runner.java"],
    main_class = "com.example.cmdline.Runner",
    deps = ["//:greeter"],
)

Target runner bergantung pada target greeter dalam paket // (sehingga label target //:greeter) - Bazel mengetahuinya melalui atribut deps. Lihat grafik dependensi:

Grafik dependensi 'runner' target

Namun, agar build berhasil, Anda harus secara eksplisit memberikan visibilitas target runner dalam //src/main/java/com/example/cmdline/BUILD ke target dalam //BUILD menggunakan atribut visibility. Hal ini karena secara default, target hanya terlihat oleh target lain dalam file BUILD yang sama. (Bazel menggunakan visibilitas target untuk mencegah masalah seperti library yang berisi detail implementasi kebocoran ke API publik.)

Untuk melakukannya, tambahkan atribut visibility ke target greeter di java-tutorial/BUILD seperti yang ditunjukkan di bawah ini:

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
    visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)

Sekarang Anda dapat mem-build paket baru dengan menjalankan perintah berikut di root ruang kerja:

bazel build //src/main/java/com/example/cmdline:runner

Bazel menghasilkan output yang mirip dengan berikut ini:

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner.jar
  bazel-bin/src/main/java/com/example/cmdline/runner
  INFO: Elapsed time: 1.576s, Critical Path: 0.81s

Sekarang, uji biner yang baru Anda build:

./bazel-bin/src/main/java/com/example/cmdline/runner

Sekarang Anda telah memodifikasi project untuk di-build sebagai dua paket, yang masing-masing berisi satu target, dan memahami dependensi di antara keduanya.

Menggunakan label untuk mereferensikan target

Dalam file BUILD dan di command line, Bazel menggunakan label target untuk mereferensikan target - misalnya, //:ProjectRunner atau //src/main/java/com/example/cmdline:runner. Sintaksnya adalah sebagai berikut:

//path/to/package:target-name

Jika targetnya adalah target aturan, path/to/package adalah jalur ke direktori yang berisi file BUILD, dan target-name adalah jalur yang Anda beri nama pada target dalam file BUILD (atribut name). Jika target adalah target file, path/to/package adalah jalur ke root paket, dan target-name adalah nama file target, termasuk jalur lengkapnya.

Saat mereferensikan target di root repositori, jalur paket kosong, cukup gunakan //:target-name. Saat mereferensikan target dalam file BUILD yang sama, Anda bahkan dapat melewati ID root ruang kerja // dan hanya menggunakan :target-name.

Misalnya, untuk target dalam file java-tutorial/BUILD, Anda tidak perlu menentukan jalur paket, karena root ruang kerja itu sendiri adalah paket (//), dan dua label target Anda hanyalah //:ProjectRunner dan //:greeter.

Namun, untuk target dalam file //src/main/java/com/example/cmdline/BUILD, Anda harus menentukan jalur paket lengkap //src/main/java/com/example/cmdline dan label target Anda adalah //src/main/java/com/example/cmdline:runner.

Memaketkan target Java untuk deployment

Sekarang, mari kita paketkan target Java untuk deployment dengan mem-build biner dengan semua dependensi runtime-nya. Hal ini memungkinkan Anda menjalankan biner di luar lingkungan pengembangan.

Seperti yang Anda ingat, aturan build java_binary menghasilkan .jar dan skrip shell wrapper. Lihat isi runner.jar menggunakan perintah ini:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar

Isinya adalah:

META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class

Seperti yang dapat Anda lihat, runner.jar berisi Runner.class, tetapi tidak berisi dependensinya, Greeting.class. Skrip runner yang dihasilkan Bazel menambahkan greeter.jar ke classpath, jadi jika Anda membiarkannya seperti ini, skrip akan berjalan secara lokal, tetapi tidak akan berjalan secara mandiri di komputer lain. Untungnya, aturan java_binary memungkinkan Anda mem-build biner mandiri yang dapat di-deploy. Untuk membuatnya, tambahkan _deploy.jar ke nama target:

bazel build //src/main/java/com/example/cmdline:runner_deploy.jar

Bazel menghasilkan output yang mirip dengan berikut ini:

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s

Anda baru saja membuat runner_deploy.jar, yang dapat dijalankan secara mandiri dari lingkungan pengembangan Anda karena berisi dependensi runtime yang diperlukan. Lihat konten JAR mandiri ini menggunakan perintah yang sama seperti sebelumnya:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar

Kontennya mencakup semua class yang diperlukan untuk dijalankan:

META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class

Bacaan lebih lanjut

Untuk mengetahui detail selengkapnya, lihat:

Selamat membangun!