หน้านี้อธิบายพื้นฐานของการใช้มาโคร รวมถึงกรณีการใช้งานทั่วไป การแก้ไขข้อบกพร่อง และแบบแผน
มาโครคือฟังก์ชันที่เรียกจากไฟล์ BUILD
ซึ่งสามารถสร้างอินสแตนซ์ของกฎได้
มาโครมีไว้สําหรับการรวมและนําโค้ดของกฎที่มีอยู่และมาโครอื่นๆ มาใช้ซ้ำเป็นหลัก
มาโครมี 2 ประเภท ได้แก่ มาโครสัญลักษณ์ที่อธิบายไว้ในหน้านี้ และมาโครเดิม เราขอแนะนำให้ใช้มาโครสัญลักษณ์เพื่อให้โค้ดมีความชัดเจน หากเป็นไปได้
มาโครเชิงสัญลักษณ์จะมีอาร์กิวเมนต์ที่กําหนดประเภท (การเปลี่ยนสตริงเป็นป้ายกำกับ โดยสัมพันธ์กับตําแหน่งที่มีการเรียกใช้มาโคร) และความสามารถในการจํากัดและระบุระดับการเข้าถึงของเป้าหมายที่สร้างขึ้น คำสั่งเหล่านี้ออกแบบมาให้ใช้กับการประเมินแบบเลื่อนเวลา (ซึ่งจะเพิ่มลงใน Bazel เวอร์ชันในอนาคต) มาโครเชิงสัญลักษณ์จะพร้อมใช้งานโดยค่าเริ่มต้นใน Bazel 8 ในกรณีที่เอกสารนี้พูดถึง macros
แสดงว่าเอกสารดังกล่าวหมายถึงมาโครเชิงสัญลักษณ์
ดูตัวอย่างมาโครสัญลักษณ์ที่เรียกใช้ได้ในที่เก็บตัวอย่าง
การใช้งาน
มาโครจะกําหนดไว้ในไฟล์ .bzl
โดยการเรียกใช้ฟังก์ชัน macro()
ที่มีพารามิเตอร์ที่จําเป็น 2 รายการ ได้แก่ attrs
และ implementation
Attributes
attrs
รับพจนานุกรมชื่อแอตทริบิวต์เป็นประเภทแอตทริบิวต์ ซึ่งแสดงอาร์กิวเมนต์ของมาโคร ระบบจะเพิ่มแอตทริบิวต์ทั่วไป 2 รายการ ได้แก่ name
และ visibility
ลงในมาโครทั้งหมดโดยปริยาย และไม่รวมไว้ในพจนานุกรมที่ส่งไปยัง attrs
# 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
การสืบทอดแอตทริบิวต์
มาโครมักมีไว้เพื่อตัดสตริงของกฎ (หรือมาโครอื่น) และนักเขียนมาโครมักต้องการส่งต่อแอตทริบิวต์ส่วนใหญ่ของสัญลักษณ์ที่ตัดโดยใช้ **kwargs
ไปยังเป้าหมายหลัก (หรือมาโครภายในหลัก) ของมาโคร
เพื่อรองรับรูปแบบนี้ มาโครสามารถรับค่าแอตทริบิวต์จากกฎหรือมาโครอื่นได้โดยการผ่านกฎหรือสัญลักษณ์มาโครไปยังอาร์กิวเมนต์ inherit_attrs
ของ macro()
(คุณยังใช้สตริงพิเศษ "common"
แทนสัญลักษณ์กฎหรือมาโครเพื่อรับค่าแอตทริบิวต์ทั่วไปที่กําหนดไว้สําหรับกฎการสร้าง Starlark ทั้งหมดได้ด้วย)
ระบบจะรับค่าแอตทริบิวต์แบบสาธารณะเท่านั้น และระบบจะลบล้างแอตทริบิวต์ที่รับค่ามาซึ่งมีชื่อเดียวกันกับแอตทริบิวต์ในพจนานุกรมattrs
ของมาโคร นอกจากนี้ คุณยังนําแอตทริบิวต์ที่รับช่วงมาออกได้โดยใช้ None
เป็นค่าในพจนานุกรม attrs
ดังนี้
# macro/macro.bzl
my_macro = macro(
inherit_attrs = native.cc_library,
attrs = {
# override native.cc_library's `local_defines` attribute
local_defines = attr.string_list(default = ["FOO"]),
# do not inherit native.cc_library's `defines` attribute
defines = None,
},
...
)
ระบบจะลบล้างค่าเริ่มต้นของแอตทริบิวต์ที่รับค่ามาซึ่งไม่บังคับให้เป็น None
เสมอ โดยไม่คำนึงถึงค่าเริ่มต้นของคำจำกัดความแอตทริบิวต์เดิม หากต้องการตรวจสอบหรือแก้ไขแอตทริบิวต์ที่ไม่บังคับซึ่งรับค่ามา เช่น หากต้องการเพิ่มแท็กลงในแอตทริบิวต์ tags
ที่รับค่ามา คุณต้องจัดการเคส None
ในฟังก์ชันการติดตั้งใช้งานของมาโคร ดังนี้
# macro/macro.bzl
_my_macro_implementation(name, visibility, tags, **kwargs):
# Append a tag; tags attr is an inherited non-mandatory attribute, and
# therefore is None unless explicitly set by the caller of our macro.
my_tags = (tags or []) + ["another_tag"]
native.cc_library(
...
tags = my_tags,
**kwargs,
)
...
การใช้งาน
implementation
ยอมรับฟังก์ชันที่มีตรรกะของมาโคร
ฟังก์ชันการใช้งานมักจะสร้างเป้าหมายโดยการเรียกใช้กฎอย่างน้อย 1 กฎ และมักจะเป็นแบบส่วนตัว (ตั้งชื่อโดยนำขีดล่างขึ้นต้น) ตามธรรมเนียมแล้ว ไฟล์เหล่านี้จะมีชื่อเหมือนกับมาโคร แต่จะมี_
อยู่หน้าและ_impl
อยู่ท้าย
ซึ่งแตกต่างจากฟังก์ชันการติดตั้งใช้งานกฎที่ใช้อาร์กิวเมนต์เดียว (ctx
) ที่มีข้อมูลอ้างอิงถึงแอตทริบิวต์ แต่ฟังก์ชันการติดตั้งใช้งานมาโครจะยอมรับพารามิเตอร์สําหรับอาร์กิวเมนต์แต่ละรายการ
# macro/macro.bzl
def _my_macro_impl(name, visibility, 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,
)
หากมาโครรับค่าแอตทริบิวต์มา ฟังก์ชันการใช้งานของมาโครต้องมีพารามิเตอร์คีย์เวิร์ดที่เหลืออยู่**kwargs
ซึ่งสามารถส่งต่อไปยังการเรียกที่เรียกใช้กฎหรือมาโครย่อยที่รับค่ามา (วิธีนี้ช่วยให้มั่นใจว่ามาโครจะไม่ใช้งานไม่ได้หากกฎหรือมาโครที่คุณรับค่ามาเพิ่มแอตทริบิวต์ใหม่)
คำประกาศ
มาโครจะประกาศโดยการโหลดและเรียกคําจํากัดความในไฟล์ 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
เช่นเดียวกับการเรียกใช้กฎ หากตั้งค่าแอตทริบิวต์ในการเรียกใช้มาโครเป็น None
ระบบจะถือว่าผู้เรียกใช้มาโครละเว้นแอตทริบิวต์นั้น ตัวอย่างเช่น การเรียกใช้มาโคร 2 รายการต่อไปนี้มีความหมายเหมือนกัน
# pkg/BUILD
my_macro(name = "abc", srcs = ["src.cc"], deps = None)
my_macro(name = "abc", srcs = ["src.cc"])
โดยทั่วไปแล้ว คำสั่งนี้ไม่มีประโยชน์ในไฟล์ BUILD
แต่มีประโยชน์เมื่อต้องตัดมาโครภายในมาโครอื่นโดยใช้โปรแกรม
รายละเอียด
รูปแบบการตั้งชื่อสำหรับเป้าหมายที่สร้าง
ชื่อของเป้าหมายหรือมาโครย่อยที่สร้างโดยมาโครสัญลักษณ์ต้องตรงกับพารามิเตอร์ name
ของมาโคร หรือต้องขึ้นต้นด้วย name
ตามด้วย _
(แนะนำ) .
หรือ -
เช่น my_macro(name = "foo")
อาจสร้างไฟล์หรือเป้าหมายที่มีชื่อ foo
หรือมี foo_
, foo-
หรือ foo.
นำหน้าเท่านั้น เช่น foo_bar
คุณประกาศเป้าหมายหรือไฟล์ที่ละเมิดรูปแบบการตั้งชื่อมาโครได้ แต่จะสร้างไม่ได้ และใช้เป็นแบบอย่างไม่ได้
ไฟล์และเป้าหมายที่ไม่ใช่มาโครภายในแพ็กเกจเดียวกับอินสแตนซ์มาโครควรไม่มีชื่อที่ขัดแย้งกับชื่อเป้าหมายมาโครที่เป็นไปได้ แม้ว่าเราจะไม่ได้บังคับใช้การผูกขาดนี้ เรากําลังอยู่ระหว่างการใช้การประเมินแบบเลื่อนเพื่อปรับปรุงประสิทธิภาพของมาโครเชิงสัญลักษณ์ ซึ่งจะทํางานได้ไม่ดีในแพ็กเกจที่ละเมิดสคีมาการตั้งชื่อ
ข้อจำกัด
มาโครเชิงสัญลักษณ์มีข้อจํากัดเพิ่มเติมบางอย่างเมื่อเทียบกับมาโครเดิม
มาโครสัญลักษณ์
- ต้องรับอาร์กิวเมนต์
name
และอาร์กิวเมนต์visibility
- ต้องมีฟังก์ชัน
implementation
- อาจไม่แสดงผลลัพธ์
- ต้องไม่เปลี่ยนรูปแบบอาร์กิวเมนต์
- ต้องไม่เรียกใช้
native.existing_rules()
เว้นแต่จะเป็นมาโครfinalizer
แบบพิเศษ - อาจโทรหา
native.package()
ไม่ได้ - อาจโทรหา
glob()
ไม่ได้ - อาจโทรหา
native.environment_group()
ไม่ได้ - ต้องสร้างเป้าหมายที่มีชื่อเป็นไปตามสคีมาการตั้งชื่อ
- อ้างอิงไฟล์อินพุตที่ไม่ได้ประกาศหรือส่งผ่านเป็นอาร์กิวเมนต์ไม่ได้
- ไม่สามารถอ้างอิงเป้าหมายส่วนตัวของผู้เรียก (ดูรายละเอียดเพิ่มเติมได้ที่ระดับการเข้าถึงและมาโคร)
ระดับการเข้าถึงและมาโคร
ระบบระดับการเข้าถึงจะช่วยปกป้องรายละเอียดการใช้งานทั้งของมาโคร (สัญลักษณ์) และผู้เรียกใช้
โดยค่าเริ่มต้น เป้าหมายที่สร้างในมาโครสัญลักษณ์จะปรากฏภายในมาโครนั้นเอง แต่ไม่จำเป็นต้องปรากฏต่อผู้เรียกมาโคร มาโครสามารถ "ส่งออก" เป้าหมายเป็น API สาธารณะได้โดยส่งต่อค่าของแอตทริบิวต์ visibility
ของตัวเอง ดังตัวอย่างใน some_rule(..., visibility = visibility)
แนวคิดสําคัญของการแสดงผลแบบมหภาคมีดังนี้
ระบบจะตรวจสอบการแสดงผลตามมาโครที่ประกาศเป้าหมาย ไม่ใช่แพ็กเกจที่เรียกใช้มาโคร
- กล่าวคือ การที่อยู่ในแพ็กเกจเดียวกันไม่ได้ทำให้เป้าหมายหนึ่งมองเห็นอีกเป้าหมายหนึ่ง ซึ่งจะช่วยป้องกันไม่ให้เป้าหมายภายในของมาโครกลายเป็นทรัพยากรที่ต้องพึ่งพาของมาโครอื่นๆ หรือเป้าหมายระดับบนสุดในแพ็กเกจ
แอตทริบิวต์
visibility
ทั้งหมดทั้งในกฎและมาโครจะรวมตำแหน่งที่มีการเรียกใช้กฎหรือมาโครโดยอัตโนมัติ- ดังนั้น เป้าหมายหนึ่งๆ จะแสดงต่อเป้าหมายอื่นๆ ที่ประกาศไว้ในมาโครเดียวกัน (หรือไฟล์
BUILD
หากไม่ได้อยู่ในมาโคร) โดยไม่มีเงื่อนไข
- ดังนั้น เป้าหมายหนึ่งๆ จะแสดงต่อเป้าหมายอื่นๆ ที่ประกาศไว้ในมาโครเดียวกัน (หรือไฟล์
ในทางปฏิบัติ หมายความว่าเมื่อมาโครประกาศเป้าหมายโดยไม่ตั้งค่า visibility
เป้าหมายจะเป็นภายในมาโครโดยค่าเริ่มต้น (ระดับการเข้าถึงเริ่มต้นของแพ็กเกจจะไม่มีผลภายในมาโคร) การส่งออกเป้าหมายหมายความว่าเป้าหมายจะปรากฏต่อผู้เรียกมาโครที่ระบุในแอตทริบิวต์ visibility
ของมาโครนั้น รวมถึงแพ็กเกจของผู้เรียกมาโครเองและโค้ดของมาโครเอง
อีกวิธีหนึ่งในการมองเรื่องนี้คือ ระดับการเข้าถึงของมาโครจะกําหนดว่าใคร (นอกเหนือจากมาโครเอง) จะเห็นเป้าหมายที่ส่งออกของมาโคร
# tool/BUILD
...
some_rule(
name = "some_tool",
visibility = ["//macro:__pkg__"],
)
# macro/macro.bzl
def _impl(name, visibility):
cc_library(
name = name + "_helper",
...
# No visibility passed in. Same as passing `visibility = None` or
# `visibility = ["//visibility:private"]`. Visible to the //macro
# package only.
)
cc_binary(
name = name + "_exported",
deps = [
# Allowed because we're also in //macro. (Targets in any other
# instance of this macro, or any other macro in //macro, can see it
# too.)
name + "_helper",
# Allowed by some_tool's visibility, regardless of what BUILD file
# we're called from.
"//tool:some_tool",
],
...
visibility = visibility,
)
my_macro = macro(implementation = _impl, ...)
# pkg/BUILD
load("//macro:macro.bzl", "my_macro")
...
my_macro(
name = "foo",
...
)
some_rule(
...
deps = [
# Allowed, its visibility is ["//pkg:__pkg__", "//macro:__pkg__"].
":foo_exported",
# Disallowed, its visibility is ["//macro:__pkg__"] and
# we are not in //macro.
":foo_helper",
]
)
หากเรียก my_macro
ด้วย visibility = ["//other_pkg:__pkg__"]
หรือหากแพ็กเกจ //pkg
ตั้งค่า default_visibility
เป็นค่านั้น //pkg:foo_exported
ก็จะใช้ได้ภายใน //other_pkg/BUILD
หรือภายในมาโครที่กําหนดไว้ใน //other_pkg:defs.bzl
แต่ //pkg:foo_helper
จะยังคงได้รับการปกป้อง
มาโครสามารถประกาศว่าเป้าหมายจะแสดงในแพ็กเกจเพื่อนได้โดยส่ง visibility = ["//some_friend:__pkg__"]
(สําหรับเป้าหมายภายใน) หรือ visibility = visibility + ["//some_friend:__pkg__"]
(สําหรับเป้าหมายที่ส่งออก)
โปรดทราบว่ามาโครไม่ควรประกาศเป้าหมายที่มีระดับการเข้าถึงแบบสาธารณะ (visibility = ["//visibility:public"]
) เนื่องจากจะทำให้ทุกแพ็กเกจมองเห็นเป้าหมายได้โดยไม่มีข้อจำกัด แม้ว่าผู้เรียกใช้จะระบุระดับการเข้าถึงที่จำกัดมากขึ้นก็ตาม
การตรวจสอบระดับการเข้าถึงทั้งหมดจะดำเนินการกับมาโครเชิงสัญลักษณ์ที่ทำงานอยู่ด้านในสุด อย่างไรก็ตาม มีกลไกการมอบสิทธิ์การแสดงผลอยู่ หากมาโครส่งผ่านป้ายกํากับเป็นค่าแอตทริบิวต์ไปยังมาโครภายใน ระบบจะตรวจสอบการใช้ป้ายกํากับในมาโครภายในโดยอิงตามมาโครภายนอก ดูรายละเอียดเพิ่มเติมได้ที่หน้าระดับการแชร์
โปรดทราบว่ามาโครเดิมจะแสดงอย่างสมบูรณ์ต่อระบบการแสดงผล และทํางานราวกับว่าตําแหน่งที่มาโครอยู่คือไฟล์ BUILD หรือมาโครเชิงสัญลักษณ์ที่เรียกใช้
เลือก
หากแอตทริบิวต์เป็น configurable
(ค่าเริ่มต้น) และค่าของแอตทริบิวต์ไม่ใช่ None
ฟังก์ชันการใช้งานมาโครจะเห็นค่าแอตทริบิวต์เป็น select
ย่อย วิธีนี้ช่วยให้ผู้เขียนมาโครพบข้อบกพร่องได้ง่ายขึ้นในกรณีที่ไม่ได้คาดคิดว่าค่าแอตทริบิวต์อาจเป็น 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"]})
หากกรณีนี้ทําให้ฟังก์ชันการใช้งานไม่สําเร็จ (เช่น เนื่องจากโค้ดพยายามเข้าถึงค่าใน deps[0]
ซึ่งไม่อนุญาตสําหรับ select
) ผู้เขียนมาโครจะเลือกได้ว่าจะเขียนมาโครใหม่ให้ใช้เฉพาะการดำเนินการที่เข้ากันได้กับ select
หรือจะทําเครื่องหมายแอตทริบิวต์ว่าไม่สามารถกําหนดค่าได้ (attr.label_list(configurable = False)
) ซึ่งตัวเลือกหลังจะทำให้ผู้ใช้ไม่สามารถส่งค่า select
ได้
เป้าหมายกฎจะเปลี่ยนรูปแบบนี้กลับ และจัดเก็บ select
ที่ไม่มีค่าเป็นค่าที่ไม่มีเงื่อนไข ในตัวอย่างข้างต้น หาก _my_macro_impl
ประกาศเป้าหมายกฎ my_rule(..., deps = deps)
ระบบจะจัดเก็บ deps
ของเป้าหมายกฎนั้นเป็น ["//a"]
วิธีนี้ช่วยให้มั่นใจได้ว่าการรวม select
จะไม่ทําให้ระบบจัดเก็บค่า select
ที่ซ้ำกันในเป้าหมายทั้งหมดที่สร้างขึ้นโดยมาโคร
หากค่าของแอตทริบิวต์ที่กำหนดค่าได้คือ None
ระบบจะไม่ตัดขึ้นบรรทัดใหม่ใน select
วิธีนี้ช่วยให้การทดสอบอย่าง my_attr == None
ยังคงทํางานได้ และเมื่อส่งต่อแอตทริบิวต์ไปยังกฎที่มีค่าเริ่มต้นที่คำนวณแล้ว กฎจะทํางานอย่างถูกต้อง (กล่าวคือ ราวกับว่าไม่มีการส่งแอตทริบิวต์เลย) แอตทริบิวต์อาจใช้ค่า None
ไม่ได้เสมอไป แต่อาจใช้กับประเภท attr.label()
และสำหรับแอตทริบิวต์ที่รับค่ามาซึ่งไม่บังคับ
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 ที่ถูกต้อง
ดึงตรรกะให้ได้มากที่สุดลงในมาโครสัญลักษณ์ที่ฝังอยู่ แต่ให้มาโครระดับบนสุดเป็นมาโครเดิม
- มาโครเดิมเรียกใช้กฎที่สร้างเป้าหมายที่ละเมิดสคีมาการตั้งชื่อ
ไม่เป็นไร เพียงแค่อย่าใช้เป้าหมายที่ "ไม่เหมาะสม" ระบบจะละเว้นการตรวจสอบการตั้งชื่อ