บทแนะนำกฎ

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

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

ดูรายละเอียดเพิ่มเติมในเอกสารประกอบของ Bazel และ Starlark รวมถึงใช้เทมเพลตของ 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" ก่อน ก่อนประเมินไฟล์ BUILD Bazel จะประเมินไฟล์ทั้งหมดที่โหลด หากมีไฟล์ BUILD หลายไฟล์กำลังโหลดอยู่ foo.bzl คุณจะเห็น "การประเมินไฟล์ bzl" เกิดขึ้นเพียงครั้งเดียวเนื่องจาก Bazel จะแคชผลลัพธ์ของการประเมินไว้
  • ระบบไม่ได้เรียกใช้ฟังก์ชัน Callback _foo_binary_impl การค้นหา Bazel จะโหลดไฟล์ BUILD แต่ไม่วิเคราะห์เป้าหมาย

หากต้องการวิเคราะห์เป้าหมาย ให้ใช้คําสั่ง cquery ("configured query") หรือ 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!

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

Attributes

หากต้องการให้กฎมีประโยชน์มากขึ้น ให้เพิ่มแอตทริบิวต์ใหม่โดยใช้โมดูล 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 นอกจากนี้ คุณยังใช้แอตทริบิวต์ประเภทอื่นๆ ได้ด้วย เช่น บูลีนหรือรายการจำนวนเต็ม

การอ้างอิง

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

ในไฟล์ 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 อย่าง อย่างแรก เมื่อเทมเพลตใหญ่ขึ้น หน่วยความจำจะมีประสิทธิภาพมากขึ้นเมื่อใส่ไว้ในไฟล์แยกต่างหากและหลีกเลี่ยงการสร้างสตริงขนาดใหญ่ในช่วงการวิเคราะห์ ประการที่ 2 คือการใช้ไฟล์แยกต่างหากจะสะดวกกว่าสำหรับผู้ใช้ แต่ให้ใช้ ctx.actions.expand_template ซึ่งจะดำเนินการแทนที่ไฟล์เทมเพลต

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

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 เป้าหมายทุกรายการจะพึ่งพาไฟล์นี้ อย่าลืมทำให้ไฟล์นี้มองเห็นได้สำหรับแพ็กเกจอื่นๆ โดยอัปเดตไฟล์ BUILD และใช้ exports_files ดังนี้

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

ก้าวต่อไป