หน้านี้อธิบายระบบการสร้างแบบอิงตามงาน วิธีทํางาน และข้อจํากัดบางอย่างที่อาจเกิดขึ้นกับระบบแบบอิงตามงาน หลังจากสคริปต์เชลล์แล้ว ระบบการบิลด์ตามงานเป็นวิวัฒนาการเชิงตรรกะถัดไปของการสร้าง
ทำความเข้าใจระบบการสร้างแบบอิงตามงาน
ในระบบการบิลด์แบบอิงตามงาน หน่วยพื้นฐานของงานคืองาน งานแต่ละรายการเป็นสคริปต์ที่ดําเนินการตรรกะประเภทใดก็ได้ และงานจะระบุงานอื่นๆ ว่าเป็นทรัพยากรที่ต้องทํางานก่อน ระบบบิลด์หลักๆ ส่วนใหญ่ที่ใช้กันในปัจจุบัน เช่น Ant, Maven, Gradle, Grunt และ Rake ทำงานตามงาน ระบบบิลด์ที่ทันสมัยส่วนใหญ่ต้องใช้วิศวกรเพื่อสร้างไฟล์บิลด์ที่อธิบายวิธีการสร้างบิลด์ แทนที่จะใช้สคริปต์ของ Shell
ลองดูตัวอย่างนี้จากคู่มือ 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>
Buildfile เขียนใน XML และกำหนดข้อมูลเมตาง่ายๆ บางอย่างเกี่ยวกับบิลด์พร้อมกับรายการงาน (แท็ก <target>
ใน XML) (Ant ใช้คำว่า target เพื่อแทน task และใช้คำว่า task เพื่ออ้างถึง commands) แต่ละงานจะดำเนินการตามรายการคำสั่งที่เป็นไปได้ซึ่ง Ant กำหนด ซึ่งรวมถึงการสร้างและลบไดเรกทอรี เรียกใช้ javac
และสร้างไฟล์ JAR คำสั่งชุดนี้สามารถขยายได้ด้วยปลั๊กอินที่ผู้ใช้ให้
เพื่อครอบคลุมตรรกะต่างๆ นอกจากนี้ งานแต่ละงานยังกำหนดงานที่ตัวเองต้องอาศัยผ่านแอตทริบิวต์ depends ได้ด้วย ทรัพยากร Dependency เหล่านี้จะสร้างกราฟแบบวนซ้ำ
ดังที่แสดงในรูปที่ 1
รูปที่ 1 กราฟที่ไม่มีวงจรแสดง Dependency
ผู้ใช้จะทำการบิลด์ได้โดยส่งงานไปยังเครื่องมือบรรทัดคำสั่งของ Ant ตัวอย่างเช่น เมื่อผู้ใช้พิมพ์ ant dist
Ant จะทําตามขั้นตอนต่อไปนี้
- โหลดไฟล์ชื่อ
build.xml
ในไดเรกทอรีปัจจุบันและแยกวิเคราะห์เพื่อสร้างโครงสร้างกราฟที่แสดงในรูปที่ 1 - มองหางานที่ชื่อ
dist
ที่ระบุไว้ในบรรทัดคำสั่ง และพบว่างานดังกล่าวมีความเกี่ยวข้องกับงานที่ชื่อcompile
- ค้นหางานชื่อ
compile
และพบว่างานดังกล่าวมีความเกี่ยวข้องกับงานชื่อinit
- มองหางานชื่อ
init
และพบว่าไม่มีงานใดที่ขึ้นต่อกัน - เรียกใช้คำสั่งที่ระบุไว้ในงาน
init
- ดำเนินการตามคำสั่งที่กําหนดไว้ในงาน
compile
โดยที่ระบบได้เรียกใช้ Dependency ทั้งหมดของงานนั้นแล้ว - ดำเนินการตามคำสั่งที่กําหนดไว้ในงาน
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 ในช่วงหลายปีที่ผ่านมาและได้เข้ามาแทนที่โดยพื้นฐานแล้วโดยการเพิ่มฟีเจอร์ เช่น การจัดการการอ้างอิงภายนอกโดยอัตโนมัติและไวยากรณ์ที่ดูสะอาดตาโดยไม่ต้องใช้ XML แต่ลักษณะของระบบใหม่เหล่านี้ยังคงเหมือนเดิม กล่าวคือ ช่วยให้วิศวกรเขียนสคริปต์การสร้างในลักษณะที่เป็นหลักการและเป็นแบบโมดูลสำหรับงานต่างๆ รวมถึงมีเครื่องมือสำหรับดำเนินการงานเหล่านั้นและจัดการการพึ่งพาระหว่างงาน
ด้านมืดของระบบบิลด์ตามงาน
เนื่องจากเครื่องมือเหล่านี้ช่วยให้วิศวกรกำหนดสคริปต์ใดก็ได้เป็นงาน เครื่องมือเหล่านี้จึงมีประสิทธิภาพสูงมาก ซึ่งช่วยให้คุณทําสิ่งต่างๆ ได้เกือบทุกอย่างที่นึกออก แต่ความสามารถนี้ก็มีข้อเสียเช่นกัน และระบบบิลด์ตามงานอาจใช้งานยากขึ้นเมื่อสคริปต์บิลด์มีความซับซ้อนมากขึ้น ปัญหาของระบบดังกล่าวคือให้อำนาจแก่วิศวกรมากเกินไปและไม่ให้อำนาจแก่ระบบมากพอ เนื่องจากระบบไม่รู้ว่าสคริปต์ทําอะไรอยู่ ประสิทธิภาพจึงลดลง เนื่องจากต้องระมัดระวังมากในการตั้งเวลาและดําเนินการขั้นตอนการสร้าง ระบบไม่มีทางที่จะยืนยันได้ว่าแต่ละสคริปต์ได้ทำในสิ่งที่ควรทำ สคริปต์จึงมีแนวโน้มที่จะเพิ่มความซับซ้อนและกลายเป็นอีกปัญหาหนึ่งที่จำเป็นต้องแก้ไขข้อบกพร่อง
ความยากในการขนานขั้นตอนการสร้าง
เวิร์กสเตชันสำหรับนักพัฒนาซอฟต์แวร์สมัยใหม่มีประสิทธิภาพสูงมาก โดยมีหลายแกนที่สามารถดำเนินการขั้นตอนการสร้างหลายขั้นตอนพร้อมกัน แต่ระบบแบบงานมักจะไม่สามารถทำงานหลายอย่างพร้อมกันได้ แม้ว่าดูเหมือนว่าควรจะทำได้ก็ตาม สมมติว่างาน ก ขึ้นอยู่กับงาน ข และ ค เนื่องจากงาน ข และ ค ไม่ได้ขึ้นอยู่กับงานอื่น การทำงานพร้อมกันจึงปลอดภัยไหมเพื่อให้ระบบทำงานงาน ก ได้เร็วขึ้น อาจจะได้ หากไม่ได้ใช้ทรัพยากรเดียวกัน หรืออาจไม่ อาจเป็นเพราะทั้ง 2 รายการใช้ไฟล์เดียวกันในการติดตามสถานะและการเรียกใช้พร้อมกันทำให้เกิดข้อขัดแย้ง โดยทั่วไปแล้วไม่มีวิธีที่ระบบจะรู้ได้ ดังนั้นจึงต้องเสี่ยงกับความขัดแย้งเหล่านี้ (นำไปสู่ปัญหาการสร้างที่พบได้ยากแต่แก้ไขข้อบกพร่องได้ยากมาก) หรือต้องจำกัดบิลด์ทั้งหมดให้ทำงานในเทรดเดียวในกระบวนการเดียว ซึ่งอาจเป็นเรื่องสิ้นเปลืองสิ้นเปลืองของเครื่องสำหรับนักพัฒนาซอฟต์แวร์ที่ทรงพลัง และกำจัดความเป็นไปได้ในการจัดจำหน่ายบิลด์ไปยังหลายๆ เครื่อง
ทำการบิลด์แบบเพิ่มทีละส่วนได้ยาก
ระบบบิลด์ที่ดีช่วยให้วิศวกรสร้างบิลด์เพิ่มขึ้นได้อย่างเสถียร เพื่อจะได้ไม่ต้องสร้างฐานของโค้ดใหม่ทั้งหมดตั้งแต่ต้น ซึ่งมีความสำคัญอย่างยิ่งหากระบบบิลด์ทำงานช้าและไม่สามารถทำงานแบบขนานในขั้นตอนการสร้างด้วยเหตุผลที่กล่าวมาข้างต้น แต่น่าเสียดายที่ระบบบิลด์แบบอิงตามงานก็มีปัญหาเช่นกัน เนื่องจาก Tasks สามารถทำสิ่งต่างๆ ได้มากมาย จึงไม่มีวิธีทั่วไปในการตรวจสอบว่างานเสร็จแล้วหรือยัง งานจำนวนมากเพียงแค่รับชุดไฟล์ต้นฉบับและเรียกใช้คอมไพเลอร์เพื่อสร้างชุดไบนารี ดังนั้นจึงไม่ต้องเรียกใช้งานเหล่านั้นอีกครั้งหากไฟล์ต้นฉบับที่สำคัญไม่มีการเปลี่ยนแปลง แต่หากไม่มีข้อมูลเพิ่มเติม ระบบจะไม่สามารถระบุได้อย่างแน่ชัด อาจเป็นเพราะงานดาวน์โหลดไฟล์ที่อาจมีการเปลี่ยนแปลง หรืออาจเขียนการประทับเวลาที่อาจแตกต่างกันไปในแต่ละครั้งที่เรียกใช้ โดยปกติแล้วระบบต้องเรียกใช้งานทุกรายการอีกครั้งในระหว่างการสร้างแต่ละครั้งเพื่อให้แน่ใจว่าถูกต้อง ระบบบิลด์บางระบบพยายามเปิดใช้บิลด์แบบเพิ่มทีละน้อยโดยอนุญาตให้วิศวกรระบุเงื่อนไขที่จำเป็นต้องเรียกใช้งานอีกครั้ง บางครั้งก็ทำได้ แต่บ่อยครั้งปัญหานี้ยากกว่าที่เห็น ตัวอย่างเช่น ในภาษาอย่าง C++ ที่อนุญาตให้ไฟล์อื่นๆ รวมไฟล์ได้โดยตรง คุณจะไม่สามารถระบุชุดไฟล์ทั้งหมดที่ต้องตรวจสอบการเปลี่ยนแปลงได้หากไม่แยกวิเคราะห์แหล่งที่มาของอินพุต วิศวกรมักจะติดทางลัด ซึ่งทางลัดเหล่านี้อาจนำไปสู่ปัญหาที่หาได้ยากและน่าหงุดหงิดซึ่งระบบนำผลการค้นหางานมาใช้ซ้ำ ทั้งๆ ที่ไม่ควรเป็นเช่นนั้น เมื่อเกิดกรณีนี้ขึ้นบ่อยครั้ง วิศวกรก็มักจะมีนิสัยในการล้างข้อมูลก่อนการบิวด์ทุกครั้งเพื่อให้ได้สถานะใหม่ ซึ่งทำให้วัตถุประสงค์ของการสร้างแบบเพิ่มเข้ามาตั้งแต่แรกเสียไปโดยสิ้นเชิง การหาว่าต้องเรียกใช้งานอีกครั้งเมื่อใดนั้นเป็นเรื่องละเอียดอ่อนมาก และเป็นสิ่งที่เครื่องจักรจัดการได้ดีกว่ามนุษย์
จัดการและแก้ไขข้อบกพร่องของสคริปต์ได้ยาก
สุดท้าย สคริปต์บิลด์ที่ระบบบิลด์แบบอิงตามงานกำหนดมักจะใช้งานยาก แม้ว่ามักจะมีการตรวจสอบอย่างละเอียดน้อยกว่า แต่สคริปต์บิลด์คือโค้ดเช่นเดียวกับที่ระบบสร้างขึ้น และเป็นที่ซ่อนข้อบกพร่องได้ง่าย ต่อไปนี้คือตัวอย่างบางส่วนของข้อบกพร่องที่พบได้บ่อยมากเมื่อทำงานกับระบบบิลด์ตามงาน
- งาน ก ขึ้นอยู่กับงาน ข ในการสร้างไฟล์หนึ่งๆ เป็นเอาต์พุต เจ้าของงาน ข. ไม่ทราบว่ามีงานอื่นๆ ที่ใช้งานดังกล่าว จึงเปลี่ยนแปลงงานดังกล่าวเพื่อสร้างเอาต์พุตในตำแหน่งอื่น ซึ่งจะตรวจไม่พบจนกว่าจะมีคนพยายามเรียกใช้งาน A แล้วพบว่าทำงานไม่สำเร็จ
- งาน ก ขึ้นอยู่กับงาน ข ซึ่งขึ้นอยู่กับงาน ค ซึ่งสร้างไฟล์หนึ่งๆ เป็นเอาต์พุตที่งาน ก ต้องการ เจ้าของงาน ข ตัดสินใจว่าไม่จำเป็นต้องขึ้นอยู่กับงาน ค อีกต่อไป ซึ่งทำให้งาน ก ดำเนินการไม่สำเร็จ แม้ว่างาน ข จะไม่สนใจงาน ค เลยก็ตาม
- นักพัฒนาซอฟต์แวร์งานชิ้นใหม่ตั้งสมมติฐานเกี่ยวกับเครื่องที่กำลังทำงานนี้โดยไม่ได้ตั้งใจ เช่น ตำแหน่งของเครื่องมือหรือค่าของตัวแปรสภาพแวดล้อมบางอย่าง งานนี้ได้ผลในเครื่องของตน แต่จะล้มเหลวเมื่อนักพัฒนาซอฟต์แวร์คนอื่นพยายามทำงาน
- งานมีองค์ประกอบที่ไม่กำหนด เช่น การดาวน์โหลดไฟล์จากอินเทอร์เน็ตหรือการเพิ่มการประทับเวลาลงในบิลด์ ตอนนี้ผู้ใช้อาจได้ผลลัพธ์ที่แตกต่างกันทุกครั้งที่เรียกใช้บิลด์ ซึ่งหมายความว่าวิศวกรอาจไม่สามารถจำลองและแก้ไขข้อผิดพลาดของกันและกันหรือข้อผิดพลาดที่เกิดขึ้นในระบบบิลด์อัตโนมัติได้เสมอไป
- งานที่มีการพึ่งพาหลายรายการอาจทำให้เกิดเงื่อนไขการแข่งขัน หากงาน A เกี่ยวข้องกับทั้งงาน B และงาน C และทั้ง B และ C ต่างก็แก้ไขไฟล์เดียวกัน งาน A จะได้รับผลลัพธ์ที่ต่างกันขึ้นอยู่กับว่างาน B และ C ใดเสร็จก่อน
ไม่มีวิธีสำหรับวัตถุประสงค์ทั่วไปในการแก้ปัญหาด้านประสิทธิภาพ ความถูกต้อง หรือความสามารถในการบำรุงรักษาเหล่านี้ภายในเฟรมเวิร์กที่อิงตามงานซึ่งได้ระบุไว้ที่นี่ ตราบใดที่วิศวกรสามารถเขียนโค้ดที่กำหนดเองที่ทำงานระหว่างบิลด์ได้ ระบบจะมีข้อมูลไม่เพียงพอที่จะเรียกใช้บิลด์ได้อย่างรวดเร็วและถูกต้องอยู่เสมอ ในการแก้ปัญหานี้ เราจำเป็นต้องนำอำนาจบางส่วนออกจากมือวิศวกรและส่งต่อให้ระบบ รวมถึงกำหนดบทบาทใหม่ให้กับระบบ ไม่ใช่เป็นงานที่กำลังทำงานอยู่ แต่เป็นการนําเสนอผลงาน
แนวทางนี้นำไปสู่การสร้างสรรค์ระบบการสร้างที่อิงตามอาร์ติแฟกต์ เช่น Blaze และ Bazel