บทแนะนำนี้ครอบคลุมพื้นฐานการสร้างแอปพลิเคชัน Java ด้วย Bazel คุณจะต้องตั้งค่าพื้นที่ทำงานและสร้างโปรเจ็กต์ Java แบบง่ายๆ ที่แสดงให้เห็นแนวคิดหลักของ Bazel เช่น เป้าหมายและไฟล์ BUILD
เวลาที่ใช้ดำเนินการจนเสร็จโดยประมาณ 30 นาที
สิ่งที่คุณจะได้เรียนรู้
ในบทแนะนำนี้ คุณจะได้เรียนรู้วิธีการต่อไปนี้
- สร้างเป้าหมาย
- แสดงภาพทรัพยากร Dependency ของโปรเจ็กต์
- แยกโปรเจ็กต์ออกเป็นเป้าหมายและแพ็กเกจหลายรายการ
- ควบคุมระดับการเข้าถึงเป้าหมายในทุกแพ็กเกจ
- อ้างอิงเป้าหมายผ่านป้ายกำกับ
- ทำให้เป้าหมายใช้งานได้
ก่อนเริ่มต้น
ติดตั้ง Bazel
ในการเตรียมพร้อมสำหรับบทแนะนำ ให้ติดตั้ง Bazel ก่อนหากยังไม่ได้ติดตั้ง
ติดตั้ง JDK
ติดตั้ง Java JDK (เวอร์ชันที่ต้องการคือ 11 แต่รองรับเวอร์ชันระหว่าง 8 ถึง 15)
ตั้งค่าตัวแปรสภาพแวดล้อม JAVA_HOME ให้ชี้ไปที่ JDK
ใน Linux/macOS:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
บน Windows:
- เปิดแผงควบคุม
- ไปที่ "ระบบและความปลอดภัย" > "ระบบ" > "การตั้งค่าระบบขั้นสูง" > แท็บ "ขั้นสูง" > "ตัวแปรสภาพแวดล้อม..."
- ภายใต้รายการ "ตัวแปรผู้ใช้" (รายการด้านบนสุด) ให้คลิก "ใหม่..."
- ในช่อง "ชื่อตัวแปร" ให้ป้อน
JAVA_HOME
- คลิก "เรียกดูไดเรกทอรี..."
- ไปที่ไดเรกทอรี JDK (เช่น
C:\Program Files\Java\jdk1.8.0_152
) - คลิก "ตกลง" บนหน้าต่างกล่องโต้ตอบทั้งหมด
รับโปรเจ็กต์ตัวอย่าง
เรียกดูโปรเจ็กต์ตัวอย่างจากที่เก็บ GitHub ของ Bazel โดยทำดังนี้
git clone https://github.com/bazelbuild/examples
โปรเจ็กต์ตัวอย่างสำหรับบทแนะนำนี้อยู่ในไดเรกทอรี examples/java-tutorial
และมีโครงสร้างดังนี้
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── MODULE.bazel
สร้างด้วย Bazel
ตั้งค่าพื้นที่ทำงาน
คุณต้องตั้งค่าพื้นที่ทำงานของโปรเจ็กต์ก่อนจึงจะสร้างโปรเจ็กต์ได้ พื้นที่ทำงานคือไดเรกทอรีที่เก็บไฟล์ต้นฉบับของโปรเจ็กต์และเอาต์พุตบิลด์ของ Bazel นอกจากนี้ยังมีไฟล์ที่ Bazel มองว่าเป็นแบบพิเศษ ดังนี้
ไฟล์
MODULE.bazel
ซึ่งระบุไดเรกทอรีและเนื้อหาในไดเรกทอรีเป็นพื้นที่ทำงานแบบบาเซล และอยู่ที่รูทของโครงสร้างไดเรกทอรีของโปรเจ็กต์ไฟล์
BUILD
อย่างน้อย 1 ไฟล์ ซึ่งบอกวิธีการสร้างส่วนต่างๆ ของโปรเจ็กต์ที่ Bazel (ไดเรกทอรีภายในพื้นที่ทำงานที่มีไฟล์BUILD
คือแพ็กเกจ คุณสามารถเรียนรู้เกี่ยวกับแพ็กเกจในภายหลังได้ในบทแนะนำนี้)
หากต้องการกำหนดไดเรกทอรีเป็นพื้นที่ทำงาน Bazel ให้สร้างไฟล์เปล่าชื่อ MODULE.bazel
ในไดเรกทอรีนั้น
เมื่อ Bazel สร้างโปรเจ็กต์ อินพุตและการอ้างอิงทั้งหมดต้องอยู่ในพื้นที่ทำงานเดียวกัน ไฟล์ที่อยู่ในพื้นที่ทำงานต่างๆ จะไม่เป็นอิสระจากที่อื่น เว้นแต่จะลิงก์กันซึ่งอยู่นอกเหนือขอบเขตของบทแนะนำนี้
ทำความเข้าใจไฟล์ BUILD
ไฟล์ BUILD
ประกอบด้วยวิธีการประเภทต่างๆ สำหรับ Bazel
ประเภทที่สำคัญที่สุดคือกฎการสร้าง ซึ่งจะบอก Bazel วิธีสร้างเอาต์พุตที่ต้องการ เช่น ไบนารีหรือไลบรารีที่สั่งการได้ แต่ละอินสแตนซ์ของกฎการสร้างในไฟล์ BUILD
จะเรียกว่าเป้าหมาย และชี้ไปที่ชุดไฟล์ต้นทางและการอ้างอิงเฉพาะ เป้าหมายยังชี้ไปยังเป้าหมายอื่นๆ ได้ด้วย
ดูไฟล์ java-tutorial/BUILD
รายการนี้
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
ในตัวอย่างของเรา เป้าหมาย ProjectRunner
จะสร้างอินสแตนซ์ของกฎ java_binary
ในตัวของ Bazel กฎนี้จะบอกให้ Bazel สร้างไฟล์ .jar
และสคริปต์เชลล์ Wrapper (ตั้งชื่อตามเป้าหมายทั้งสอง)
แอตทริบิวต์ในเป้าหมายจะระบุทรัพยากร Dependency และตัวเลือกอย่างชัดเจน
แม้ว่าแอตทริบิวต์ name
จะเป็นแอตทริบิวต์บังคับ แต่ก็มีหลายรายการที่ไม่จำเป็นต้องระบุ เช่น ในเป้าหมายของกฎ ProjectRunner
name
คือชื่อของเป้าหมาย srcs
จะระบุไฟล์ต้นทางที่ Bazel ใช้ในการสร้างเป้าหมาย และ main_class
ระบุคลาสที่มีเมธอดหลัก (คุณอาจสังเกตเห็นว่าตัวอย่างของเราใช้ glob เพื่อส่งชุดไฟล์ต้นฉบับไปยัง Bazel
แทนการแสดงไฟล์ทีละรายการ)
สร้างโปรเจ็กต์
หากต้องการสร้างโปรเจ็กต์ตัวอย่าง ให้ไปที่ไดเรกทอรี java-tutorial
แล้วเรียกใช้
bazel build //:ProjectRunner
ในป้ายกำกับเป้าหมาย ส่วน //
คือตำแหน่งของไฟล์ BUILD
ที่สัมพันธ์กับรูทของพื้นที่ทำงาน (ในกรณีนี้คือรูทเอง) และ ProjectRunner
เป็นชื่อเป้าหมายในไฟล์ BUILD
(คุณจะได้เรียนรู้เพิ่มเติมเกี่ยวกับป้ายกำกับเป้าหมายในตอนท้ายของบทแนะนำนี้)
Bazel ให้เอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้
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
ยินดีด้วย คุณเพิ่งสร้างเป้าหมาย Bazel แรกของคุณ Bazel จะวางเอาต์พุตบิลด์ในไดเรกทอรี bazel-bin
ที่รูทของพื้นที่ทำงาน เรียกดูเนื้อหาในเนื้อหานั้นเพื่อหาแนวคิดสำหรับโครงสร้างเอาต์พุตของ Bazel
ต่อไปให้ทดสอบไบนารีที่สร้างใหม่
bazel-bin/ProjectRunner
ตรวจสอบกราฟการอ้างอิง
Bazel กำหนดให้ต้องประกาศทรัพยากร Dependency ของบิลด์อย่างชัดเจนในไฟล์ BUILD Bazel จะใช้คำสั่งเหล่านั้นเพื่อสร้างกราฟการอ้างอิงของโปรเจ็กต์ ซึ่งช่วยให้สร้างบิลด์ส่วนเพิ่มที่แม่นยำได้
หากต้องการแสดงภาพทรัพยากร Dependency ของโปรเจ็กต์ตัวอย่าง คุณสร้างการแสดงข้อความของกราฟทรัพยากร Dependency ได้โดยเรียกใช้คำสั่งนี้ที่รูทของพื้นที่ทำงาน
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
คำสั่งด้านบนจะบอกให้ Bazel ค้นหาทรัพยากร Dependency ทั้งหมดสำหรับเป้าหมาย //:ProjectRunner
(ยกเว้นทรัพยากร Dependency ของโฮสต์และการอ้างอิงโดยนัย) และจัดรูปแบบเอาต์พุตเป็นกราฟ
จากนั้นจึงวางข้อความลงใน GraphViz
คุณจะเห็นว่าโปรเจ็กต์มีเป้าหมายเดียวที่สร้างไฟล์ต้นฉบับ 2 ไฟล์โดยไม่มีทรัพยากร Dependency เพิ่มเติม
หลังจากตั้งค่าพื้นที่ทำงาน สร้างโปรเจ็กต์ และตรวจสอบการอ้างอิงของพื้นที่ทำงานแล้ว คุณก็เพิ่มความซับซ้อนได้
ปรับแต่งบิลด์ของ Bazel
แม้ว่าเป้าหมายเดียวก็เพียงพอสำหรับโปรเจ็กต์ขนาดเล็กแล้ว คุณอาจต้องแบ่งโปรเจ็กต์ขนาดใหญ่ออกเป็นหลายเป้าหมายและแพ็กเกจหลายๆ แบบเพื่อให้สร้างงานแบบค่อยเป็นค่อยไปได้อย่างรวดเร็ว (ซึ่งก็คือสร้างเฉพาะสิ่งที่เปลี่ยนแปลงใหม่เท่านั้น) และเพิ่มความเร็วในการสร้างด้วยการสร้างหลายๆ ส่วนของโปรเจ็กต์พร้อมกัน
ระบุเป้าหมายบิลด์หลายรายการ
คุณสามารถแยกบิลด์ของโปรเจ็กต์ตัวอย่างออกเป็น 2 เป้าหมายได้ แทนที่เนื้อหาของไฟล์ java-tutorial/BUILD
ด้วยข้อมูลต่อไปนี้
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"],
)
ด้วยการกำหนดค่านี้ Bazel จะสร้างไลบรารี greeter
ก่อน จากนั้นจึงสร้างไบนารี ProjectRunner
แอตทริบิวต์ deps
ใน java_binary
จะบอกบาเซิลว่าไลบรารี greeter
จำเป็นต้องสร้างไบนารี ProjectRunner
หากต้องการสร้างโปรเจ็กต์เวอร์ชันใหม่นี้ ให้เรียกใช้คำสั่งต่อไปนี้
bazel build //:ProjectRunner
Bazel ให้เอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้
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
ต่อไปให้ทดสอบไบนารีที่สร้างใหม่
bazel-bin/ProjectRunner
หากแก้ไข ProjectRunner.java
และสร้างโปรเจ็กต์ใหม่ Bazel จะรวบรวมข้อมูลไฟล์นั้นอีกครั้งเท่านั้น
เมื่อดูกราฟการใช้ทรัพยากร Dependency แล้ว คุณจะเห็นว่า ProjectRunner
อาศัยอินพุตเดียวกันกับที่เคยทำก่อนหน้านี้ แต่โครงสร้างของบิลด์จะแตกต่างกัน ดังนี้
ตอนนี้คุณได้สร้างโปรเจ็กต์ที่มี 2 เป้าหมายแล้ว เป้าหมาย ProjectRunner
จะสร้างไฟล์แหล่งที่มา 2 ไฟล์และขึ้นอยู่กับเป้าหมายอีก 1 รายการ (:greeter
) ซึ่งจะสร้างไฟล์ต้นทางเพิ่มเติม 1 ไฟล์
ใช้แพ็กเกจหลายรายการ
ตอนนี้เรามาแบ่งโปรเจ็กต์ออกเป็นหลายๆ แพ็กเกจ หากคุณดูไดเรกทอรี src/main/java/com/example/cmdline
คุณจะเห็นว่าไดเรกทอรีดังกล่าวมีไฟล์ BUILD
รวมถึงไฟล์ต้นฉบับบางรายการด้วย ดังนั้นสำหรับ Bazel แล้วตอนนี้พื้นที่ทำงานมี 2 แพ็กเกจ ได้แก่ //src/main/java/com/example/cmdline
และ //
(เนื่องจากมีไฟล์ BUILD
ที่รูทของพื้นที่ทำงาน)
ดูไฟล์ src/main/java/com/example/cmdline/BUILD
รายการนี้
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
เป้าหมาย runner
ขึ้นอยู่กับเป้าหมาย greeter
ในแพ็กเกจ //
(ดังนั้นป้ายกำกับเป้าหมาย //:greeter
) - Bazel จะทราบเรื่องนี้ผ่านแอตทริบิวต์ deps
ดูกราฟทรัพยากร Dependency
อย่างไรก็ตาม คุณต้องให้ระดับการเข้าถึงเป้าหมาย runner
ใน //src/main/java/com/example/cmdline/BUILD
แก่เป้าหมายใน //BUILD
อย่างชัดแจ้งโดยใช้แอตทริบิวต์ visibility
เพื่อให้บิลด์ประสบความสำเร็จ ทั้งนี้เนื่องจากโดยค่าเริ่มต้น เป้าหมายอื่นจะแสดงต่อเป้าหมายอื่นในไฟล์ BUILD
เดียวกันเท่านั้น (Bazel ใช้ระดับการเข้าถึงเป้าหมายเพื่อป้องกันปัญหาต่างๆ เช่น ไลบรารีที่มีรายละเอียดการใช้งานที่รั่วไหลเข้าสู่ API สาธารณะ)
โดยเพิ่มแอตทริบิวต์ visibility
ไปยังเป้าหมาย greeter
ใน
java-tutorial/BUILD
ดังที่แสดงด้านล่าง
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
ตอนนี้คุณสร้างแพ็กเกจใหม่ได้โดยเรียกใช้คำสั่งต่อไปนี้ที่รูทของพื้นที่ทำงาน
bazel build //src/main/java/com/example/cmdline:runner
Bazel ให้เอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้
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
ต่อไปให้ทดสอบไบนารีที่สร้างใหม่
./bazel-bin/src/main/java/com/example/cmdline/runner
ตอนนี้คุณได้แก้ไขโปรเจ็กต์เพื่อสร้างเป็น 2 แพ็กเกจ โดยแต่ละแพ็กเกจมี 1 เป้าหมาย และทำความเข้าใจทรัพยากร Dependency ของแพ็กเกจเหล่านั้น
ใช้ป้ายกำกับเพื่ออ้างอิงเป้าหมาย
ใน BUILD
ไฟล์และที่บรรทัดคำสั่ง Bazel จะใช้ป้ายกำกับเป้าหมายเพื่ออ้างอิงเป้าหมาย เช่น //:ProjectRunner
หรือ //src/main/java/com/example/cmdline:runner
ไวยากรณ์มีดังนี้
//path/to/package:target-name
หากเป้าหมายเป็นเป้าหมายของกฎ path/to/package
จะเป็นเส้นทางไปยังไดเรกทอรีที่มีไฟล์ BUILD
และ target-name
คือชื่อที่คุณตั้งชื่อเป้าหมายในไฟล์ BUILD
(แอตทริบิวต์ name
) หากเป้าหมายคือไฟล์เป้าหมาย path/to/package
คือเส้นทางไปยังรูทของแพ็กเกจ และ target-name
คือชื่อไฟล์เป้าหมายรวมถึงเส้นทางแบบเต็ม
เมื่ออ้างอิงเป้าหมายที่รูทของที่เก็บ เส้นทางแพ็กเกจจะว่างเปล่า เพียงใช้ //:target-name
เมื่ออ้างอิงเป้าหมายภายในไฟล์ BUILD
เดียวกัน คุณสามารถข้ามตัวระบุรูทของพื้นที่ทำงาน //
และใช้เฉพาะ :target-name
ได้ด้วย
เช่น สำหรับเป้าหมายในไฟล์ java-tutorial/BUILD
คุณไม่จำเป็นต้องระบุเส้นทางแพ็กเกจ เนื่องจากรูทของพื้นที่ทำงานเป็นแพ็กเกจ (//
) และป้ายกำกับเป้าหมายทั้ง 2 รายการมีเพียง //:ProjectRunner
และ //:greeter
อย่างไรก็ตาม สำหรับเป้าหมายในไฟล์ //src/main/java/com/example/cmdline/BUILD
คุณต้องระบุเส้นทางแพ็กเกจแบบเต็มของ //src/main/java/com/example/cmdline
และป้ายกำกับเป้าหมายคือ //src/main/java/com/example/cmdline:runner
สร้างแพ็กเกจเป้าหมาย Java สำหรับการทำให้ใช้งานได้
มาสร้างแพ็กเกจเป้าหมาย Java สำหรับการทำให้ใช้งานได้โดยการสร้างไบนารีด้วยทรัพยากร Dependency ของรันไทม์ทั้งหมด วิธีนี้ช่วยให้คุณเรียกใช้ไบนารีนอกสภาพแวดล้อมการพัฒนาได้
อย่าลืมว่ากฎการสร้าง java_binary จะสร้าง .jar
และสคริปต์เชลล์ Wrapper ดูเนื้อหาของ runner.jar
โดยใช้คำสั่งนี้
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
เนื้อหามีดังนี้
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
คุณจะเห็นได้ว่า runner.jar
มี Runner.class
แต่ไม่ใช่ทรัพยากร Dependency Greeting.class
สคริปต์ runner
ที่ Bazel สร้างจะเพิ่ม greeter.jar
ไปยังคลาสพาธ ดังนั้นหากคุณปล่อยไว้แบบนี้ สคริปต์จะทำงานภายในเครื่อง แต่จะไม่ทำงานแบบสแตนด์อโลนในเครื่องอื่น โชคดีที่กฎ java_binary
อนุญาตให้คุณสร้างไบนารีที่มีในตัวเองและทำให้ใช้งานได้ได้ หากต้องการสร้างค่านี้ ให้เพิ่ม _deploy.jar
ต่อท้ายชื่อเป้าหมาย ดังนี้
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel ให้เอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้
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
คุณเพิ่งสร้าง runner_deploy.jar
ซึ่งจะเรียกใช้แบบสแตนด์อโลนจากสภาพแวดล้อมการพัฒนาได้เนื่องจากมีทรัพยากร Dependency ของรันไทม์ที่จำเป็น ดูเนื้อหาของ JAR แบบสแตนด์อโลนนี้โดยใช้คำสั่งเดียวกับก่อนหน้านี้
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
เนื้อหาจะมีคลาสที่จำเป็นทั้งหมดในการเรียกใช้
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
อ่านเพิ่มเติม
โปรดดูรายละเอียดเพิ่มเติมจากหัวข้อต่อไปนี้
rules_jvm_external สำหรับกฎในการจัดการทรัพยากร Dependency ของ Maven
ทรัพยากร Dependency ภายนอกเพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับการทำงานกับที่เก็บในเครื่องและที่เก็บระยะไกล
กฎอื่นๆ หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับ Bazel
บทแนะนำการสร้าง C++ สำหรับการเริ่มสร้างโปรเจ็กต์ C++ ด้วย Bazel
บทแนะนำแอปพลิเคชัน Android และบทแนะนำแอปพลิเคชัน iOS) เพื่อเริ่มต้นสร้างแอปพลิเคชันบนอุปกรณ์เคลื่อนที่สำหรับ Android และ iOS ด้วย Bazel
ขอให้สนุกกับการสร้าง