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