หน้านี้ครอบคลุมระบบบิลด์ที่อิงตามงาน วิธีการทำงาน และความซับซ้อนบางอย่างที่อาจเกิดขึ้นกับระบบที่อิงตามงาน ระบบบิลด์ที่อิงตามงานเป็นการพัฒนาที่สมเหตุสมผลถัดจากการใช้สคริปต์เชลล์
ทำความเข้าใจระบบบิลด์ที่อิงตามงาน
ในระบบบิลด์ที่อิงตามงาน หน่วยงานพื้นฐานคือ "งาน" งานแต่ละรายการคือสคริปต์ที่สามารถดำเนินการลอจิกทุกประเภท และงานจะระบุงานอื่นๆ เป็นทรัพยากร 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
รูปที่ 1 กราฟแบบไม่มีวงจรที่แสดงทรัพยากร Dependency
ผู้ใช้ดำเนินการบิลด์โดยระบุงานให้กับเครื่องมือบรรทัดคำสั่งของ Ant ตัวอย่างเช่น เมื่อผู้ใช้พิมพ์ ant dist Ant จะทำตามขั้นตอนต่อไปนี้
- โหลดไฟล์ชื่อ
build.xmlในไดเรกทอรีปัจจุบันและแยกวิเคราะห์ไฟล์เพื่อสร้างโครงสร้างกราฟที่แสดงในรูปที่ 1 - ค้นหางานชื่อ
distที่ระบุไว้ในบรรทัดคำสั่งและพบว่างานนี้มีทรัพยากร Dependency เป็นงานชื่อcompile - ค้นหางานชื่อ
compileและพบว่างานนี้มีทรัพยากร Dependency เป็นงานชื่อinit - ค้นหางานชื่อ
initและพบว่างานนี้ไม่มีทรัพยากร Dependency - ดำเนินการคำสั่งที่กำหนดไว้ในงาน
init - ดำเนินการคำสั่งที่กำหนดไว้ในงาน
compileเมื่อทรัพยากร Dependency ทั้งหมดของงานนั้นทำงานแล้ว - ดำเนินการคำสั่งที่กำหนดไว้ในงาน
distเมื่อทรัพยากร Dependency ทั้งหมดของงานนั้นทำงานแล้ว
สุดท้ายแล้ว โค้ดที่ Ant ดำเนินการเมื่อเรียกใช้งาน dist จะเทียบเท่ากับสคริปต์ของ Shell ต่อไปนี้
./createTimestamp.shmkdir 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 โดยการเพิ่มฟีเจอร์ต่างๆ เช่น การจัดการทรัพยากร Dependency ภายนอกโดยอัตโนมัติและไวยากรณ์ที่ชัดเจนขึ้นโดยไม่มี XML แต่ลักษณะของระบบใหม่เหล่านี้ยังคงเหมือนเดิม นั่นคือช่วยให้นักพัฒนาซอฟต์แวร์เขียนสคริปต์บิลด์ในลักษณะที่เป็นหลักการและเป็นโมดูลในรูปแบบงาน และมีเครื่องมือสำหรับเรียกใช้งานเหล่านั้นและจัดการทรัพยากร Dependency ระหว่างงาน
ข้อเสียของระบบบิลด์ที่อิงตามงาน
เนื่องจากเครื่องมือเหล่านี้ช่วยให้นักพัฒนาซอฟต์แวร์กำหนดสคริปต์ใดก็ได้เป็นงาน จึงมีประสิทธิภาพมากและช่วยให้คุณทำสิ่งต่างๆ ได้เกือบทุกอย่างที่นึกออก แต่ประสิทธิภาพดังกล่าวก็มาพร้อมกับข้อเสีย และระบบบิลด์ที่อิงตามงานอาจใช้งานได้ยากขึ้นเมื่อสคริปต์บิลด์มีความซับซ้อนมากขึ้น ปัญหาของระบบดังกล่าวคือระบบให้ อำนาจแก่นักพัฒนาซอฟต์แวร์มากเกินไปและให้อำนาจแก่ระบบไม่เพียงพอ เนื่องจากระบบไม่ทราบว่าสคริปต์กำลังทำอะไรอยู่ ประสิทธิภาพจึงลดลง เนื่องจากระบบต้องระมัดระวังอย่างมากในการกำหนดเวลาและเรียกใช้งานขั้นตอนบิลด์ และระบบไม่มีวิธีตรวจสอบว่าสคริปต์แต่ละรายการกำลังทำสิ่งที่ควรทำ สคริปต์จึงมีแนวโน้มที่จะซับซ้อนมากขึ้นและกลายเป็นอีกสิ่งหนึ่งที่ต้องแก้ไขข้อบกพร่อง
ความยากในการขนานขั้นตอนบิลด์
เวิร์กสเตชันการพัฒนาที่ทันสมัยมีประสิทธิภาพมาก โดยมีหลายคอร์ที่สามารถเรียกใช้งานขั้นตอนบิลด์หลายขั้นตอนพร้อมกันได้ แต่ระบบที่อิงตามงานมักไม่สามารถขนานการเรียกใช้งานได้แม้ว่าดูเหมือนว่าระบบจะทำได้ สมมติว่างาน A ขึ้นอยู่กับงาน B และ C เนื่องจากงาน B และ C ไม่ได้ขึ้นอยู่กับงานอื่น จึงเรียกใช้งานพร้อมกันได้อย่างปลอดภัยเพื่อให้ระบบสามารถไปยังงาน A ได้เร็วขึ้นหรือไม่ อาจทำได้หากงานทั้งสองไม่ได้ใช้ทรัพยากรเดียวกัน แต่ก็อาจทำไม่ได้เช่นกัน เนื่องจากทั้งสองงานอาจใช้ไฟล์เดียวกันเพื่อติดตามสถานะของตนเอง และการเรียกใช้งานพร้อมกันอาจทำให้เกิดความขัดแย้ง โดยทั่วไปแล้วระบบไม่มีทางทราบได้ ดังนั้นระบบจึงต้องเสี่ยงต่อความขัดแย้งเหล่านี้ (ซึ่งนำไปสู่ปัญหาบิลด์ที่เกิดขึ้นไม่บ่อยนักแต่แก้ไขข้อบกพร่องได้ยากมาก) หรือต้องจำกัดบิลด์ทั้งหมดให้ทำงานในเธรดเดียวในกระบวนการเดียว ซึ่งอาจเป็นการสิ้นเปลืองทรัพยากรของเครื่องมือพัฒนาที่มีประสิทธิภาพอย่างมาก และตัดความเป็นไปได้ในการกระจายบิลด์ไปยังเครื่องหลายเครื่องออกไปโดยสิ้นเชิง
ความยากในการดำเนินการบิลด์แบบเพิ่มทีละน้อย
ระบบบิลด์ที่ดีช่วยให้วิศวกรดำเนินการบิลด์แบบเพิ่มทีละน้อยได้อย่างน่าเชื่อถือ เพื่อให้การเปลี่ยนแปลงเล็กน้อยไม่จำเป็นต้องสร้างฐานของโค้ดทั้งหมดใหม่ตั้งแต่ต้น ซึ่งสำคัญอย่างยิ่งหากระบบบิลด์ช้าและไม่สามารถขนานขั้นตอนบิลด์ได้ด้วยเหตุผลที่กล่าวถึงข้างต้น แต่โชคไม่ดีที่ระบบบิลด์ที่อิงตามงานก็ประสบปัญหาในส่วนนี้เช่นกัน เนื่องจากงานสามารถทำอะไรก็ได้ โดยทั่วไปแล้วจึงไม่มีวิธีตรวจสอบว่างานนั้นเสร็จสมบูรณ์แล้วหรือไม่ งานจำนวนมากเพียงแค่รับชุดไฟล์ต้นทางและเรียกใช้คอมไพเลอร์เพื่อสร้างชุดไบนารี ดังนั้นจึงไม่จำเป็นต้องเรียกใช้งานอีกครั้งหากไฟล์ต้นทางพื้นฐานไม่เปลี่ยนแปลง แต่หากไม่มีข้อมูลเพิ่มเติม ระบบก็ไม่สามารถพูดได้อย่างแน่นอนว่างานนั้นเสร็จสมบูรณ์แล้วหรือไม่ เนื่องจากงานอาจดาวน์โหลดไฟล์ที่อาจมีการเปลี่ยนแปลง หรืออาจเขียนการประทับเวลาที่อาจแตกต่างกันในแต่ละครั้งที่เรียกใช้งาน โดยทั่วไปแล้วระบบจะต้องเรียกใช้งานทุกงานอีกครั้งในระหว่างการบิลด์แต่ละครั้งเพื่อให้มั่นใจในความถูกต้อง ระบบบิลด์บางระบบพยายามเปิดใช้บิลด์แบบเพิ่มทีละน้อยโดยอนุญาตให้นักพัฒนาซอฟต์แวร์ระบุเงื่อนไขที่ต้องเรียกใช้งานอีกครั้ง บางครั้งก็ทำได้ แต่ส่วนใหญ่มักเป็นปัญหาที่ซับซ้อนกว่าที่เห็น ตัวอย่างเช่น ในภาษาอย่าง C++ ที่อนุญาตให้ไฟล์อื่นๆ รวมไฟล์ได้โดยตรง เป็นไปไม่ได้ที่จะกำหนดชุดไฟล์ทั้งหมดที่ต้องตรวจสอบการเปลี่ยนแปลงโดยไม่แยกวิเคราะห์แหล่งที่มาของอินพุต นักพัฒนาซอฟต์แวร์มักใช้ทางลัด และทางลัดเหล่านี้อาจนำไปสู่ปัญหาที่เกิดขึ้นไม่บ่อยนักและน่าหงุดหงิด ซึ่งผลลัพธ์ของงานจะถูกนำกลับมาใช้ซ้ำแม้ว่าจะไม่ควรทำก็ตาม เมื่อเกิดเหตุการณ์นี้บ่อยครั้ง นักพัฒนาซอฟต์แวร์จะติดนิสัยในการเรียกใช้การล้างข้อมูลก่อนการบิลด์ทุกครั้งเพื่อให้ได้สถานะใหม่ ซึ่งเป็นการทำลายวัตถุประสงค์ของการมีบิลด์แบบเพิ่มทีละน้อยไปโดยสิ้นเชิง การพิจารณาว่าเมื่อใดที่ต้องเรียกใช้งานอีกครั้งเป็นเรื่องที่ซับซ้อนอย่างน่าประหลาดใจ และเป็นงานที่เครื่องจักรจัดการได้ดีกว่ามนุษย์
ความยากในการดูแลรักษาและแก้ไขข้อบกพร่องของสคริปต์
สุดท้ายนี้ สคริปต์บิลด์ที่กำหนดโดยระบบบิลด์ที่อิงตามงานมักใช้งานได้ยาก แม้ว่าสคริปต์บิลด์มักจะได้รับการตรวจสอบน้อยกว่า แต่สคริปต์บิลด์ก็เป็นโค้ดเช่นเดียวกับระบบที่กำลังสร้าง และเป็นที่ซ่อนข้อบกพร่องได้ง่าย ต่อไปนี้เป็นตัวอย่างข้อบกพร่องที่พบบ่อยมากเมื่อใช้ระบบบิลด์ที่อิงตามงาน
- งาน A ขึ้นอยู่กับงาน B ในการสร้างไฟล์หนึ่งๆ เป็นเอาต์พุต เจ้าของงาน B ไม่ทราบว่างานอื่นๆ ขึ้นอยู่กับงานนี้ จึงเปลี่ยนงาน B เพื่อสร้างเอาต์พุตในตำแหน่งอื่น ระบบจะตรวจพบการเปลี่ยนแปลงนี้ไม่ได้จนกว่าจะมีคนพยายามเรียกใช้งาน A และพบว่างานนั้นล้มเหลว
- งาน A ขึ้นอยู่กับงาน B ซึ่งขึ้นอยู่กับงาน C ซึ่งสร้างไฟล์หนึ่งๆ เป็นเอาต์พุตที่งาน A ต้องการ เจ้าของงาน B ตัดสินใจว่างาน B ไม่จำเป็นต้องขึ้นอยู่กับงาน C อีกต่อไป ซึ่งทำให้งาน A ล้มเหลวแม้ว่างาน B จะไม่สนใจงาน C เลยก็ตาม
- นักพัฒนาซอฟต์แวร์ของงานใหม่สันนิษฐานเกี่ยวกับเครื่องที่เรียกใช้งานโดยไม่ตั้งใจ เช่น ตำแหน่งของเครื่องมือหรือค่าของตัวแปรสภาพแวดล้อมหนึ่งๆ งานนี้ทำงานในเครื่องของนักพัฒนาซอฟต์แวร์ แต่ล้มเหลวทุกครั้งที่นักพัฒนาซอฟต์แวร์คนอื่นลองใช้
- งานมีคอมโพเนนต์แบบไม่กำหนดผลลัพธ์ เช่น การดาวน์โหลดไฟล์จากอินเทอร์เน็ตหรือการเพิ่มการประทับเวลาลงในบิลด์ ตอนนี้ผู้ใช้จะได้รับผลลัพธ์ที่อาจแตกต่างกันทุกครั้งที่เรียกใช้งานบิลด์ ซึ่งหมายความว่านักพัฒนาซอฟต์แวร์จะไม่สามารถทำซ้ำและแก้ไขความล้มเหลวของผู้อื่นหรือความล้มเหลวที่เกิดขึ้นในระบบบิลด์อัตโนมัติได้เสมอไป
- งานที่มีทรัพยากร Dependency หลายรายการอาจทำให้เกิดสภาวะการแข่งขัน หากงาน A ขึ้นอยู่กับทั้งงาน B และงาน C และทั้งงาน B และ C แก้ไขไฟล์เดียวกัน งาน A จะได้รับผลลัพธ์ที่แตกต่างกันขึ้นอยู่กับว่างาน B หรือ C งานใดเสร็จก่อน
ไม่มีวิธีอเนกประสงค์ในการแก้ปัญหาด้านประสิทธิภาพ ความถูกต้อง หรือความสามารถในการดูแลรักษาเหล่านี้ภายในเฟรมเวิร์กที่อิงตามงานที่ระบุไว้ที่นี่ ตราบใดที่นักพัฒนาซอฟต์แวร์สามารถเขียนโค้ดที่กำหนดเองซึ่งเรียกใช้งานระหว่างการบิลด์ได้ ระบบก็จะมีข้อมูลไม่เพียงพอที่จะเรียกใช้งานบิลด์ได้อย่างรวดเร็วและถูกต้องเสมอไป หากต้องการแก้ปัญหา เราต้องลดอำนาจของนักพัฒนาซอฟต์แวร์และมอบอำนาจนั้นคืนให้กับระบบ รวมถึงเปลี่ยนแนวคิดเกี่ยวกับบทบาทของระบบจากระบบที่เรียกใช้งานเป็นระบบที่สร้างอาร์ติแฟกต์
แนวทางนี้นำไปสู่การสร้างระบบบิลด์ที่อิงตามอาร์ติแฟกต์ เช่น Blaze และ Bazel
