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