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 ด้วยคำสั่ง print บางรายการ
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 file evaluation" เป็นรายการแรก Bazel จะประเมินไฟล์ทั้งหมดที่โหลดก่อนที่จะประเมินไฟล์
BUILDหากไฟล์BUILDหลายไฟล์โหลด foo.bzl คุณจะเห็น "bzl file evaluation" เพียงครั้งเดียว เนื่องจาก Bazel แคชผลการประเมิน - ระบบจะไม่เรียกใช้ฟังก์ชัน Callback
_foo_binary_implการค้นหา Bazel จะโหลดไฟล์BUILDแต่ไม่วิเคราะห์เป้าหมาย
หากต้องการวิเคราะห์เป้าหมาย ให้ใช้ cquery ("configured
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 file evaluation" อีกครั้ง แม้ว่าระบบจะแคชการประเมิน foo.bzl ไว้แล้วหลังจากการเรียกใช้ bazel query Bazel จะไม่ประเมินโค้ดอีกครั้ง แต่จะเล่นซ้ำเฉพาะเหตุการณ์การพิมพ์ คุณจะได้รับเอาต์พุตเดียวกันไม่ว่าสถานะแคชจะเป็นอย่างไร
การสร้างไฟล์
หากต้องการให้กฎมีประโยชน์มากขึ้น ให้อัปเดตกฎเพื่อสร้างไฟล์ ก่อนอื่น ให้ประกาศไฟล์และตั้งชื่อไฟล์ ในตัวอย่างนี้ ให้สร้างไฟล์ที่มีชื่อเดียวกับเป้าหมาย
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
นอกจากนี้ คุณยังใช้แอตทริบิวต์ประเภทอื่นๆ ได้ด้วย เช่น บูลีน
หรือ รายการจำนวนเต็ม
แท็กเริ่มการทำงาน
แอตทริบิวต์ทรัพยากร 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 ไม่ได้ ตอนนี้เทมเพลตเป็น แท็กเริ่มการทำงานโดยนัย แล้ว ซึ่งหมายความว่าเป้าหมาย hello_world
ทุกรายการมีแท็กเริ่มการทำงานในไฟล์นี้ อย่าลืมทำให้ไฟล์นี้มองเห็นได้
สำหรับแพ็กเกจอื่นๆ โดยการอัปเดตไฟล์ BUILD และใช้
exports_files:
exports_files(["file.cc.tpl"])
ดำเนินการต่อ
- ดูเอกสารอ้างอิงสำหรับกฎ
- ทำความคุ้นเคยกับ depsets
- ดูที่เก็บข้อมูลตัวอย่าง ซึ่งมีตัวอย่างกฎเพิ่มเติม