แพลตฟอร์ม

บทนำ

Bazel สามารถสร้างและทดสอบโค้ดในฮาร์ดแวร์ ระบบปฏิบัติการ และการกำหนดค่าระบบต่างๆ ซึ่งอาจเกี่ยวข้องกับเครื่องมือบิลด์เวอร์ชันต่างๆ เช่น Linker และคอมไพเลอร์ Bazel จึงมีแนวคิดเรื่อง ข้อจำกัดและ แพลตฟอร์มเพื่อช่วยจัดการความซับซ้อนนี้

ข้อจำกัดคือพร็อพเพอร์ตี้ที่แยกความแตกต่างของเครื่องบิลด์หรือเครื่องที่ใช้จริง ข้อจำกัดทั่วไป ได้แก่ สถาปัตยกรรม CPU, การมีหรือไม่มี GPU หรือคอมไพเลอร์เวอร์ชันที่ติดตั้งไว้ในเครื่อง แต่ข้อจำกัดอาจเป็น อะไรก็ได้ที่แยกความแตกต่างของเครื่องได้อย่างมีความหมายเมื่อจัดระเบียบงานสร้าง

แพลตฟอร์มคือชุดข้อจำกัดที่ระบุเครื่องที่สมบูรณ์ Bazel ใช้แนวคิดนี้เพื่อให้ผู้พัฒนาเลือกเครื่องที่ต้องการสร้างสำหรับ เครื่องที่ควรเรียกใช้การดำเนินการคอมไพล์และทดสอบ รวมถึง Toolchainที่การดำเนินการสร้างควรคอมไพล์ด้วย

นอกจากนี้ ผู้พัฒนายังใช้ข้อจำกัดเพื่อ เลือก พร็อพเพอร์ตี้ที่กำหนดเองหรือทรัพยากร Dependency ในกฎการบิลด์ได้ด้วย เช่น "ใช้ src_arm.cc เมื่อการสร้างกำหนดเป้าหมายเป็นเครื่อง Arm"

ประเภทแพลตฟอร์ม

Bazel รู้จักบทบาท 3 บทบาทที่แพลตฟอร์มอาจมี ดังนี้

  • โฮสต์ - แพลตฟอร์มที่ Bazel ทำงานอยู่
  • การดำเนินการ - แพลตฟอร์มที่เรียกใช้การดำเนินการคอมไพล์เพื่อสร้างเอาต์พุตบิลด์
  • เป้าหมาย - แพลตฟอร์มที่โค้ดที่สร้างขึ้นควรทำงาน

โดยทั่วไปการสร้างจะมีความสัมพันธ์กับแพลตฟอร์ม 3 ประเภท ดังนี้

  • การสร้างแพลตฟอร์มเดียว - แพลตฟอร์มโฮสต์ การดำเนินการ และเป้าหมายเป็นแพลตฟอร์มเดียวกัน เช่น การสร้างในเครื่องของผู้พัฒนาโดยไม่มีการดำเนินการระยะไกล แล้วเรียกใช้ไบนารีที่สร้างขึ้นในเครื่องเดียวกัน

  • การสร้างการคอมไพล์ข้ามแพลตฟอร์ม - แพลตฟอร์มโฮสต์และการดำเนินการเป็นแพลตฟอร์มเดียวกัน แต่แพลตฟอร์มเป้าหมายเป็นแพลตฟอร์มอื่น เช่น การสร้างแอป iOS ใน Macbook Pro โดยไม่มีการดำเนินการระยะไกล

  • การสร้างหลายแพลตฟอร์ม - แพลตฟอร์มโฮสต์ การดำเนินการ และเป้าหมายเป็นแพลตฟอร์มอื่นทั้งหมด เช่น การสร้างแอป iOS ใน Macbook Pro และใช้เครื่อง Linux ระยะไกลเพื่อคอมไพล์การดำเนินการ C++ ที่ไม่จำเป็นต้องใช้ Xcode

การระบุแพลตฟอร์ม

วิธีที่ผู้พัฒนาใช้แพลตฟอร์มกันมากที่สุดคือการระบุเครื่องเป้าหมายที่ต้องการด้วยแฟล็ก --platforms ดังนี้

$ bazel build //:my_linux_app --platforms=//myplatforms:linux_x86

โดยทั่วไปองค์กรจะดูแลรักษาคำจำกัดความของแพลตฟอร์มของตนเอง เนื่องจากชุดเครื่องสร้างจะแตกต่างกันไปในแต่ละองค์กร

เมื่อไม่ได้ตั้งค่า --platforms ระบบจะตั้งค่าเริ่มต้นเป็น @platforms//host ซึ่งกำหนดไว้เป็นพิเศษเพื่อตรวจหาพร็อพเพอร์ตี้ของระบบปฏิบัติการและ CPU ของเครื่องโฮสต์โดยอัตโนมัติ เพื่อให้การสร้างกำหนดเป้าหมายเป็นเครื่องเดียวกับที่ Bazel ทำงานอยู่ กฎการบิลด์สามารถ เลือกพร็อพเพอร์ตี้เหล่านี้ได้ด้วย @platforms/ระบบปฏิบัติการ และ @platforms/CPU การจำกัด

