บทแนะนำนี้ครอบคลุมพื้นฐานการสร้างแอปพลิเคชัน 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ซึ่งระบุไดเรกทอรีและเนื้อหาเป็นพื้นที่ทำงานของ Bazel และอยู่ในรูทของโครงสร้างไดเรกทอรีของโปรเจ็กต์ไฟล์
BUILDอย่างน้อย 1 ไฟล์ ซึ่งบอก Bazel ถึงวิธีสร้างส่วนต่างๆ ของโปรเจ็กต์ (ไดเรกทอรีภายในพื้นที่ทำงานที่มีไฟล์BUILDคือ แพ็กเกจ คุณจะได้เรียนรู้เกี่ยวกับแพ็กเกจในส่วนอื่นของบทแนะนำนี้)
หากต้องการกำหนดไดเรกทอรีเป็นพื้นที่ทำงานของ Bazel ให้สร้างไฟล์ว่างชื่อ MODULE.bazel ในไดเรกทอรีนั้น
เมื่อ Bazel สร้างโปรเจ็กต์ อินพุตและทรัพยากร Dependency ทั้งหมดต้องอยู่ในพื้นที่ทำงานเดียวกัน ไฟล์ที่อยู่ในพื้นที่ทำงานต่างๆ จะเป็นอิสระต่อกัน เว้นแต่จะมีการลิงก์ ซึ่งอยู่นอกเหนือขอบเขตของบทแนะนำนี้
ทำความเข้าใจไฟล์ BUILD
ไฟล์ BUILD มีคำแนะนำหลายประเภทสำหรับ Bazel
ประเภทที่สำคัญที่สุดคือ กฎบิลด์ ซึ่งบอก Bazel ถึงวิธีสร้าง
เอาต์พุตที่ต้องการ เช่น ไบนารีที่เรียกใช้งานได้หรือไลบรารี อินสแตนซ์แต่ละรายการของกฎบิลด์ในไฟล์ BUILD เรียกว่า เป้าหมาย และชี้ไปยังชุดไฟล์ต้นฉบับและทรัพยากร Dependency ที่เฉพาะเจาะจง เป้าหมายยังชี้ไปยังเป้าหมายอื่นๆ ได้ด้วย
ลองดูไฟล์ java-tutorial/BUILD
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
ในตัวอย่างของเรา เป้าหมาย ProjectRunner จะสร้างอินสแตนซ์ของกฎในตัวของ Bazel
java_binary กฎนี้จะบอก Bazel ให้สร้างไฟล์ .jar และสคริปต์ของ Shell Wrapper (ทั้ง 2 ไฟล์จะตั้งชื่อตามเป้าหมาย)
แอตทริบิวต์ในเป้าหมายจะระบุทรัพยากร 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ตรวจสอบกราฟทรัพยากร Dependency
Bazel กำหนดให้ประกาศทรัพยากร Dependency ของบิลด์อย่างชัดเจนในไฟล์ BUILD Bazel ใช้คำสั่งเหล่านั้นเพื่อสร้างกราฟทรัพยากร Dependency ของโปรเจ็กต์ ซึ่งช่วยให้สร้างบิลด์แบบเพิ่มทีละส่วนได้อย่างถูกต้อง
หากต้องการแสดงภาพทรัพยากร Dependency ของโปรเจ็กต์ตัวอย่าง คุณสามารถสร้างการแสดงข้อความของกราฟทรัพยากร Dependency ได้โดยเรียกใช้คำสั่งนี้ที่รูทของพื้นที่ทำงาน
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graphคำสั่งข้างต้นจะบอก Bazel ให้ค้นหาทรัพยากร Dependency ทั้งหมดสำหรับเป้าหมาย //:ProjectRunner (ไม่รวมทรัพยากร Dependency ของโฮสต์และทรัพยากร Dependency โดยนัย) และจัดรูปแบบเอาต์พุตเป็นกราฟ
จากนั้นวางข้อความลงใน GraphViz
อย่างที่คุณเห็น โปรเจ็กต์มีเป้าหมายเดียวที่สร้างไฟล์ต้นฉบับ 2 ไฟล์โดยไม่มีทรัพยากร Dependency เพิ่มเติม
หลังจากตั้งค่าพื้นที่ทำงาน สร้างโปรเจ็กต์ และตรวจสอบทรัพยากร 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 จะบอก Bazel ว่าต้องใช้ไลบรารี greeter ในการสร้างไบนารี ProjectRunner
หากต้องการสร้างโปรเจ็กต์เวอร์ชันใหม่นี้ ให้เรียกใช้คำสั่งต่อไปนี้
bazel build //:ProjectRunnerBazel จะสร้างเอาต์พุตที่คล้ายกับเอาต์พุตต่อไปนี้
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 ไฟล์และขึ้นอยู่กับเป้าหมายอื่น (: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:runnerBazel จะสร้างเอาต์พุตที่คล้ายกับเอาต์พุตต่อไปนี้
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 แพ็กเกจ ซึ่งแต่ละแพ็กเกจมีเป้าหมายเดียว และเข้าใจทรัพยากร 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 และสคริปต์ของ Shell 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 ลงใน classpath ดังนั้นหากปล่อยไว้เช่นนี้ สคริปต์จะทำงานในเครื่อง แต่จะไม่ทำงานแบบสแตนด์อโลนในเครื่องอื่น โชคดีที่กฎ java_binary ช่วยให้คุณสร้างไบนารีแบบสแตนด์อโลนที่ทำให้ใช้งานได้ หากต้องการสร้างไบนารี ให้ต่อท้าย _deploy.jar กับชื่อเป้าหมาย
bazel build //src/main/java/com/example/cmdline:runner_deploy.jarBazel จะสร้างเอาต์พุตที่คล้ายกับเอาต์พุตต่อไปนี้
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 ภายนอก เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับการทำงานกับ ที่เก็บในเครื่องและที่เก็บระยะไกล
บทแนะนำการสร้าง C++ เพื่อเริ่มต้นสร้างโปรเจ็กต์ C++ ด้วย Bazel
บทแนะนำแอปพลิเคชัน Android และ iOS) เพื่อเริ่มต้นสร้างแอปพลิเคชันบนอุปกรณ์เคลื่อนที่สำหรับ Android และ iOS ด้วย Bazel
ขอให้สนุกกับการสร้าง