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

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

บทนำ

หากเพิ่งเริ่มใช้ Bazel คุณมาถูกที่แล้ว ทำตามบทแนะนำการสร้างครั้งแรกนี้เพื่อรับข้อมูลเบื้องต้นที่เข้าใจง่ายเกี่ยวกับการใช้ Bazel บทแนะนำนี้จะอธิบายคำศัพท์สำคัญตามบริบทของ Bazel และแนะนำเวิร์กโฟลว์พื้นฐานของ Bazel คุณจะเริ่มต้นด้วยเครื่องมือที่จําเป็น จากนั้นจะสร้างและเรียกใช้โปรเจ็กต์ 3 รายการที่มีความซับซ้อนมากขึ้น และเรียนรู้วิธีและเหตุผลที่โปรเจ็กต์มีความซับซ้อนมากขึ้น

แม้ว่า Bazel จะเป็นระบบการสร้างที่รองรับการสร้างหลายภาษา แต่บทแนะนำนี้จะใช้โปรเจ็กต์ C++ เป็นตัวอย่าง รวมถึงให้หลักเกณฑ์และขั้นตอนทั่วไปที่ใช้ได้กับภาษาส่วนใหญ่

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

ข้อกำหนดเบื้องต้น

เริ่มต้นด้วยการติดตั้ง Bazel หากยังไม่ได้ดำเนินการ บทแนะนำนี้ใช้ Git สำหรับการควบคุมแหล่งที่มา ดังนั้นโปรดติดตั้ง Git ด้วยเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด

ถัดไป ให้ดึงข้อมูลโปรเจ็กต์ตัวอย่างจากที่เก็บ GitHub ของ Bazel โดยเรียกใช้คำสั่งต่อไปนี้ในเครื่องมือบรรทัดคำสั่งที่คุณเลือก

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

โปรเจ็กต์ตัวอย่างสำหรับบทแนะนำนี้อยู่ในไดเรกทอรี examples/cpp-tutorial

ลองดูโครงสร้างของระบบด้านล่างนี้

examples
└── cpp-tutorial
    ├──stage1
      ├── main
         ├── BUILD
         └── hello-world.cc
      └── WORKSPACE
    ├──stage2
      ├── main
         ├── BUILD
         ├── hello-world.cc
         ├── hello-greet.cc
         └── hello-greet.h
      └── WORKSPACE
    └──stage3
       ├── main
          ├── BUILD
          ├── hello-world.cc
          ├── hello-greet.cc
          └── hello-greet.h
       ├── lib
          ├── BUILD
          ├── hello-time.cc
          └── hello-time.h
       └── WORKSPACE

ไฟล์มี 3 ชุด โดยแต่ละชุดแสดงขั้นตอนในบทแนะนำนี้ ในขั้นแรก คุณจะต้องสร้างเป้าหมายเดียวที่อยู่ในแพ็กเกจเดียว ในขั้นตอนที่ 2 คุณจะสร้างทั้งไฟล์ไบนารีและไลบรารีจากแพ็กเกจเดียว ในขั้นตอนที่ 3 ซึ่งเป็นขั้นตอนสุดท้าย คุณจะต้องสร้างโปรเจ็กต์ที่มีแพ็กเกจหลายรายการและสร้างโปรเจ็กต์โดยมีเป้าหมายหลายรายการ

สรุป: บทนำ

การติดตั้ง Bazel (และ Git) และโคลนที่เก็บสำหรับบทแนะนำนี้เป็นการปูพื้นฐานสำหรับบิลด์แรกด้วย Bazel ไปที่ส่วนถัดไปเพื่อกำหนดคำศัพท์บางอย่างและตั้งค่าพื้นที่ทำงาน

เริ่มต้นใช้งาน

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

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

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

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

