ระบบบิลด์ตามงาน

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

หน้านี้ครอบคลุมระบบการสร้างตามงาน วิธีการทำงาน และความซับซ้อนบางอย่างที่อาจเกิดขึ้นกับระบบตามงาน หลังจากสคริปต์เชลล์ ระบบบิลด์ตามงานคือวิวัฒนาการเชิงตรรกะถัดไปของการบิลด์

ทำความเข้าใจระบบบิลด์ที่อิงตามงาน

ในระบบบิลด์ที่อิงตามงาน หน่วยงานพื้นฐานคือ งาน แต่ละงานคือสคริปต์ที่สามารถเรียกใช้ตรรกะใดก็ได้ และงานจะระบุงานอื่นๆ เป็นการอ้างอิงที่ต้องเรียกใช้ก่อน ระบบบิลด์หลักๆ ที่ใช้กันในปัจจุบัน เช่น 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 ใช้คำว่าเป้าหมายเพื่อแสดงถึงงาน และใช้คำว่างานเพื่ออ้างถึงคำสั่ง) แต่ละงานจะดำเนินการตามรายการคำสั่งที่เป็นไปได้ซึ่งกำหนดโดย Ant ซึ่งในที่นี้รวมถึงการสร้างและลบไดเรกทอรี การเรียกใช้ javac และ การสร้างไฟล์ JAR ชุดคำสั่งนี้สามารถขยายได้โดยใช้ปลั๊กอินที่ผู้ใช้ระบุเพื่อครอบคลุมตรรกะทุกประเภท นอกจากนี้ แต่ละงานยังกำหนดงานที่ขึ้นอยู่กับงานนั้นได้ผ่านแอตทริบิวต์ depends การอ้างอิงเหล่านี้จะสร้างกราฟแบบไม่มีวงจร ดังที่แสดงในรูปที่ 1

กราฟอะคริลิกแสดงทรัพยากร Dependency

รูปที่ 1 กราฟแบบไม่มีวงจรที่แสดงทรัพยากร Dependency

ผู้ใช้จะสร้างโดยการระบุงานให้กับเครื่องมือบรรทัดคำสั่งของ Ant ตัวอย่างเช่น เมื่อผู้ใช้พิมพ์ ant dist, Ant จะทำตามขั้นตอนต่อไปนี้

  1. โหลดไฟล์ชื่อ build.xml ในไดเรกทอรีปัจจุบันและแยกวิเคราะห์เพื่อ สร้างโครงสร้างกราฟที่แสดงในรูปที่ 1
  2. ค้นหางานที่ชื่อ dist ซึ่งระบุไว้ในบรรทัดคำสั่ง และ พบว่างานดังกล่าวมีงานที่ชื่อ compile เป็นงานที่ต้องทำก่อน
  3. มองหางานชื่อ compile และพบว่างานนี้ขึ้นอยู่กับ งานชื่อ init
  4. มองหางานชื่อ init และพบว่าไม่มีการขึ้นต่อกัน
  5. เรียกใช้คำสั่งที่กำหนดไว้ในงาน init
  6. เรียกใช้คำสั่งที่กำหนดไว้ในcompileงาน โดยพิจารณาว่ามีการเรียกใช้ทรัพยากร Dependency ทั้งหมดของงานนั้นแล้ว
  7. เรียกใช้คำสั่งที่กำหนดไว้ใน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 เนื่องจากงาน B และ C ไม่มีการขึ้นต่อกัน จึงสามารถเรียกใช้พร้อมกันได้ไหม เพื่อให้ระบบเข้าถึงงาน A ได้เร็วขึ้น อาจเป็นไปได้ หากผู้ใช้ไม่ได้แตะต้องทรัพยากรเดียวกัน แต่ก็อาจจะไม่เป็นเช่นนั้น เนื่องจากทั้ง 2 อย่างอาจใช้ไฟล์เดียวกันเพื่อติดตาม สถานะของตัวเอง และการเรียกใช้พร้อมกันอาจทำให้เกิดข้อขัดแย้ง โดยทั่วไปแล้ว ระบบไม่มีทางทราบได้ ดังนั้นระบบจึงต้องเสี่ยงต่อความขัดแย้งเหล่านี้ (ซึ่งนำไปสู่ปัญหาการสร้างที่พบได้ยากแต่แก้ไขได้ยากมาก) หรือต้อง จำกัดการสร้างทั้งหมดให้ทำงานในเธรดเดียวในกระบวนการเดียว ซึ่งอาจทำให้เครื่องมือพัฒนาซอฟต์แวร์ที่มีประสิทธิภาพสูงสูญเปล่า และยังตัดความเป็นไปได้ในการกระจายบิลด์ไปยังเครื่องหลายเครื่องโดยสิ้นเชิง

