ทรัพยากร Dependency

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

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

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

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

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

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

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

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

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

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

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

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

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

ในตอนแรก ทุกอย่างจะทำงานได้ โค้ดในแพ็กเกจ a ใช้โค้ดในแพ็กเกจ b โค้ดในแพ็กเกจ b ใช้โค้ดในแพ็กเกจ 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. การเพิ่มการอ้างอิงที่ไม่ได้ประกาศ

อันตรายที่ซ่อนอยู่จะเกิดขึ้นเมื่อมีผู้เพิ่มโค้ดลงใน a ซึ่งสร้างการอ้างอิงจริงโดยตรงกับ 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 จริงมากเกินไปอีกต่อไป ซึ่งอาจสร้างได้เนื่องจาก Closure แบบทรานซิทีฟของกราฟทั้ง 2 เท่ากัน แต่จะซ่อนปัญหาไว้ นั่นคือ a มีการขึ้นต่อกันจริงแต่ไม่ได้ประกาศไว้กับ c

3. ความแตกต่างระหว่างกราฟการขึ้นต่อกันที่ประกาศไว้กับกราฟการขึ้นต่อกันจริง

ความเสี่ยงจะปรากฏขึ้นเมื่อมีผู้ปรับโครงสร้าง 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 จริง แม้ว่าจะปิดแบบทรานซิทีฟแล้วก็ตาม ซึ่งมีแนวโน้มที่บิลด์จะล้มเหลว

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

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

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

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

srcs ทรัพยากร Dependency

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

deps ทรัพยากร Dependency

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

data ทรัพยากร Dependency

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

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

ไฟล์ BUILD ระดับการเข้าถึง