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

รายงานปัญหา ดูแหล่งที่มา

ข้อมูลเบื้องต้น

หากยังไม่เคยใช้ Bazel คุณมาถูกที่แล้ว ทําตามบทแนะนําของ Build นี้สําหรับคําแนะนําเบื้องต้น เกี่ยวกับการใช้ 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 คือแพ็กเกจ (รายละเอียดเพิ่มเติมเกี่ยวกับแพ็กเกจ ในบทแนะนํานี้)

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

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

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

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

ดูไฟล์ 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” เป็นสิ่งพิมพ์

กราฟ Dependency ของระยะที่ 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-greet:

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

สรุป: ขั้นที่ 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) - บาเซลจะรู้เรื่องนี้ผ่านแอตทริบิวต์ 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 รายการ และเข้าใจทรัพยากร Dependency ระหว่างโปรเจ็กต์แล้ว ซึ่งช่วยให้คุณได้ออกเดินทางและสร้างโปรเจ็กต์ในอนาคตกับ Bazel ในส่วนถัดไปมาดูวิธีเดินทางใน Waze ของคุณกันต่อ

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

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

ขอให้มีความสุขกับการสร้าง!