การโต้ตอบกับ Bazel ในแต่ละวันส่วนใหญ่จะเกิดขึ้นผ่านคำสั่ง 2-3 คำสั่ง ได้แก่ build, test และ run อย่างไรก็ตาม บางครั้งคำสั่งเหล่านี้อาจดูมีข้อจำกัด เช่น คุณอาจต้องการพุชแพ็กเกจไปยังที่เก็บ เผยแพร่เอกสารประกอบสำหรับผู้ใช้ปลายทาง หรือทำให้แอปพลิเคชันใช้งานได้ด้วย Kubernetes แต่ Bazel ไม่มีคำสั่ง publish หรือ deploy แล้วการดำเนินการเหล่านี้จะอยู่ที่ไหน
คำสั่ง bazel run
การที่ Bazel มุ่งเน้นไปที่ความสามารถในการทำงานแบบ hermetic, ความสามารถในการทำซ้ำ และความสามารถในการเพิ่มขึ้นทีละน้อย หมายความว่าคำสั่ง build และ test ไม่เป็นประโยชน์สำหรับงานข้างต้น การดำเนินการเหล่านี้อาจทำงานในแซนด์บ็อกซ์ที่มีการเข้าถึงเครือข่ายแบบจำกัด และไม่รับประกันว่าจะมีการเรียกใช้ซ้ำกับ bazel build ทุกครั้ง
ให้ใช้ bazel run แทน ซึ่งเป็นเครื่องมือหลักสำหรับงานที่คุณ ต้องการ ให้มีผลข้างเคียง ผู้ใช้ Bazel คุ้นเคยกับกฎที่สร้างไฟล์ที่เรียกใช้งานได้ และผู้เขียนกฎสามารถทำตามชุดรูปแบบทั่วไปเพื่อขยายกฎนี้ให้เป็น "คำกริยาที่กำหนดเอง"
ในทางปฏิบัติ: rules_k8s
ตัวอย่างเช่น ลองพิจารณา rules_k8s,
ซึ่งเป็นกฎ Kubernetes สำหรับ Bazel สมมติว่าคุณมีเป้าหมายต่อไปนี้
# BUILD file in //application/k8s
k8s_object(
name = "staging",
kind = "deployment",
cluster = "testing",
template = "deployment.yaml",
)
กฎ k8s_object จะสร้างไฟล์ YAML ของ Kubernetes มาตรฐานเมื่อใช้ bazel build กับเป้าหมาย staging อย่างไรก็ตาม เป้าหมายเพิ่มเติมจะสร้างขึ้นโดยมาโคร k8s_object ด้วยชื่อ เช่น staging.apply และ :staging.delete ซึ่งจะสร้างสคริปต์
เพื่อดำเนินการเหล่านั้น และเมื่อเรียกใช้ด้วย bazel run
staging.apply สคริปต์เหล่านี้จะทำงานเหมือนคำสั่ง bazel k8s-apply หรือ bazel
k8s-delete ของเราเอง
อีกตัวอย่างหนึ่ง: ts_api_guardian_test
คุณจะเห็นรูปแบบนี้ได้ในโปรเจ็กต์ Angular ด้วย มาโคร
ts_api_guardian_test
จะสร้างเป้าหมาย 2 รายการ เป้าหมายแรกคือเป้าหมาย nodejs_test มาตรฐานซึ่งจะเปรียบเทียบเอาต์พุตที่สร้างขึ้นกับไฟล์ "golden" (นั่นคือไฟล์ที่มีเอาต์พุตที่คาดไว้) คุณสามารถสร้างและเรียกใช้เป้าหมายนี้ได้ด้วยการเรียกใช้ bazel
test ตามปกติ ใน angular-cli คุณสามารถเรียกใช้ เป้าหมาย
ดังกล่าว
ได้ด้วย bazel test //etc/api:angular_devkit_core_api
เมื่อเวลาผ่านไป คุณอาจต้องอัปเดตไฟล์ golden ด้วยเหตุผลที่สมควร
การอัปเดตไฟล์นี้ด้วยตนเองเป็นเรื่องน่าเบื่อและอาจเกิดข้อผิดพลาดได้ ดังนั้นมาโครนี้จึงมีเป้าหมาย nodejs_binary ที่อัปเดตไฟล์ golden แทนที่จะเปรียบเทียบกับไฟล์ดังกล่าว กล่าวคือ คุณสามารถเขียนสคริปต์ทดสอบเดียวกันให้ทำงานในโหมด "verify" หรือ "accept" ได้โดยขึ้นอยู่กับวิธีเรียกใช้ ซึ่งเป็นไปตามรูปแบบเดียวกับที่คุณได้เรียนรู้ไปแล้ว นั่นคือไม่มีคำสั่ง bazel test-accept แบบเนทีฟ แต่คุณสามารถใช้ bazel run //etc/api:angular_devkit_core_api.accept เพื่อให้ได้ผลลัพธ์เดียวกัน
รูปแบบนี้มีประสิทธิภาพมาก และพบได้บ่อยเมื่อคุณเรียนรู้ที่จะจดจำรูปแบบนี้
การปรับกฎของคุณเอง
มาโครเป็นหัวใจสำคัญของรูปแบบนี้ มาโครใช้เหมือนกฎ แต่สามารถสร้างเป้าหมายได้หลายรายการ โดยปกติแล้ว มาโครจะสร้างเป้าหมายที่มีชื่อที่ระบุซึ่งดำเนินการสร้างหลัก เช่น สร้างไบนารีปกติ, อิมเมจ Docker หรือไฟล์เก็บถาวรของซอร์สโค้ด ในรูปแบบนี้ ระบบจะสร้างเป้าหมายเพิ่มเติมเพื่อสร้างสคริปต์ที่ดำเนินการผลข้างเคียงตามเอาต์พุตของเป้าหมายหลัก เช่น การเผยแพร่ไบนารีที่ได้หรือการอัปเดตเอาต์พุตการทดสอบที่คาดไว้
เพื่อแสดงให้เห็นถึงเรื่องนี้ ให้ห่อกฎสมมติที่สร้างเว็บไซต์ด้วย Sphinx ด้วยมาโครเพื่อสร้างเป้าหมายเพิ่มเติมที่อนุญาตให้ผู้ใช้เผยแพร่เว็บไซต์เมื่อพร้อม ลองพิจารณากฎที่มีอยู่ต่อไปนี้สำหรับการสร้างเว็บไซต์ด้วย Sphinx
_sphinx_site = rule(
implementation = _sphinx_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)
จากนั้นลองพิจารณากฎต่อไปนี้ ซึ่งสร้างสคริปต์ที่เผยแพร่หน้าที่สร้างขึ้นเมื่อเรียกใช้
_sphinx_publisher = rule(
implementation = _publish_impl,
attrs = {
"site": attr.label(),
"_publisher": attr.label(
default = "//internal/sphinx:publisher",
executable = True,
),
},
executable = True,
)
สุดท้าย ให้กำหนดมาโครสัญลักษณ์ต่อไปนี้ (มีใน Bazel 8 ขึ้นไป) เพื่อสร้างเป้าหมายสำหรับกฎทั้ง 2 ข้อข้างต้นพร้อมกัน
def _sphinx_site_impl(name, visibility, srcs, **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML. We
# set `visibility = visibility` to make it visible to callers of the
# macro.
_sphinx_site(name = name, visibility = visibility, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above. We don't want it to be visible to callers of
# our macro, so we omit visibility for it.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
sphinx_site = macro(
implementation = _sphinx_site_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
# Inherit common attributes like tags and testonly
inherit_attrs = "common",
)
หรือหากต้องการรองรับ Bazel เวอร์ชันที่เก่ากว่า Bazel 8 คุณจะต้องกำหนดมาโครเดิมแทน
def sphinx_site(name, srcs = [], **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML.
_sphinx_site(name = name, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
ในไฟล์ BUILD ให้ใช้มาโครราวกับว่ามาโครจะสร้างเป้าหมายหลักเท่านั้น
sphinx_site(
name = "docs",
srcs = ["index.md", "providers.md"],
)
ในตัวอย่างนี้ ระบบจะสร้างเป้าหมาย "docs" ราวกับว่ามาโครเป็นกฎ Bazel เดียวมาตรฐาน เมื่อสร้างแล้ว กฎจะสร้างการกำหนดค่าบางอย่างและเรียกใช้ Sphinx เพื่อสร้างเว็บไซต์ HTML ซึ่งพร้อมสำหรับการตรวจสอบด้วยตนเอง อย่างไรก็ตาม ระบบจะสร้างเป้าหมาย "docs.publish" เพิ่มเติมด้วย ซึ่งจะสร้างสคริปต์สำหรับการเผยแพร่เว็บไซต์ เมื่อตรวจสอบเอาต์พุตของเป้าหมายหลักแล้ว คุณสามารถใช้ bazel run :docs.publish เพื่อเผยแพร่เอาต์พุตดังกล่าวให้สาธารณชนได้ใช้ เช่นเดียวกับคำสั่ง bazel publish สมมติ
คุณอาจไม่ทราบทันทีว่าการใช้งานกฎ _sphinx_publisher จะเป็นอย่างไร โดยปกติแล้ว การดำเนินการเช่นนี้จะเขียนสคริปต์เชลล์ launcher
วิธีนี้มักเกี่ยวข้องกับการใช้
ctx.actions.expand_template
เพื่อเขียนสคริปต์เชลล์ที่เรียบง่ายมาก ในกรณีนี้คือการเรียกใช้ไบนารีของผู้เผยแพร่โฆษณา
ด้วยเส้นทางไปยังเอาต์พุตของเป้าหมายหลัก วิธีนี้จะช่วยให้การใช้งานของผู้เผยแพร่โฆษณายังคงเป็นแบบทั่วไป กฎ _sphinx_site สามารถสร้าง HTML ได้ และสคริปต์ขนาดเล็กนี้เป็นสิ่งเดียวที่จำเป็นในการรวมทั้ง 2 อย่างเข้าด้วยกัน
ใน rules_k8s สิ่งที่ .apply ทำคือ
expand_template
จะเขียนสคริปต์ Bash ที่เรียบง่ายมากโดยอิงตาม
apply.sh.tpl,
ซึ่งจะเรียกใช้ kubectl ด้วยเอาต์พุตของเป้าหมายหลัก จากนั้นคุณสามารถสร้างและเรียกใช้สคริปต์นี้ด้วย bazel run :staging.apply ซึ่งจะให้คำสั่ง k8s-apply สำหรับเป้าหมาย k8s_object