คู่มือแนะนำรูปแบบการสร้าง

เลือกใช้ไฟล์ DAMP BUILD แทน DRY

หลักการ DRY ("Don't Repeat Yourself") สนับสนุนความไม่ซ้ำกันโดยการนำการแยกย่อย เช่น ตัวแปรและฟังก์ชัน มาใช้เพื่อหลีกเลี่ยงความซ้ำซ้อนในโค้ด

ในทางตรงกันข้าม หลักการ DAMP ("Descriptive and Meaningful Phrases") สนับสนุนความสามารถในการอ่านมากกว่าความไม่ซ้ำกัน เพื่อให้เข้าใจและดูแลรักษาไฟล์ได้ง่ายขึ้น

ไฟล์ BUILD ไม่ใช่โค้ด แต่เป็นการกำหนดค่า จึงไม่ได้ทดสอบเหมือนโค้ด แต่ต้องได้รับการดูแลรักษาโดยบุคคลและเครื่องมือ ซึ่งทำให้ DAMP เหมาะสมกว่า DRY

การจัดรูปแบบไฟล์ BUILD.bazel

การจัดรูปแบบไฟล์ BUILD เป็นไปตามแนวทางเดียวกับ Go ซึ่งเครื่องมือมาตรฐานจะจัดการปัญหาการจัดรูปแบบส่วนใหญ่ Buildifier เป็นเครื่องมือที่แยกวิเคราะห์และ แสดงซอร์สโค้ดในรูปแบบมาตรฐาน ดังนั้นไฟล์ BUILD ทุกไฟล์จึงได้รับการจัดรูปแบบด้วยวิธีอัตโนมัติแบบเดียวกัน ซึ่งทำให้การจัดรูปแบบไม่ใช่ปัญหาในระหว่างการตรวจสอบโค้ด นอกจากนี้ยังช่วยให้เครื่องมือต่างๆ เข้าใจ แก้ไข และสร้างไฟล์ BUILD ได้ง่ายขึ้นด้วย

การจัดรูปแบบไฟล์ BUILD ต้องตรงกับเอาต์พุตของ buildifier

ตัวอย่างการจัดรูปแบบ

# Test code implementing the Foo controller.
package(default_testonly = True)

py_test(
    name = "foo_test",
    srcs = glob(["*.py"]),
    data = [
        "//data/production/foo:startfoo",
        "//foo",
        "//third_party/java/jdk:jdk-k8",
    ],
    flaky = True,
    deps = [
        ":check_bar_lib",
        ":foo_data_check",
        ":pick_foo_port",
        "//pyglib",
        "//testing/pybase",
    ],
)

โครงสร้างของไฟล์

คำแนะนำ: ใช้ลำดับต่อไปนี้ (ทุกองค์ประกอบไม่บังคับ)

  • คำอธิบายแพ็กเกจ (ความคิดเห็น)

  • คำสั่ง load() ทั้งหมด

  • ฟังก์ชัน package()

  • การเรียกกฎและมาโคร

Buildifier แยกความแตกต่างระหว่างความคิดเห็นแบบสแตนด์อโลนกับความคิดเห็นที่แนบมากับองค์ประกอบ หากความคิดเห็นไม่ได้แนบมากับองค์ประกอบที่เฉพาะเจาะจง ให้ใช้บรรทัดว่างหลังความคิดเห็น การแยกความแตกต่างนี้มีความสำคัญเมื่อทำการเปลี่ยนแปลงอัตโนมัติ (เช่น เพื่อเก็บหรือนำความคิดเห็นออกเมื่อลบกฎ)

# Standalone comment (such as to make a section in a file)

# Comment for the cc_library below
cc_library(name = "cc")

การอ้างอิงเป้าหมายในแพ็กเกจปัจจุบัน

ควรอ้างอิงไฟล์ตามเส้นทางที่สัมพันธ์กับไดเรกทอรีแพ็กเกจ (โดยไม่ใช้การอ้างอิงขึ้น เช่น ..) ไฟล์ที่สร้างขึ้นควรมีคำนำหน้าเป็น ":" เพื่อระบุว่าไม่ใช่ไฟล์ต้นฉบับ ไฟล์ต้นฉบับไม่ควรมีคำนำหน้าเป็น : กฎควรมีคำนำหน้าเป็น : ตัวอย่างเช่น สมมติว่า x.cc เป็นไฟล์ต้นฉบับ

cc_library(
    name = "lib",
    srcs = ["x.cc"],
    hdrs = [":gen_header"],
)

