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

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

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

ทําความเข้าใจระบบงานที่อิงตามงาน

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

ดูตัวอย่างจากคู่มือมด

<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>

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

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

รูปที่ 1 กราฟแบบหมุนเพื่อแสดงทรัพยากร Dependency

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

  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 ซึ่งกําหนดทุกอย่างที่จําเป็นต้องเรียกใช้

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

ด้านมืดของระบบงานที่อิงตามงาน

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

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

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

ความยากลําบากในการสร้างบิลด์เพิ่มเติม

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

การบํารุงรักษาและแก้ไขข้อบกพร่องของสคริปต์

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

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

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

แนวทางนี้นําไปสู่การสร้างระบบบิวด์ที่ใช้วัตถุโบราณ เช่น Blaze และ Bazel