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

เลือกใช้ไฟล์ 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 สาธารณะของโปรเจ็กต์เท่านั้น ซึ่งอาจเป็นไลบรารีที่ออกแบบมาเพื่อให้โปรเจ็กต์ภายนอกอ้างอิง หรือไบนารีที่กระบวนการบิลด์ของโปรเจ็กต์ภายนอกอาจใช้

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

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

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

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

Globs

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

แบบเรียกซ้ำ

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

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

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

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

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

โดยทั่วไป 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 สั้นลง