บทแนะนำ Bazel: สร้างโปรเจ็กต์ Java

รายงานปัญหา ดูแหล่งที่มา ตอนกลางคืน · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

บทแนะนำนี้ครอบคลุมพื้นฐานการสร้างแอปพลิเคชัน Java ด้วย Bazel คุณจะตั้งค่าพื้นที่ทำงานและสร้างโปรเจ็กต์ Java แบบง่ายๆ ที่ แสดงแนวคิดหลักของ Bazel เช่น เป้าหมายและไฟล์ BUILD

เวลาที่ใช้ดำเนินการจนเสร็จสิ้นโดยประมาณ: 30 นาที

สิ่งที่คุณจะได้เรียนรู้

ในบทแนะนำนี้ คุณจะได้เรียนรู้วิธีต่อไปนี้

  • สร้างเป้าหมาย
  • แสดงภาพทรัพยากร Dependency ของโปรเจ็กต์
  • แบ่งโปรเจ็กต์ออกเป็นหลายเป้าหมายและแพ็กเกจ
  • ควบคุมระดับการเข้าถึงเป้าหมายในแพ็กเกจต่างๆ
  • อ้างอิงเป้าหมายผ่านป้ายกำกับ
  • ทำให้เป้าหมายใช้งานได้

ก่อนเริ่มต้น

ติดตั้ง Bazel

หากต้องการเตรียมพร้อมสําหรับบทแนะนำ ให้ติดตั้ง Bazel ก่อนหาก คุณยังไม่ได้ติดตั้ง

ติดตั้ง JDK

  1. ติดตั้ง Java JDK (เวอร์ชันที่ต้องการคือ 11 แต่รองรับเวอร์ชัน 8 ถึง 15)

  2. ตั้งค่าตัวแปรสภาพแวดล้อม JAVA_HOME ให้ชี้ไปที่ JDK

    • ใน Linux/macOS:

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

      1. เปิดแผงควบคุม
      2. ไปที่ "ระบบและความปลอดภัย" "ระบบ" "การตั้งค่าระบบขั้นสูง" "ขั้นสูง" แท็บ > "ตัวแปรสภาพแวดล้อม..."
      3. ใต้ "ตัวแปรผู้ใช้" รายการ (รายการด้านบนสุด) ให้คลิก "ใหม่..."
      4. ใน "ชื่อตัวแปร" ให้ป้อน JAVA_HOME
      5. คลิก "เรียกดูไดเรกทอรี..."
      6. ไปที่ไดเรกทอรี JDK (เช่น C:\Program Files\Java\jdk1.8.0_152)
      7. คลิก "ตกลง" ในหน้าต่างกล่องโต้ตอบทั้งหมด

รับโปรเจ็กต์ตัวอย่าง

เรียกข้อมูลโปรเจ็กต์ตัวอย่างจากที่เก็บ 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 Workspace และอยู่ที่รูทของโครงสร้างไดเรกทอรีของโปรเจ็กต์

  • ไฟล์ BUILD อย่างน้อย 1 ไฟล์ ซึ่งบอก Bazel ถึงวิธีสร้างส่วนต่างๆ ของ ให้กับโครงการ (ไดเรกทอรีภายในพื้นที่ทํางานที่มีไฟล์ BUILD เป็นแพ็กเกจ คุณจะได้เรียนรู้เกี่ยวกับแพ็กเกจภายหลังในบทแนะนำนี้)

หากต้องการกำหนดไดเรกทอรีเป็นพื้นที่ทำงาน Bazel ให้สร้างไฟล์เปล่าชื่อ MODULE.bazel ในไดเรกทอรีนั้น

เมื่อ Bazel สร้างโปรเจ็กต์ อินพุตและการอ้างอิงทั้งหมดต้องเหมือนกัน Google Workspace ได้อย่างเต็มประสิทธิภาพ ไฟล์ที่อยู่ในพื้นที่ทำงานที่ต่างกันจะแยกออกจากกันเป็น 1 ไฟล์ วิดีโออื่นเว้นแต่จะมีการลิงก์ไว้ ซึ่งเป็นสิ่งที่อยู่นอกเหนือขอบเขตของบทแนะนำนี้

ทำความเข้าใจไฟล์ 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 และสคริปต์ Wrapper ของ Shell (ซึ่งทั้งคู่ตั้งชื่อตามเป้าหมาย)

แอตทริบิวต์ในเป้าหมายระบุทรัพยากร 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 ของโฮสต์และโดยนัย) และจัดรูปแบบ เป็นกราฟ

จากนั้นวางข้อความใน GraphViz

คุณจะเห็นได้ว่า โปรเจ็กต์นี้มีเป้าหมายเดียวที่สร้างไฟล์ต้นฉบับ 2 ไฟล์ด้วย ไม่มีการอ้างอิงเพิ่มเติม:

กราฟการขึ้นต่อกันของเป้าหมาย "ProjectRunner"

หลังจากตั้งค่าพื้นที่ทำงานแล้ว ให้สร้างโปรเจ็กต์และตรวจสอบ ทรัพยากร 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 //: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 ขึ้นอยู่กับ ป้อนข้อมูลเหมือนเดิม แต่โครงสร้างของบิลด์จะแตกต่างออกไป

กราฟการขึ้นต่อกันของเป้าหมาย "ProjectRunner" หลังจากเพิ่มทรัพยากร Dependency

ตอนนี้คุณได้สร้างโปรเจ็กต์ที่มีเป้าหมาย 2 รายการแล้ว บิลด์ ProjectRunner เป้าหมาย ไฟล์ต้นฉบับ 1 ไฟล์และขึ้นอยู่กับเป้าหมายอื่นอีก 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 แพ็กเกจ กำหนดเป้าหมายและทำความเข้าใจการพึ่งพากันระหว่างกัน

ใช้ป้ายกำกับเพื่ออ้างอิงเป้าหมาย

ในไฟล์ 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 ไปยังคลาสพาธ ดังนั้นหากคุณปล่อยไว้แบบนี้ รายการก็จะทำงานภายใน แต่ จะไม่ทำงานแบบสแตนด์อโลนในเครื่องอื่น โชคดีที่กฎ 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

อ่านเพิ่มเติม

โปรดดูรายละเอียดเพิ่มเติมที่หัวข้อต่อไปนี้

ขอให้สนุกกับการสร้าง