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

7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

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

ข้อเสียของระบบบิลด์แบบอิงตามงาน

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

ความยากในการขนานขั้นตอนการสร้าง

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

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

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

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

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

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

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

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