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

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

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