หมายเหตุ: เมื่อ Bazel บิลด์โปรเจ็กต์ อินพุตทั้งหมดต้องอยู่ในเวิร์กスペースเดียวกัน ไฟล์ที่อยู่ในพื้นที่ทำงานที่ต่างกันจะเป็นอิสระต่อกัน เว้นแต่จะมีการเชื่อมโยงกัน ดูข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับกฎพื้นที่ทำงานได้ในคู่มือนี้

ทำความเข้าใจไฟล์ BUILD

ไฟล์ BUILD มีคำสั่งหลายประเภทสำหรับ Bazel ไฟล์ BUILD แต่ละไฟล์ต้องมีกฎอย่างน้อย 1 รายการเป็นชุดคำสั่งที่จะบอก Bazel ว่าจะสร้างเอาต์พุตที่ต้องการอย่างไร เช่น ไฟล์ปฏิบัติการหรือไลบรารี อินสแตนซ์แต่ละรายการของกฎการสร้างในไฟล์ BUILD เรียกว่าเป้าหมาย และชี้ไปยังชุดไฟล์ต้นทางและข้อกําหนดเบื้องต้นที่เฉพาะเจาะจง เป้าหมายชี้ไปยังเป้าหมายอื่นได้ด้วย

ดูไฟล์ BUILD ในไดเรกทอรี cpp-tutorial/stage1/main

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

ในตัวอย่างของเรา เป้าหมาย hello-world จะสร้างอินสแตนซ์ของ cc_binary rule ในตัว Bazel กฎนี้จะบอกให้ Bazel สร้างไฟล์ปฏิบัติการแบบสแตนด์อโลนจากไฟล์ต้นฉบับ hello-world.cc ที่ไม่มี Dependency

สรุป: การเริ่มต้นใช้งาน

ตอนนี้คุณคุ้นเคยกับคําศัพท์สําคัญบางคําและความหมายของคําเหล่านั้นในบริบทของโปรเจ็กต์นี้และ Bazel โดยทั่วไปแล้ว ในส่วนถัดไป คุณจะต้องสร้างและทดสอบระยะที่ 1 ของโปรเจ็กต์

ระยะที่ 1: เป้าหมายเดียว แพ็กเกจเดียว

ได้เวลาสร้างโปรเจ็กต์ส่วนแรกแล้ว โครงสร้างของส่วนระยะที่ 1 ของโปรเจ็กต์มีดังนี้สำหรับใช้เป็นข้อมูลอ้างอิง

examples
└── cpp-tutorial
    └──stage1
       ├── main
          ├── BUILD
          └── hello-world.cc
       └── WORKSPACE

เรียกใช้คำสั่งต่อไปนี้เพื่อย้ายไปยังไดเรกทอรี cpp-tutorial/stage1

cd cpp-tutorial/stage1

ต่อไป ให้เรียกใช้

bazel build //main:hello-world

ในป้ายกำกับเป้าหมาย ส่วน //main: คือตำแหน่งของไฟล์ BUILD ที่สัมพันธ์กับรูทของพื้นที่ทำงาน และ hello-world คือชื่อเป้าหมายในไฟล์ BUILD

Bazel จะสร้างสิ่งที่มีลักษณะดังนี้

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

คุณเพิ่งสร้างเป้าหมาย Bazel รายการแรก Bazel จะวางเอาต์พุตการสร้างในไดเรกทอรี bazel-bin ที่รูทของเวิร์กช็อป

ตอนนี้ให้ทดสอบไบนารีที่เพิ่งสร้างขึ้น ซึ่งก็คือ

bazel-bin/main/hello-world

การดำเนินการนี้ส่งผลให้ข้อความ "Hello world" แสดงในการพิมพ์

กราฟการขึ้นต่อกันของระยะที่ 1 มีดังนี้

กราฟความเกี่ยวข้องของ hello-world แสดงเป้าหมายรายการเดียวที่มีไฟล์ต้นฉบับรายการเดียว

สรุป: ระยะที่ 1

เมื่อคุณสร้างงานสร้างแรกเสร็จแล้ว คุณจะมีไอเดียพื้นฐานว่างานสร้างมีโครงสร้างอย่างไร ในขั้นตอนถัดไป คุณจะเพิ่มความซับซ้อนโดยการเพิ่มเป้าหมายอื่น

