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" เพียง 1 ครั้ง เนื่องจาก Bazel แคชผลการประเมิน - ไม่เรียกฟังก์ชันเรียกกลับ
_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 ครั้งสำหรับแต่ละเป้าหมาย
โปรดทราบว่าจะไม่มีการพิมพ์ "การประเมินไฟล์ 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 จะไม่สร้างไฟล์จนกว่าจะมีคำขอจริง ดังนั้นสิ่งสุดท้ายที่ต้องทำคือบอกบาเซลว่าไฟล์นี้เป็นเอาต์พุตของกฎ ไม่ใช่ไฟล์ชั่วคราวที่ใช้ภายในกฎการใช้งาน
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",
)
หากต้องการเข้าถึงค่าในฟังก์ชันเรียกกลับ ให้ใช้ 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"])
ไปให้ไกลกว่านั้น
- ดูเอกสารอ้างอิงสำหรับกฎ
- ทำความคุ้นเคยกับการลดลง
- โปรดดูตัวอย่างที่เก็บซึ่งมีตัวอย่างเพิ่มเติมของกฎ