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