ระยะที่ 2: เป้าหมายการสร้างหลายรายการ

แม้ว่าเป้าหมายเดียวจะเพียงพอสําหรับโปรเจ็กต์ขนาดเล็ก แต่คุณอาจต้องแยกโปรเจ็กต์ขนาดใหญ่ออกเป็นหลายเป้าหมายและแพ็กเกจ วิธีนี้ช่วยให้การบิลด์แบบเพิ่มข้อมูลทำได้อย่างรวดเร็ว กล่าวคือ Bazel จะสร้างเฉพาะสิ่งที่เปลี่ยนแปลงไปเท่านั้น และช่วยเร่งความเร็วในการบิลด์ด้วยการสร้างหลายส่วนของโปรเจ็กต์พร้อมกัน ขั้นตอนนี้ของบทแนะนำจะเพิ่มเป้าหมาย ส่วนถัดไปจะเพิ่มแพ็กเกจ

นี่คือไดเรกทอรีที่คุณกำลังใช้งานสำหรับระยะที่ 2:

    ├──stage2
      ├── main
         ├── BUILD
         ├── hello-world.cc
         ├── hello-greet.cc
         └── hello-greet.h
      └── WORKSPACE

โปรดดูไฟล์ BUILD ในไดเรกทอรี cpp-tutorial/stage2/main ด้านล่าง

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

เมื่อใช้ไฟล์ BUILD นี้ Bazel จะสร้างไลบรารี hello-greet ก่อน (โดยใช้ cc_library rule ในตัวของ Bazel) จากนั้นจึงสร้างไฟล์ไบนารี hello-world แอตทริบิวต์ deps ในเป้าหมาย hello-world จะบอก Bazel ว่าไลบรารี hello-greet จำเป็นต้องสร้างไบนารี hello-world

คุณต้องเปลี่ยนไดเรกทอรีและเปลี่ยนเป็นไดเรกทอรี cpp-tutorial/stage2 โดยเรียกใช้รายการต่อไปนี้ก่อนสร้างโปรเจ็กต์เวอร์ชันใหม่นี้

cd ../stage2

ตอนนี้คุณสร้างไบนารีใหม่ได้โดยใช้คําสั่งที่คุ้นเคยต่อไปนี้

bazel build //main:hello-world

Bazel ผลิตเนื้อหาที่มีลักษณะเช่นนี้อีกแล้ว

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.399s, Critical Path: 0.30s

ตอนนี้คุณทดสอบไบนารีที่สร้างใหม่ได้แล้ว ซึ่งจะแสดงผล “Hello world” อีกรายการหนึ่ง:

bazel-bin/main/hello-world

ตอนนี้หากคุณแก้ไข hello-greet.cc และสร้างโปรเจ็กต์อีกครั้ง Bazel จะคอมไพล์ไฟล์นั้นอีกครั้งเท่านั้น

เมื่อดูกราฟทรัพยากร Dependency คุณจะเห็นว่า hello-world ขึ้นอยู่กับอินพุตเพิ่มเติมที่มีชื่อว่า hello-greet

กราฟทรัพยากร Dependency ของ `hello-world` แสดงการเปลี่ยนแปลงทรัพยากร Dependency หลังจากแก้ไขไฟล์

สรุป: ระยะที่ 2

