Hướng dẫn của Bazel: Xây dựng dự án C++

Báo cáo sự cố Xem nguồn

Giới thiệu

Bạn mới dùng Bazel? Bạn đã đến đúng nơi. Hãy làm theo hướng dẫn Xây dựng đầu tiên này để được giới thiệu đơn giản về cách sử dụng Bazel. Phần hướng dẫn này định nghĩa các thuật ngữ chính được dùng trong ngữ cảnh của Bazel, đồng thời hướng dẫn bạn về các khái niệm cơ bản về quy trình công việc của Bazel. Bắt đầu với các công cụ cần thiết, bạn sẽ xây dựng và chạy 3 dự án với độ phức tạp ngày càng tăng, đồng thời tìm hiểu cách thức và lý do khiến các dự án này trở nên phức tạp hơn.

Mặc dù Bazel là một hệ thống xây dựng hỗ trợ các bản dựng đa ngôn ngữ, nhưng hướng dẫn này lại lấy dự án C++ làm ví dụ và đưa ra các nguyên tắc cũng như quy trình chung áp dụng cho hầu hết ngôn ngữ.

Thời gian hoàn thành ước tính: 30 phút.

Điều kiện tiên quyết

Bắt đầu bằng cách cài đặt Bazel, nếu bạn chưa cài đặt. Hướng dẫn này sử dụng Git để kiểm soát nguồn, vì vậy, để có kết quả tốt nhất, hãy cài đặt Git.

Tiếp theo, hãy truy xuất dự án mẫu từ kho lưu trữ GitHub của Bazel bằng cách chạy nội dung sau trong công cụ dòng lệnh mà bạn chọn:

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

Dự án mẫu cho hướng dẫn này nằm trong thư mục examples/cpp-tutorial.

Hãy xem phần bên dưới về cấu trúc:

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

Có ba nhóm tệp, mỗi nhóm đại diện cho một giai đoạn trong hướng dẫn này. Trong giai đoạn đầu tiên, bạn sẽ tạo một mục tiêu nằm trong một gói duy nhất. Trong giai đoạn thứ hai, bạn sẽ tạo cả tệp nhị phân và thư viện từ một gói duy nhất. Trong giai đoạn thứ ba và cuối cùng, bạn sẽ xây dựng một dự án có nhiều gói và xây dựng dự án đó với nhiều mục tiêu.

Tóm tắt: Giới thiệu

Bằng cách cài đặt Bazel (và Git) và sao chép kho lưu trữ cho hướng dẫn này, bạn đã đặt nền móng cho bản dựng đầu tiên của mình bằng Bazel. Hãy tiếp tục chuyển sang phần tiếp theo để định nghĩa một số điều khoản và thiết lập không gian làm việc của bạn.

Bắt đầu

Thiết lập không gian làm việc

Bạn cần thiết lập không gian làm việc của dự án trước khi có thể tạo dự án. Không gian làm việc là một thư mục lưu trữ các tệp nguồn của dự án và đầu ra bản dựng của Bazel. Tệp này cũng chứa các tệp quan trọng sau:

  • WORKSPACE file giúp xác định thư mục và nội dung trong đó là một không gian làm việc Bazel và nằm ở gốc trong cấu trúc thư mục của dự án.
  • Một hoặc nhiều BUILD files để cho Bazel biết cách xây dựng nhiều phần của dự án. Một thư mục trong không gian làm việc chứa tệp BUILD là một gói. (Tìm hiểu thêm về các gói ở phần sau của hướng dẫn này.)

Trong các dự án sau này, để chỉ định một thư mục làm không gian làm việc Bazel, hãy tạo một tệp trống có tên WORKSPACE trong thư mục đó. Trong phạm vi của hướng dẫn này, tệp WORKSPACE đã có sẵn trong mỗi giai đoạn.

LƯU Ý: Khi Bazel xây dựng dự án, tất cả dữ liệu đầu vào phải nằm trong cùng một không gian làm việc. Các tệp nằm trong những không gian làm việc khác nhau độc lập với nhau trừ phi được liên kết. Bạn có thể xem thêm thông tin chi tiết về các quy tắc không gian làm việc trong hướng dẫn này.

Tìm hiểu về tệp BUILD

