หน้านี้ครอบคลุมระบบบิลด์ตามงาน วิธีการทำงาน และความซับซ้อนบางอย่างที่อาจเกิดขึ้นกับระบบตามงาน ระบบบิลด์ตามงานเป็นการพัฒนาที่สมเหตุสมผลถัดไปของการบิลด์หลังจากสคริปต์เชลล์
ทำความเข้าใจระบบบิลด์ตามงาน
ในระบบบิลด์ตามงาน หน่วยงานพื้นฐานคือ งาน งานแต่ละรายการคือสคริปต์ที่สามารถดำเนินการลอจิกทุกประเภท และงานจะระบุงานอื่นๆ เป็นทรัพยากร 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 จะเทียบเท่า
กับสคริปต์เชลล์ต่อไปนี้
./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 ได้เร็วขึ้น อาจทำได้หากงานทั้ง 2 ไม่ได้ใช้ทรัพยากรเดียวกัน แต่ก็อาจทำไม่ได้เช่นกัน เนื่องจากทั้ง 2 งานอาจใช้ไฟล์เดียวกันเพื่อติดตาม สถานะของตนเอง และการเรียกใช้งานพร้อมกันอาจทำให้เกิดความขัดแย้ง โดยทั่วไปแล้วระบบไม่มีทางทราบได้ ดังนั้นระบบจึงต้องเสี่ยงกับความขัดแย้งเหล่านี้ (ซึ่งนำไปสู่ปัญหาการบิลด์ที่เกิดขึ้นไม่บ่อยนักแต่แก้ไขข้อบกพร่องได้ยากมาก) หรือต้อง จำกัดการบิลด์ทั้งหมดให้ทำงานในเธรดเดียวในกระบวนการเดียว ซึ่งอาจเป็นการสิ้นเปลืองทรัพยากรของเครื่องมือพัฒนาที่มีประสิทธิภาพอย่างมาก และตัดความเป็นไปได้ในการกระจายการบิลด์ไปยังเครื่องหลายเครื่องออกไปโดยสิ้นเชิง
ความยากในการดำเนินการบิลด์แบบเพิ่มทีละส่วน
ระบบบิลด์ที่ดีช่วยให้นักพัฒนาซอฟต์แวร์ดำเนินการบิลด์แบบเพิ่มทีละส่วนได้อย่างน่าเชื่อถือ เพื่อให้การเปลี่ยนแปลงเล็กน้อยไม่จำเป็นต้องสร้างโค้ดเบสทั้งหมดใหม่ตั้งแต่ต้น ซึ่งมีความสำคัญอย่างยิ่งหากระบบบิลด์ทำงานช้าและไม่สามารถ ขนานขั้นตอนการบิลด์ได้ด้วยเหตุผลที่กล่าวไว้ข้างต้น แต่โชคไม่ดีที่ ระบบบิลด์ตามงานก็ประสบปัญหาในส่วนนี้เช่นกัน เนื่องจากงานสามารถทำอะไรก็ได้ โดยทั่วไปแล้วจึงไม่มีวิธีตรวจสอบว่างานเสร็จสมบูรณ์แล้วหรือยัง งานจำนวนมาก เพียงแค่รับชุดไฟล์ต้นฉบับและเรียกใช้คอมไพเลอร์เพื่อสร้างชุด ไบนารี ดังนั้นจึงไม่จำเป็นต้องเรียกใช้อีกครั้งหากไฟล์ต้นฉบับพื้นฐาน ไม่เปลี่ยนแปลง แต่หากไม่มีข้อมูลเพิ่มเติม ระบบก็ไม่สามารถพูดได้อย่างแน่นอนว่างานเสร็จสมบูรณ์แล้วหรือยัง เนื่องจากงานอาจดาวน์โหลดไฟล์ที่อาจมีการเปลี่ยนแปลง หรืออาจ เขียนการประทับเวลาที่อาจแตกต่างกันในแต่ละครั้งที่เรียกใช้งาน โดยทั่วไปแล้วระบบจะต้องเรียกใช้งานทุกงานอีกครั้งระหว่างการบิลด์แต่ละครั้งเพื่อให้มั่นใจในความถูกต้อง ระบบบิลด์บางระบบพยายามเปิดใช้การบิลด์แบบเพิ่มทีละส่วนโดยอนุญาตให้นักพัฒนาซอฟต์แวร์ระบุเงื่อนไขที่ต้องเรียกใช้งานอีกครั้ง บางครั้งก็ทำได้ แต่ ส่วนใหญ่มักเป็นปัญหาที่ซับซ้อนกว่าที่เห็น ตัวอย่างเช่น ในภาษา อย่าง C++ ที่อนุญาตให้ไฟล์อื่นๆ รวมไฟล์ได้โดยตรง เป็นไปไม่ได้ที่จะกำหนดชุดไฟล์ทั้งหมดที่ต้องตรวจสอบการเปลี่ยนแปลง โดยไม่แยกวิเคราะห์แหล่งที่มาของอินพุต นักพัฒนาซอฟต์แวร์มักจะใช้ทางลัด และ ทางลัดเหล่านี้อาจนำไปสู่ปัญหาที่เกิดขึ้นไม่บ่อยนักและน่าหงุดหงิด ซึ่งผลลัพธ์ของงานจะถูกนำกลับมาใช้ซ้ำแม้ว่าจะไม่ควรทำก็ตาม เมื่อเกิดเหตุการณ์นี้บ่อยครั้ง นักพัฒนาซอฟต์แวร์จะติดนิสัยในการเรียกใช้คำสั่งล้างข้อมูลก่อนการบิลด์ทุกครั้งเพื่อให้ได้สถานะใหม่ ซึ่งเป็นการทำลายวัตถุประสงค์ของการบิลด์แบบเพิ่มทีละส่วนตั้งแต่แรก การพิจารณาว่าเมื่อใดที่ต้องเรียกใช้งานอีกครั้งเป็นเรื่องที่ซับซ้อนอย่างน่าประหลาดใจ และเป็น งานที่เครื่องจักรจัดการได้ดีกว่ามนุษย์
ความยากในการดูแลรักษาและแก้ไขข้อบกพร่องของสคริปต์
สุดท้ายนี้ สคริปต์บิลด์ที่กำหนดโดยระบบบิลด์ตามงานมักใช้งานได้ยาก แม้ว่าสคริปต์บิลด์มักจะได้รับการตรวจสอบน้อยกว่า แต่สคริปต์บิลด์ ก็เป็นโค้ดเช่นเดียวกับระบบที่กำลังสร้าง และเป็นที่ซ่อนข้อบกพร่องได้ง่าย ตัวอย่างข้อบกพร่องที่พบได้บ่อยมากเมื่อใช้ระบบบิลด์ตามงานมีดังนี้
- งาน A ขึ้นอยู่กับงาน B ในการสร้างไฟล์หนึ่งๆ เป็นเอาต์พุต เจ้าของ งาน 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
