บทแนะนำกฎ

Starlark เป็นภาษาการกำหนดค่าที่คล้ายกับ Python ซึ่งเดิมพัฒนาขึ้นเพื่อใช้ใน Bazel และเครื่องมืออื่นๆ ได้นำไปใช้ในภายหลัง ไฟล์ BUILD และ .bzl ของ Bazel เขียนด้วยภาษา Starlark ซึ่งรู้จักกันในชื่อ "Build Language" แม้ว่ามักจะเรียกว่า "Starlark" เฉยๆ โดยเฉพาะอย่างยิ่งเมื่อเน้นว่าฟีเจอร์หนึ่งๆ แสดงใน Build Language ไม่ใช่ส่วนหนึ่งของ Bazel ที่มีอยู่ภายในหรือ "เนทีฟ" Bazel เพิ่มฟังก์ชันที่เกี่ยวข้องกับการสร้างจำนวนมาก เช่น glob, genrule, java_binary และอื่นๆ ลงในภาษาหลัก

ดูรายละเอียดเพิ่มเติมได้ในเอกสารประกอบของ Bazel และ Starlark รวมถึง เทมเพลต Rules SIG เป็น จุดเริ่มต้นสำหรับชุดกฎใหม่

กฎว่าง

หากต้องการสร้างกฎแรก ให้สร้างไฟล์ foo.bzl ดังนี้

def _foo_binary_impl(ctx):
    pass

foo_binary = rule(
    implementation = _foo_binary_impl,
)

เมื่อเรียกใช้ฟังก์ชัน rule คุณ ต้องกำหนดฟังก์ชัน Callback โดยตรรกะจะอยู่ในฟังก์ชันนี้ แต่คุณสามารถปล่อยให้ฟังก์ชันว่างไว้ก่อนได้ อาร์กิวเมนต์ ctx ให้ข้อมูลเกี่ยวกับเป้าหมาย

คุณสามารถโหลดกฎและใช้กฎจากไฟล์ BUILD ได้

สร้างไฟล์ BUILD ในไดเรกทอรีเดียวกัน

load(":foo.bzl", "foo_binary")

foo_binary(name = "bin")

ตอนนี้สามารถสร้างเป้าหมายได้แล้ว

$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)

แม้ว่ากฎจะไม่มีการดำเนินการใดๆ แต่กฎก็ทำงานเหมือนกฎอื่นๆ แล้ว โดยมีชื่อที่ต้องระบุ และรองรับแอตทริบิวต์ทั่วไป เช่น visibility, testonly และ tags

โมเดลการประเมิน

ก่อนที่จะดำเนินการต่อ คุณควรทำความเข้าใจวิธีประเมินโค้ด

อัปเดต foo.bzl ด้วยคำสั่งพิมพ์บางรายการ

def _foo_binary_impl(ctx):
    print("analyzing", ctx.label)

foo_binary = rule(
    implementation = _foo_binary_impl,
)

print("bzl file evaluation")

และ BUILD

load(":foo.bzl", "foo_binary")

print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")

ctx.label สอดคล้องกับป้ายกำกับของเป้าหมายที่กำลังวิเคราะห์ ออบเจ็กต์ ctx มี ฟิลด์และเมธอดที่เป็นประโยชน์มากมาย คุณสามารถดูรายการทั้งหมดได้ใน เอกสารอ้างอิง API

ค้นหาโค้ด

$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1

สังเกตสิ่งต่อไปนี้

  • ระบบจะพิมพ์ "การประเมินไฟล์ bzl" เป็นอันดับแรก Bazel จะประเมินไฟล์ทั้งหมดที่โหลดก่อนที่จะประเมินไฟล์ BUILD หากไฟล์ BUILD หลายไฟล์กำลังโหลด foo.bzl คุณจะเห็น "การประเมินไฟล์ bzl" เพียงครั้งเดียว เนื่องจาก Bazel แคชผลการประเมิน
  • ระบบจะไม่เรียกใช้ฟังก์ชัน Callback _foo_binary_impl การค้นหา Bazel จะโหลดไฟล์ BUILD แต่ไม่วิเคราะห์เป้าหมาย

หากต้องการวิเคราะห์เป้าหมาย ให้ใช้ cquery ("การค้นหาที่กำหนดค่า") หรือคำสั่ง build

$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...

คุณจะเห็นว่าตอนนี้ระบบเรียกใช้ _foo_binary_impl 2 ครั้ง ซึ่งครั้งละ 1 เป้าหมาย

