ทรัพยากร Dependency

เป้าหมาย A ขึ้นอยู่กับ เป้าหมาย B หาก A ต้องใช้ B ในเวลาบิลด์หรือเวลาดำเนินการ ความสัมพันธ์ ขึ้นอยู่กับ จะทำให้เกิด Directed Acyclic Graph (DAG) ในเป้าหมาย และเรียกว่า กราฟทรัพยากร Dependency

ทรัพยากร Dependency โดยตรง ของเป้าหมายคือเป้าหมายอื่นๆ ที่เข้าถึงได้ด้วยเส้นทางที่มีความยาว 1 ในกราฟทรัพยากร Dependency ทรัพยากร Dependency แบบถ่ายทอด ของเป้าหมายคือเป้าหมายที่เป้าหมายนั้นขึ้นอยู่กับเส้นทางที่มีความยาวใดก็ได้ผ่านกราฟ

ในความเป็นจริง ในบริบทของบิลด์จะมีกราฟทรัพยากร Dependency 2 กราฟ ได้แก่ กราฟ ทรัพยากร Dependency จริง และกราฟ ทรัพยากร Dependency ที่ประกาศ ส่วนใหญ่แล้วกราฟทั้ง 2 จะคล้ายกันมากจนไม่จำเป็นต้องแยกความแตกต่าง แต่การแยกความแตกต่างนี้จะเป็นประโยชน์สำหรับการอภิปรายด้านล่าง

ทรัพยากร Dependency จริงและทรัพยากร Dependency ที่ประกาศ

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

เป้าหมาย X มี ทรัพยากร Dependency ที่ประกาศ ในเป้าหมาย Y หากมีขอบทรัพยากร Dependency จาก X ไปยัง Y ในแพ็กเกจของ X

กราฟทรัพยากร Dependency จริง A ต้องเป็นกราฟย่อยของกราฟทรัพยากร Dependency ที่ประกาศ D เพื่อให้สร้างได้อย่างถูกต้อง นั่นคือ โหนดที่เชื่อมต่อกันโดยตรงทุกคู่ในA ต้องเชื่อมต่อกันโดยตรงในD ด้วยx --> y กล่าวได้ว่า D เป็น การประมาณค่ามากเกินไป ของ A

ผู้เขียนไฟล์ BUILD ต้องประกาศทรัพยากร Dependency โดยตรงทั้งหมดสำหรับกฎแต่ละข้อต่อระบบบิลด์อย่างชัดเจน และไม่ควรประกาศทรัพยากร Dependency อื่นๆ เพิ่มเติม

การไม่ปฏิบัติตามหลักการนี้จะทำให้เกิดลักษณะการทำงานที่ไม่แน่นอน กล่าวคือ บิลด์อาจล้มเหลว แต่ที่แย่กว่านั้นคือ บิลด์อาจขึ้นอยู่กับการดำเนินการก่อนหน้าบางอย่าง หรือขึ้นอยู่กับทรัพยากร Dependency ที่ประกาศแบบถ่ายทอดที่เป้าหมายมี Bazel จะตรวจสอบทรัพยากร Dependency ที่ขาดหายไปและรายงานข้อผิดพลาด แต่การตรวจสอบนี้อาจไม่สมบูรณ์ในบางกรณี

คุณไม่จำเป็นต้อง (และไม่ควร) พยายามแสดงรายการทุกอย่างที่นำเข้าโดยอ้อม แม้ว่า A จำเป็นต้องใช้ ในเวลาดำเนินการก็ตาม

ระหว่างการสร้างเป้าหมาย X เครื่องมือบิลด์จะตรวจสอบการปิดทรัพยากร Dependency แบบถ่ายทอดทั้งหมดของ X เพื่อให้แน่ใจว่าการเปลี่ยนแปลงในเป้าหมายเหล่านั้นจะแสดงในผลลัพธ์สุดท้าย โดยจะสร้างรายการระดับกลางใหม่ตามความจำเป็น

ลักษณะแบบถ่ายทอดของทรัพยากร Dependency ทำให้เกิดข้อผิดพลาดที่พบบ่อย บางครั้งโค้ดในไฟล์หนึ่งอาจใช้โค้ดที่จัดทำโดยทรัพยากร Dependency ทางอ้อม ซึ่งเป็นขอบแบบถ่ายทอดแต่ไม่ใช่ขอบโดยตรงในกราฟทรัพยากร Dependency ที่ประกาศ ทรัพยากร Dependency ทางอ้อมจะไม่ปรากฏในไฟล์ BUILD เนื่องจากกฎไม่ได้ขึ้นอยู่กับผู้ให้บริการโดยตรง จึงไม่มีวิธีติดตามการเปลี่ยนแปลง ดังที่แสดงในไทม์ไลน์ตัวอย่างต่อไปนี้

