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

บทนำ

หากเพิ่งเริ่มใช้ 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 จะอยู่ในแต่ละระยะอยู่แล้ว

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

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

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

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

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

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

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

ตอนนี้คุณคุ้นเคยกับคำสำคัญบางคำและความหมายของคำเหล่านั้นในบริบทของโปรเจ็กต์นี้และ 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

กราฟทรัพยากร Dependency สำหรับ 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 จะคอมไพล์ไฟล์นั้นใหม่เท่านั้น

เมื่อดูกราฟการขึ้นต่อกัน คุณจะเห็นว่า hello-world ขึ้นอยู่กับอินพุตเดิม แต่โครงสร้างของการบิลด์จะแตกต่างกัน

กราฟการขึ้นต่อกันของ `hello-world` จะแสดงการเปลี่ยนแปลงโครงสร้างหลังจากแก้ไขไฟล์

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

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

ระยะที่ 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 ต่อไปได้

  • หากต้องการมุ่งเน้นที่ C++ ต่อไป ให้อ่านเกี่ยวกับกรณีการใช้งานบิลด์ C++ ทั่วไป
  • หากต้องการเริ่มต้นบิลด์แอปพลิเคชันอื่นๆ ด้วย Bazel โปรดดูบทแนะนำ สำหรับ Java, แอปพลิเคชัน Android, หรือ แอปพลิเคชัน iOS
  • หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการทำงานกับที่เก็บในเครื่องและที่เก็บระยะไกล โปรดอ่านเกี่ยวกับ ทรัพยากร Dependency ภายนอก
  • หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับกฎอื่นๆ ของ Bazel โปรดดูคู่มืออ้างอิงนี้

ขอให้สนุกกับการบิลด์