در این صفحه نحوه ساختن برنامه با Bazel، ساخت دستور دستوری و دستور الگوی syntax توضیح داده شده است.
شروع سریع
برای اجرای Bazel، به دایرکتوری فضای کار پایه یا هر یک از زیر شاخه های آن بروید و bazel
را تایپ کنید. اگر نیاز به ایجاد یک فضای کاری جدید دارید، بیلد را ببینید.
bazel help
[Bazel release bazel version]
Usage: bazel command options ...
دستورات موجود
-
analyze-profile
: داده های نمایه ساخت را تجزیه و تحلیل می کند. -
aquery
: یک پرس و جو را در نمودار اقدام پس از تجزیه و تحلیل اجرا می کند. -
build
: اهداف مشخص شده را می سازد. -
canonicalize-flags
: پرچم های Bazel را متعارف کنید. -
clean
: فایل های خروجی را حذف می کند و به صورت اختیاری سرور را متوقف می کند. -
cquery
: یک پرس و جو گراف وابستگی پس از تجزیه و تحلیل را اجرا می کند. -
dump
: وضعیت داخلی فرآیند سرور Bazel را تخلیه می کند. -
help
: راهنمای دستورات یا فهرست را چاپ می کند. -
info
: اطلاعات زمان اجرا را در مورد سرور bazel نمایش می دهد. -
fetch
: تمام وابستگی های خارجی یک هدف را واکشی می کند. -
mobile-install
: برنامه ها را روی دستگاه های تلفن همراه نصب می کند. -
query
: یک پرس و جو گراف وابستگی را اجرا می کند. -
run
: هدف مشخص شده را اجرا می کند. -
shutdown
: سرور Bazel را متوقف می کند. -
test
: اهداف آزمایشی مشخص شده را می سازد و اجرا می کند. -
version
: اطلاعات نسخه را برای Bazel چاپ می کند.
کمک گرفتن
-
bazel help command
: راهنما و گزینه هایcommand
را چاپ می کند. -
bazel help
startup_options
: گزینه هایی برای میزبانی JVM Bazel. -
bazel help
target-syntax
: نحوی را برای تعیین اهداف توضیح می دهد. -
bazel help info-keys
: فهرستی از کلیدهای استفاده شده توسط دستور info را نمایش می دهد.
ابزار bazel
عملکردهای زیادی را انجام می دهد که به آنها دستورات می گویند. متداول ترین آنها bazel build
bazel test
هستند. می توانید با استفاده از bazel help
پیام های راهنمای آنلاین را مرور کنید.
ساختن یک هدف
قبل از شروع ساخت، به یک فضای کاری نیاز دارید. فضای کاری یک درخت دایرکتوری است که شامل تمام فایل های منبع مورد نیاز برای ساخت برنامه شما می باشد. Bazel به شما این امکان را می دهد که یک ساخت را از یک حجم کاملاً خواندنی انجام دهید.
برای ساختن یک برنامه با Bazel، bazel build
تایپ کنید و سپس هدفی را که می خواهید بسازید.
bazel build //foo
پس از صدور دستور ساخت //foo
، خروجی مشابه زیر را مشاهده خواهید کرد:
INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions
ابتدا، Bazel تمام بسته های موجود در نمودار وابستگی هدف شما را بارگذاری می کند. این شامل وابستگیهای اعلامشده ، فایلهایی است که مستقیماً در فایل BUILD
هدف فهرست شدهاند، و وابستگیهای انتقالی ، فایلهای فهرستشده در فایلهای BUILD
وابستگیهای هدف شما. پس از شناسایی تمام وابستگی ها، بازل آنها را از نظر صحت تجزیه و تحلیل می کند و اکشن های ساخت را ایجاد می کند. در آخر، Bazel کامپایلرها و سایر ابزارهای بیلد را اجرا می کند.
در طول مرحله اجرای ساخت، Bazel پیام های پیشرفت را چاپ می کند. پیامهای پیشرفت شامل مرحله ساخت فعلی (مانند کامپایلر یا لینککننده) در هنگام شروع، و تعداد تکمیلشده بر روی تعداد کل اقدامات ساخت است. با شروع ساخت، تعداد کل اکشن ها اغلب با کشف کل نمودار اکشن افزایش می یابد، اما تعداد آنها در عرض چند ثانیه تثبیت می شود.
در پایان ساخت، Bazel چاپ می کند که کدام اهداف درخواست شده است، آیا آنها با موفقیت ساخته شده اند یا نه، و اگر چنین است، کجا می توان فایل های خروجی را پیدا کرد. اسکریپت هایی که بیلدها را اجرا می کنند می توانند به طور قابل اعتمادی این خروجی را تجزیه کنند. برای جزئیات بیشتر به --show_result
مراجعه کنید.
اگر دوباره همان دستور را تایپ کنید، ساخت خیلی سریعتر تمام می شود.
bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action
این یک ساخت تهی است. از آنجا که هیچ چیز تغییر نکرده است، هیچ بسته ای برای بارگذاری مجدد و هیچ مرحله ساختی برای اجرا وجود ندارد. اگر چیزی در 'foo' یا وابستگیهای آن تغییر میکرد، Bazel برخی از اکشنهای ساخت را دوباره اجرا میکرد یا یک ساخت افزایشی را تکمیل میکرد.
ساختن اهداف متعدد
Bazel چندین روش را برای تعیین اهداف ساخته شده امکان پذیر می کند. در مجموع، اینها به عنوان الگوهای هدف شناخته می شوند. این نحو در دستوراتی مانند build
، test
یا query
استفاده می شود.
در حالی که از برچسب ها برای تعیین اهداف فردی استفاده می شود، مانند برای اعلام وابستگی ها در فایل های BUILD
، الگوهای هدف Bazel چندین هدف را مشخص می کند. الگوهای هدف تعمیم نحو برچسب برای مجموعه ای از اهداف، با استفاده از حروف عام است. در ساده ترین حالت، هر برچسب معتبر نیز یک الگوی هدف معتبر است که مجموعه ای از دقیقاً یک هدف را شناسایی می کند.
تمام الگوهای هدف که با //
شروع می شوند نسبت به فضای کاری فعلی حل می شوند.
//foo/bar:wiz | فقط هدف واحد //foo/bar:wiz . |
//foo/bar | معادل //foo/bar:bar . |
//foo/bar:all | همه اهداف قانون در بسته foo/bar . |
//foo/... | همه قوانین در همه بسته های زیر دایرکتوری foo هدف قرار می گیرند. |
//foo/...:all | همه قوانین در همه بسته های زیر دایرکتوری foo هدف قرار می گیرند. |
//foo/...:* | همه اهداف (قوانین و فایل ها) در همه بسته های زیر دایرکتوری foo . |
//foo/...:all-targets | همه اهداف (قوانین و فایل ها) در همه بسته های زیر دایرکتوری foo . |
//... | همه اهداف در بسته های موجود در فضای کاری. این شامل اهداف مخازن خارجی نمی شود. |
//:all | اگر فایل «BUILD» در ریشه فضای کاری وجود داشته باشد، همه اهداف در بسته سطح بالا وجود دارد. |
الگوهای هدفی که با //
شروع نمی شوند نسبت به دایرکتوری فعلی حل می شوند. این مثال ها یک فهرست کاری از foo
را فرض می کنند:
:foo | معادل //foo:foo . |
bar:wiz | معادل //foo/bar:wiz . |
bar/wiz | معادل:
|
bar:all | معادل //foo/bar:all . |
:all | معادل //foo:all . |
...:all | معادل //foo/...:all . |
... | معادل //foo/...:all . |
bar/...:all | معادل //foo/bar/...:all . |
بهطور پیشفرض، پیوندهای نمادی دایرکتوری برای الگوهای هدف بازگشتی دنبال میشوند، به جز مواردی که به زیر پایه خروجی اشاره میکنند، مانند پیوندهای نمادین راحت که در فهرست اصلی فضای کاری ایجاد میشوند.
علاوه بر این، Bazel هنگام ارزیابی الگوهای هدف بازگشتی در هر فهرستی که حاوی فایلی به نام زیر است، از پیوندهای نماد پیروی نمی کند: DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN
foo/...
یک علامت عام روی بستهها است که همه بستهها را به صورت بازگشتی در زیر پوشه foo
(برای همه ریشههای مسیر بسته) نشان میدهد. :all
یک علامت عام بر روی اهداف است که با تمام قوانین درون یک بسته مطابقت دارد. ممکن است این دو با هم ترکیب شوند، مانند foo/...:all
، و زمانی که از هر دو علامت عام استفاده می شود، این ممکن است به اختصار foo/...
باشد.
علاوه بر این، :*
(یا :all-targets
) یک علامت عام است که با هر هدف در بستههای منطبق، از جمله فایلهایی که معمولاً توسط هیچ قانونی ساخته نمیشوند، مانند فایلهای _deploy.jar
مرتبط با قوانین java_binary
مطابقت دارد.
این به این معنی است که :*
بر مجموعه ای از :all
; در حالی که به طور بالقوه گیج کننده است، این نحو اجازه می دهد تا از وایلدکارت آشنا :all
برای ساخت های معمولی استفاده شود، جایی که ساخت اهداف مانند _deploy.jar
مورد نظر نیست.
علاوه بر این، Bazel اجازه می دهد تا از یک اسلش به جای دو نقطه مورد نیاز دستور برچسب استفاده شود. این اغلب هنگام استفاده از گسترش نام فایل Bash راحت است. به عنوان مثال، foo/bar/wiz
معادل //foo/bar:wiz
(اگر بسته foo/bar
وجود دارد) یا //foo:bar/wiz
(اگر بسته foo
وجود دارد) است.
بسیاری از دستورات Bazel لیستی از الگوهای هدف را به عنوان آرگومان می پذیرند و همگی عملگر انکار پیشوند -
. این می تواند برای کم کردن مجموعه ای از اهداف از مجموعه ای که توسط آرگومان های قبلی مشخص شده است استفاده شود. توجه داشته باشید که این بدان معنی است که نظم مهم است. مثلا،
bazel build foo/... bar/...
به معنی "ساخت همه اهداف زیر foo
و همه اهداف زیر bar
"، در حالی که
bazel build -- foo/... -foo/bar/...
به این معنی است که "همه اهداف را در زیر foo
بسازید به جز اهداف زیر foo/bar
". (آگومان --
برای جلوگیری از تفسیر آرگومان های بعدی که با -
شروع می شوند به عنوان گزینه های اضافی مورد نیاز است.)
البته ذکر این نکته مهم است که تفریق اهداف به این روش تضمینی برای ساخته نشدن آنها نخواهد بود، زیرا ممکن است وابستگی به اهدافی باشند که کم نشده اند. به عنوان مثال، اگر یک هدف //foo:all-apis
وجود داشت که در میان دیگران به //foo/bar:api
بستگی داشت، آنگاه هدف دوم به عنوان بخشی از ساخت اولی ساخته می شد.
اهداف با tags = ["manual"]
در الگوهای هدف عام ( ...
، :*
، :all
، و غیره) در دستوراتی مانند bazel build
bazel test
نمی شوند. اگر می خواهید Bazel آنها را بسازد/آزمایش کند، باید چنین اهداف آزمایشی را با الگوهای هدف صریح در خط فرمان مشخص کنید. در مقابل، bazel query
چنین فیلترینگی را به طور خودکار انجام نمی دهد (که هدف از bazel query
را شکست می دهد).
واکشی وابستگی های خارجی
بهطور پیشفرض، Bazel وابستگیهای خارجی را در طول ساخت دانلود کرده و به صورت نمادین پیوند میدهد. با این حال، این میتواند نامطلوب باشد، یا به این دلیل که میخواهید بدانید چه زمانی وابستگیهای خارجی جدید اضافه میشوند یا به این دلیل که میخواهید وابستگیها را «از قبل واکشی کنید» (مثلاً قبل از پروازی که در آن آفلاین هستید). اگر میخواهید از اضافه شدن وابستگیهای جدید در طول ساختها جلوگیری کنید، میتوانید پرچم --fetch=false
را مشخص کنید. توجه داشته باشید که این پرچم فقط برای قوانین مخزن اعمال می شود که به دایرکتوری در سیستم فایل محلی اشاره نمی کنند. بهعنوان مثال، تغییرات در local_repository
، new_local_repository
و قوانین مخزن Android SDK و NDK همیشه بدون توجه به مقدار --fetch
.
اگر واکشی در طول ساختها را ممنوع کنید و Bazel وابستگیهای خارجی جدیدی پیدا کند، بیلد شما با شکست مواجه میشود.
با اجرای bazel fetch
می توانید وابستگی ها را به صورت دستی واکشی کنید. اگر واکشی در حین ساخت را مجاز نکنید، باید bazel fetch
را اجرا کنید:
- قبل از اینکه برای اولین بار بسازید.
- بعد از اینکه یک وابستگی خارجی جدید اضافه کردید.
پس از اجرای آن، تا زمانی که فایل WORKSPACE تغییر نکند، نیازی به اجرای مجدد آن ندارید.
fetch
لیستی از اهداف را برای واکشی وابستگی ها می گیرد. به عنوان مثال، این وابستگی های مورد نیاز برای ساخت //foo:bar
و //bar:baz
:
bazel fetch //foo:bar //bar:baz
برای واکشی همه وابستگی های خارجی برای یک فضای کاری، اجرا کنید:
bazel fetch //...
اگر همه ابزارهایی که استفاده می کنید (از شیشه های کتابخانه گرفته تا خود JDK) را در زیر ریشه فضای کاری خود دارید، اصلاً نیازی به اجرای bazel fetch ندارید. با این حال، اگر از هر چیزی خارج از فهرست فضای کاری استفاده میکنید، Bazel بهطور خودکار قبل از اجرای bazel build
، bazel fetch
اجرا میکند.
کش مخزن
Bazel سعی میکند از واکشی چندین بار یک فایل خودداری کند، حتی اگر همان فایل در فضاهای کاری مختلف مورد نیاز باشد، یا اگر تعریف یک مخزن خارجی تغییر کرده باشد اما همچنان به همان فایل برای دانلود نیاز دارد. برای انجام این کار، bazel تمام فایل های دانلود شده در حافظه پنهان مخزن را که به طور پیش فرض در آدرس ~/.cache/bazel/_bazel_$USER/cache/repos/v1/
قرار دارد، ذخیره می کند. مکان را می توان با گزینه --repository_cache
تغییر داد. حافظه پنهان بین تمام فضاهای کاری و نسخه های نصب شده bazel به اشتراک گذاشته می شود. اگر بازل مطمئن باشد که یک کپی از فایل صحیح را دارد، یک ورودی از حافظه پنهان گرفته میشود، یعنی اگر درخواست دانلود دارای مجموع SHA256 از فایل مشخص شده باشد و فایلی با آن هش در حافظه پنهان باشد. بنابراین تعیین هش برای هر فایل خارجی نه تنها از منظر امنیتی ایده خوبی است. همچنین به جلوگیری از دانلودهای غیر ضروری کمک می کند.
با هر ضربه کش، زمان اصلاح فایل در کش به روز می شود. به این ترتیب، آخرین استفاده از یک فایل در دایرکتوری کش را می توان به راحتی تعیین کرد، به عنوان مثال برای پاکسازی دستی کش. کش هرگز به طور خودکار پاک نمی شود، زیرا ممکن است حاوی کپی از فایلی باشد که دیگر در بالادست در دسترس نیست.
دایرکتوری های فایل های توزیع
دایرکتوری توزیع یکی دیگر از مکانیسم های Bazel برای جلوگیری از دانلودهای غیر ضروری است. Bazel دایرکتوری های توزیع را قبل از حافظه پنهان مخزن جستجو می کند. تفاوت اصلی این است که دایرکتوری توزیع نیاز به آماده سازی دستی دارد.
با استفاده از --distdir=/path/to-directory
، میتوانید فهرستهای فقط خواندنی اضافی را برای جستجوی فایلها به جای واکشی آنها مشخص کنید. اگر نام فایل برابر با نام اصلی URL باشد، یک فایل از چنین دایرکتوری گرفته می شود و علاوه بر این، هش فایل برابر با آنچه در درخواست دانلود مشخص شده است باشد. این فقط در صورتی کار می کند که هش فایل در اعلان WORKSPACE مشخص شده باشد.
در حالی که شرط نام فایل برای صحت لازم نیست، تعداد فایل های کاندید را به یک در هر دایرکتوری مشخص شده کاهش می دهد. به این ترتیب، تعیین دایرکتوری های فایل های توزیع کارآمد باقی می ماند، حتی اگر تعداد فایل ها در چنین دایرکتوری زیاد شود.
اجرای بازل در محیطی با فاصله هوایی
برای کوچک نگه داشتن اندازه باینری Bazel، وابستگی های ضمنی Bazel هنگام اجرا برای اولین بار از طریق شبکه واکشی می شوند. این وابستگی های ضمنی حاوی زنجیره های ابزار و قوانینی هستند که ممکن است برای همه ضروری نباشد. به عنوان مثال، ابزارهای Android تنها در هنگام ساختن پروژه های اندروید جدا و واکشی می شوند.
با این حال، این وابستگیهای ضمنی ممکن است هنگام اجرای Bazel در یک محیط دارای فاصله هوایی، مشکلاتی ایجاد کنند، حتی اگر همه وابستگیهای WORKSPACE خود را عرضه کرده باشید. برای حل آن، میتوانید یک فهرست توزیع حاوی این وابستگیها را روی یک ماشین با دسترسی شبکه آماده کنید و سپس با رویکرد آفلاین آنها را به محیط airgapped منتقل کنید.
برای تهیه فهرست توزیع ، از پرچم --distdir
استفاده کنید. شما باید این کار را یک بار برای هر نسخه باینری جدید Bazel انجام دهید، زیرا وابستگی های ضمنی می توانند برای هر نسخه متفاوت باشند.
برای ایجاد این وابستگی ها در خارج از محیط airgapped خود، ابتدا درخت منبع Bazel را در نسخه مناسب بررسی کنید:
git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"
سپس، تاربال حاوی وابستگی های زمان اجرا ضمنی را برای آن نسخه خاص Bazel بسازید:
bazel build @additional_distfiles//:archives.tar
این tarball را به دایرکتوری صادر کنید که می تواند در محیط airgapped شما کپی شود. به پرچم --strip-components
توجه کنید، زیرا --distdir
با سطح تودرتوی دایرکتوری می تواند بسیار مشکل باشد:
tar xvf bazel-bin/external/additional_distfiles/archives.tar \
-C "$NEW_DIRECTORY" --strip-components=3
در نهایت، هنگامی که از Bazel در محیط airgapped خود استفاده می کنید، پرچم --distdir
را که به دایرکتوری اشاره می کند عبور دهید. برای راحتی، می توانید آن را به عنوان یک ورودی .bazelrc
اضافه کنید:
build --distdir=path/to/directory
ساخت تنظیمات و کامپایل متقابل
تمام ورودی هایی که رفتار و نتیجه یک ساخت معین را مشخص می کنند را می توان به دو دسته مجزا تقسیم کرد. نوع اول، اطلاعات ذاتی است که در فایل های BUILD
پروژه شما ذخیره می شود: قانون ساخت، مقادیر ویژگی های آن، و مجموعه کامل وابستگی های انتقالی آن. نوع دوم، داده های خارجی یا محیطی است که توسط کاربر یا ابزار ساخت ارائه می شود: انتخاب معماری هدف، گزینه های کامپایل و پیوند، و سایر گزینه های پیکربندی زنجیره ابزار. ما به مجموعه کاملی از داده های محیطی به عنوان یک پیکربندی اشاره می کنیم.
در هر بیلد معین، ممکن است بیش از یک پیکربندی وجود داشته باشد. یک کامپایل متقابل را در نظر بگیرید که در آن یک //foo:bin
قابل اجرا برای معماری 64 بیتی می سازید، اما ایستگاه کاری شما یک ماشین 32 بیتی است. واضح است که بیلد به ساخت //foo:bin
با استفاده از یک زنجیره ابزار با قابلیت ایجاد فایل های اجرایی 64 بیتی نیاز دارد، اما سیستم ساخت باید ابزارهای مختلفی را نیز بسازد که در حین ساخت استفاده می شود - به عنوان مثال ابزارهایی که از منبع ساخته می شوند و سپس استفاده می شوند. مثلاً در یک ژانر - و اینها باید برای اجرا در ایستگاه کاری شما ساخته شوند. بنابراین میتوانیم دو پیکربندی را شناسایی کنیم: پیکربندی میزبان ، که برای ساخت ابزارهایی که در حین ساخت اجرا میشوند استفاده میشود، و پیکربندی هدف (یا پیکربندی درخواست ، اما ما اغلب «پیکربندی هدف» را میگوییم، حتی اگر آن کلمه از قبل معانی زیادی داشته باشد). ، که برای ساخت باینری که در نهایت درخواست کردید استفاده می شود.
به طور معمول، کتابخانه های زیادی وجود دارند که پیش نیازهای هر دو هدف ساخت درخواستی ( //foo:bin
) و یک یا چند ابزار میزبان هستند، به عنوان مثال برخی از کتابخانه های پایه. چنین کتابخانه هایی باید دو بار ساخته شوند، یک بار برای پیکربندی میزبان و یک بار برای پیکربندی هدف. Bazel مراقبت می کند که هر دو نوع ساخته شده باشند و فایل های مشتق شده جدا نگهداری شوند تا از تداخل جلوگیری شود. معمولاً چنین اهدافی را می توان همزمان ساخت، زیرا آنها مستقل از یکدیگر هستند. اگر پیام های پیشرفتی را مشاهده کردید که نشان می دهد یک هدف معین دو بار ساخته شده است، به احتمال زیاد این توضیح است.
Bazel از یکی از دو روش برای انتخاب پیکربندی میزبان استفاده می کند، بر اساس گزینه --distinct_host_configuration
. این گزینه بولی تا حدودی ظریف است و تنظیمات ممکن است سرعت ساختهای شما را بهبود بخشد (یا بدتر کند).
--distinct_host_configuration=false
هنگامی که این گزینه نادرست است، پیکربندی میزبان و درخواست یکسان است: تمام ابزارهای مورد نیاز در طول ساخت دقیقاً به همان شیوه برنامه های هدف ساخته می شوند. این تنظیمات به این معنی است که هیچ کتابخانه ای نیازی به ساخت دوبار در طول یک ساخت ندارد.
با این حال، به این معنی است که هر تغییری در پیکربندی درخواست شما بر پیکربندی میزبان شما نیز تأثیر میگذارد و باعث میشود که همه ابزارها بازسازی شوند و سپس هر چیزی که به خروجی ابزار بستگی دارد نیز بازسازی شود. بنابراین، به عنوان مثال، تغییر ساده یک گزینه لینکر بین بیلدها ممکن است باعث شود همه ابزارها مجدداً پیوند شوند و سپس همه اقدامات با استفاده از آنها دوباره اجرا شوند و به همین ترتیب، منجر به بازسازی بسیار بزرگی شود.
--distinct_host_configuration=true
(پیشفرض)
اگر این گزینه درست باشد، به جای استفاده از همان پیکربندی برای هاست و درخواست، از یک پیکربندی میزبان کاملاً متمایز استفاده می شود. پیکربندی میزبان از پیکربندی هدف به شرح زیر مشتق شده است:
- از همان نسخه Crosstool (
--crosstool_top
) که در پیکربندی درخواست مشخص شده است استفاده کنید، مگر اینکه--host_crosstool_top
مشخص شده باشد. - از مقدار
--host_cpu
برای--cpu
(پیشفرض:k8
). - از همان مقادیر این گزینهها که در پیکربندی درخواست مشخص شده است استفاده کنید:
--compiler
،--use_ijars
، و اگر--host_crosstool_top
استفاده شود، از مقدار--host_cpu
برای جستجویdefault_toolchain
در Crosstool استفاده میشود (با نادیده گرفتن--compiler
) برای پیکربندی میزبان. - از مقدار
--host_javabase
برای--javabase
- از مقدار
--host_java_toolchain
برای--java_toolchain
- از ساخت های بهینه سازی شده برای کد ++C استفاده کنید (
-c opt
). - هیچ اطلاعات اشکال زدایی ایجاد نکنید (
--copt=-g0
). - اطلاعات اشکال زدایی را از فایل های اجرایی و کتابخانه های مشترک حذف کنید (
--strip=always
). - همه فایل های مشتق شده را در یک مکان خاص، متمایز از مکان مورد استفاده در هر پیکربندی درخواست احتمالی قرار دهید.
- سرکوب مهر زنی باینری ها با داده های ساخت (به گزینه های
--embed_*
مراجعه کنید). - همه مقادیر دیگر در پیش فرض خود باقی می مانند.
دلایل زیادی وجود دارد که ممکن است ترجیح داده شود یک پیکربندی میزبان مجزا از پیکربندی درخواست انتخاب شود. برخی از آنها بیش از حد باطنی هستند که در اینجا نمی توان به آنها اشاره کرد، اما دو مورد از آنها ارزش اشاره دارد.
اولاً، با استفاده از باینریهای بهینهسازیشده، زمان صرف شده برای پیوند و اجرای ابزارها، فضای دیسک اشغال شده توسط ابزارها و زمان ورودی/خروجی شبکه در ساختهای توزیعشده را کاهش میدهید.
ثانیاً، با جدا کردن پیکربندی هاست و درخواست در همه ساختها، از بازسازیهای بسیار گرانقیمتی که در نتیجه تغییرات جزئی در پیکربندی درخواست (مانند تغییر گزینههای پیوند دهنده انجام میشود) جلوگیری میکنید، همانطور که قبلاً توضیح داده شد.
گفته می شود، برای ساخت های خاص، این گزینه ممکن است مانعی باشد. بهویژه، ساختهایی که در آنها تغییرات پیکربندی به ندرت انجام میشود (مخصوصاً ساختهای جاوا خاص)، و ساختهایی که در آنها مقدار کدی که باید در هر دو پیکربندی میزبان و هدف ساخته شود، زیاد است، ممکن است سودی نبرند.
بازسازی های تدریجی صحیح
یکی از اهداف اولیه پروژه بازل اطمینان از بازسازی های تدریجی صحیح است. ابزارهای ساخت قبلی، به ویژه آنهایی که مبتنی بر Make هستند، چندین فرض نادرست را در اجرای ساختهای افزایشی ایجاد میکنند.
اولاً، مُهرهای زمانی فایل ها به طور یکنواخت افزایش می یابد. در حالی که این مورد معمولی است، بسیار آسان است که با این فرض مخالفت کنیم. همگام سازی با نسخه قبلی یک فایل باعث می شود زمان اصلاح آن فایل کاهش یابد. سیستم های مبتنی بر ساخت دوباره ساخته نمی شوند.
به طور کلی، در حالی که Make تغییرات فایل ها را تشخیص می دهد، تغییرات در دستورات را تشخیص نمی دهد. اگر گزینههای ارسال شده به کامپایلر را در یک مرحله ساخت معین تغییر دهید، Make کامپایلر را دوباره اجرا نمیکند و لازم است خروجیهای نامعتبر ساخت قبلی را با استفاده از make clean
به صورت دستی حذف کنید.
همچنین، Make در برابر خاتمه ناموفق یکی از زیرفرایندهای خود پس از شروع نوشتن آن در فایل خروجی خود مقاوم نیست. در حالی که اجرای فعلی Make ناموفق خواهد بود، فراخوانی بعدی Make کورکورانه فرض میکند که فایل خروجی کوتاه شده معتبر است (زیرا از ورودیهای آن جدیدتر است) و دوباره ساخته نخواهد شد. به طور مشابه، اگر فرآیند Make از بین برود، وضعیت مشابهی ممکن است رخ دهد.
بازل از این فرضیات و سایر فرضیات اجتناب می کند. Bazel یک پایگاه داده از تمام کارهایی که قبلا انجام شده نگهداری می کند، و تنها در صورتی یک مرحله ساخت را حذف می کند که متوجه شود مجموعه فایل های ورودی (و مهر زمانی آنها) به آن مرحله ساخت، و دستور کامپایل برای آن مرحله ساخت، دقیقاً با یکی از مراحل ساخت مطابقت دارند. پایگاه داده، و اینکه مجموعه فایل های خروجی (و مهرهای زمانی آنها) برای ورودی پایگاه داده دقیقاً با مهرهای زمانی فایل های روی دیسک مطابقت دارد. هر گونه تغییر در فایل های ورودی یا خروجی یا خود دستور باعث اجرای مجدد مرحله ساخت می شود.
مزیت استفاده کنندگان از ساخت های افزایشی صحیح این است: اتلاف زمان کمتر به دلیل سردرگمی. (همچنین زمان کمتری صرف انتظار برای بازسازی های ناشی از استفاده از make clean
، چه ضروری و چه پیشگیرانه می باشد.)
ایجاد سازگاری و ساخت های افزایشی
به طور رسمی، وضعیت یک بیلد را زمانی که همه فایلهای خروجی مورد انتظار وجود داشته باشند، و محتویات آنها درست باشد، مطابق با مراحل یا قوانین مورد نیاز برای ایجاد آنها مشخص میکنیم. هنگامی که یک فایل منبع را ویرایش می کنید، وضعیت ساخت ناسازگار گفته می شود و تا زمانی که ابزار ساخت را برای تکمیل موفقیت آمیز اجرا نکنید، ناسازگار می ماند. ما این وضعیت را به عنوان ناسازگاری ناپایدار توصیف میکنیم، زیرا این وضعیت فقط موقتی است و با اجرای ابزار ساخت، ثبات بازیابی میشود.
نوع دیگری از ناسازگاری وجود دارد که مضر است: ناسازگاری پایدار . اگر ساخت به یک حالت ناسازگار پایدار برسد، آنگاه فراخوانی موفق مکرر ابزار ساخت، سازگاری را باز نمیگرداند: ساخت «گیر کرده» و خروجیها نادرست باقی میمانند. حالت های ناسازگار پایدار دلیل اصلی make clean
کاربران از نوع Make (و سایر ابزارهای ساخت) است. کشف اینکه ابزار ساخت به این روش شکست خورده است (و سپس بازیابی از آن) می تواند زمان بر و بسیار خسته کننده باشد.
از نظر مفهومی، سادهترین راه برای دستیابی به یک ساخت ثابت این است که تمام خروجیهای ساخت قبلی را دور بریزید و دوباره شروع کنید: هر ساختنی را به یک ساخت تمیز تبدیل کنید. بدیهی است که این رویکرد برای عملی بودن بیش از حد وقت گیر است (به جز شاید برای مهندسان آزاد)، و بنابراین برای مفید بودن، ابزار ساخت باید قادر به انجام ساخت های افزایشی بدون به خطر انداختن ثبات باشد.
تجزیه و تحلیل صحیح وابستگی افزایشی دشوار است، و همانطور که در بالا توضیح داده شد، بسیاری از ابزارهای ساخت دیگر کار ضعیفی برای اجتناب از حالت های ناسازگار پایدار در طول ساخت های افزایشی انجام می دهند. در مقابل، Bazel تضمین زیر را ارائه میکند: پس از فراخوانی موفقیتآمیز ابزار ساخت که طی آن هیچ ویرایشی انجام ندادید، بیلد در وضعیت ثابتی قرار میگیرد. (اگر فایل های منبع خود را در طول ساخت ویرایش کنید، Bazel هیچ تضمینی در مورد سازگاری نتیجه ساخت فعلی نمی دهد. اما تضمین می کند که نتایج ساخت بعدی ثبات را بازیابی می کند.)
مانند همه ضمانتها، برخی از ضمانتها نیز وجود دارد: راههای شناختهشدهای برای رسیدن به وضعیت ناسازگار با Bazel وجود دارد. ما تضمینی برای بررسی چنین مشکلات ناشی از تلاشهای عمدی برای یافتن اشکالات در تحلیل وابستگی افزایشی نخواهیم داشت، اما ما بررسی میکنیم و تمام تلاش خود را برای رفع همه حالتهای ناسازگار پایدار ناشی از استفاده عادی یا "معقول" از ابزار ساخت، بررسی میکنیم.
اگر تا به حال وضعیت ناسازگار پایداری با Bazel تشخیص دادید، لطفاً یک اشکال را گزارش کنید.
اجرای سندباکس
Bazel از جعبه های شنی برای تضمین اجرای هرمتیک و درست عملکردها استفاده می کند. Bazel در جعبههای ماسهای که تنها حاوی حداقل مجموعه فایلهایی هستند که ابزار برای انجام کارش به آنها نیاز دارد، spawn ها را اجرا میکند. در حال حاضر sandboxing روی لینوکس 3.12 یا جدیدتر با فعال بودن گزینه CONFIG_USER_NS
و همچنین در macOS 10.11 یا جدیدتر کار میکند.
اگر سیستم شما از sandboxing پشتیبانی نکند، Bazel یک هشدار چاپ میکند تا به شما هشدار دهد که هرمتیک بودن ساختها تضمین نمیشود و ممکن است به روشهای ناشناخته روی سیستم میزبان تأثیر بگذارد. برای غیرفعال کردن این هشدار، میتوانید پرچم --ignore_unsupported_sandboxing
را به Bazel ارسال کنید.
در برخی از پلتفرمها مانند گرههای خوشهای موتور Google Kubernetes یا Debian، فضاهای نام کاربری بهطور پیشفرض به دلیل نگرانیهای امنیتی غیرفعال میشوند. این را می توان با مشاهده فایل /proc/sys/kernel/unprivileged_userns_clone
بررسی کرد: اگر وجود داشته باشد و دارای 0 باشد، فضای نام کاربری را می توان با sudo sysctl kernel.unprivileged_userns_clone=1
فعال کرد.
در برخی موارد، جعبه شنی Bazel به دلیل تنظیم سیستم، قوانین را اجرا نمی کند. این علامت عموماً خرابی است که پیامی شبیه به namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory
وجود ندارد. در این صورت، سعی کنید sandbox را برای ژانرهای با --strategy=Genrule=standalone
و برای قوانین دیگر با --spawn_strategy=standalone
کنید. همچنین لطفاً یک اشکال را در ردیاب مشکل ما گزارش کنید و ذکر کنید که از کدام توزیع لینوکس استفاده میکنید تا بتوانیم در نسخه بعدی آن را بررسی و رفع کنیم.
مراحل ساخت
در بازل، ساخت در سه مرحله مجزا رخ می دهد. به عنوان یک کاربر، درک تفاوت بین آنها بینشی را در مورد گزینه هایی که یک ساخت را کنترل می کنند ارائه می دهد (به زیر مراجعه کنید).
فاز بارگذاری
اولین مورد بارگذاری است که در طی آن تمام فایل های BUILD لازم برای اهداف اولیه و بسته شدن موقت وابستگی های آنها، بارگیری، تجزیه، ارزیابی و کش می شوند.
برای اولین ساخت پس از راه اندازی سرور Bazel، مرحله بارگیری معمولاً چندین ثانیه طول می کشد زیرا بسیاری از فایل های BUILD از سیستم فایل بارگذاری می شوند. در ساخت های بعدی، به خصوص اگر هیچ فایل BUILD تغییر نکرده باشد، بارگذاری خیلی سریع اتفاق می افتد.
خطاهای گزارش شده در این مرحله عبارتند از: بسته پیدا نشد، هدف یافت نشد، خطاهای لغوی و دستوری در فایل BUILD و خطاهای ارزیابی.
مرحله تحلیل
فاز دوم، تجزیه و تحلیل ، شامل تجزیه و تحلیل معنایی و اعتبار سنجی هر قانون ساخت، ساخت یک نمودار وابستگی ساخت و تعیین دقیقاً چه کاری است که در هر مرحله از ساخت انجام می شود.
مانند بارگذاری، تجزیه و تحلیل نیز هنگام محاسبه کامل چندین ثانیه طول می کشد. با این حال، Bazel گراف وابستگی را از یک بیلد به ساخت بعدی ذخیره میکند و فقط آنچه را که باید انجام دهد، دوباره آنالیز میکند، که میتواند در مواردی که بستهها نسبت به ساخت قبلی تغییر نکردهاند، ساختهای افزایشی را بسیار سریع کند.
خطاهای گزارش شده در این مرحله عبارتند از: وابستگی های نامناسب، ورودی های نامعتبر به یک قانون، و همه پیام های خطای خاص قانون.
مراحل بارگذاری و تجزیه و تحلیل سریع هستند زیرا Bazel در این مرحله از ورود/خروجی فایل های غیرضروری اجتناب می کند و فقط فایل های BUILD را می خواند تا کار را مشخص کند. این به دلیل طراحی است، و Bazel را به یک پایه خوب برای ابزارهای تجزیه و تحلیل، مانند دستور پرس و جو Bazel، که در مرحله بارگذاری پیاده سازی می شود، تبدیل می کند.
مرحله اجرا
مرحله سوم و آخر ساخت، اجراست . این مرحله تضمین میکند که خروجیهای هر مرحله در ساخت با ورودیهای آن، اجرای مجدد کامپایل/پیوند کردن/و غیره مطابقت دارد. ابزار در صورت لزوم این مرحله جایی است که بیلد بیشتر زمان خود را می گذراند، از چند ثانیه تا بیش از یک ساعت برای یک ساخت بزرگ. خطاهای گزارش شده در این مرحله عبارتند از: فایل های منبع از دست رفته، خطا در یک ابزار اجرا شده توسط برخی از اقدامات ساخت، یا شکست یک ابزار در تولید مجموعه خروجی های مورد انتظار.
،در این صفحه نحوه ساختن برنامه با Bazel، ساخت دستور دستوری و دستور الگوی syntax توضیح داده شده است.
شروع سریع
برای اجرای Bazel، به دایرکتوری فضای کار پایه یا هر یک از زیر شاخه های آن بروید و bazel
را تایپ کنید. اگر نیاز به ایجاد یک فضای کاری جدید دارید، بیلد را ببینید.
bazel help
[Bazel release bazel version]
Usage: bazel command options ...
دستورات موجود
-
analyze-profile
: داده های نمایه ساخت را تجزیه و تحلیل می کند. -
aquery
: یک پرس و جو را در نمودار اقدام پس از تجزیه و تحلیل اجرا می کند. -
build
: اهداف مشخص شده را می سازد. -
canonicalize-flags
: پرچم های Bazel را متعارف کنید. -
clean
: فایل های خروجی را حذف می کند و به صورت اختیاری سرور را متوقف می کند. -
cquery
: یک پرس و جو گراف وابستگی پس از تجزیه و تحلیل را اجرا می کند. -
dump
: وضعیت داخلی فرآیند سرور Bazel را تخلیه می کند. -
help
: راهنمای دستورات یا فهرست را چاپ می کند. -
info
: اطلاعات زمان اجرا را در مورد سرور bazel نمایش می دهد. -
fetch
: تمام وابستگی های خارجی یک هدف را واکشی می کند. -
mobile-install
: برنامه ها را روی دستگاه های تلفن همراه نصب می کند. -
query
: یک پرس و جو گراف وابستگی را اجرا می کند. -
run
: هدف مشخص شده را اجرا می کند. -
shutdown
: سرور Bazel را متوقف می کند. -
test
: اهداف آزمایشی مشخص شده را می سازد و اجرا می کند. -
version
: اطلاعات نسخه را برای Bazel چاپ می کند.
کمک گرفتن
-
bazel help command
: راهنما و گزینه هایcommand
را چاپ می کند. -
bazel help
startup_options
: گزینه هایی برای میزبانی JVM Bazel. -
bazel help
target-syntax
: نحوی را برای تعیین اهداف توضیح می دهد. -
bazel help info-keys
: فهرستی از کلیدهای استفاده شده توسط دستور info را نمایش می دهد.
ابزار bazel
عملکردهای زیادی را انجام می دهد که به آنها دستورات می گویند. متداول ترین آنها bazel build
bazel test
هستند. می توانید با استفاده از bazel help
پیام های راهنمای آنلاین را مرور کنید.
ساختن یک هدف
قبل از شروع ساخت، به یک فضای کاری نیاز دارید. فضای کاری یک درخت دایرکتوری است که شامل تمام فایل های منبع مورد نیاز برای ساخت برنامه شما می باشد. Bazel به شما این امکان را می دهد که یک ساخت را از یک حجم کاملاً خواندنی انجام دهید.
برای ساختن یک برنامه با Bazel، bazel build
تایپ کنید و سپس هدفی را که می خواهید بسازید.
bazel build //foo
پس از صدور دستور ساخت //foo
، خروجی مشابه زیر را مشاهده خواهید کرد:
INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions
ابتدا، Bazel تمام بسته های موجود در نمودار وابستگی هدف شما را بارگذاری می کند. این شامل وابستگیهای اعلامشده ، فایلهایی است که مستقیماً در فایل BUILD
هدف فهرست شدهاند، و وابستگیهای انتقالی ، فایلهای فهرستشده در فایلهای BUILD
وابستگیهای هدف شما. پس از شناسایی تمام وابستگی ها، بازل آنها را از نظر صحت تجزیه و تحلیل می کند و اکشن های ساخت را ایجاد می کند. در آخر، Bazel کامپایلرها و سایر ابزارهای بیلد را اجرا می کند.
در طول مرحله اجرای ساخت، Bazel پیام های پیشرفت را چاپ می کند. پیامهای پیشرفت شامل مرحله ساخت فعلی (مانند کامپایلر یا لینککننده) در هنگام شروع، و تعداد تکمیلشده بر روی تعداد کل اقدامات ساخت است. با شروع ساخت، تعداد کل اکشن ها اغلب با کشف کل نمودار اکشن افزایش می یابد، اما تعداد آنها در عرض چند ثانیه تثبیت می شود.
در پایان ساخت، Bazel چاپ می کند که کدام اهداف درخواست شده است، آیا آنها با موفقیت ساخته شده اند یا نه، و اگر چنین است، کجا می توان فایل های خروجی را پیدا کرد. اسکریپت هایی که بیلدها را اجرا می کنند می توانند به طور قابل اعتمادی این خروجی را تجزیه کنند. برای جزئیات بیشتر به --show_result
مراجعه کنید.
اگر دوباره همان دستور را تایپ کنید، ساخت خیلی سریعتر تمام می شود.
bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action
این یک ساخت تهی است. از آنجا که هیچ چیز تغییر نکرده است، هیچ بسته ای برای بارگذاری مجدد و هیچ مرحله ساختی برای اجرا وجود ندارد. اگر چیزی در 'foo' یا وابستگیهای آن تغییر میکرد، Bazel برخی از اکشنهای ساخت را دوباره اجرا میکرد یا یک ساخت افزایشی را تکمیل میکرد.
ساختن اهداف متعدد
Bazel چندین روش را برای تعیین اهداف ساخته شده امکان پذیر می کند. در مجموع، اینها به عنوان الگوهای هدف شناخته می شوند. این نحو در دستوراتی مانند build
، test
یا query
استفاده می شود.
در حالی که از برچسب ها برای تعیین اهداف فردی استفاده می شود، مانند برای اعلام وابستگی ها در فایل های BUILD
، الگوهای هدف Bazel چندین هدف را مشخص می کند. الگوهای هدف تعمیم نحو برچسب برای مجموعه ای از اهداف، با استفاده از حروف عام است. In the simplest case, any valid label is also a valid target pattern, identifying a set of exactly one target.
All target patterns starting with //
are resolved relative to the current workspace.
//foo/bar:wiz | Just the single target //foo/bar:wiz . |
//foo/bar | Equivalent to //foo/bar:bar . |
//foo/bar:all | All rule targets in the package foo/bar . |
//foo/... | All rule targets in all packages beneath the directory foo . |
//foo/...:all | All rule targets in all packages beneath the directory foo . |
//foo/...:* | All targets (rules and files) in all packages beneath the directory foo . |
//foo/...:all-targets | All targets (rules and files) in all packages beneath the directory foo . |
//... | All targets in packages in the workspace. This does not include targets from external repositories . |
//:all | All targets in the top-level package, if there is a `BUILD` file at the root of the workspace. |
Target patterns that do not begin with //
are resolved relative to the current working directory . These examples assume a working directory of foo
:
:foo | Equivalent to //foo:foo . |
bar:wiz | Equivalent to //foo/bar:wiz . |
bar/wiz | Equivalent to:
|
bar:all | Equivalent to //foo/bar:all . |
:all | Equivalent to //foo:all . |
...:all | Equivalent to //foo/...:all . |
... | Equivalent to //foo/...:all . |
bar/...:all | Equivalent to //foo/bar/...:all . |
By default, directory symlinks are followed for recursive target patterns, except those that point to under the output base, such as the convenience symlinks that are created in the root directory of the workspace.
In addition, Bazel does not follow symlinks when evaluating recursive target patterns in any directory that contains a file named as follows: DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN
foo/...
is a wildcard over packages , indicating all packages recursively beneath directory foo
(for all roots of the package path). :all
is a wildcard over targets , matching all rules within a package. These two may be combined, as in foo/...:all
, and when both wildcards are used, this may be abbreviated to foo/...
.
In addition, :*
(or :all-targets
) is a wildcard that matches every target in the matched packages, including files that aren't normally built by any rule, such as _deploy.jar
files associated with java_binary
rules.
This implies that :*
denotes a superset of :all
; while potentially confusing, this syntax does allow the familiar :all
wildcard to be used for typical builds, where building targets like the _deploy.jar
is not desired.
In addition, Bazel allows a slash to be used instead of the colon required by the label syntax; this is often convenient when using Bash filename expansion. For example, foo/bar/wiz
is equivalent to //foo/bar:wiz
(if there is a package foo/bar
) or to //foo:bar/wiz
(if there is a package foo
).
Many Bazel commands accept a list of target patterns as arguments, and they all honor the prefix negation operator -
. This can be used to subtract a set of targets from the set specified by the preceding arguments. Note that this means order matters. For example,
bazel build foo/... bar/...
means "build all targets beneath foo
and all targets beneath bar
", whereas
bazel build -- foo/... -foo/bar/...
means "build all targets beneath foo
except those beneath foo/bar
". (The --
argument is required to prevent the subsequent arguments starting with -
from being interpreted as additional options.)
It's important to point out though that subtracting targets this way will not guarantee that they are not built, since they may be dependencies of targets that weren't subtracted. For example, if there were a target //foo:all-apis
that among others depended on //foo/bar:api
, then the latter would be built as part of building the former.
Targets with tags = ["manual"]
are not included in wildcard target patterns ( ...
, :*
, :all
, etc.) when specified in commands like bazel build
and bazel test
; you should specify such test targets with explicit target patterns on the command line if you want Bazel to build/test them. In contrast, bazel query
doesn't perform any such filtering automatically (that would defeat the purpose of bazel query
).
Fetching external dependencies
By default, Bazel will download and symlink external dependencies during the build. However, this can be undesirable, either because you'd like to know when new external dependencies are added or because you'd like to "prefetch" dependencies (say, before a flight where you'll be offline). If you would like to prevent new dependencies from being added during builds, you can specify the --fetch=false
flag. Note that this flag only applies to repository rules that do not point to a directory in the local file system. Changes, for example, to local_repository
, new_local_repository
and Android SDK and NDK repository rules will always take effect regardless of the value --fetch
.
If you disallow fetching during builds and Bazel finds new external dependencies, your build will fail.
You can manually fetch dependencies by running bazel fetch
. If you disallow during-build fetching, you'll need to run bazel fetch
:
- Before you build for the first time.
- After you add a new external dependency.
Once it has been run, you should not need to run it again until the WORKSPACE file changes.
fetch
takes a list of targets to fetch dependencies for. For example, this would fetch dependencies needed to build //foo:bar
and //bar:baz
:
bazel fetch //foo:bar //bar:baz
To fetch all external dependencies for a workspace, run:
bazel fetch //...
You do not need to run bazel fetch at all if you have all of the tools you are using (from library jars to the JDK itself) under your workspace root. However, if you're using anything outside of the workspace directory then Bazel will automatically run bazel fetch
before running bazel build
.
The repository cache
Bazel tries to avoid fetching the same file several times, even if the same file is needed in different workspaces, or if the definition of an external repository changed but it still needs the same file to download. To do so, bazel caches all files downloaded in the repository cache which, by default, is located at ~/.cache/bazel/_bazel_$USER/cache/repos/v1/
. The location can be changed by the --repository_cache
option. The cache is shared between all workspaces and installed versions of bazel. An entry is taken from the cache if Bazel knows for sure that it has a copy of the correct file, that is, if the download request has a SHA256 sum of the file specified and a file with that hash is in the cache. So specifying a hash for each external file is not only a good idea from a security perspective; it also helps avoiding unnecessary downloads.
Upon each cache hit, the modification time of the file in the cache is updated. In this way, the last use of a file in the cache directory can easily be determined, for example to manually clean up the cache. The cache is never cleaned up automatically, as it might contain a copy of a file that is no longer available upstream.
Distribution files directories
The distribution directory is another Bazel mechanism to avoid unnecessary downloads. Bazel searches distribution directories before the repository cache. The primary difference is that the distribution directory requires manual preparation.
Using the --distdir=/path/to-directory
option, you can specify additional read-only directories to look for files instead of fetching them. A file is taken from such a directory if the file name is equal to the base name of the URL and additionally the hash of the file is equal to the one specified in the download request. This only works if the file hash is specified in the WORKSPACE declaration.
While the condition on the file name is not necessary for correctness, it reduces the number of candidate files to one per specified directory. In this way, specifying distribution files directories remains efficient, even if the number of files in such a directory grows large.
Running Bazel in an airgapped environment
To keep Bazel's binary size small, Bazel's implicit dependencies are fetched over the network while running for the first time. These implicit dependencies contain toolchains and rules that may not be necessary for everyone. For example, Android tools are unbundled and fetched only when building Android projects.
However, these implicit dependencies may cause problems when running Bazel in an airgapped environment, even if you have vendored all of your WORKSPACE dependencies. To solve that, you can prepare a distribution directory containing these dependencies on a machine with network access, and then transfer them to the airgapped environment with an offline approach.
To prepare the distribution directory , use the --distdir
flag. You will need to do this once for every new Bazel binary version, since the implicit dependencies can be different for every release.
To build these dependencies outside of your airgapped environment, first checkout the Bazel source tree at the right version:
git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"
Then, build the tarball containing the implicit runtime dependencies for that specific Bazel version:
bazel build @additional_distfiles//:archives.tar
Export this tarball to a directory that can be copied into your airgapped environment. Note the --strip-components
flag, because --distdir
can be quite finicky with the directory nesting level:
tar xvf bazel-bin/external/additional_distfiles/archives.tar \
-C "$NEW_DIRECTORY" --strip-components=3
Finally, when you use Bazel in your airgapped environment, pass the --distdir
flag pointing to the directory. For convenience, you can add it as an .bazelrc
entry:
build --distdir=path/to/directory
Build configurations and cross-compilation
All the inputs that specify the behavior and result of a given build can be divided into two distinct categories. The first kind is the intrinsic information stored in the BUILD
files of your project: the build rule, the values of its attributes, and the complete set of its transitive dependencies. The second kind is the external or environmental data, supplied by the user or by the build tool: the choice of target architecture, compilation and linking options, and other toolchain configuration options. We refer to a complete set of environmental data as a configuration .
In any given build, there may be more than one configuration. Consider a cross-compile, in which you build a //foo:bin
executable for a 64-bit architecture, but your workstation is a 32-bit machine. Clearly, the build will require building //foo:bin
using a toolchain capable of creating 64-bit executables, but the build system must also build various tools used during the build itself—for example tools that are built from source, then subsequently used in, say, a genrule—and these must be built to run on your workstation. Thus we can identify two configurations: the host configuration , which is used for building tools that run during the build, and the target configuration (or request configuration , but we say "target configuration" more often even though that word already has many meanings), which is used for building the binary you ultimately requested.
Typically, there are many libraries that are prerequisites of both the requested build target ( //foo:bin
) and one or more of the host tools, for example some base libraries. Such libraries must be built twice, once for the host configuration, and once for the target configuration. Bazel takes care of ensuring that both variants are built, and that the derived files are kept separate to avoid interference; usually such targets can be built concurrently, since they are independent of each other. If you see progress messages indicating that a given target is being built twice, this is most likely the explanation.
Bazel uses one of two ways to select the host configuration, based on the --distinct_host_configuration
option. This boolean option is somewhat subtle, and the setting may improve (or worsen) the speed of your builds.
--distinct_host_configuration=false
When this option is false, the host and request configurations are identical: all tools required during the build will be built in exactly the same way as target programs. This setting means that no libraries need to be built twice during a single build.
However, it does mean that any change to your request configuration also affects your host configuration, causing all the tools to be rebuilt, and then anything that depends on the tool output to be rebuilt too. Thus, for example, simply changing a linker option between builds might cause all tools to be re-linked, and then all actions using them re-executed, and so on, resulting in a very large rebuild.
--distinct_host_configuration=true
(default)
If this option is true, then instead of using the same configuration for the host and request, a completely distinct host configuration is used. The host configuration is derived from the target configuration as follows:
- Use the same version of Crosstool (
--crosstool_top
) as specified in the request configuration, unless--host_crosstool_top
is specified. - Use the value of
--host_cpu
for--cpu
(default:k8
). - Use the same values of these options as specified in the request configuration:
--compiler
,--use_ijars
, and if--host_crosstool_top
is used, then the value of--host_cpu
is used to look up adefault_toolchain
in the Crosstool (ignoring--compiler
) for the host configuration. - Use the value of
--host_javabase
for--javabase
- Use the value of
--host_java_toolchain
for--java_toolchain
- Use optimized builds for C++ code (
-c opt
). - Generate no debugging information (
--copt=-g0
). - Strip debug information from executables and shared libraries (
--strip=always
). - Place all derived files in a special location, distinct from that used by any possible request configuration.
- Suppress stamping of binaries with build data (see
--embed_*
options). - All other values remain at their defaults.
There are many reasons why it might be preferable to select a distinct host configuration from the request configuration. Some are too esoteric to mention here, but two of them are worth pointing out.
Firstly, by using stripped, optimized binaries, you reduce the time spent linking and executing the tools, the disk space occupied by the tools, and the network I/O time in distributed builds.
Secondly, by decoupling the host and request configurations in all builds, you avoid very expensive rebuilds that would result from minor changes to the request configuration (such as changing a linker options does), as described earlier.
That said, for certain builds, this option may be a hindrance. In particular, builds in which changes of configuration are infrequent (especially certain Java builds), and builds where the amount of code that must be built in both host and target configurations is large, may not benefit.
Correct incremental rebuilds
One of the primary goals of the Bazel project is to ensure correct incremental rebuilds. Previous build tools, especially those based on Make, make several unsound assumptions in their implementation of incremental builds.
Firstly, that timestamps of files increase monotonically. While this is the typical case, it is very easy to fall afoul of this assumption; syncing to an earlier revision of a file causes that file's modification time to decrease; Make-based systems will not rebuild.
More generally, while Make detects changes to files, it does not detect changes to commands. If you alter the options passed to the compiler in a given build step, Make will not re-run the compiler, and it is necessary to manually discard the invalid outputs of the previous build using make clean
.
Also, Make is not robust against the unsuccessful termination of one of its subprocesses after that subprocess has started writing to its output file. While the current execution of Make will fail, the subsequent invocation of Make will blindly assume that the truncated output file is valid (because it is newer than its inputs), and it will not be rebuilt. Similarly, if the Make process is killed, a similar situation can occur.
Bazel avoids these assumptions, and others. Bazel maintains a database of all work previously done, and will only omit a build step if it finds that the set of input files (and their timestamps) to that build step, and the compilation command for that build step, exactly match one in the database, and, that the set of output files (and their timestamps) for the database entry exactly match the timestamps of the files on disk. Any change to the input files or output files, or to the command itself, will cause re-execution of the build step.
The benefit to users of correct incremental builds is: less time wasted due to confusion. (Also, less time spent waiting for rebuilds caused by use of make clean
, whether necessary or pre-emptive.)
Build consistency and incremental builds
Formally, we define the state of a build as consistent when all the expected output files exist, and their contents are correct, as specified by the steps or rules required to create them. When you edit a source file, the state of the build is said to be inconsistent , and remains inconsistent until you next run the build tool to successful completion. We describe this situation as unstable inconsistency , because it is only temporary, and consistency is restored by running the build tool.
There is another kind of inconsistency that is pernicious: stable inconsistency . If the build reaches a stable inconsistent state, then repeated successful invocation of the build tool does not restore consistency: the build has gotten "stuck", and the outputs remain incorrect. Stable inconsistent states are the main reason why users of Make (and other build tools) type make clean
. Discovering that the build tool has failed in this manner (and then recovering from it) can be time consuming and very frustrating.
Conceptually, the simplest way to achieve a consistent build is to throw away all the previous build outputs and start again: make every build a clean build. This approach is obviously too time-consuming to be practical (except perhaps for release engineers), and therefore to be useful, the build tool must be able to perform incremental builds without compromising consistency.
Correct incremental dependency analysis is hard, and as described above, many other build tools do a poor job of avoiding stable inconsistent states during incremental builds. In contrast, Bazel offers the following guarantee: after a successful invocation of the build tool during which you made no edits, the build will be in a consistent state. (If you edit your source files during a build, Bazel makes no guarantee about the consistency of the result of the current build. But it does guarantee that the results of the next build will restore consistency.)
As with all guarantees, there comes some fine print: there are some known ways of getting into a stable inconsistent state with Bazel. We won't guarantee to investigate such problems arising from deliberate attempts to find bugs in the incremental dependency analysis, but we will investigate and do our best to fix all stable inconsistent states arising from normal or "reasonable" use of the build tool.
If you ever detect a stable inconsistent state with Bazel, please report a bug.
Sandboxed execution
Bazel uses sandboxes to guarantee that actions run hermetically and correctly. Bazel runs spawns (loosely speaking: actions) in sandboxes that only contain the minimal set of files the tool requires to do its job. Currently sandboxing works on Linux 3.12 or newer with the CONFIG_USER_NS
option enabled, and also on macOS 10.11 or newer.
Bazel will print a warning if your system does not support sandboxing to alert you to the fact that builds are not guaranteed to be hermetic and might affect the host system in unknown ways. To disable this warning you can pass the --ignore_unsupported_sandboxing
flag to Bazel.
On some platforms such as Google Kubernetes Engine cluster nodes or Debian, user namespaces are deactivated by default due to security concerns. This can be checked by looking at the file /proc/sys/kernel/unprivileged_userns_clone
: if it exists and contains a 0, then user namespaces can be activated with sudo sysctl kernel.unprivileged_userns_clone=1
.
In some cases, the Bazel sandbox fails to execute rules because of the system setup. The symptom is generally a failure that output a message similar to namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory
. In that case, try to deactivate the sandbox for genrules with --strategy=Genrule=standalone
and for other rules with --spawn_strategy=standalone
. Also please report a bug on our issue tracker and mention which Linux distribution you're using so that we can investigate and provide a fix in a subsequent release.
Phases of a build
In Bazel, a build occurs in three distinct phases; as a user, understanding the difference between them provides insight into the options which control a build (see below).
Loading phase
The first is loading during which all the necessary BUILD files for the initial targets, and their transitive closure of dependencies, are loaded, parsed, evaluated and cached.
For the first build after a Bazel server is started, the loading phase typically takes many seconds as many BUILD files are loaded from the file system. In subsequent builds, especially if no BUILD files have changed, loading occurs very quickly.
Errors reported during this phase include: package not found, target not found, lexical and grammatical errors in a BUILD file, and evaluation errors.
Analysis phase
The second phase, analysis , involves the semantic analysis and validation of each build rule, the construction of a build dependency graph, and the determination of exactly what work is to be done in each step of the build.
Like loading, analysis also takes several seconds when computed in its entirety. However, Bazel caches the dependency graph from one build to the next and only reanalyzes what it has to, which can make incremental builds extremely fast in the case where the packages haven't changed since the previous build.
Errors reported at this stage include: inappropriate dependencies, invalid inputs to a rule, and all rule-specific error messages.
The loading and analysis phases are fast because Bazel avoids unnecessary file I/O at this stage, reading only BUILD files in order to determine the work to be done. This is by design, and makes Bazel a good foundation for analysis tools, such as Bazel's query command, which is implemented atop the loading phase.
Execution phase
The third and final phase of the build is execution . This phase ensures that the outputs of each step in the build are consistent with its inputs, re-running compilation/linking/etc. tools as necessary. This step is where the build spends the majority of its time, ranging from a few seconds to over an hour for a large build. Errors reported during this phase include: missing source files, errors in a tool executed by some build action, or failure of a tool to produce the expected set of outputs.