آموزش بازل: ساخت پروژه جاوا

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

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

چیزی که یاد خواهید گرفت

در این آموزش یاد می گیرید که چگونه:

  • یک هدف بسازید
  • وابستگی های پروژه را تجسم کنید
  • پروژه را به چندین هدف و بسته تقسیم کنید
  • کنترل دید هدف در سراسر بسته ها
  • ارجاع به اهداف از طریق برچسب ها
  • یک هدف را مستقر کنید

قبل از اینکه شروع کنی

Bazel را نصب کنید

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

JDK را نصب کنید

  1. جاوا JDK را نصب کنید (نسخه ترجیحی 11 است، اما نسخه های بین 8 و 15 پشتیبانی می شوند).

  2. متغیر محیطی JAVA_HOME را طوری تنظیم کنید که به JDK اشاره کند.

    • در Linux/macOS:

      export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
      
    • در ویندوز:

      1. کنترل پنل را باز کنید.
      2. به "System and Security" > "System" > "Advanced System Settings" > "Advanced" تب > "Environment Variables..." بروید.
      3. در زیر لیست «متغیرهای کاربر» (متغیرهای بالا)، روی «جدید...» کلیک کنید.
      4. در قسمت "نام متغیر"، JAVA_HOME را وارد کنید.
      5. روی "مرور دایرکتوری..." کلیک کنید.
      6. به فهرست JDK بروید (به عنوان مثال C:\Program Files\Java\jdk1.8.0_152 ).
      7. روی "OK" در تمام پنجره های گفتگو کلیک کنید.

نمونه پروژه را دریافت کنید

نمونه پروژه را از مخزن Bazel's GitHub بازیابی کنید:

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

پروژه نمونه این آموزش در پوشه examples/java-tutorial دارد و ساختار آن به صورت زیر است:

java-tutorial
├── BUILD
├── src
│   └── main
│       └── java
│           └── com
│               └── example
│                   ├── cmdline
│                   │   ├── BUILD
│                   │   └── Runner.java
│                   ├── Greeting.java
│                   └── ProjectRunner.java
└── WORKSPACE

ساخت با بازل

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

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

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

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

برای تعیین دایرکتوری به عنوان فضای کاری Bazel، یک فایل خالی به نام WORKSPACE در آن دایرکتوری ایجاد کنید.

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

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

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

به فایل java-tutorial/BUILD نگاهی بیندازید:

java_binary(
    name = "ProjectRunner",
    srcs = glob(["src/main/java/com/example/*.java"]),
)

در مثال ما، هدف ProjectRunner قانون java_binary را نشان می‌دهد. این قانون به Bazel می‌گوید که یک فایل .jar و یک اسکریپت wrapper shell (هر دو به نام هدف) بسازد.

ویژگی های موجود در هدف به صراحت وابستگی ها و گزینه های آن را بیان می کنند. در حالی که ویژگی name اجباری است، بسیاری از آنها اختیاری هستند. به عنوان مثال، در هدف قانون ProjectRunner ، name نام هدف است، srcs فایل‌های منبعی را که Bazel برای ساختن هدف استفاده می‌کند، و main_class کلاسی را که متد اصلی را شامل می‌شود، مشخص می‌کند. (شاید متوجه شده باشید که مثال ما از glob برای ارسال مجموعه ای از فایل های منبع به Bazel به جای فهرست کردن یک به یک آنها استفاده می کند.)

پروژه را بسازید

برای ساخت نمونه پروژه خود، به دایرکتوری java-tutorial بروید و اجرا کنید:

bazel build //:ProjectRunner

در برچسب هدف، قسمت // محل فایل BUILD نسبت به ریشه فضای کاری (در این مورد، خود ریشه) است و ProjectRunner نام هدف در فایل BUILD است. (در پایان این آموزش با برچسب های هدف با جزئیات بیشتری آشنا خواهید شد.)

Bazel خروجی مشابه زیر تولید می کند:

   INFO: Found 1 target...
   Target //:ProjectRunner up-to-date:
      bazel-bin/ProjectRunner.jar
      bazel-bin/ProjectRunner
   INFO: Elapsed time: 1.021s, Critical Path: 0.83s

تبریک می‌گوییم، شما اولین هدف بازل خود را ساختید! Bazel خروجی های ساخت را در دایرکتوری bazel-bin در ریشه فضای کاری قرار می دهد. محتویات آن را مرور کنید تا از ساختار خروجی Bazel ایده بگیرید.

اکنون باینری تازه ساخته شده خود را تست کنید:

bazel-bin/ProjectRunner

نمودار وابستگی را مرور کنید

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

