บทแนะนำกฎ

วันที่ รายงานปัญหา ดูแหล่งที่มา ตอนกลางคืน · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Starlark เป็นอักขระที่มีลักษณะคล้าย Python ภาษาการกำหนดค่าที่พัฒนาขึ้นเพื่อใช้ใน Bazel และนับจากนำมาใช้ เครื่องมืออื่นๆ ไฟล์ BUILD และ .bzl ของ Bazel เขียนด้วยภาษาถิ่น Starlark รู้จักกันในชื่อ "ภาษาของบิลด์" แม้พูดง่ายๆ ว่า ถูกเรียกว่า "Starlark" โดยเฉพาะเมื่อเน้นว่าคุณลักษณะ แสดงไว้ใน Build Language แทนที่จะเป็นแบบบิวท์อินหรือ "เนทีฟ" ชิ้นส่วน ของ 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 ("กำหนดค่าแล้ว query") หรือคำสั่ง build ดังนี้

$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
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" จะพิมพ์อีกครั้ง การประเมิน foo.bzl ถูกแคชไว้หลังจากการเรียกไปยัง bazel query บาเซล ไม่ประเมินโค้ดอีกครั้ง แต่จะเล่นเหตุการณ์การพิมพ์ซ้ำเท่านั้น ไม่ว่าจะ สถานะแคช คุณจะได้รับเอาต์พุตเดียวกัน

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

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

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

การอ้างอิง

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

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

หลายไฟล์

โดยค่าเริ่มต้น เฉพาะเป้าหมายที่สร้างโดยกฎเท่านั้นที่อาจปรากฏเป็นทรัพยากร Dependency (เช่น 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 เพื่อประกาศทรัพยากร 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"])

ก้าวต่อไป