Hướng dẫn dành cho Bazel: Xây dựng dự án C++

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo sự cố Xem nguồn

Giới thiệu

Bạn mới sử dụng Bazel? Bạn đã đến đúng chỗ. Hãy làm theo hướng dẫn Bản dựng đầu tiên này để xem hướng dẫn đơn giản về cách sử dụng Bazel. Hướng dẫn này xác định các thuật ngữ chính vì chúng được dùng trong ngữ cảnh của Bazel và hướng dẫn bạn các khái niệm cơ bản về quy trình công việc của Bazel. Bắt đầu bằng các công cụ cần thiết, bạn sẽ xây dựng và chạy 3 dự án với mức độ phức tạp cao hơn và tìm hiểu cách thức cũng như lý do chúng 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 sử dụng dự án C++ làm ví dụ và cung cấp các nguyên tắc và hướng dẫn chung áp dụng cho hầu hết các ngôn ngữ.

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

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

Hãy 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, truy xuất dự án mẫu từ kho lưu trữ GitHub của Bazel bằng cách chạy dự án sau đây 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 cấu trúc bên dưới:

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 tập hợp tệp, mỗi tập đại diện cho một giai đoạn trong hướng dẫn này. Trong giai đoạn đầu, bạn sẽ xây dựng một mục tiêu duy nhất nằm trong một gói duy nhất. Trong giai đoạn 2, 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ẽ tạo một dự án có nhiều gói và xây dựng 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 tảng cho bản dựng đầu tiên của mình bằng Bazel. Tiếp tục chuyển đến phần tiếp theo để xác định một số điều khoản và thiết lập không gian làm việc.

Bắt đầu

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

Trước khi có thể tạo dự án, bạn cần thiết lập không gian làm việc của dự án đó. Không gian làm việc là một thư mục chứa các tệp nguồn của dự án và kết quả của 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 của thư mục đó là không gian làm việc Bazel và nằm ở thư mục gốc của cấu trúc thư mục của dự án.
  • Một hoặc nhiều BUILD files sẽ cho Bazel biết cách xây dựng các phần khác nhau 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ề gói sau trong 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 là WORKSPACE trong thư mục đó. Theo mục đích của hướng dẫn này, một tệp WORKSPACE đã có trong mỗi giai đoạn.

LƯU Ý: Khi Bazel xây dựng dự án, tất cả cá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 không gian làm việc khác nhau sẽ độc lập với nhau trừ khi được liên kết. Bạn có thể tìm 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 nhiều loại hướng dẫn khác nhau cho Bazel. Mỗi tệp BUILD yêu cầu ít nhất một quy tắc là một tập hợp lệnh, hướng dẫn Bazel cách tạo đầu ra 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 phiên bản của một quy tắc xây dựng trong tệp BUILD được gọi là một target và trỏ đến một tập hợp tệp nguồn cụ thể và phần phụ thuộc. 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 sẽ tạo bản sao cc_binary rule được tích hợp sẵn của Bazel. Quy tắc này yêu cầu Bazel tạo tệp nhị phân có thể thực thi độc lập từ tệp nguồn hello-world.cc không có phần phụ thuộc.

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

Hiện tại, bạn đã quen thuộc với một số thuật ngữ chính và ý nghĩa của những thuật ngữ này trong bối cảnh của dự án này và Bazel nói chung. Trong 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 duy nhất, một gói duy nhất

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

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

Hãy chạy các mục sau để 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 tương ứng 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 hình thức 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 đầu ra bản dựng trong thư mục bazel-bin ở gốc của không gian làm việc.

Bây giờ, hãy kiểm tra tệp nhị phân mới tạo của bạn, tức là:

bazel-bin/main/hello-world

Thao tác này sẽ tạo ra thông báo “Hello world” được in.

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

Biểu đồ phần phụ thuộc của hello-world hiển thị một mục tiêu duy nhất có một tệp nguồn duy nhất.

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

Bây giờ, bạn đã hoàn tất bản dựng đầu tiên, bạn đã có ý tưởng cơ bản về cách cấu trúc một 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à đủ cho các dự án nhỏ, bạn có thể muố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 gia tăng nhanh chóng – tức là Bazel chỉ tạo lại những gì đã thay đổi – và tăng tốc độ cho các bản dựng của bạn bằng cách tạo nhiều phần của một dự án cùng một lúc. Giai đoạn này của hướng dẫn sẽ thêm một mục tiêu và bước tiếp theo sẽ thêm một gói.

Đây là thư mục mà bạn đang làm việc 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 sẽ xây dựng thư viện hello-greet (bằng cách sử dụng cc_library rule tích hợp sẵn của Bazel), rồi đến 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 bạn 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 của dự án này, 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

Bây giờ, 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 sẽ tạo ra một mã 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

Bây giờ, bạn có thể kiểm tra tệp nhị phân mới được tạo, tệp này sẽ trả về một “Hello world” khác:

bazel-bin/main/hello-world

Nếu bạn hiện sửa đổi hello-greet.cc và tạo lại dự án, Bazel sẽ 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 các dữ liệu đầu vào như trước, nhưng cấu trúc của bản dựng thì khác:

Biểu đồ phần phụ thuộc của `xin chào thế giới` hiển thị các thay đổi về cấu trúc sau khi sửa đổi tệp.

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

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). Mục tiêu này tạo hai tệp nguồn bổ sung. Trong phần tiếp theo, hãy thực hiện thêm một bước nữa 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à tạo 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 rằng hiện có hai thư mục con và mỗi thư mục chứa một tệp BUILD. Do đó, đối với Bazel, không gian làm việc hiện chứa hai 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ại 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êuhello-time trong gói lib (do đó, 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 biểu đồ phần phụ thuộc:

Biểu đồ phần phụ thuộc của `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`.

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

Bây giờ, hãy xây dựng phiên bản cuối cùng này 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, chạy lệnh sau:

bazel build //main:hello-world

Bazel tạo ra hình thức 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 tra tệp nhị phân cuối cùng của hướng dẫn này để tìm thông báo Hello world cuối cùng:

bazel-bin/main/hello-world

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

Bây giờ, bạn đã xây dựng dự án dưới dạng hai gói với ba mục tiêu và hiểu các phần phụ thuộc giữa các gói đó, giúp bạn tiếp tục và xây dựng các dự án trong tương lai với Bazel. Trong phần tiếp theo, hãy xem cách tiếp tục hành trình của Blackaz.

Các bước tiếp theo

Giờ đây, bạn đã hoàn tất bản dựng cơ bản đầu tiên với Bazel, nhưng đây chỉ là khởi đầu. Dưới đây là một số tài nguyên khác để tiếp tục học với Bazel:

Chúc bạn xây dựng vui vẻ!