برای تجسم وابستگی های پروژه نمونه، می توانید با اجرای این دستور در ریشه فضای کاری، یک نمایش متنی از نمودار وابستگی ایجاد کنید:

bazel query  --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph

دستور بالا به Bazel می گوید که تمام وابستگی ها را برای هدف //:ProjectRunner (به استثنای وابستگی های میزبان و ضمنی) جستجو کند و خروجی را به صورت نمودار فرمت کند.

سپس، متن را در GraphViz قرار دهید.

همانطور که می بینید، پروژه دارای یک هدف واحد است که دو فایل منبع را بدون وابستگی اضافی می سازد:

نمودار وابستگی هدف 'ProjectRunner'

بعد از اینکه فضای کاری خود را راه اندازی کردید، پروژه خود را ساختید و وابستگی های آن را بررسی کردید، سپس می توانید کمی پیچیدگی اضافه کنید.

ساخت Bazel خود را اصلاح کنید

در حالی که یک هدف واحد برای پروژه‌های کوچک کافی است، ممکن است بخواهید پروژه‌های بزرگ‌تر را به چندین هدف و بسته تقسیم کنید تا امکان ساخت‌های افزایشی سریع را فراهم کنید (یعنی فقط آنچه را که تغییر کرده است بازسازی کنید) و با ساختن چندین بخش از یک پروژه سرعت ساخت‌های خود را افزایش دهید. فورا.

چندین هدف ساخت را مشخص کنید

شما می توانید نمونه ساخت پروژه را به دو هدف تقسیم کنید. محتویات فایل java-tutorial/BUILD را با موارد زیر جایگزین کنید:

java_binary(
    name = "ProjectRunner",
    srcs = ["src/main/java/com/example/ProjectRunner.java"],
    main_class = "com.example.ProjectRunner",
    deps = [":greeter"],
)

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
)

با این پیکربندی، Bazel ابتدا کتابخانه greeter و سپس ProjectRunner باینری را می سازد. ویژگی deps در java_binary به Bazel می گوید که کتابخانه greeter برای ساخت باینری ProjectRunner مورد نیاز است.

برای ساخت این نسخه جدید از پروژه، دستور زیر را اجرا کنید:

bazel build //:ProjectRunner

Bazel خروجی مشابه زیر تولید می کند:

INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
  bazel-bin/ProjectRunner.jar
  bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s

اکنون باینری تازه ساخته شده خود را تست کنید:

bazel-bin/ProjectRunner

اگر اکنون ProjectRunner.java تغییر دهید و پروژه را بازسازی کنید، Bazel فقط آن فایل را دوباره کامپایل می کند.

با نگاهی به نمودار وابستگی، می توانید ببینید که ProjectRunner به همان ورودی هایی که قبلاً انجام می داد بستگی دارد، اما ساختار ساخت متفاوت است:

نمودار وابستگی هدف «ProjectRunner» پس از افزودن یک وابستگی

شما اکنون پروژه را با دو هدف ساخته اید. هدف ProjectRunner دو فایل منبع می‌سازد و به یک هدف دیگر ( :greeter ) وابسته است، که یک فایل منبع اضافی را می‌سازد.

از بسته های متعدد استفاده کنید

حالا بیایید پروژه را به چند بسته تقسیم کنیم. اگر به دایرکتوری src/main/java/com/example/cmdline نگاهی بیندازید، می بینید که شامل یک فایل BUILD به اضافه چند فایل منبع است. بنابراین، برای بازل، فضای کاری اکنون شامل دو بسته است، //src/main/java/com/example/cmdline و // (از آنجایی که یک فایل BUILD در ریشه فضای کاری وجود دارد).

به فایل src/main/java/com/example/cmdline/BUILD نگاهی بیندازید:

java_binary(
    name = "runner",
    srcs = ["Runner.java"],
    main_class = "com.example.cmdline.Runner",
    deps = ["//:greeter"],
)

هدف runner به هدف استقبال کننده در بسته // بستگی دارد (از این رو برچسب هدف //:greeter greeter - Bazel این را از طریق ویژگی deps می داند. به نمودار وابستگی نگاهی بیندازید:

نمودار وابستگی "دونده" هدف

با این حال، برای موفقیت ساخت، باید به طور واضح به هدف runner در //src/main/java/com/example/cmdline/BUILD قابلیت مشاهده را به اهداف در //BUILD با استفاده از ویژگی visibility بدهید. این به این دلیل است که اهداف به طور پیش فرض فقط برای اهداف دیگر در همان فایل BUILD قابل مشاهده هستند. (بازل از دید هدف برای جلوگیری از نشت مسائلی مانند کتابخانه های حاوی جزئیات پیاده سازی به API های عمومی استفاده می کند.)

