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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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