ข้อจำกัดและแพลตฟอร์มที่มีประโยชน์โดยทั่วไป

ทีม Bazel ดูแลรักษาที่เก็บที่มีคำจำกัดความของข้อจำกัดสำหรับสถาปัตยกรรม CPU และระบบปฏิบัติการที่ได้รับความนิยมมากที่สุด เพื่อให้ระบบนิเวศมีความสอดคล้องกัน ซึ่งทั้งหมดกำหนดไว้ใน https://github.com/bazelbuild/platforms

Bazel มาพร้อมกับคำจำกัดความของแพลตฟอร์มพิเศษต่อไปนี้ @platforms//host (กำหนดชื่อแทนเป็น @bazel_tools//tools:host_platform) ซึ่งจะตรวจหาพร็อพเพอร์ตี้ของระบบปฏิบัติการและ CPU ของเครื่องที่ Bazel ทำงานอยู่โดยอัตโนมัติ

การกำหนดข้อจำกัด

ข้อจำกัดจะจำลองด้วยconstraint_setting และ constraint_value กฎการสร้าง

constraint_setting ประกาศประเภทของพร็อพเพอร์ตี้ เช่น

constraint_setting(name = "cpu")

constraint_value ประกาศค่าที่เป็นไปได้สำหรับพร็อพเพอร์ตี้นั้น

constraint_value(
    name = "x86",
    constraint_setting = ":cpu"
)

คุณสามารถอ้างอิงค่าเหล่านี้เป็นป้ายกำกับเมื่อกำหนดแพลตฟอร์มหรือปรับแต่งกฎการสร้างในแพลตฟอร์ม หากกำหนดตัวอย่างข้างต้นไว้ใน cpus/BUILD คุณสามารถอ้างอิง ข้อจำกัด x86 เป็น //cpus:x86

หากการมองเห็นอนุญาต คุณสามารถขยาย constraint_setting ที่มีอยู่ได้โดยกำหนดค่าของคุณเองสำหรับข้อจำกัดนั้น

การกำหนดแพลตฟอร์ม

กฎการสร้าง platform กำหนดแพลตฟอร์มเป็นคอลเล็กชันของ constraint_value ดังนี้

platform(
    name = "linux_x86",
    constraint_values = [
        "//oses:linux",
        "//cpus:x86",
    ],
)

ซึ่งจะจำลองเครื่องที่ต้องมีทั้งข้อจำกัด //oses:linux และ //cpus:x86

แพลตฟอร์มจะมี constraint_value ได้เพียงรายการเดียวสำหรับ constraint_setting ที่กำหนด ซึ่งหมายความว่าแพลตฟอร์มจะมี CPU 2 รายการไม่ได้ เว้นแต่คุณจะสร้างประเภท constraint_setting อื่นเพื่อจำลองค่าที่ 2

การข้ามเป้าหมายที่ไม่เข้ากัน

เมื่อสร้างสำหรับแพลตฟอร์มเป้าหมายที่เฉพาะเจาะจง คุณมักจะต้องการข้ามเป้าหมายที่จะไม่ทำงานในแพลตฟอร์มนั้น ตัวอย่างเช่น ไดรเวอร์อุปกรณ์ Windows มีแนวโน้มที่จะสร้างข้อผิดพลาดของคอมไพเลอร์จำนวนมากเมื่อสร้างในเครื่อง Linux ด้วย //... ใช้แอตทริบิวต์ target_compatible_with เพื่อบอก Bazel ว่าโค้ดของคุณมีข้อจำกัดของแพลตฟอร์มเป้าหมายใดบ้าง

การใช้แอตทริบิวต์นี้ที่ง่ายที่สุดคือการจำกัดเป้าหมายไว้ที่แพลตฟอร์มเดียว ระบบจะไม่สร้างเป้าหมายสำหรับแพลตฟอร์มที่ไม่เป็นไปตามข้อจำกัดทั้งหมด ตัวอย่างต่อไปนี้จำกัด win_driver_lib.cc ไว้ที่ Windows 64 บิต

cc_library(
    name = "win_driver_lib",
    srcs = ["win_driver_lib.cc"],
    target_compatible_with = [
        "@platforms//cpu:x86_64",
        "@platforms//os:windows",
    ],
)

:win_driver_lib เข้ากันได้ เฉพาะกับการสร้างด้วย Windows 64 บิต และ ไม่เข้ากันกับแพลตฟอร์มอื่นๆ การไม่เข้ากันจะถ่ายทอดได้ ระบบจะถือว่าเป้าหมายใดก็ตามที่ขึ้นต่อกันแบบถ่ายทอดกับเป้าหมายที่ไม่เข้ากันนั้นไม่เข้ากันด้วย

