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