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

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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