โปรดสังเกตว่าระบบจะไม่พิมพ์ "การประเมินไฟล์ bzl" หรือ "ไฟล์ BUILD" อีก เนื่องจากระบบแคชการประเมิน foo.bzl ไว้หลังจากการเรียกใช้ bazel query Bazel จะแสดงคำสั่ง print เมื่อมีการเรียกใช้คำสั่งเหล่านั้นจริงๆ เท่านั้น

การสร้างไฟล์

หากต้องการให้กฎมีประโยชน์มากขึ้น ให้อัปเดตกฎเพื่อสร้างไฟล์ โดยเริ่มจากประกาศไฟล์และตั้งชื่อไฟล์ ในตัวอย่างนี้ ให้สร้างไฟล์ที่มีชื่อเดียวกับเป้าหมาย

ctx.actions.declare_file(ctx.label.name)

หากตอนนี้คุณเรียกใช้ bazel build :all คุณจะได้รับข้อผิดพลาด

The following files have no generating action:
bin2

เมื่อใดก็ตามที่คุณประกาศไฟล์ คุณต้องบอก Bazel ว่าจะสร้างไฟล์อย่างไรโดยการสร้างการดำเนินการ ใช้ ctx.actions.write, เพื่อสร้างไฟล์ที่มีเนื้อหาที่ระบุ

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello\n",
    )

โค้ดถูกต้อง แต่จะไม่มีการดำเนินการใดๆ

$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)

ฟังก์ชัน ctx.actions.write ลงทะเบียนการดำเนินการ ซึ่งสอนให้ Bazel ทราบวิธีสร้างไฟล์ แต่ Bazel จะไม่สร้างไฟล์จนกว่าจะมีการขอไฟล์จริงๆ ดังนั้นสิ่งสุดท้ายที่ต้องทำคือบอก Bazel ว่าไฟล์เป็นเอาต์พุตของกฎ ไม่ใช่ไฟล์ชั่วคราวที่ใช้ในการใช้งานกฎ

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello!\n",
    )
    return [DefaultInfo(files = depset([out]))]

ดูฟังก์ชัน DefaultInfo และ depset ในภายหลัง ตอนนี้ให้ถือว่าบรรทัดสุดท้ายเป็นวิธีเลือกเอาต์พุตของกฎ

ตอนนี้ให้เรียกใช้ Bazel

$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
  bazel-bin/bin1

$ cat bazel-bin/bin1
Hello!

คุณสร้างไฟล์เรียบร้อยแล้ว

แอตทริบิวต์

หากต้องการให้กฎมีประโยชน์มากขึ้น ให้เพิ่มแอตทริบิวต์ใหม่โดยใช้ โมดูล attr และอัปเดตคำจำกัดความของกฎ

เพิ่มแอตทริบิวต์สตริงที่ชื่อ username

foo_binary = rule(
    implementation = _foo_binary_impl,
    attrs = {
        "username": attr.string(),
    },
)

จากนั้นตั้งค่าแอตทริบิวต์ในไฟล์ BUILD

foo_binary(
    name = "bin",
    username = "Alice",
)

หากต้องการเข้าถึงค่าในฟังก์ชัน Callback ให้ใช้ ctx.attr.username เช่น

def _foo_binary_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name)
    ctx.actions.write(
        output = out,
        content = "Hello {}!\n".format(ctx.attr.username),
    )
    return [DefaultInfo(files = depset([out]))]

โปรดทราบว่าคุณสามารถกำหนดให้แอตทริบิวต์เป็นแอตทริบิวต์ที่ต้องระบุหรือตั้งค่าเริ่มต้นได้ ดูเอกสารประกอบของ attr.string นอกจากนี้ คุณยังใช้แอตทริบิวต์ประเภทอื่นๆ ได้ด้วย เช่น บูลีน หรือ รายการจำนวนเต็ม

แท็กเริ่มการทำงาน

แอตทริบิวต์ทรัพยากร Dependency เช่น attr.label และ attr.label_list, จะประกาศทรัพยากร Dependency จากเป้าหมายที่เป็นเจ้าของแอตทริบิวต์ไปยังเป้าหมายที่มี ป้ายกำกับปรากฏในค่าของแอตทริบิวต์ แอตทริบิวต์ประเภทนี้เป็นพื้นฐานของกราฟเป้าหมาย

