ระบบบิลด์ตามงาน

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

การทำความเข้าใจระบบบิลด์ตามงาน

ในระบบการบิลด์แบบอิงตามงาน หน่วยพื้นฐานของงานคืองาน งานแต่ละรายการเป็นสคริปต์ที่ดําเนินการตรรกะประเภทใดก็ได้ และงานจะระบุงานอื่นๆ ว่าเป็นทรัพยากรที่ต้องเรียกใช้ก่อน ระบบบิลด์หลักๆ ส่วนใหญ่ที่ใช้กันในปัจจุบัน เช่น Ant, Maven, Gradle, Grunt และ Rake ทำงานตามงาน แทนที่จะเป็น สคริปต์ Shell ระบบบิลด์ที่ทันสมัยส่วนใหญ่ต้องการวิศวกรในการสร้างไฟล์บิลด์ ที่อธิบายวิธีดำเนินการสร้าง

ลองดูตัวอย่างนี้จากคู่มือ Ant

<project name="MyProject" default="dist" basedir=".">
   <description>
     simple example build file
   </description>
   <!-- set global properties for this build -->
   <property name="src" location="src"/>
   <property name="build" location="build"/>
   <property name="dist" location="dist"/>

   <target name="init">
     <!-- Create the time stamp -->
     <tstamp/>
     <!-- Create the build directory structure used by compile -->
     <mkdir dir="${build}"/>
   </target>
   <target name="compile" depends="init"
       description="compile the source">
     <!-- Compile the Java code from ${src} into ${build} -->
     <javac srcdir="${src}" destdir="${build}"/>
   </target>
   <target name="dist" depends="compile"
       description="generate the distribution">
     <!-- Create the distribution directory -->
     <mkdir dir="${dist}/lib"/>
     <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
     <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
   </target>
   <target name="clean"
       description="clean up">
     <!-- Delete the ${build} and ${dist} directory trees -->
     <delete dir="${build}"/>
     <delete dir="${dist}"/>
   </target>
</project>

Buildfile เขียนใน XML และระบุข้อมูลเมตาง่ายๆ บางอย่างเกี่ยวกับบิลด์ พร้อมด้วยรายการงาน (แท็ก <target> ใน XML) (Ant ใช้คำว่า target เพื่อแสดงถึง task และใช้คำว่า task เพื่ออ้างอิงถึง commands) แต่ละงานจะประมวลผลรายการคำสั่งที่เป็นไปได้ที่ Ant กำหนดไว้ ซึ่งรวมการสร้างและลบไดเรกทอรี, การเรียกใช้ javac และ สร้างไฟล์ JAR คำสั่งชุดนี้สามารถขยายได้โดยผู้ใช้ระบุ ปลั๊กอินที่ครอบคลุมตรรกะต่างๆ แต่ละงานยังสามารถกําหนดงาน ขึ้นอยู่กับแอตทริบิวต์ Dependency ความสัมพันธ์เหล่านี้จะสร้างเป็นกราฟที่ไม่มีวงรอบดังที่เห็นในรูปที่ 1

กราฟอะคริลิกแสดงทรัพยากร Dependency

รูปที่ 1 กราฟแบบวนซ้ำแสดงทรัพยากร Dependency

ผู้ใช้จะทำการบิลด์ได้โดยส่งงานไปยังเครื่องมือบรรทัดคำสั่งของ Ant ตัวอย่างเช่น เมื่อผู้ใช้พิมพ์ ant dist Ant จะทําตามขั้นตอนต่อไปนี้

  1. โหลดไฟล์ชื่อ build.xml ในไดเรกทอรีปัจจุบันและแยกวิเคราะห์เป็น สร้างโครงสร้างกราฟที่แสดงในรูปที่ 1
  2. มองหางานที่ชื่อ dist ที่ระบุไว้ในบรรทัดคำสั่ง และพบว่างานดังกล่าวมีความเกี่ยวข้องกับงานที่ชื่อ compile
  3. ค้นหางานชื่อ compile และพบว่างานดังกล่าวมีการขึ้นต่อกัน งานที่ชื่อ init
  4. ค้นหางานชื่อ init และพบว่าไม่มีทรัพยากร Dependency
  5. เรียกใช้คำสั่งที่ระบุไว้ในงาน init
  6. ดำเนินการตามคำสั่งที่กําหนดไว้ในงาน compile โดยที่ระบบได้เรียกใช้ Dependency ทั้งหมดของงานนั้นแล้ว
  7. เรียกใช้คำสั่งที่กำหนดไว้ในงาน dist โดยขึ้นอยู่กับคำสั่งทั้งหมด เรียกใช้ทรัพยากร Dependency ของงานแล้ว

ในท้ายที่สุด โค้ดที่ Ant จะเรียกใช้เมื่อเรียกใช้งาน dist จะมีค่าเทียบเท่ากัน ลงในสคริปต์ Shell ต่อไปนี้

./createTimestamp.sh
mkdir build/
javac src/* -d build/
mkdir -p dist/lib/
jar cf dist/lib/MyProject-$(date --iso-8601).jar build/*

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

Ant เป็นซอฟต์แวร์เก่าแก่ที่เปิดตัวครั้งแรกในปี 2000 เครื่องมืออื่นๆ เช่น Maven และ Gradle ได้ปรับปรุง Ant ในช่วงหลายปีที่ผ่านมา และเข้ามาแทนที่ Ant โดยการเพิ่มฟีเจอร์ต่างๆ เช่น การจัดการอย่างอัตโนมัติของข้อกำหนดภายนอกและไวยากรณ์ที่สะอาดขึ้นโดยไม่มี XML แต่ลักษณะของเนื้อหาที่ใหม่กว่า ระบบต่างๆ ยังคงเหมือนเดิม นั่นคืออนุญาตให้วิศวกรเขียนสคริปต์บิลด์ใน ทำงานอย่างมีหลักการและเป็นโมดูล และมอบเครื่องมือเพื่อดำเนินงานเหล่านั้น และจัดการทรัพยากร Dependency ได้

ด้านมืดของระบบบิลด์ตามงาน

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

ความยากของขั้นตอนบิลด์พร้อมกัน

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

ปัญหาในการสร้างบิลด์ที่เพิ่มขึ้น

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

จัดการและแก้ไขข้อบกพร่องของสคริปต์ได้ยาก

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

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

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

แนวทางนี้นำไปสู่การสร้างระบบบิลด์ที่ใช้อาร์ติแฟกต์อย่างเช่น Blaze และ Bazel