1. ทรัพยากร Dependency ที่ประกาศตรงกับทรัพยากร Dependency จริง

ในตอนแรกทุกอย่างทำงานได้ โค้ดในแพ็กเกจ a ใช้โค้ดในแพ็กเกจ b โค้ดในแพ็กเกจ b ใช้โค้ดในแพ็กเก1จ c ดังนั้น a จึงขึ้นอยู่กับ c แบบถ่ายทอด

a/BUILD b/BUILD
rule(
    name = "a",
    srcs = "a.in",
    deps = "//b:b",
)
      
rule(
    name = "b",
    srcs = "b.in",
    deps = "//c:c",
)
      
a / a.in b / b.in
import b;
b.foo();
    
import c;
function foo() {
  c.bar();
}
      
กราฟทรัพยากร Dependency ที่ประกาศพร้อมลูกศรที่เชื่อมต่อ a, b และ c
กราฟทรัพยากร Dependency ที่ประกาศ
กราฟทรัพยากร Dependency จริงที่ตรงกับกราฟทรัพยากร Dependency ที่ประกาศ
                  พร้อมลูกศรที่เชื่อมต่อ a, b และ c
กราฟทรัพยากร Dependency จริง

ทรัพยากร Dependency ที่ประกาศเป็นการประมาณค่ามากเกินไปของทรัพยากร Dependency จริง ทุกอย่างเรียบร้อย

2. การเพิ่มทรัพยากร Dependency ที่ไม่ได้ประกาศ

อันตรายที่ซ่อนอยู่จะเกิดขึ้นเมื่อมีคนเพิ่มโค้ดลงใน a ซึ่งสร้าง ทรัพยากร Dependency จริง โดยตรงใน c แต่ลืมประกาศในไฟล์บิลด์ a/BUILD

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
กราฟทรัพยากร Dependency ที่ประกาศพร้อมลูกศรที่เชื่อมต่อ a, b และ c
กราฟทรัพยากร Dependency ที่ประกาศ
กราฟทรัพยากร Dependency จริงที่มีลูกศรเชื่อมต่อ a, b และ c ตอนนี้ลูกศรเชื่อมต่อ A กับ C ด้วย ซึ่งไม่ตรงกับ
                  กราฟทรัพยากร Dependency ที่ประกาศไว้
กราฟทรัพยากร Dependency จริง

ทรัพยากร Dependency ที่ประกาศไม่ได้เป็นการประมาณค่ามากเกินไปของทรัพยากร Dependency จริงอีกต่อไป การสร้างนี้อาจสร้างได้ตามปกติเนื่องจากการปิดทรัพยากร Dependency แบบถ่ายทอดของกราฟทั้ง 2 เท่ากัน แต่จะซ่อนปัญหาไว้ นั่นคือ a มีทรัพยากร Dependency จริงแต่ไม่ได้ประกาศใน c

3. ความแตกต่างระหว่างกราฟทรัพยากร Dependency ที่ประกาศและกราฟทรัพยากร Dependency จริง

อันตรายจะปรากฏขึ้นเมื่อมีคนปรับโครงสร้าง b เพื่อให้ไม่ขึ้นอยู่กับ c อีกต่อไป ซึ่งจะทำให้ a ใช้งานไม่ได้โดยไม่ได้ตั้งใจ

  b/BUILD
 
rule(
    name = "b",
    srcs = "b.in",
    deps = "//d:d",
)
      
  b / b.in
 
      import d;
      function foo() {
        d.baz();
      }
      
กราฟทรัพยากร Dependency ที่ประกาศพร้อมลูกศรที่เชื่อมต่อ a และ b
                  b ไม่เชื่อมต่อกับ c อีกต่อไป ซึ่งทำให้การเชื่อมต่อของ a กับ c ขาดตอน
กราฟทรัพยากร Dependency ที่ประกาศ
กราฟการขึ้นต่อกันจริงที่แสดงการเชื่อมต่อ a กับ b และ c
                  แต่ b ไม่ได้เชื่อมต่อกับ c อีกต่อไป
กราฟทรัพยากร Dependency จริง

ตอนนี้กราฟทรัพยากร Dependency ที่ประกาศเป็นการประมาณค่าต่ำเกินไปของทรัพยากร Dependency จริง แม้ว่าจะปิดทรัพยากร Dependency แบบถ่ายทอดแล้วก็ตาม บิลด์จึงมีแนวโน้มที่จะล้มเหลว

