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