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

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

หน้านี้อธิบายระบบการสร้างแบบอิงตามงาน วิธีทํางาน และข้อจํากัดบางอย่างที่อาจเกิดขึ้นกับระบบแบบอิงตามงาน หลังจากสคริปต์เชลล์แล้ว ระบบการบิลด์ตามงานเป็นวิวัฒนาการเชิงตรรกะถัดไปของการสร้าง

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

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

ลองดูตัวอย่างนี้จากคู่มือ 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>

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

กราฟอะคริลิกแสดง Dependency

รูปที่ 1 กราฟที่ไม่มีวงจรแสดง Dependency

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

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

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

./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 ในช่วงหลายปีที่ผ่านมาและได้เข้ามาแทนที่โดยพื้นฐานแล้วโดยการเพิ่มฟีเจอร์ เช่น การจัดการการอ้างอิงภายนอกโดยอัตโนมัติและไวยากรณ์ที่ดูสะอาดตาโดยไม่ต้องใช้ XML แต่ลักษณะของระบบใหม่เหล่านี้ยังคงเหมือนเดิม กล่าวคือ ช่วยให้วิศวกรเขียนสคริปต์การสร้างในลักษณะที่เป็นหลักการและเป็นแบบโมดูลสำหรับงานต่างๆ รวมถึงมีเครื่องมือสำหรับดำเนินการงานเหล่านั้นและจัดการการพึ่งพาระหว่างงาน

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

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

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

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

ทำการบิลด์แบบเพิ่มทีละส่วนได้ยาก

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

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

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

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

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

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