การใช้มาโครเพื่อสร้างคำกริยาที่กำหนดเอง

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

การโต้ตอบกับ Bazel ในแต่ละวันจะทําได้ผ่านคําสั่ง 3 รายการหลักๆ ดังนี้ build, test และ run แต่บางครั้งคุณอาจรู้สึกว่าเครื่องมือเหล่านี้มีข้อจำกัด เช่น คุณอาจต้องการพุชแพ็กเกจไปยังที่เก็บ เผยแพร่เอกสารประกอบสำหรับผู้ใช้ปลายทาง หรือทำให้แอปพลิเคชันใช้งานได้ด้วย Kubernetes แต่ Bazel ไม่มีคำสั่ง publish หรือ deploy แล้วการดำเนินการเหล่านี้จะใส่ไว้ตรงไหน

คำสั่ง bazel run

การที่ Bazel มุ่งเน้นที่การปิดผนึก ความสามารถในการทําซ้ำ และการเพิ่มประสิทธิภาพ หมายความว่าคําสั่ง 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 มาตรฐานซึ่งจะเปรียบเทียบเอาต์พุตที่สร้างขึ้นบางส่วนกับไฟล์ "โกลด์" (นั่นคือไฟล์ที่มีเอาต์พุตที่คาดไว้) ซึ่งจะสร้างและเรียกใช้ได้ด้วยการเรียกใช้ bazel test ปกติ ใน angular-cli คุณสามารถเรียกใช้เป้าหมายดังกล่าว 1 รายการด้วย bazel test //etc/api:angular_devkit_core_api

เมื่อเวลาผ่านไป ไฟล์โกลด์นี้อาจต้องได้รับการอัปเดตด้วยเหตุผลอันสมควร การอัปเดตด้วยตนเองนั้นน่าเบื่อและอาจเกิดข้อผิดพลาดได้ ดังนั้นมาโครนี้จึงมีเป้าหมาย nodejs_binary ที่อัปเดตไฟล์โกลด์แทนการเปรียบเทียบกับไฟล์โกลด์ ในทางปฏิบัติ คุณสามารถเขียนสคริปต์ทดสอบเดียวกันให้ทำงานในโหมด "ยืนยัน" หรือ "ยอมรับ" โดยขึ้นอยู่กับวิธีเรียกใช้ การดำเนินการนี้เป็นไปตามรูปแบบเดียวกับที่คุณได้เรียนรู้ไปแล้ว นั่นคือไม่มีคำสั่ง 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กฎอาจมีลักษณะเป็นอย่างไร การดำเนินการเช่นนี้มักจะเขียนสคริปต์ Shell ของตัวเปิด โดยปกติแล้ววิธีนี้เกี่ยวข้องกับการใช้ ctx.actions.expand_template เพื่อเขียนเชลล์สคริปต์อย่างง่าย ซึ่งในกรณีนี้จะเป็นการเรียกใช้ไบนารีของผู้เผยแพร่โฆษณาพร้อมเส้นทางไปยังเอาต์พุตของเป้าหมายหลัก วิธีนี้ช่วยให้ผู้เผยแพร่โฆษณาใช้การนําไปใช้แบบทั่วไปได้ กฎ _sphinx_site จะสร้างได้แค่ HTML และสคริปต์เล็กๆ นี้เป็นสิ่งที่จําเป็นทั้งหมดในการรวมทั้งสองเข้าด้วยกัน

ใน rules_k8s .apply ทําสิ่งต่อไปนี้จริงๆ expand_template เขียนสคริปต์ Bash ที่เรียบง่ายมาก โดยอิงตาม apply.sh.tpl ซึ่งเรียกใช้ kubectl ด้วยเอาต์พุตของเป้าหมายหลัก จากนั้นจึงจะสร้างและเรียกใช้สคริปต์นี้ด้วย bazel run :staging.apply ซึ่งจะระบุคําสั่ง k8s-apply สําหรับเป้าหมาย k8s_object ได้อย่างมีประสิทธิภาพ