Tệp BUILD chứa một số loại hướng dẫn về Bazel. Mỗi tệp BUILD cần có ít nhất một quy tắc dưới dạng một bộ hướng dẫn, cho Bazel biết cách tạo các kết quả mong muốn, chẳng hạn như các tệp nhị phân hoặc thư viện có thể thực thi. Mỗi thực thể của quy tắc xây dựng trong tệp BUILD được gọi là một mục tiêu và trỏ đến một nhóm tệp nguồn cũng như phần phụ thuộc cụ thể. Một mục tiêu cũng có thể trỏ đến các mục tiêu khác.

Hãy xem tệp BUILD trong thư mục cpp-tutorial/stage1/main:

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

Trong ví dụ của chúng ta, mục tiêu hello-world tạo thực thể cho cc_binary rule tích hợp của Bazel. Quy tắc này yêu cầu Bazel tạo một tệp nhị phân có thể thực thi độc lập từ tệp nguồn hello-world.cc mà không có phần phụ thuộc.

Tóm tắt: bắt đầu

Bây giờ, bạn đã quen thuộc với một số thuật ngữ chính cũng như ý nghĩa của các thuật ngữ đó trong bối cảnh của dự án này và Bazel nói chung. Ở phần tiếp theo, bạn sẽ xây dựng và kiểm thử Giai đoạn 1 của dự án.

Giai đoạn 1: một mục tiêu, một gói đơn

Đã đến lúc xây dựng phần đầu tiên của dự án. Để bạn có thể tham khảo trực quan, cấu trúc của phần Giai đoạn 1 của dự án là:

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

Chạy lệnh sau để di chuyển đến thư mục cpp-tutorial/stage1:

cd cpp-tutorial/stage1

Tiếp theo, hãy chạy:

bazel build //main:hello-world

Trong nhãn mục tiêu, phần //main: là vị trí của tệp BUILD so với thư mục gốc của không gian làm việc và hello-world là tên mục tiêu trong tệp BUILD.

Bazel tạo ra nội dung có dạng như sau:

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

Bạn vừa xây dựng mục tiêu Bazel đầu tiên của mình. Bazel đặt các đầu ra của bản dựng trong thư mục bazel-bin ở gốc không gian làm việc.

Bây giờ, hãy kiểm thử tệp nhị phân mới tạo, đó là:

bazel-bin/main/hello-world

Kết quả là một thông báo “Hello world” sẽ xuất hiện.

Dưới đây là biểu đồ phần phụ thuộc của Giai đoạn 1:

Biểu đồ phần phụ thuộc cho Hello-world hiển thị một mục tiêu duy nhất với một tệp nguồn duy nhất.

Tóm tắt: giai đoạn 1

Giờ đây, khi đã hoàn thành bản dựng đầu tiên, bạn đã nắm được khái niệm cơ bản về cấu trúc của bản dựng. Trong giai đoạn tiếp theo, bạn sẽ thêm độ phức tạp bằng cách thêm một mục tiêu khác.

Giai đoạn 2: nhiều mục tiêu bản dựng

Mặc dù một mục tiêu duy nhất là đủ đối với các dự án nhỏ, nhưng bạn nên chia các dự án lớn hơn thành nhiều mục tiêu và gói. Điều này cho phép các bản dựng tăng tốc nhanh chóng – tức là Bazel chỉ xây dựng lại những nội dung đã thay đổi – và tăng tốc các bản dựng bằng cách xây dựng nhiều phần của dự án cùng một lúc. Giai đoạn này của hướng dẫn thêm một mục tiêu, còn giai đoạn tiếp theo sẽ thêm một gói.

Đây là thư mục bạn đang sử dụng cho Giai đoạn 2:

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

Hãy xem tệp BUILD trong thư mục cpp-tutorial/stage2/main bên dưới:

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",
    ],
)

Với tệp BUILD này, trước tiên Bazel tạo thư viện hello-greet (sử dụng cc_library rule tích hợp sẵn của Bazel), sau đó là tệp nhị phân hello-world. Thuộc tính deps trong mục tiêu hello-world cho Bazel biết rằng cần có thư viện hello-greet để tạo tệp nhị phân hello-world.

Trước khi có thể tạo phiên bản mới này của dự án, bạn cần thay đổi thư mục, chuyển sang thư mục cpp-tutorial/stage2 bằng cách chạy:

cd ../stage2

Giờ đây, bạn có thể tạo tệp nhị phân mới bằng lệnh quen thuộc sau:

bazel build //main:hello-world

Một lần nữa, Bazel tạo ra nội dung có dạng như sau:

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

Giờ đây, bạn có thể kiểm thử tệp nhị phân mới tạo, trả về một "Hello world" khác:

bazel-bin/main/hello-world

Nếu bây giờ bạn sửa đổi hello-greet.cc và xây dựng lại dự án, thì Bazel chỉ biên dịch lại tệp đó.

Nhìn vào biểu đồ phần phụ thuộc, bạn có thể thấy hello-world phụ thuộc vào một dữ liệu đầu vào bổ sung có tên là hello-greet:

Biểu đồ phần phụ thuộc cho "hello-world" cho thấy các thay đổi về phần phụ thuộc sau khi sửa đổi tệp.

Tóm tắt: giai đoạn 2

Bây giờ, bạn đã xây dựng dự án với 2 mục tiêu. Mục tiêu hello-world tạo một tệp nguồn và phụ thuộc vào một mục tiêu khác (//main:hello-greet), đồng thời tạo thêm 2 tệp nguồn bổ sung. Trong phần tiếp theo, hãy tiến thêm một bước và thêm một gói khác.

Giai đoạn 3: nhiều gói

Giai đoạn tiếp theo này sẽ thêm một lớp chức năng khác và xây dựng một dự án có nhiều gói. Hãy xem cấu trúc và nội dung của thư mục cpp-tutorial/stage3 ở bên dưới:

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

Bạn có thể thấy hiện tại có 2 thư mục con và mỗi thư mục con chứa một tệp BUILD. Do đó, đối với Bazel, không gian làm việc hiện chứa 2 gói: libmain.

Hãy xem tệp lib/BUILD:

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

Và ở tệp 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",
    ],
)

Mục tiêu hello-world trong gói chính phụ thuộc vào mục tiêu hello-time trong gói lib (do đó có nhãn mục tiêu //lib:hello-time) – Bazel biết điều này thông qua thuộc tính deps. Bạn có thể thấy điều này được phản ánh trong đồ thị phần phụ thuộc:

Biểu đồ phần phụ thuộc cho "hello-world" cho thấy cách mục tiêu trong gói chính phụ thuộc vào mục tiêu trong gói "lib".

Để tạo bản dựng thành công, bạn cần hiển thị mục tiêu //lib:hello-time trong lib/BUILD một cách rõ ràng cho các mục tiêu trong main/BUILD bằng thuộc tính chế độ hiển thị. Điều này là do theo mặc định, các mục tiêu chỉ hiển thị với các mục tiêu khác trong cùng một tệp BUILD. Bazel sử dụng chế độ hiển thị mục tiêu để ngăn chặn các vấn đề như thư viện chứa chi tiết triển khai bị rò rỉ vào các API công khai.

Bây giờ, hãy tạo phiên bản cuối cùng của dự án. Chuyển sang thư mục cpp-tutorial/stage3 bằng cách chạy:

cd  ../stage3

Một lần nữa, hãy chạy lệnh sau:

bazel build //main:hello-world

Bazel tạo ra nội dung có dạng như sau:

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

Bây giờ, hãy kiểm thử tệp nhị phân cuối cùng của hướng dẫn này để xem thông báo Hello world cuối cùng:

bazel-bin/main/hello-world

Tóm tắt: giai đoạn 3

Hiện tại, bạn đã xây dựng dự án dưới dạng 2 gói với 3 mục tiêu và hiểu được sự phụ thuộc giữa các gói đó, từ đó trang bị cho bạn kiến thức để tiếp tục và xây dựng các dự án trong tương lai bằng Bazel. Trong phần tiếp theo, hãy xem cách tiếp tục hành trình của bạn trên Bazel.

Các bước tiếp theo

Hiện bạn đã hoàn thành bản dựng cơ bản đầu tiên bằng Bazel, nhưng đây mới chỉ là khởi đầu. Sau đây là một số tài nguyên khác để bạn tiếp tục học tập cùng Bazel:

Chúc bạn tạo dựng vui vẻ!