genrule(
    name = "gen_header",
    srcs = [],
    outs = ["x.h"],
    cmd = "echo 'int x();' > $@",
)

การตั้งชื่อเป้าหมาย

ชื่อเป้าหมายควรสื่อความหมาย หากเป้าหมายมีไฟล์ต้นฉบับ 1 ไฟล์ โดยทั่วไปเป้าหมายควรมีชื่อที่ได้มาจากไฟล์ต้นฉบับนั้น (เช่น cc_library สำหรับ chat.cc อาจชื่อ chat หรือ java_library สำหรับ DirectMessage.java อาจชื่อ direct_message)

เป้าหมายที่มีชื่อเดียวกับแพ็กเกจ (เป้าหมายที่มีชื่อเดียวกับไดเรกทอรีที่บรรจุอยู่) ควรมีฟังก์ชันการทำงานตามที่อธิบายไว้ในชื่อไดเรกทอรี หากไม่มีเป้าหมายดังกล่าว ให้สร้างเป้าหมายที่มีชื่อเดียวกับแพ็กเกจ

ควรใช้ชื่อย่อเมื่ออ้างอิงเป้าหมายที่มีชื่อเดียวกับแพ็กเกจ (//x แทน //x:x) หากคุณอยู่ในแพ็กเกจเดียวกัน ให้ใช้การอ้างอิงในเครื่อง (:x แทน //x)

หลีกเลี่ยงการใช้ชื่อเป้าหมาย "ที่สงวนไว้" ซึ่งมีความหมายพิเศษ ซึ่งรวมถึง all, __pkg__ และ __subpackages__ ชื่อเหล่านี้มีความหมายพิเศษและอาจทำให้เกิดความสับสนและพฤติกรรมที่ไม่คาดคิดเมื่อมีการใช้งาน

หากไม่มีข้อตกลงของทีมที่ใช้กันทั่วไป นี่คือคำแนะนำที่ไม่ผูกมัดซึ่งใช้กันอย่างแพร่หลายใน Google

  • โดยทั่วไปให้ใช้ "snake_case"
    • สำหรับ java_library ที่มี src 1 รายการ หมายความว่าให้ใช้ชื่อที่ไม่เหมือนกับชื่อไฟล์โดยไม่มีนามสกุล
    • สำหรับกฎ *_binary และ *_test ของ Java ให้ใช้ "Upper CamelCase" ซึ่งจะช่วยให้ชื่อเป้าหมายตรงกับ src รายการใดรายการหนึ่ง สำหรับ java_test วิธีนี้จะช่วยให้ระบบอนุมานแอตทริบิวต์ test_class จากชื่อเป้าหมายได้
  • หากเป้าหมายหนึ่งๆ มีหลายตัวแปร ให้เพิ่มคำต่อท้ายเพื่อแยกความแตกต่าง (เช่น :foo_dev, :foo_prod หรือ :bar_x86, :bar_x64)
  • เพิ่มคำต่อท้าย _test, _unittest, Test หรือ Tests ให้เป้าหมาย _test
  • หลีกเลี่ยงคำต่อท้ายที่ไม่มีความหมาย เช่น _lib หรือ _library (เว้นแต่จำเป็นเพื่อ หลีกเลี่ยงความขัดแย้งระหว่างเป้าหมาย _library กับเป้าหมาย _binary ที่เกี่ยวข้อง)
  • สำหรับเป้าหมายที่เกี่ยวข้องกับ Proto ให้ทำดังนี้
    • เป้าหมาย proto_library ควรมีชื่อที่ลงท้ายด้วย _proto
    • กฎ *_proto_library ที่เฉพาะเจาะจงตามภาษาควรตรงกับ Proto พื้นฐาน แต่ให้แทนที่ _proto ด้วยคำต่อท้ายที่เฉพาะเจาะจงตามภาษา เช่น:
      • cc_proto_library: _cc_proto
      • java_proto_library: _java_proto
      • java_lite_proto_library: _java_proto_lite

การมองเห็น

ระดับการแชร์ควรมีขอบเขตที่แคบที่สุดเท่าที่จะทำได้ แต่ยังคงอนุญาตให้การทดสอบและการอ้างอิงย้อนกลับเข้าถึงได้ ใช้ __pkg__ และ __subpackages__ ตามความเหมาะสม

หลีกเลี่ยงการตั้งค่า default_visibility ของแพ็กเกจเป็น //visibility:public ควรตั้งค่า //visibility:public เป็นรายบุคคลสำหรับเป้าหมายใน API สาธารณะของโปรเจ็กต์เท่านั้น ซึ่งอาจเป็นไลบรารีที่ออกแบบมาให้โปรเจ็กต์ภายนอกใช้เป็นทรัพยากร Dependency ที่ต้องพึ่งพา หรือไบนารีที่กระบวนการบิลด์ของโปรเจ็กต์ภายนอกอาจใช้

แท็กเริ่มการทำงาน

แท็กเริ่มการทำงานควรจำกัดไว้เฉพาะแท็กเริ่มการทำงานโดยตรง (แท็กเริ่มการทำงานที่จำเป็นสำหรับแหล่งที่มาที่ระบุไว้ในกฎ) อย่าระบุแท็กเริ่มการทำงานแบบทรานซิทีฟ

ควรระบุแท็กเริ่มการทำงานในแพ็กเกจก่อนและอ้างอิงในลักษณะที่เข้ากันได้กับส่วนการอ้างอิงเป้าหมายในแพ็กเกจปัจจุบันด้านบน (ไม่ใช่ตามชื่อแพ็กเกจแบบสัมบูรณ์)

ควรระบุแท็กเริ่มการทำงานโดยตรงเป็นรายการเดียว การใส่แท็กเริ่มการทำงาน "ทั่วไป" ของเป้าหมายหลายรายการลงในตัวแปรจะลดความสามารถในการดูแลรักษา ทำให้เครื่องมือต่างๆ เปลี่ยนแท็กเริ่มการทำงานของเป้าหมายไม่ได้ และอาจทำให้เกิดแท็กเริ่มการทำงานที่ไม่ได้ใช้

Globs

ระบุ "ไม่มีเป้าหมาย" ด้วย [] อย่าใช้ Glob ที่ไม่ตรงกับสิ่งใดๆ เนื่องจากมีแนวโน้มที่จะเกิดข้อผิดพลาดมากกว่าและไม่ชัดเจนเท่ากับรายการว่าง

แบบเรียกซ้ำ

อย่าใช้ Globs แบบเรียกซ้ำเพื่อจับคู่ไฟล์ต้นฉบับ (เช่น glob(["**/*.java"]))

Globs แบบเรียกซ้ำทำให้ไฟล์ BUILD เข้าใจได้ยากเนื่องจากข้ามไดเรกทอรีย่อยที่มีไฟล์ BUILD

โดยทั่วไป Globs แบบเรียกซ้ำจะมีประสิทธิภาพน้อยกว่าการมีไฟล์ BUILD ต่อไดเรกทอรีที่มีการกำหนดกราฟทรัพยากร Dependency ระหว่างกัน เนื่องจากวิธีนี้ช่วยให้แคชระยะไกลและการทำงานแบบขนานมีประสิทธิภาพมากขึ้น

การสร้างไฟล์ BUILD ในแต่ละไดเรกทอรีและกำหนดกราฟแท็กเริ่มการทำงานระหว่างกันถือเป็นแนวทางปฏิบัติที่ดี

แบบไม่เรียกซ้ำ

โดยทั่วไป Globs แบบไม่เรียกซ้ำเป็นที่ยอมรับ

หลีกเลี่ยงการใช้ List Comprehension

หลีกเลี่ยงการใช้ List Comprehension ที่ระดับบนสุดของไฟล์ BUILD.bazel ทำให้การเรียกซ้ำๆ เป็นไปโดยอัตโนมัติด้วยการสร้างเป้าหมายที่มีชื่อแต่ละรายการด้วยกฎระดับบนสุดหรือการเรียกมาโครแยกกัน กำหนดพารามิเตอร์ name สั้นๆ ให้แต่ละรายการเพื่อความชัดเจน

List Comprehension จะลดสิ่งต่อไปนี้

  • ความสามารถในการดูแลรักษา ผู้ดูแลระบบและเครื่องมืออัตโนมัติขนาดใหญ่จะอัปเดต List Comprehension อย่างถูกต้องได้ยากหรือเป็นไปไม่ได้
  • การค้นพบได้ เนื่องจากรูปแบบไม่มีพารามิเตอร์ name จึงค้นหากฎตามชื่อได้ยาก

การใช้งานรูปแบบ List Comprehension ที่พบบ่อยคือการสร้างการทดสอบ เช่น

[[java_test(
    name = "test_%s_%s" % (backend, count),
    srcs = [ ... ],
    deps = [ ... ],
    ...
) for backend in [
    "fake",
    "mock",
]] for count in [
    1,
    10,
]]

เราขอแนะนำให้ใช้ตัวเลือกที่ง่ายกว่า เช่น กำหนดมาโครที่สร้างการทดสอบ 1 รายการ แล้วเรียกใช้มาโครนั้นสำหรับ name ระดับบนสุดแต่ละรายการ

my_java_test(name = "test_fake_1",
    ...)
my_java_test(name = "test_fake_10",
    ...)
...

อย่าใช้ตัวแปร Deps

อย่าใช้ตัวแปรรายการเพื่อห่อหุ้มแท็กเริ่มการทำงานทั่วไป

COMMON_DEPS = [
  "//d:e",
  "//x/y:z",
]

cc_library(name = "a",
    srcs = ["a.cc"],
    deps = COMMON_DEPS + [ ... ],
)

cc_library(name = "b",
    srcs = ["b.cc"],
    deps = COMMON_DEPS + [ ... ],
)

ในทำนองเดียวกัน อย่าใช้เป้าหมายไลบรารีที่มี exports เพื่อจัดกลุ่มแท็กเริ่มการทำงาน

แต่ให้ระบุแท็กเริ่มการทำงานแยกกันสำหรับแต่ละเป้าหมาย

cc_library(name = "a",
    srcs = ["a.cc"],
    deps = [
      "//a:b",
      "//x/y:z",
      ...
    ],
)

cc_library(name = "b",
    srcs = ["b.cc"],
    deps = [
      "//a:b",
      "//x/y:z",
      ...
    ],
)

ปล่อยให้ Gazelle และเครื่องมืออื่นๆ ดูแลรักษาแท็กเริ่มการทำงาน แม้จะมีการทำซ้ำ แต่คุณก็ไม่ต้องกังวลว่าจะจัดการแท็กเริ่มการทำงานอย่างไร

เลือกใช้สตริงตัวอักษร

แม้ว่า Starlark จะมีตัวดำเนินการสตริงสำหรับการเชื่อมต่อ (+) และการจัดรูปแบบ (%) แต่ให้ใช้ด้วยความระมัดระวัง คุณอาจต้องการแยกส่วนสตริงทั่วไปออกเพื่อให้การแสดงออกกระชับขึ้นหรือแบ่งบรรทัดยาวๆ อย่างไรก็ตาม

ดังนั้น ให้เลือกใช้สตริงตัวอักษรที่ชัดเจนแทนสตริงที่เชื่อมต่อหรือจัดรูปแบบ โดยเฉพาะอย่างยิ่งในแอตทริบิวต์ประเภทป้ายกำกับ เช่น name และ deps ตัวอย่างเช่น ส่วนย่อย BUILD นี้

NAME = "foo"
PACKAGE = "//a/b"

proto_library(
  name = "%s_proto" % NAME,
  deps = [PACKAGE + ":other_proto"],
  alt_dep = "//surprisingly/long/chain/of/package/names:" +
            "extravagantly_long_target_name",
)

ควรเขียนใหม่เป็น

proto_library(
  name = "foo_proto",
  deps = ["//a/b:other_proto"],
  alt_dep = "//surprisingly/long/chain/of/package/names:extravagantly_long_target_name",
)

จำกัดสัญลักษณ์ที่ส่งออกโดยไฟล์ .bzl แต่ละไฟล์

ลดจำนวนสัญลักษณ์ (กฎ มาโคร ค่าคงที่ ฟังก์ชัน) ที่ส่งออกโดยไฟล์ .bzl (Starlark) สาธารณะแต่ละไฟล์ เราขอแนะนำว่าไฟล์ควรส่งออกสัญลักษณ์หลายรายการก็ต่อเมื่อมั่นใจว่าจะมีการใช้สัญลักษณ์เหล่านั้นร่วมกัน มิฉะนั้น ให้แยก ไฟล์ออกเป็นไฟล์ .bzl หลายไฟล์ โดยแต่ละไฟล์จะมี bzl_library ของตัวเอง

สัญลักษณ์ที่มากเกินไปอาจทำให้ไฟล์ .bzl ขยายใหญ่ขึ้นจนกลายเป็น "ไลบรารี" สัญลักษณ์ขนาดใหญ่ ซึ่งทำให้การเปลี่ยนแปลงไฟล์เดียวบังคับให้ Bazel สร้างเป้าหมายหลายรายการใหม่

ข้อกำหนดอื่นๆ

  • ใช้ตัวพิมพ์ใหญ่และขีดล่างเพื่อประกาศค่าคงที่ (เช่น GLOBAL_CONSTANT) และใช้ตัวพิมพ์เล็กและขีดล่างเพื่อประกาศตัวแปร (เช่น my_variable)

  • ไม่ควรแยกป้ายกำกับ แม้ว่าป้ายกำกับจะยาวเกิน 79 อักขระก็ตาม ป้ายกำกับควรเป็นสตริงตัวอักษรทุกครั้งที่เป็นไปได้ เหตุผล: ทำให้การ ค้นหาและแทนที่ทำได้ง่าย นอกจากนี้ยังช่วยให้อ่านได้ง่ายขึ้นด้วย

  • ค่าของแอตทริบิวต์ name ควรเป็นสตริงค่าคงที่ตัวอักษร (ยกเว้นในมาโคร) เหตุผล: เครื่องมือภายนอกใช้แอตทริบิวต์ name เพื่ออ้างอิง กฎ เครื่องมือเหล่านี้จำเป็นต้องค้นหากฎโดยไม่ต้องตีความโค้ด

  • เมื่อตั้งค่าแอตทริบิวต์ประเภทบูลีน ให้ใช้ค่าบูลีน ไม่ใช่ค่าจำนวนเต็ม กฎยังคงแปลงจำนวนเต็มเป็นบูลีนตามความจำเป็นเนื่องจากเหตุผลเดิม แต่เราไม่แนะนำให้ทำเช่นนี้ เหตุผล: flaky = 1 อาจอ่านผิดเป็น "เลิกทำเครื่องหมายเป้าหมายนี้ว่าไม่เสถียรโดยการเรียกใช้เป้าหมายอีกครั้ง 1 ครั้ง" flaky = True บอกอย่างชัดเจนว่า "การทดสอบนี้ไม่เสถียร"

ความแตกต่างกับคู่มือสไตล์ Python

แม้ว่าความเข้ากันได้กับ คู่มือสไตล์ Python จะเป็นเป้าหมาย แต่ก็มีความแตกต่างบางประการดังนี้

  • ไม่มีการจำกัดความยาวบรรทัดที่เข้มงวด ความคิดเห็นและสตริงยาวๆ มักจะถูกแยกออกเป็น 79 คอลัมน์ แต่ไม่บังคับ ไม่ควรบังคับใช้ในการตรวจสอบโค้ดหรือสคริปต์ก่อนส่ง เหตุผล: ป้ายกำกับอาจยาวและเกินขีดจำกัดนี้ ไฟล์ BUILD มักจะสร้างหรือแก้ไขโดยเครื่องมือ ซึ่งไม่เหมาะกับการจำกัดความยาวบรรทัด

  • ระบบไม่รองรับการเชื่อมต่อสตริงโดยนัย ให้ใช้ตัวดำเนินการ + เหตุผล: ไฟล์ BUILD มีรายการสตริงจำนวนมาก คุณอาจลืมใส่คอมมาได้ง่ายๆ ซึ่งจะทำให้ได้ผลลัพธ์ที่แตกต่างไปโดยสิ้นเชิง และเคยทำให้เกิดข้อบกพร่องมากมายในอดีต ดูการสนทนานี้ด้วย

  • ใช้ช่องว่างรอบๆ เครื่องหมาย = สำหรับอาร์กิวเมนต์คีย์เวิร์ดในกฎ เหตุผล: อาร์กิวเมนต์ที่มีชื่อเกิดขึ้นบ่อยกว่าใน Python มากและอยู่ใน บรรทัดแยกกันเสมอ ช่องว่างช่วยให้อ่านได้ง่ายขึ้น ข้อกำหนดนี้ใช้กันมานานแล้ว และไม่คุ้มค่าที่จะแก้ไขไฟล์ BUILD ที่มีอยู่ทั้งหมด

  • ใช้เครื่องหมายอัญประกาศคู่สำหรับสตริงโดยค่าเริ่มต้น เหตุผล: คู่มือสไตล์ Python ไม่ได้ ระบุไว้ แต่แนะนำให้ใช้แบบเดียวกัน ดังนั้นเราจึงตัดสินใจใช้เฉพาะสตริงที่อยู่ในเครื่องหมายอัญประกาศคู่ หลายภาษาใช้เครื่องหมายอัญประกาศคู่สำหรับสตริงตัวอักษร

  • ใช้บรรทัดว่าง 1 บรรทัดระหว่างคำจำกัดความระดับบนสุด 2 รายการ เหตุผล: โครงสร้างของไฟล์ BUILD ไม่เหมือนกับไฟล์ Python ทั่วไป โดยมีเพียงคำสั่งระดับบนสุด การใช้บรรทัดว่าง 1 บรรทัดจะทำให้ไฟล์ BUILD สั้นลง