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

รายงานปัญหา ดูแหล่งที่มา

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

คำสั่งเรียกใช้ Bazel

การที่ 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 จะสร้างไฟล์ Kubernetes YAML มาตรฐานเมื่อใช้ 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 คุณจะเรียกใช้เป้าหมายดังกล่าว 1 เป้าหมายได้ด้วย bazel test //etc/api:angular_devkit_core_api

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

และสุดท้าย กำหนดมาโครต่อไปนี้เพื่อสร้างเป้าหมายสำหรับกฎทั้ง 2 ข้อข้างต้นร่วมกัน

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"],
)

ในตัวอย่างนี้ ระบบจะสร้างเป้าหมาย "เอกสาร" ขึ้นมาเหมือนกับว่ามาโครเป็นกฎ 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 ได้อย่างมีประสิทธิภาพ