บทแนะนำ 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
└── WORKSPACE

สร้างด้วย Bazel

ตั้งค่าพื้นที่ทำงาน

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

  • ไฟล์ WORKSPACE ซึ่งระบุไดเรกทอรีและเนื้อหาในไดเรกทอรีเป็น Bazel Workspace และอยู่ที่รูทของโครงสร้างไดเรกทอรีของโปรเจ็กต์

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

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

เมื่อ 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 และสคริปต์ Shell 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

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

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

ในไฟล์ 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

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

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

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