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

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

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

ทำความเข้าใจระบบการสร้างแบบอิงตามงาน

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

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

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

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

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

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

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

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

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

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

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

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