ความยากในการสร้างแบบเพิ่มทีละส่วน

ระบบบิลด์ที่ดีช่วยให้วิศวกรทำการบิลด์แบบเพิ่มทีละรายการได้อย่างน่าเชื่อถือ เพื่อให้การเปลี่ยนแปลงเล็กๆ น้อยๆ ไม่จำเป็นต้องสร้างฐานของโค้ดทั้งหมดใหม่ตั้งแต่ต้น ซึ่งมีความสำคัญอย่างยิ่งหากระบบบิลด์ทำงานช้าและไม่สามารถ ดำเนินการขั้นตอนการบิลด์แบบขนานได้ด้วยเหตุผลที่กล่าวมาข้างต้น แต่โชคไม่ดีที่ ระบบบิลด์ที่อิงตามงานก็มีปัญหาในเรื่องนี้เช่นกัน เนื่องจากงานทำอะไรก็ได้ จึงไม่มีวิธีตรวจสอบโดยทั่วไปว่างานนั้นเสร็จแล้วหรือยัง งานหลายอย่าง เพียงแค่ใช้ชุดไฟล์ต้นฉบับและเรียกใช้คอมไพเลอร์เพื่อสร้างชุด ไบนารี จึงไม่จำเป็นต้องเรียกใช้อีกครั้งหากไฟล์ต้นฉบับพื้นฐาน ไม่มีการเปลี่ยนแปลง แต่หากไม่มีข้อมูลเพิ่มเติม ระบบก็ไม่สามารถระบุได้อย่างแน่นอนว่า เป็นเช่นนั้นหรือไม่ เนื่องจากอาจเป็นไปได้ว่างานจะดาวน์โหลดไฟล์ที่อาจมีการเปลี่ยนแปลง หรืออาจ เขียนการประทับเวลาที่อาจแตกต่างกันในแต่ละครั้งที่เรียกใช้ โดยปกติแล้ว ระบบจะต้องเรียกใช้ทุกงานอีกครั้งในระหว่างการสร้างแต่ละครั้งเพื่อให้มั่นใจว่า ถูกต้อง ระบบบิลด์บางระบบพยายามเปิดใช้การบิลด์แบบเพิ่มขึ้นโดยอนุญาตให้วิศวกรระบุ เงื่อนไขที่ต้องเรียกใช้ซ้ำ บางครั้งก็ทำได้ แต่ บ่อยครั้งที่ปัญหานี้ซับซ้อนกว่าที่เห็นมาก ตัวอย่างเช่น ในภาษาอย่าง C++ ที่อนุญาตให้ไฟล์อื่นๆ รวมไฟล์ได้โดยตรง จะเป็นไปไม่ได้ที่จะกำหนดชุดไฟล์ทั้งหมดที่ต้องตรวจสอบการเปลี่ยนแปลงโดยไม่ต้องแยกวิเคราะห์แหล่งที่มาของอินพุต วิศวกรมักจะใช้ทางลัด และ ทางลัดเหล่านี้อาจทำให้เกิดปัญหาที่หายากและน่าหงุดหงิดซึ่งผลลัพธ์ของงานจะ นำกลับมาใช้ซ้ำแม้ว่าจะไม่ควรก็ตาม เมื่อเกิดกรณีนี้บ่อยๆ วิศวกรจะ ติดนิสัยในการเรียกใช้การล้างข้อมูลก่อนทุกครั้งที่สร้างเพื่อรับสถานะใหม่ ซึ่งทำให้วัตถุประสงค์ของการมีบิลด์ที่เพิ่มขึ้นตั้งแต่แรก ไม่เป็นผล การพิจารณาว่าเมื่อใดที่ต้องเรียกใช้การทำงานอีกครั้งเป็นเรื่องที่ซับซ้อนอย่างน่าประหลาดใจ และเป็นงานที่เครื่องจักรจัดการได้ดีกว่ามนุษย์

ความยากในการดูแลรักษาและแก้ไขข้อบกพร่องของสคริปต์

