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

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

บทนำ

หากเพิ่งเริ่มใช้ 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 โดยทั่วไป ในส่วนถัดไป คุณจะสร้างและทดสอบ Stage 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" พิมพ์ออกมา

ต่อไปนี้คือกราฟทรัพยากร Dependency ของระยะที่ 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 ไฟล์และขึ้นอยู่กับเป้าหมายอื่น (//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 ต่อไปมีดังนี้

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