หน้านี้อธิบายพื้นฐานของการใช้มาโคร รวมถึงกรณีการใช้งานทั่วไป การแก้ไขข้อบกพร่อง และแบบแผน
มาโครคือฟังก์ชันที่เรียกจากไฟล์ BUILD
ซึ่งสามารถสร้างอินสแตนซ์ของกฎได้
มาโครมีไว้สําหรับการรวมและนําโค้ดของกฎที่มีอยู่และมาโครอื่นๆ มาใช้ซ้ำเป็นหลัก
มาโครมี 2 ประเภท ได้แก่ มาโครสัญลักษณ์ที่อธิบายไว้ในหน้านี้ และมาโครเดิม เราขอแนะนำให้ใช้มาโครสัญลักษณ์เพื่อให้โค้ดมีความชัดเจน หากเป็นไปได้
มาโครเชิงสัญลักษณ์จะมีอาร์กิวเมนต์ที่กําหนดประเภท (การเปลี่ยนสตริงเป็นป้ายกำกับ โดยสัมพันธ์กับตําแหน่งที่มีการเรียกใช้มาโคร) และความสามารถในการจํากัดและระบุระดับการมองเห็นของเป้าหมายที่สร้างขึ้น ไฟล์เหล่านี้ออกแบบมาให้ใช้การประเมินแบบเลื่อนเวลา (ซึ่งจะเพิ่มลงใน Bazel เวอร์ชันในอนาคต) มาโครสัญลักษณ์จะมีให้โดยค่าเริ่มต้นใน Bazel 8 ในกรณีที่เอกสารนี้พูดถึง macros
แสดงว่าเอกสารดังกล่าวหมายถึงมาโครเชิงสัญลักษณ์
การใช้งาน
มาโครจะกำหนดไว้ในไฟล์ .bzl
โดยการเรียกใช้ฟังก์ชัน macro()
ที่มีพารามิเตอร์ 2 รายการ ได้แก่ attrs
และ implementation
Attributes
attrs
ยอมรับพจนานุกรมชื่อแอตทริบิวต์เป็นประเภทแอตทริบิวต์ ซึ่งแสดงอาร์กิวเมนต์ของมาโคร ระบบจะเพิ่มแอตทริบิวต์ทั่วไป 2 รายการ ได้แก่ ชื่อและระดับการแชร์ ลงในมาโครทั้งหมดโดยปริยาย และไม่รวมไว้ในพจนานุกรมที่ส่งไปยังแอตทริบิวต์
# macro/macro.bzl
my_macro = macro(
attrs = {
"deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
"create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
},
implementation = _my_macro_impl,
)
การประกาศประเภทแอตทริบิวต์ยอมรับพารามิเตอร์ mandatory
, default
และ doc
แอตทริบิวต์ประเภทส่วนใหญ่ยังยอมรับพารามิเตอร์ configurable
ด้วย ซึ่งจะกำหนดว่าแอตทริบิวต์ยอมรับ select
หรือไม่ หากแอตทริบิวต์เป็น configurable
ระบบจะแยกวิเคราะห์ค่าที่ไม่ใช่ select
ว่าเป็น select
ที่ไม่สามารถกําหนดค่าได้ - "foo"
จะกลายเป็น select({"//conditions:default": "foo"})
ดูข้อมูลเพิ่มเติมในselects
การใช้งาน
implementation
ยอมรับฟังก์ชันที่มีตรรกะของมาโคร
ฟังก์ชันการใช้งานมักจะสร้างเป้าหมายโดยการเรียกใช้กฎอย่างน้อย 1 กฎ และมักจะเป็นแบบส่วนตัว (ตั้งชื่อโดยนำขีดล่างขึ้นต้น) ซึ่งโดยทั่วไปจะมีชื่อเหมือนกับมาโคร แต่นำหน้าด้วย _
และลงท้ายด้วย _impl
ฟังก์ชันการใช้งานมาโครจะยอมรับพารามิเตอร์สำหรับอาร์กิวเมนต์แต่ละรายการ ซึ่งแตกต่างจากฟังก์ชันการใช้งานกฎซึ่งใช้อาร์กิวเมนต์เดียว (ctx
) ที่มีการอ้างอิงแอตทริบิวต์
# macro/macro.bzl
def _my_macro_impl(name, deps, create_test):
cc_library(
name = name + "_cc_lib",
deps = deps,
)
if create_test:
cc_test(
name = name + "_test",
srcs = ["my_test.cc"],
deps = deps,
)
คำประกาศ
มาโครจะประกาศโดยการโหลดและเรียกคําจํากัดความในไฟล์ BUILD
# pkg/BUILD
my_macro(
name = "macro_instance",
deps = ["src.cc"] + select(
{
"//config_setting:special": ["special_source.cc"],
"//conditions:default": [],
},
),
create_tests = True,
)
การดําเนินการนี้จะสร้างเป้าหมาย
//pkg:macro_instance_cc_lib
และ//pkg:macro_instance_test
รายละเอียด
รูปแบบการตั้งชื่อสําหรับเป้าหมายที่สร้าง
ชื่อของเป้าหมายหรือมาโครย่อยที่สร้างโดยมาโครสัญลักษณ์ต้องตรงกับพารามิเตอร์ name
ของมาโคร หรือต้องขึ้นต้นด้วย name
ตามด้วย _
(แนะนำ) .
หรือ -
เช่น my_macro(name = "foo")
อาจสร้างไฟล์หรือเป้าหมายที่มีชื่อ foo
หรือมี foo_
, foo-
หรือ foo.
นำหน้าเท่านั้น เช่น foo_bar
คุณประกาศเป้าหมายหรือไฟล์ที่ละเมิดรูปแบบการตั้งชื่อมาโครได้ แต่จะสร้างไม่ได้ และใช้เป็นแบบอย่างไม่ได้
เป้าหมายและไฟล์ที่ไม่ใช่มาโครภายในแพ็กเกจเดียวกับอินสแตนซ์มาโครจะไม่มีชื่อที่ขัดแย้งกับชื่อเป้าหมายมาโครที่เป็นไปได้ แม้ว่าจะไม่ได้บังคับใช้เอกสิทธิ์เฉพาะนี้ก็ตาม เรากําลังอยู่ระหว่างการใช้การประเมินแบบเลื่อนเพื่อปรับปรุงประสิทธิภาพของมาโครเชิงสัญลักษณ์ ซึ่งจะทํางานได้ไม่ดีในแพ็กเกจที่ละเมิดสคีมาการตั้งชื่อ
ข้อจำกัด
มาโครเชิงสัญลักษณ์มีข้อจํากัดเพิ่มเติมบางอย่างเมื่อเทียบกับมาโครเดิม
มาโครสัญลักษณ์
- ต้องรับอาร์กิวเมนต์
name
และอาร์กิวเมนต์visibility
- ต้องมีฟังก์ชัน
implementation
- อาจไม่แสดงค่า
- ต้องไม่เปลี่ยนแปลง
args
- ต้องไม่เรียกใช้
native.existing_rules()
เว้นแต่จะเป็นมาโครfinalizer
แบบพิเศษ - อาจโทรหา
native.package()
ไม่ได้ - อาจโทรหา
glob()
ไม่ได้ - อาจโทรหา
native.environment_group()
ไม่ได้ - ต้องสร้างเป้าหมายที่มีชื่อเป็นไปตามสคีมาการตั้งชื่อ
- ไม่สามารถอ้างอิงไฟล์อินพุตที่ไม่ได้ประกาศหรือส่งเป็นอาร์กิวเมนต์ (ดูรายละเอียดเพิ่มเติมในการเปิดเผย)
ระดับการแชร์
สิ่งที่ต้องทำ: ขยายส่วนนี้
ระดับการเข้าถึงเป้าหมาย
โดยค่าเริ่มต้น เป้าหมายที่สร้างโดยมาโครสัญลักษณ์จะแสดงต่อแพ็กเกจที่สร้างมาโครดังกล่าว นอกจากนี้ ยังยอมรับแอตทริบิวต์ visibility
ซึ่งสามารถขยายระดับการเข้าถึงนั้นไปยังผู้เรียกใช้มาโคร (โดยการส่งแอตทริบิวต์ visibility
จากคําเรียกมาโครไปยังเป้าหมายที่สร้างขึ้นโดยตรง) และไปยังแพ็กเกจอื่นๆ (โดยการระบุแอตทริบิวต์อย่างชัดเจนในการเข้าถึงของเป้าหมาย)
ระดับการเข้าถึงทรัพยากร
มาโครต้องมีสิทธิ์เข้าถึงไฟล์และเป้าหมายที่อ้างอิง โดยทำได้ด้วยวิธีใดวิธีหนึ่งต่อไปนี้
- ส่งผ่านค่า
attr
ไปยังมาโครอย่างชัดแจ้ง
# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
- ค่าเริ่มต้นที่ไม่เจาะจงของค่า
attr
# my_macro:macro.bzl
my_macro = macro(
attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])} )
- ปรากฏในคําจํากัดความมาโครอยู่แล้ว
# other_package/BUILD
cc_binary(
name = "my_tool",
visibility = "//my_macro:\\__pkg__",
)
เลือก
หากแอตทริบิวต์คือ configurable
ฟังก์ชันการใช้งานมาโครจะเห็นค่าแอตทริบิวต์เป็น select
เสมอ ตัวอย่างเช่น ลองดูมาโครต่อไปนี้
my_macro = macro(
attrs = {"deps": attr.label_list()}, # configurable unless specified otherwise
implementation = _my_macro_impl,
)
หากเรียกใช้ my_macro
ด้วย deps = ["//a"]
ระบบจะเรียกใช้ _my_macro_impl
โดยตั้งค่าพารามิเตอร์ deps
เป็น select({"//conditions:default":
["//a"]})
เป้าหมายกฎจะย้อนกลับการเปลี่ยนรูปแบบนี้ และจัดเก็บ select
ที่ไม่สำคัญเป็นค่าที่ไม่มีเงื่อนไข ในตัวอย่างนี้ หาก _my_macro_impl
ประกาศเป้าหมายกฎ my_rule(..., deps = deps)
ระบบจะจัดเก็บ deps
ของเป้าหมายกฎนั้นเป็น ["//a"]
Finalizers
ตัวสิ้นสุดกฎคือมาโครสัญลักษณ์พิเศษซึ่งจะได้รับการประเมินในระยะสุดท้ายของการโหลดแพ็กเกจ ไม่ว่าจะอยู่ในตําแหน่งใดในไฟล์ BUILD ก็ตาม หลังจากที่กําหนดเป้าหมายที่ไม่ใช่ตัวสิ้นสุดทั้งหมดแล้ว ต่างจากมาโครสัญลักษณ์ทั่วไปตรงที่ตัวสิ้นสุดสามารถเรียก native.existing_rules()
ซึ่งจะทำงานแตกต่างจากในมาโครเดิมเล็กน้อย โดยจะแสดงเฉพาะชุดเป้าหมายกฎที่ไม่ใช่ตัวสิ้นสุด ตัวสรุปอาจยืนยันสถานะของชุดนั้นหรือกําหนดเป้าหมายใหม่
หากต้องการประกาศตัวดำเนินการสิ้นสุด ให้เรียก macro()
ด้วย finalizer = True
ดังนี้
def _my_finalizer_impl(name, visibility, tags_filter):
for r in native.existing_rules().values():
for tag in r.get("tags", []):
if tag in tags_filter:
my_test(
name = name + "_" + r["name"] + "_finalizer_test",
deps = [r["name"]],
data = r["srcs"],
...
)
continue
my_finalizer = macro(
attrs = {"tags_filter": attr.string_list(configurable = False)},
implementation = _impl,
finalizer = True,
)
ความขี้เกียจ
สำคัญ: เรากําลังอยู่ระหว่างการขยายและประเมินมาโครแบบ Lazy ฟีเจอร์นี้ยังไม่พร้อมใช้งาน
ปัจจุบันระบบจะประเมินมาโครทั้งหมดทันทีที่โหลดไฟล์ BUILD ซึ่งอาจส่งผลเสียต่อประสิทธิภาพของเป้าหมายในแพ็กเกจที่มีมาโครที่ไม่เกี่ยวข้องและมีค่าใช้จ่ายสูง ในอนาคต ระบบจะประเมินมาโครสัญลักษณ์ที่ไม่มีการสรุปผลเฉพาะเมื่อจําเป็นสําหรับบิลด์เท่านั้น สคีมาการตั้งชื่อคำนำหน้าช่วยให้ Bazel ระบุได้ว่าจะขยายมาโครใดตามเป้าหมายที่ขอ
การแก้ปัญหาการย้ายข้อมูล
ต่อไปนี้คือปัญหาที่พบบ่อยในการย้ายข้อมูลและวิธีแก้ไข
- การเรียกมาโครแบบเดิม
glob()
ย้ายการเรียก glob()
ไปยังไฟล์ BUILD (หรือไปยังมาโครเดิมที่เรียกจากไฟล์ BUILD) และส่งค่า glob()
ไปยังมาโครสัญลักษณ์โดยใช้แอตทริบิวต์รายการป้ายกำกับ ดังนี้
# BUILD file
my_macro(
...,
deps = glob(...),
)
- มาโครเดิมมีพารามิเตอร์ที่ไม่ใช่ประเภท
attr
ของ Starlark ที่ถูกต้อง
ดึงตรรกะให้ได้มากที่สุดลงในมาโครสัญลักษณ์ที่ฝังอยู่ แต่ให้มาโครระดับบนสุดเป็นมาโครเดิม
- มาโครเดิมเรียกใช้กฎที่สร้างเป้าหมายที่ละเมิดสคีมาการตั้งชื่อ
ไม่เป็นไร เพียงแค่อย่าใช้เป้าหมายที่ "ไม่เหมาะสม" ระบบจะละเว้นการตรวจสอบการตั้งชื่อ