สุดท้ายนี้ สคริปต์บิลด์ที่กำหนดโดยระบบบิลด์ตามงานมักจะใช้งานได้ยาก แม้ว่าสคริปต์บิลด์มักจะได้รับการตรวจสอบน้อยกว่า แต่ก็เป็นโค้ดเช่นเดียวกับระบบที่กำลังสร้าง และเป็นที่ที่ข้อบกพร่องซ่อนตัวได้ง่าย ตัวอย่างข้อบกพร่องที่พบบ่อยมากเมื่อใช้ระบบบิลด์ที่อิงตามงานมีดังนี้

  • งาน A ขึ้นอยู่กับงาน B ในการสร้างไฟล์ที่เฉพาะเจาะจงเป็นเอาต์พุต เจ้าของ งาน B ไม่ทราบว่างานอื่นๆ ขึ้นอยู่กับงานนี้ จึงเปลี่ยนงาน B ให้ สร้างเอาต์พุตในตำแหน่งอื่น เราจะตรวจหาข้อผิดพลาดนี้ไม่ได้จนกว่าจะมีคน พยายามเรียกใช้งาน A และพบว่าไม่สำเร็จ
  • งาน A ขึ้นอยู่กับงาน B ซึ่งขึ้นอยู่กับงาน C ซึ่งสร้าง ไฟล์ที่เฉพาะเจาะจงเป็นเอาต์พุตที่งาน A ต้องการ เจ้าของงาน B ตัดสินใจว่าไม่จำเป็นต้องขึ้นอยู่กับงาน C อีกต่อไป ซึ่งทำให้งาน A ล้มเหลวแม้ว่างาน B จะไม่สนใจงาน C เลยก็ตาม
  • นักพัฒนาแอปที่สร้างงานใหม่เข้าใจผิดเกี่ยวกับเครื่องที่รันงาน เช่น ตำแหน่งของเครื่องมือหรือค่าของตัวแปรสภาพแวดล้อมที่เฉพาะเจาะจง งานดังกล่าวทำงานในเครื่องของตนเองได้ แต่จะล้มเหลว ทุกครั้งที่นักพัฒนาซอฟต์แวร์คนอื่นลองใช้
  • งานมีคอมโพเนนต์ที่ไม่แน่นอน เช่น การดาวน์โหลดไฟล์จากอินเทอร์เน็ตหรือการเพิ่มการประทับเวลาในการบิลด์ ตอนนี้ผู้ใช้จะได้รับผลลัพธ์ที่อาจแตกต่างกันทุกครั้งที่เรียกใช้บิลด์ ซึ่งหมายความว่าวิศวกรอาจไม่สามารถทำซ้ำและแก้ไขข้อผิดพลาดของกันและกัน หรือข้อผิดพลาดที่เกิดขึ้นในระบบบิลด์อัตโนมัติได้เสมอไป
  • งานที่มีการอ้างอิงหลายรายการอาจทำให้เกิดสภาวะการแข่งขัน หากงาน ก ขึ้นอยู่กับทั้งงาน ข และงาน ค และทั้งงาน ข และงาน ค แก้ไขไฟล์เดียวกัน งาน ก จะได้ผลลัพธ์ที่แตกต่างกันขึ้นอยู่กับว่างาน ข หรืองาน ค งานใดเสร็จก่อน

ไม่มีวิธีทั่วไปในการแก้ปัญหาด้านประสิทธิภาพ ความถูกต้อง หรือความสามารถในการบำรุงรักษาเหล่านี้ภายในเฟรมเวิร์กตามงานที่ระบุไว้ที่นี่ ตราบใดที่วิศวกรยังเขียนโค้ดที่กำหนดเองซึ่งทำงานระหว่างการบิลด์ได้ ระบบก็จะมีข้อมูลไม่เพียงพอที่จะเรียกใช้บิลด์ได้อย่างรวดเร็วและถูกต้องเสมอ ในการแก้ปัญหานี้ เราจำเป็นต้องดึงอำนาจบางอย่างออกจากมือของ วิศวกรและคืนอำนาจนั้นให้กับระบบ รวมถึงปรับแนวคิดใหม่เกี่ยวกับ บทบาทของระบบ ไม่ใช่ในฐานะผู้รันงาน แต่ในฐานะผู้ผลิตอาร์ติแฟกต์

แนวทางนี้ทำให้เกิดระบบบิลด์ที่อิงตามอาร์ติแฟกต์ เช่น Blaze และ Bazel