آموزش بازل: ساخت یک پروژه ++C

مقدمه

تازه وارد بازل شده اید؟ شما در جای مناسب هستید. برای آشنایی ساده با استفاده از Bazel، این آموزش First Build را دنبال کنید. این آموزش عبارات کلیدی را همانطور که در زمینه Bazel استفاده می شود تعریف می کند و شما را با اصول اولیه گردش کار Bazel راهنمایی می کند. با شروع با ابزارهایی که نیاز دارید، سه پروژه را با پیچیدگی فزاینده ساخته و اجرا خواهید کرد و یاد خواهید گرفت که چگونه و چرا پیچیده تر می شوند.

در حالی که Bazel یک سیستم ساخت است که از ساخت‌های چند زبانه پشتیبانی می‌کند، این آموزش از یک پروژه C++ به عنوان مثال استفاده می‌کند و دستورالعمل‌ها و جریان کلی را ارائه می‌دهد که برای اکثر زبان‌ها اعمال می‌شود.

زمان تخمینی تکمیل: 30 دقیقه

پیش نیازها

اگر قبلاً این کار را نکرده اید، با نصب Bazel شروع کنید. این آموزش از Git برای کنترل منبع استفاده می کند، بنابراین برای بهترین نتیجه، Git را نیز نصب کنید .

سپس، پروژه نمونه را از مخزن Bazel's GitHub با اجرای موارد زیر در ابزار خط فرمان انتخابی خود بازیابی کنید:

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

سه مجموعه فایل وجود دارد که هر مجموعه نشان دهنده یک مرحله در این آموزش است. در مرحله اول، شما یک هدف واحد را در یک بسته می‌سازید . در مرحله دوم، شما هم یک باینری و هم یک کتابخانه از یک بسته واحد خواهید ساخت. در مرحله سوم و آخر شما یک پروژه با بسته های متعدد می سازید و آن را با چندین هدف می سازید.

خلاصه: مقدمه

با نصب Bazel (و Git) و کلون کردن مخزن این آموزش، شما پایه و اساس اولین ساخت خود را با Bazel گذاشتید. برای تعریف برخی اصطلاحات و تنظیم فضای کاری خود به بخش بعدی بروید.

شروع شدن

فضای کاری را تنظیم کنید

قبل از اینکه بتوانید یک پروژه بسازید، باید فضای کاری آن را تنظیم کنید. فضای کاری دایرکتوری است که فایل های منبع پروژه شما و خروجی های ساخت Bazel را در خود نگه می دارد. همچنین حاوی این فایل های مهم است:

  • WORKSPACE file که دایرکتوری و محتویات آن را به عنوان یک فضای کاری Bazel شناسایی می کند و در ریشه ساختار دایرکتوری پروژه قرار دارد.
  • یک یا چند BUILD files که به Bazel می‌گوید چگونه قسمت‌های مختلف پروژه را بسازد. دایرکتوری در فضای کاری که حاوی یک فایل BUILD است یک بسته است. (اطلاعات بیشتر در مورد بسته ها در ادامه این آموزش.)

در پروژه های آینده، برای تعیین یک دایرکتوری به عنوان فضای کاری Bazel، یک فایل خالی به نام WORKSPACE در آن دایرکتوری ایجاد کنید. برای اهداف این آموزش، یک فایل WORKSPACE از قبل در هر مرحله وجود دارد.

توجه : وقتی Bazel پروژه را می‌سازد، همه ورودی‌ها باید در یک فضای کاری باشند. فایل هایی که در فضاهای کاری مختلف قرار دارند مستقل از یکدیگر هستند مگر اینکه پیوند داده شوند. اطلاعات دقیق تر در مورد قوانین فضای کاری را می توان در این راهنما یافت.

فایل BUILD را درک کنید

یک فایل BUILD شامل چندین نوع مختلف دستورالعمل برای Bazel است. هر فایل BUILD حداقل به یک قانون به عنوان مجموعه‌ای از دستورالعمل‌ها نیاز دارد که به Bazel می‌گوید چگونه خروجی‌های مورد نظر مانند باینری‌های اجرایی یا کتابخانه‌ها را بسازد. هر نمونه از یک قانون ساخت در فایل BUILD یک هدف نامیده می شود و به مجموعه خاصی از فایل های منبع و وابستگی ها اشاره می کند. یک هدف همچنین می تواند به اهداف دیگر اشاره کند.

به فایل BUILD در دایرکتوری cpp-tutorial/stage1/main نگاهی بیندازید:

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

در مثال ما، هدف hello-world cc_binary rule نشان می‌دهد. این قانون به Bazel می گوید که یک باینری اجرایی مستقل از فایل منبع hello-world.cc بدون وابستگی بسازد.

خلاصه: شروع کردن

اکنون با برخی از اصطلاحات کلیدی و معنای آنها در زمینه این پروژه و به طور کلی بازل آشنا شدید. در قسمت بعدی مرحله 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 graph for hello-world displays a single target with a single source file.

خلاصه: مرحله 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 )، سپس باینری 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 به همان ورودی‌های قبلی بستگی دارد، اما ساختار ساخت متفاوت است:

Dependency graph for `hello-world` displays structure changes after modification to the file.

خلاصه: مرحله 2

اکنون پروژه را با دو هدف ساخته اید. هدف hello-world یک فایل منبع می‌سازد و به یک هدف دیگر بستگی دارد ( //main:hello-greet )، که دو فایل منبع اضافی را می‌سازد. در قسمت بعدی یک قدم جلوتر رفته و بسته دیگری اضافه کنید.

مرحله 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

می بینید که اکنون دو زیرمجموعه وجود دارد و هر کدام حاوی یک فایل BUILD هستند. بنابراین، برای Bazel، فضای کاری اکنون شامل دو بسته است: 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 می داند. می توانید این را در نمودار وابستگی منعکس شده مشاهده کنید:

Dependency graph for `hello-world` displays how the target in the main package depends on the target in the `lib` package.

برای موفقیت ساخت، هدف //lib:hello-time را در lib/BUILD BUILD با استفاده از ویژگی visibility به طور واضح برای اهداف در 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

شما اکنون پروژه را به صورت دو بسته با سه هدف ساخته‌اید و وابستگی‌های بین آنها را درک کرده‌اید، که شما را برای پیشبرد و ساخت پروژه‌های آینده با Bazel مجهز می‌کند. در بخش بعدی، نگاهی به نحوه ادامه سفر بازل خود بیندازید.

مراحل بعدی

شما اکنون اولین ساخت اولیه خود را با Bazel تکمیل کرده اید، اما این تازه شروع کار است. در اینجا چند منبع دیگر برای ادامه یادگیری با Bazel آورده شده است:

ساختمان مبارک!