คุณสามารถหลีกเลี่ยงปัญหานี้ได้โดยตรวจสอบว่าทรัพยากร Dependency จริงจาก a ไปยัง c ที่เพิ่มในขั้นตอนที่ 2 ได้รับการประกาศอย่างถูกต้องในไฟล์ BUILD

ประเภทของทรัพยากร Dependency

กฎการสร้างส่วนใหญ่มีแอตทริบิวต์ 3 รายการสำหรับระบุทรัพยากร Dependency ทั่วไปประเภทต่างๆ ได้แก่ srcs, deps และ data ซึ่งอธิบายไว้ด้านล่าง ดูรายละเอียดเพิ่มเติมได้ที่ แอตทริบิวต์ที่กฎทั้งหมดมีร่วมกัน

กฎหลายข้อยังมีแอตทริบิวต์เพิ่มเติมสำหรับทรัพยากร Dependency ประเภทเฉพาะกฎด้วย เช่น compiler หรือ resources ซึ่งมีรายละเอียดอยู่ใน Build Encyclopedia

ทรัพยากร Dependency srcs

ไฟล์ที่กฎหรือกฎที่ส่งออกไฟล์ต้นฉบับใช้โดยตรง

ทรัพยากร Dependency deps

กฎที่ชี้ไปยังโมดูลที่คอมไพล์แยกกันซึ่งมีไฟล์ส่วนหัว สัญลักษณ์ ไลบรารี ข้อมูล ฯลฯ

ทรัพยากร Dependency data

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

ระบบบิลด์จะเรียกใช้การทดสอบในไดเรกทอรีที่แยกต่างหากซึ่งมีเพียงไฟล์ที่ระบุเป็น data เท่านั้น ดังนั้น หากไบนารี/ไลบรารี/การทดสอบต้องใช้ไฟล์บางไฟล์ในการเรียกใช้ ให้ระบุไฟล์เหล่านั้น (หรือกฎการบิลด์ที่มีไฟล์เหล่านั้น) ใน data เช่น

# I need a config file from a directory named env:
java_binary(
    name = "setenv",
    ...
    data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
    name = "regtest",
    srcs = ["regtest.sh"],
    data = [
        "//data:file1.txt",
        "//data:file2.txt",
        ...
    ],
)

ไฟล์เหล่านี้พร้อมใช้งานโดยใช้เส้นทางสัมพัทธ์ path/to/data/file ในการทดสอบ คุณสามารถอ้างอิงไฟล์เหล่านี้ได้โดยรวมเส้นทางของไดเรกทอรีต้นฉบับของการทดสอบและเส้นทางสัมพัทธ์ของ Workspace เช่น ${TEST_SRCDIR}/workspace/path/to/data/file

การใช้ป้ายกำกับเพื่ออ้างอิงไดเรกทอรี

เมื่อดูไฟล์ BUILD คุณอาจสังเกตเห็นว่าป้ายกำกับ data บางป้ายกำกับอ้างอิงถึงไดเรกทอรี ป้ายกำกับเหล่านี้ลงท้ายด้วย /. หรือ / เช่น ตัวอย่างต่อไปนี้ ซึ่งคุณไม่ควรใช้:

ไม่แนะนำdata = ["//data/regression:unittest/."]

ไม่แนะนำdata = ["testdata/."]

ไม่แนะนำdata = ["testdata/"]

วิธีนี้ดูเหมือนจะสะดวก โดยเฉพาะอย่างยิ่งสำหรับการทดสอบ เนื่องจากช่วยให้การทดสอบใช้ไฟล์ข้อมูลทั้งหมดในไดเรกทอรีได้

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

แนะนำdata = glob(["testdata/**"])

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

หากต้องใช้ป้ายกำกับไดเรกทอรี โปรดทราบว่าคุณไม่สามารถอ้างอิงแพ็กเกจระดับบนสุดด้วยเส้นทางสัมพัทธ์ ../ แต่ให้ใช้เส้นทางสมบูรณ์ เช่น //data/regression:unittest/. แทน

กฎภายนอกใดๆ เช่น การทดสอบที่ต้องใช้ไฟล์หลายไฟล์ จะต้องประกาศการขึ้นต่อกันของไฟล์เหล่านั้นอย่างชัดเจน คุณสามารถใช้ filegroup() เพื่อจัดกลุ่มไฟล์เข้าด้วยกันในไฟล์ BUILD ได้ดังนี้

filegroup(
        name = 'my_data',
        srcs = glob(['my_unittest_data/*'])
)

จากนั้นคุณสามารถอ้างอิงป้ายกำกับ my_data เป็นทรัพยากร Dependency ของข้อมูลในการทดสอบ

ไฟล์ BUILD การมองเห็น