ตอนนี้คุณได้สร้างโปรเจ็กต์ที่มีเป้าหมาย 2 รายการแล้ว เป้าหมาย hello-world จะสร้างไฟล์ต้นฉบับ 1 ไฟล์และขึ้นอยู่กับเป้าหมายอื่น 1 รายการ (//main:hello-greet) ซึ่งจะสร้างไฟล์ต้นฉบับอีก 2 ไฟล์ ในส่วนถัดไป ดำเนินการเพิ่ม และเพิ่มแพ็กเกจอีกรายการ

ระยะที่ 3: แพ็กเกจหลายรายการ

ระยะถัดไปนี้มีความซับซ้อนมากขึ้นและสร้างโปรเจ็กต์ที่มีแพ็กเกจหลายรายการ โปรดดูโครงสร้างและเนื้อหาของไดเรกทอรี cpp-tutorial/stage3 ด้านล่าง

└──stage3
   ├── main
      ├── BUILD
      ├── hello-world.cc
      ├── hello-greet.cc
      └── hello-greet.h
   ├── lib
      ├── BUILD
      ├── hello-time.cc
      └── hello-time.h
   └── WORKSPACE

คุณจะเห็นว่ามีไดเรกทอรีย่อย 2 รายการ และแต่ละรายการมีไฟล์ BUILD ดังนั้นสำหรับ Bazel ตอนนี้พื้นที่ทำงานจะมี 2 แพ็กเกจ ได้แก่ lib และ main

โปรดดูไฟล์ lib/BUILD

cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

และที่ไฟล์ main/BUILD

cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

เป้าหมาย hello-world ในแพ็กเกจหลักจะขึ้นอยู่กับเป้าหมาย hello-time ในแพ็กเกจ lib (จึงเป็นที่มาของป้ายกำกับเป้าหมาย //lib:hello-time) ซึ่ง Bazel จะทราบข้อมูลนี้ผ่านแอตทริบิวต์ deps คุณจะเห็นข้อมูลนี้ในกราฟความเกี่ยวข้อง

กราฟความเกี่ยวข้องของ `hello-world` แสดงวิธีที่เป้าหมายในแพ็กเกจหลักใช้เป้าหมายในแพ็กเกจ `lib`

หากต้องการให้บิลด์สําเร็จ คุณต้องทําให้เป้าหมาย //lib:hello-time ใน lib/BUILD ปรากฏต่อเป้าหมายใน main/BUILD อย่างชัดเจนโดยใช้แอตทริบิวต์การแสดงผล เนื่องจากโดยค่าเริ่มต้น เป้าหมายจะมองเห็นได้เฉพาะเป้าหมายอื่นๆ ในไฟล์เดียวกันBUILD Bazel ใช้ระดับการเข้าถึงเป้าหมายเพื่อป้องกันปัญหาต่างๆ เช่น ไลบรารีที่มีรายละเอียดการใช้งานรั่วไหลใน API สาธารณะ

ตอนนี้ให้สร้างโปรเจ็กต์เวอร์ชันสุดท้ายนี้ เปลี่ยนไปใช้ไดเรกทอรี cpp-tutorial/stage3 โดยเรียกใช้คำสั่งต่อไปนี้

cd  ../stage3

เรียกใช้คําสั่งต่อไปนี้อีกครั้ง

bazel build //main:hello-world

Bazel จะสร้างสิ่งที่มีลักษณะดังนี้

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.167s, Critical Path: 0.00s

ตอนนี้ให้ทดสอบไบนารีสุดท้ายของบทแนะนำนี้เพื่อดูข้อความ Hello world สุดท้าย

bazel-bin/main/hello-world

สรุป: ระยะที่ 3

ตอนนี้คุณสร้างโปรเจ็กต์เป็นแพ็กเกจ 2 รายการที่มีเป้าหมาย 3 รายการ และเข้าใจความสัมพันธ์ระหว่างแพ็กเกจต่างๆ แล้ว ซึ่งจะช่วยให้คุณสร้างโปรเจ็กต์ในอนาคตด้วย Bazel ได้ ในส่วนถัดไป มาดูวิธีดำเนินการต่อในเส้นทาง Bazel

ขั้นตอนถัดไป

ตอนนี้คุณสร้างบิลด์พื้นฐานครั้งแรกด้วย Bazel เสร็จแล้ว แต่นี่เป็นเพียงจุดเริ่มต้น แหล่งข้อมูลเพิ่มเติมสำหรับการเรียนรู้เกี่ยวกับ Bazel มีดังนี้

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