ระบบจะข้ามเป้าหมายเมื่อใด

ระบบจะข้ามเป้าหมายเมื่อถือว่าเป้าหมายนั้นไม่เข้ากันและรวมอยู่ในบิลด์เป็นส่วนหนึ่งของการขยายรูปแบบเป้าหมาย ตัวอย่างเช่น การเรียกใช้ 2 รายการต่อไปนี้จะข้ามเป้าหมายที่ไม่เข้ากันที่พบในการขยายรูปแบบเป้าหมาย

$ bazel build --platforms=//:myplatform //...
$ bazel build --platforms=//:myplatform //:all

ระบบจะข้ามการทดสอบที่ไม่เข้ากันใน test_suite ในลักษณะเดียวกันหากระบุ test_suite ในบรรทัดคำสั่งด้วย --expand_test_suites กล่าวอีกนัยหนึ่งคือ เป้าหมาย test_suite ในบรรทัดคำสั่งจะทำงานเหมือนกับ :all และ ... การใช้ --noexpand_test_suites จะป้องกันการขยายและทำให้ test_suite เป้าหมายที่มีการทดสอบที่ไม่เข้ากันไม่เข้ากันด้วย

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

$ bazel build --platforms=//:myplatform //:target_incompatible_with_myplatform
...
ERROR: Target //:target_incompatible_with_myplatform is incompatible and cannot be built, but was explicitly requested.
...
FAILED: Build did NOT complete successfully

ระบบจะข้ามเป้าหมายที่ชัดแจ้งที่ไม่เข้ากันโดยไม่แจ้งให้ทราบหากเปิดใช้ --skip_incompatible_explicit_targets

ข้อจำกัดที่แสดงออกได้มากขึ้น

หากต้องการความยืดหยุ่นมากขึ้นในการแสดงข้อจำกัด ให้ใช้ @platforms//:incompatible constraint_value ที่ไม่มีแพลตฟอร์มใดเป็นไปตามข้อจำกัด

ใช้ select() ร่วมกับ @platforms//:incompatible เพื่อแสดงข้อจำกัดที่ซับซ้อนมากขึ้น เช่น ใช้เพื่อใช้ตรรกะ OR พื้นฐาน ตัวอย่างต่อไปนี้จะทำเครื่องหมายไลบรารีว่าเข้ากันได้กับ macOS และ Linux แต่ไม่เข้ากันกับแพลตฟอร์มอื่นๆ

cc_library(
    name = "unixish_lib",
    srcs = ["unixish_lib.cc"],
    target_compatible_with = select({
        "@platforms//os:osx": [],
        "@platforms//os:linux": [],
        "//conditions:default": ["@platforms//:incompatible"],
    }),
)

คุณสามารถตีความตัวอย่างข้างต้นได้ดังนี้

  1. เมื่อกำหนดเป้าหมายเป็น macOS เป้าหมายจะไม่มีข้อจำกัด
  2. เมื่อกำหนดเป้าหมายเป็น Linux เป้าหมายจะไม่มีข้อจำกัด
  3. มิเช่นนั้น เป้าหมายจะมีข้อจำกัด @platforms//:incompatible เนื่องจาก @platforms//:incompatible ไม่ได้เป็นส่วนหนึ่งของแพลตฟอร์มใดๆ ระบบจึงถือว่าเป้าหมายนั้นไม่เข้ากัน

ใช้ skylib's selects.with_or()เพื่อให้ข้อจำกัดอ่านง่ายขึ้น

คุณสามารถแสดงความเข้ากันได้แบบผกผันในลักษณะที่คล้ายกัน ตัวอย่างต่อไปนี้อธิบายไลบรารีที่เข้ากันได้กับทุกอย่าง ยกเว้น ARM

cc_library(
    name = "non_arm_lib",
    srcs = ["non_arm_lib.cc"],
    target_compatible_with = select({
        "@platforms//cpu:arm": ["@platforms//:incompatible"],
        "//conditions:default": [],
    }),
)

การตรวจหาเป้าหมายที่ไม่เข้ากันโดยใช้ bazel cquery

คุณสามารถใช้ IncompatiblePlatformProvider ใน bazel cquery's รูปแบบเอาต์พุต Starlark เพื่อแยกเป้าหมายที่ไม่เข้ากันออกจากเป้าหมายที่เข้ากันได้

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

$ cat example.cquery

def format(target):
  if "IncompatiblePlatformProvider" not in providers(target):
    return target.label
  return ""


$ bazel cquery //... --output=starlark --starlark:file=example.cquery

ปัญหาที่ทราบ

เป้าหมายที่ไม่เข้ากันจะละเว้นข้อจำกัดด้านการมองเห็น