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
คุณต้องกำหนดฟังก์ชันเรียกกลับ โดยคุณจะเขียนตรรกะไว้ที่นี่ แต่ตอนนี้คุณ
ปล่อยให้ฟังก์ชันว่างไว้ก่อนได้ อาร์กิวเมนต์ 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")
และสร้าง
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 ครั้ง ซึ่งครั้งหนึ่งสำหรับแต่ละเป้าหมาย
โปรดสังเกตว่าไม่มีการพิมพ์ "การประเมินไฟล์ 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
หลายไฟล์
โดยค่าเริ่มต้น มีเพียงเป้าหมายที่สร้างโดยกฎเท่านั้นที่อาจปรากฏเป็นทรัพยากร Dependency (เช่น เป้าหมาย a
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
เป้าหมายทุกรายการมี Dependency ในไฟล์นี้ อย่าลืมทำให้ไฟล์นี้แสดงต่อแพ็กเกจอื่นๆ โดยการอัปเดตไฟล์ BUILD
และใช้ exports_files
exports_files(["file.cc.tpl"])
ไปต่อ
- ดูเอกสารอ้างอิงสำหรับกฎ
- ทำความคุ้นเคยกับ depsets
- ดูที่เก็บตัวอย่าง ซึ่งมีตัวอย่างกฎเพิ่มเติม