برای انجام این کار، مطابق شکل زیر، ویژگی visibility را به هدف greeter در java-tutorial/BUILD اضافه کنید:

java_library(
    name = "greeter",
    srcs = ["src/main/java/com/example/Greeting.java"],
    visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)

اکنون می توانید بسته جدید را با اجرای دستور زیر در ریشه فضای کاری بسازید:

bazel build //src/main/java/com/example/cmdline:runner

Bazel خروجی مشابه زیر تولید می کند:

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner.jar
  bazel-bin/src/main/java/com/example/cmdline/runner
  INFO: Elapsed time: 1.576s, Critical Path: 0.81s

اکنون باینری تازه ساخته شده خود را تست کنید:

./bazel-bin/src/main/java/com/example/cmdline/runner

شما اکنون پروژه را به گونه ای تغییر داده اید که به صورت دو بسته که هر کدام شامل یک هدف است ساخته شود و وابستگی های بین آنها را درک کنید.

از برچسب ها برای ارجاع به اهداف استفاده کنید

در فایل های BUILD و در خط فرمان، Bazel از برچسب های هدف برای ارجاع به اهداف استفاده می کند - به عنوان مثال، //:ProjectRunner یا //src/main/java/com/example/cmdline:runner . نحو آنها به شرح زیر است:

//path/to/package:target-name

اگر هدف یک هدف قانون است، path/to/package مسیری است به دایرکتوری حاوی فایل BUILD ، و target-name همان چیزی است که هدف را در فایل BUILD نامگذاری کرده‌اید (ویژگی name ). اگر هدف یک فایل هدف است، path/to/package مسیری است که به ریشه بسته می‌رسد، و target-name نام فایل هدف، شامل مسیر کامل آن است.

هنگام ارجاع به اهداف در ریشه مخزن، مسیر بسته خالی است، فقط از //:target-name استفاده کنید. هنگام ارجاع به اهداف در یک فایل BUILD ، حتی می‌توانید از // شناسه ریشه فضای کاری رد شوید و فقط از :target-name استفاده کنید.

به عنوان مثال، برای اهداف در فایل java-tutorial/BUILD ، لازم نیست مسیر بسته را مشخص کنید، زیرا ریشه فضای کاری خود یک بسته ( // ) است و دو برچسب هدف شما به سادگی //:ProjectRunner و //:greeter هستند. //:greeter .

با این حال، برای اهداف در فایل //src/main/java/com/example/cmdline/BUILD باید مسیر بسته کامل //src/main/java/com/example/cmdline را مشخص می‌کردید و برچسب هدف شما //src/main/java/com/example/cmdline:runner .

یک هدف جاوا را برای استقرار بسته بندی کنید

بیایید اکنون یک هدف جاوا را برای استقرار با ساخت باینری با تمام وابستگی‌های زمان اجرا بسته بندی کنیم. این به شما امکان می دهد باینری را خارج از محیط توسعه خود اجرا کنید.

همانطور که به یاد دارید، قانون ساخت java_binary یک .jar . و یک اسکریپت پوسته wrapper تولید می کند. با استفاده از این دستور به محتویات runner.jar نگاهی بیندازید:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar

مطالب عبارتند از:

META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class

همانطور که می بینید، runner.jar حاوی Runner.class است، اما نه وابستگی آن، Greeting.class . اسکریپت runner که Bazel تولید greeter.jar را به classpath اضافه می‌کند، بنابراین اگر آن را به این شکل رها کنید، به صورت محلی اجرا می‌شود، اما به‌صورت مستقل روی ماشین دیگری اجرا نمی‌شود. خوشبختانه، قانون java_binary به شما امکان می دهد یک باینری مستقل و قابل استقرار بسازید. برای ساخت آن، _deploy.jar را به نام هدف اضافه کنید:

bazel build //src/main/java/com/example/cmdline:runner_deploy.jar

Bazel خروجی مشابه زیر تولید می کند:

INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
  bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s

شما runner_deploy.jar را ساخته‌اید، که می‌توانید آن را به‌صورت مستقل و به دور از محیط توسعه خود اجرا کنید زیرا حاوی وابستگی‌های زمان اجرا مورد نیاز است. با استفاده از دستور قبلی به محتویات این JAR مستقل نگاهی بیندازید:

jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar

محتویات شامل تمام کلاس های لازم برای اجرا می شود:

META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class

بیشتر خواندن

برای جزئیات بیشتر، نگاه کنید به:

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