ในไฟล์ BUILD ป้ายกำกับเป้าหมายจะปรากฏเป็นออบเจ็กต์สตริง เช่น //pkg:name ในฟังก์ชันการใช้งาน คุณจะเข้าถึงเป้าหมายเป็นออบเจ็กต์ Target ได้ เช่น ดูไฟล์ที่เป้าหมายส่งคืน โดยใช้ Target.files

หลายไฟล์

โดยค่าเริ่มต้น มีเพียงเป้าหมายที่สร้างโดยกฎเท่านั้นที่อาจปรากฏเป็นแท็กเริ่มการทำงาน (เช่น เป้าหมาย foo_library()) หากต้องการให้แอตทริบิวต์ยอมรับเป้าหมายที่เป็นไฟล์อินพุต (เช่น ไฟล์ซอร์สในที่เก็บข้อมูล) คุณสามารถทำได้โดยใช้ allow_files และระบุรายการนามสกุลไฟล์ที่ยอมรับ (หรือ True เพื่ออนุญาตนามสกุลไฟล์ทั้งหมด)

"srcs": attr.label_list(allow_files = [".java"]),

คุณเข้าถึงรายการไฟล์ได้ด้วย ctx.files.<attribute name> เช่น คุณเข้าถึงรายการไฟล์ในแอตทริบิวต์ srcs ได้ผ่าน

ctx.files.srcs

ไฟล์เดียว

หากต้องการไฟล์เพียงไฟล์เดียว ให้ใช้ allow_single_file

"src": attr.label(allow_single_file = [".java"])

จากนั้นคุณจะเข้าถึงไฟล์นี้ได้ใน ctx.file.<attribute name>

ctx.file.src

สร้างไฟล์ด้วยเทมเพลต

คุณสามารถสร้างกฎที่สร้างไฟล์ .cc ตามเทมเพลตได้ นอกจากนี้ คุณยังใช้ ctx.actions.write เพื่อแสดงสตริงที่สร้างขึ้นในฟังก์ชันการใช้งานกฎได้ แต่จะมีปัญหา 2 อย่าง อย่างแรก เมื่อเทมเพลตมีขนาดใหญ่ขึ้น การใส่เทมเพลตไว้ในไฟล์แยกต่างหากและการหลีกเลี่ยงการสร้างสตริงขนาดใหญ่ในระยะการวิเคราะห์จะช่วยประหยัดหน่วยความจำได้มากขึ้น อย่างที่สอง การใช้ไฟล์แยกต่างหากจะสะดวกกว่าสำหรับผู้ใช้ ดังนั้นให้ใช้ ctx.actions.expand_template, ซึ่งจะทำการแทนที่ในไฟล์เทมเพลต

สร้างแอตทริบิวต์ template เพื่อประกาศทรัพยากร Dependency ในไฟล์เทมเพลต

def _hello_world_impl(ctx):
    out = ctx.actions.declare_file(ctx.label.name + ".cc")
    ctx.actions.expand_template(
        output = out,
        template = ctx.file.template,
        substitutions = {"{NAME}": ctx.attr.username},
    )
    return [DefaultInfo(files = depset([out]))]

hello_world = rule(
    implementation = _hello_world_impl,
    attrs = {
        "username": attr.string(default = "unknown person"),
        "template": attr.label(
            allow_single_file = [".cc.tpl"],
            mandatory = True,
        ),
    },
)

ผู้ใช้สามารถใช้กฎได้ดังนี้

hello_world(
    name = "hello",
    username = "Alice",
    template = "file.cc.tpl",
)

cc_binary(
    name = "hello_bin",
    srcs = [":hello"],
)

หากไม่ต้องการแสดงเทมเพลตต่อผู้ใช้ปลายทางและใช้เทมเพลตเดิมเสมอ คุณสามารถตั้งค่าเริ่มต้นและทำให้แอตทริบิวต์เป็นแบบส่วนตัวได้

    "_template": attr.label(
        allow_single_file = True,
        default = "file.cc.tpl",
    ),

แอตทริบิวต์ที่ขึ้นต้นด้วยขีดล่างจะเป็นแบบส่วนตัวและตั้งค่าในไฟล์ BUILD ไม่ได้ ตอนนี้เทมเพลตเป็น ทรัพยากร Dependency โดยนัย แล้ว นั่นคือเป้าหมาย hello_world ทุกเป้าหมายมีทรัพยากร Dependency ในไฟล์นี้ อย่าลืมทำให้ไฟล์นี้มองเห็นได้ สำหรับแพ็กเกจอื่นๆ โดยการอัปเดตไฟล์ BUILD และใช้ exports_files:

exports_files(["file.cc.tpl"])

ดำเนินการต่อ