1 คะแนน โดย GN⁺ 2024-09-22 | 2 ความคิดเห็น | แชร์ทาง WhatsApp
  • ถึงแม้จะมีเครื่องมือ build ใหม่ ๆ ออกมาอย่างต่อเนื่อง Makefile ก็ยังคงมีประโยชน์ในฐานะ เครื่องมือพื้นฐานแบบเบา ๆ สำหรับรวมงานที่ต้องทำซ้ำในโปรเจ็กต์
  • ด้วย คำสั่งตามธรรมเนียม อย่าง make, make build, make install, make dev จึงสามารถลอง build, ติดตั้ง และรันเซิร์ฟเวอร์พัฒนาของโปรเจ็กต์ที่ไม่คุ้นเคยได้อย่างรวดเร็ว
  • ไม่ว่าสแตกจะเป็น Jekyll, Hugo, 11ty, Python web framework, Docker หรือ gulp, Makefile ก็ทำหน้าที่เป็น อินเทอร์เฟซการรันร่วมกัน ได้
  • ในโปรเจ็กต์ส่วนตัว หลายครั้งก็เพียงพอแล้วที่จะมี target ที่เรียบง่าย ไม่กี่ตัว เช่น dev, build, deploy แทนการใช้เงื่อนไขหรือแฟลก
  • GNU Make แพร่หลายอยู่แล้ว และถึงไม่มี ก็ยังสามารถรันคำสั่งเชลล์ใน Makefile ได้โดยตรง จึงมีภาระน้อยแม้ใน สภาพแวดล้อมที่มีข้อจำกัด

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

  • Makefile ถูกใช้เพื่อห่อโปรเจ็กต์ที่มีวิธี build, รัน, และ deploy ต่างกัน ให้ใช้งานผ่าน ชุดคำสั่งที่คุ้นเคย
  • ถ้าโปรเจ็กต์ที่ไม่เคยเห็นมาก่อนมี Makefile ก็มักจะเริ่มลองจากคำสั่งต่อไปนี้ได้
    • make
    • make build
    • make install
  • แม้จะกลับมาเปิดโปรเจ็กต์ส่วนตัวเก่าอีกครั้ง ก็สามารถตั้งค่าให้แค่รัน make dev แล้วจัดการทั้งขั้นตอน build ที่จำเป็นและการเปิด development server ได้ในครั้งเดียว
  • แม้เทคโนโลยีสแตกจะต่างกัน เช่น Jekyll, Hugo, 11ty หรือ Python web framework หลายแบบ ก็ยังคงใช้จุดเข้าใช้งานเดียวกันได้
  • แม้ในกรณีที่ใช้ Docker หรือ gulp, Makefile ก็ไม่ได้มาแทนเครื่องมือเหล่านั้น แต่ทำงานเป็น ชั้น orchestration
    • ตัวอย่าง: make build สามารถ build Docker image ที่จำเป็นพร้อมส่งอาร์กิวเมนต์เพิ่มเติมเฉพาะโปรเจ็กต์ได้

วิธีใช้งานแบบเรียบง่ายในโปรเจ็กต์ส่วนตัว

  • การใช้ Makefile จะเน้นที่ การรวมคำสั่งเชลล์ มากกว่าฟีเจอร์ที่ซับซ้อน
    • ไม่ใช้ฟีเจอร์อย่างเงื่อนไขหรือแฟลก
    • หน่วยงานของ make ในทางเทคนิคคือ target แต่ในการใช้งานจริงจะมองเหมือน task
    • แม้จะทำเป็นฟังก์ชันใน bash script ได้ แต่ Makefile ถูกมองว่าง่ายและเร็วกว่า
  • งานที่ใช้บ่อยในโปรเจ็กต์ส่วนตัวโดยทั่วไปมีเพียง 2–3 อย่าง
    • dev: เริ่ม development server
    • build: build โปรเจ็กต์เมื่อจำเป็น
    • deploy: deploy หรือเผยแพร่โปรเจ็กต์
    • watch: งานเสริมสำหรับรันงาน build ซ้ำอัตโนมัติเมื่อไฟล์ซอร์สมีการเปลี่ยนแปลง
  • Makefile ของบล็อกปัจจุบันมีเพียง target dev ตัวเดียว
    • ใต้ dev: จะรัน npm run dev
  • ในโปรเจ็กต์ส่วนตัวที่ซับซ้อนกว่า Makefile จะรวมงานต่อไปนี้ไว้
    • dev: รัน development server ด้วย bundle exec jekyll serve
    • build: build asset ด้วย npm run gulp build
    • watch: เฝ้าดูโฟลเดอร์ที่กำหนดด้วย npm run gulp watch
    • deploy: หลังจาก build Jekyll ในสภาพแวดล้อม production แล้ว รัน make encrypt, netlify deploy --prod
    • encrypt: เข้ารหัสไฟล์ HTML ในโฟลเดอร์ _site ด้วย npx staticrypt

ข้อจำกัดและข้อดีที่ควรรู้

  • ในตัวอย่างนี้ละ phony targets ไว้ แต่ถ้ามีไฟล์ชื่อ dev, build, watch, deploy, encrypt อยู่ Makefile อาจไม่ทำงานตามที่คาดไว้
  • GNU Make คือซอฟต์แวร์ที่ใช้รัน makefile ซึ่งมีโอกาสติดตั้งอยู่แล้วบน Linux และก็แพร่หลายบน MacBook มากจนแทบจำไม่ได้ว่าต้องติดตั้งเพิ่มต่างหาก
  • Make ไม่ต้องพึ่งพา dependency เพิ่มเติมมากนัก จึงอาจมีประโยชน์ใน สภาพแวดล้อมที่มีข้อจำกัด ซึ่งการติดตั้งแพ็กเกจอาจทำได้ยากหรือทำไม่ได้เลยด้วยเหตุผลด้านความปลอดภัย
  • ถึงไม่มี Make ก็ยังสามารถคัดลอกคำสั่งใน Makefile ไปวางรันในเชลล์ได้โดยตรง
    • ในทางกลับกัน ถ้า gulp ไม่มีอยู่บนเซิร์ฟเวอร์ ก็ยากที่จะคัดลอกโค้ด JavaScript ไปวางรันในเทอร์มินัลได้ตรง ๆ
  • ต่อให้พบเครื่องมือ build รุ่นใหม่ที่ดีกว่าและเร็วกว่า Make ก็ยังสามารถถูกใช้ต่อไปในฐานะ ชั้นคำสั่งร่วมกัน สำหรับจัดการเครื่องมือและการตั้งค่าที่แตกต่างกัน

2 ความคิดเห็น

 
kayws426 2024-09-22

หากเป็น makefile ที่ไม่ได้กำหนดการพึ่งพาไว้ การแทนที่ด้วย justfile จะให้ประสบการณ์การใช้งานที่ดีกว่า

 
GN⁺ 2024-09-22
ความคิดเห็นจาก Hacker News
  • ไม่จำเป็นต้องท้อใจกับคำพูดในเธรดนี้ที่ว่า “คุณกำลังใช้ make ผิดวิธี” หนึ่งในเหตุผลที่ Make เป็นเครื่องมือที่ดีก็คือ มันเริ่มต้นได้ง่ายกว่าที่เห็น
    ถ้าไม่ใช้ .PHONY ก็อาจเกิดปัญหาได้ แต่สำหรับโปรเจกต์เล็ก ๆ มันอาจเป็นกับดักที่คุณจะเหยียบในอีก 1 ปีให้หลัง และถึงตอนนั้นก็น่าจะจบได้ด้วยการเกาหัวคิดสักชั่วโมง ใน 99% ของกรณี คุณเริ่มได้ทันทีโดยไม่ต้องสนใจ “วิธีที่ถูกต้องตามตำรา” และ Make ต้องการความซับซ้อนแค่พอไม่ให้พังเท่านั้น

    • หนึ่งในจุดแย่ที่สุดของ Make คือ มันดูเหมือนเรียบง่ายจากภายนอก
      Make ทำหน้าที่รับไฟล์อินพุตและ dependency แล้วสร้าง ไฟล์เอาต์พุตเพียงไฟล์เดียวเท่านั้น กฎที่ไม่สร้างเอาต์พุตอย่าง install, all, clean หรือ target ทั้งหมดในบทความ ต้องพึ่ง target เวทมนตร์แบบพิเศษอย่าง .PHONY แต่ .PHONY ไม่มีอยู่ใน POSIX ปี 2017 (IEEE Std 1003.1-2017, https://pubs.opengroup.org/onlinepubs/9699919799/utilities/m...) และเพิ่งถูกใส่เข้ามาในมาตรฐานปัจจุบัน (IEEE Std 1003.1-2024, https://pubs.opengroup.org/onlinepubs/9799919799/utilities/m...) ถ้าต้องการสร้างหลายไฟล์ เช่น object file, module หรือ precompiled header ก็ต้องเขียนกลเม็ดที่เปราะบางเอง ไม่ใช่ Make ทุกตัวจะเป็น GNU Make และสาย nix อย่าง BSD, Solaris/Illumos ก็ยังมีอยู่
      ไม่ได้หมายความว่า Make ไร้ประโยชน์ มันมีประโยชน์กับโปรเจกต์ที่ซับซ้อนพอสมควร แต่ยังไม่ถึงขั้นต้องใช้ระบบ build ที่ “ดีกว่า” ปัญหาคือโปรเจกต์แบบนั้นจะซับซ้อนขึ้นเมื่อโค้ดเพิ่มขึ้น และสุดท้ายก็จะมีสคริปต์/โปรแกรมที่สร้าง Makefile หรือบางส่วนของมัน จนกลายเป็นระบบ meta build แบบเฉพาะกิจ
      ปัญหาที่ใหญ่กว่านั้นไม่ใช่การใช้มัน แต่เป็นการเสนอให้เป็นคำตอบสำหรับ “ทุกคน” และทันทีที่มีไดเรกทอรีหรือไฟล์ชื่อ build หรือ dev เกิดขึ้นที่ root ของโปรเจกต์ Makefile นั้นก็จะหยุดทำงาน
    • ตัวอย่าง Makefile ที่ซับซ้อนขึ้นเล็กน้อยซึ่งใช้ตอนเริ่มโปรเจกต์ TypeScript ใหม่อยู่ที่นี่: https://github.com/borisovg/node-ts-template/blob/main/Makef...
      ช่วงนี้ถึงจะเปลี่ยนไปใช้ pnpm แล้ว แต่ก็ยังไม่มองว่ามันซับซ้อนมากนัก แม้ต้องรู้ไวยากรณ์อยู่บ้าง แต่ก็ยังยากน้อยกว่าการทำให้เครื่องมืออื่น ๆ ทำงานได้ถูกต้องตั้งแต่แรก
    • ใช้ make ได้ดีมาตลอดกว่า 20 ปีในหลายโปรเจกต์และหลายภาษา และไม่เคยเจอปัญหาจากงาน .PHONY ที่ผู้คนกังวลกันนักหนาเลย
      มันเรียบง่าย อ่านง่าย แก้ไขได้ ประกอบเข้ากับอย่างอื่นได้ และติดตั้งอยู่แล้วแทบทุกที่ มันทำสิ่งที่เขียนไว้บนผิวหน้า และแทบไม่ทำอะไรเกินกว่านั้น
      อนึ่ง เวลาต้องรับมือกับระบบ build ตามกระแสหรือระบบ build ชวนฝันร้ายในโปรเจกต์อื่น ๆ ก็ใช้ make ครอบมันอีกที
    • เห็นด้วยเต็มที่ ควรใช้เครื่องมือที่เหมาะกับงานจะดีกว่า และถึงจะไม่ใช่ผู้เชี่ยวชาญ ก็ยังดีกว่ามากเมื่อเทียบกับการเลือกเครื่องมือผิดเพียงเพื่อหลีกเลี่ยง คำวิจารณ์แบบสมบูรณ์แบบนิยม
      ทุกคนต้องเริ่มจากที่ไหนสักแห่ง และเมื่อมันเริ่มทำงานได้แล้ว การปรับปรุงทีละขั้นก็เพิ่มเข้าไปได้ง่าย
    • recipe ทุกตัวใน makefile ควรสร้างเอาต์พุตเพียงหนึ่งเดียวพอดีคือ $@ ส่วน Makefile ทั้งไฟล์สามารถสร้างเอาต์พุตจำนวนเท่าใดก็ได้ เพราะกฎหนึ่งสามารถพึ่งพากฎอื่นได้
      จากตรงนี้จะได้หลักจำง่าย ๆ สำหรับ phony target ถ้า recipe ไม่แตะต้องแค่ $@ ก็ควรทำเครื่องหมาย $@ เป็น phony การจัดการ phony target เป็นรายการจะง่ายกว่ามาก
      phonies :=
      phonies += something
      something:
      ./do-something
      phonies += something-else
      something-else: something
      ./do-something-else
      # touches $@ and thus does not need to be phony
      create-file:
      ./generate-some-output > $@
      .PHONY: $(phonies)
  • Makefile เป็นเทคโนโลยีที่แย่มาก เพียงแต่มันแย่น้อยกว่าระบบ build อื่น ๆ ส่วนใหญ่ที่เราสร้างขึ้นมานิดหน่อย จึงกลายเป็นว่า มีประโยชน์ในเชิงทรมานตัวเอง
    ระบบ build มักจะพื้นฐานเกินไปจนพอ build อะไรที่มากกว่าของเล่นก็กลายเป็นความวุ่นวาย หรือซับซ้อนเกินไปจนต้องมีความรู้ล่วงหน้า ขั้นตอนราชการ การซิงก์ และ boilerplate มากเกินไป หรือไม่มี standard library ทำให้ build pattern เดียวกันกระจัดกระจายเป็น implementation ที่เข้ากันไม่ได้เป็นหมื่นแบบ หรือมีข้อจำกัดมากเกินไปจนพอความต้องการเพิ่มขึ้นก็ต้องย้ายระบบ หรือมีเวทมนตร์มากเกินไป หรือ syntax เข้าใจยากและไม่สม่ำเสมอ

    • ผมคิดว่า Makefile เป็นเทคโนโลยีที่ยอดเยี่ยม เพียงแต่หลายคนไม่ได้เรียนรู้มันอย่างถูกต้อง หรือไม่ได้ใช้ตามวิธีที่ตั้งใจไว้เท่านั้น
      ซอฟต์แวร์มากกว่าครึ่งที่ผมใช้ใช้แค่ Makefile ธรรมดา ๆ และบางครั้งก็มีสคริปต์ configure เท่านั้น ไม่ใช้ autotools แค่รัน ./configure แบบเลือกได้ แล้ว make, make install ก็ทำงานได้เลย นี่ไม่ใช่การตั้งค่าแบบของเล่นเลย และประกอบขึ้นจากโปรแกรมเล็ก ๆ ที่ทำงานเดียวได้ดี
      Make และวิธีที่มันทำงานนั้น อย่างน้อยสำหรับผม เข้าใจง่าย ผมมองว่าประสบการณ์แย่ ๆ ที่ผู้คนเจอมักเกิดจากการตั้งค่า build ที่แย่ของโปรเจกต์ซับซ้อน หรือการขาดความรู้ที่จำเป็น มากกว่าจะเป็นตัว Make เอง
      การไม่มี standard library เป็นการตั้งใจให้ ไม่ขึ้นกับเครื่องมือ และเพราะอย่างนั้นมันจึงใช้ได้ทั่วไปกับเครื่องมือ ภาษา และวัตถุประสงค์หลากหลาย ผมมองว่าไม่ใช่ข้อบกพร่อง แต่เป็นฟีเจอร์ ชั้นที่เรียบง่ายคือ dependency tracking และ target กับ dependency ก็ชัดเจน จึงยากจะมองว่าเป็นเวทมนตร์ ส่วน syntax นั้นแน่นอนว่ายังปรับปรุงให้ดีขึ้นได้
    • ระบบ build ที่แย่ที่สุดคือระบบที่สร้างขึ้นโดยมีศูนย์กลางอยู่ที่ภาษาโปรแกรมมิงเฉพาะภาษา ถ้าจำนวนภาษาคือ N>>1 ระบบ build ก็จะเป็น N>>1 เช่นกัน และ cognitive load จะสูงเกินไปจนขยายต่อไม่ได้
      ระบบ build อเนกประสงค์ที่ครอบคลุมหลายภาษาได้มีแค่ระบบที่ target ไปที่ make อย่าง make หรือ CMake เท่านั้น เหตุผลที่มันไม่ค่อยดีคือเพราะ make ไม่ค่อยดี make ใช้ให้ถูกจริง ๆ ยากมาก มีปัญหาอย่าง recursive make กับ non-recursive make มี variant ที่เข้ากันไม่ได้มากมาย เช่น Unix/POSIX make, BSD make, GNU make, Windows nmake และก็ค่อนข้างน่าเกลียด
      ถึงอย่างนั้น make ที่ใช้ถูกต้องก็ค่อนข้างดี การที่มี make เป็น ตัวหารร่วมต่ำสุด นับว่าโชคดีจริง ๆ
    • เห็นด้วย ตัวเลือกทดแทนจำนวนไม่น้อยก็ไม่ได้เป็นเครื่องมืออเนกประสงค์แบบ “รันอะไรสักอย่าง” เหมือน Make แต่เน้นภาษาเฉพาะภาษา
      มันค่อนข้างน่าเหลือเชื่อที่ Make จัดการแม้แต่ subdirectory ให้เรียบร้อยไม่ได้ด้วยซ้ำ สงสัยเหมือนกันว่ามีอะไรที่ดีกว่า Make ไหม Ninja ก็มีอยู่ แต่ไม่ได้ออกแบบมาให้คนเขียนใช้โดยตรง
    • จะบอกได้ไหมว่าความวุ่นวายนั้น “ในเชิงสมมติมาก ๆ” ไม่ได้เกิดจาก compiler หรือภาษาที่ไม่มี module เลยแม้แต่น้อย
      คิดว่าอยู่ระดับไหน? สัก 20%, 40%, 70% ก็ใช่ไม่ใช่หรือ
    • ถ้าพูดว่า “หนึ่งอย่างขึ้นไป” ก็มีช่องให้ตีความเยอะ ผมเดาแบบนี้: Makefile ไม่ได้พื้นฐานเกินไป อาจซับซ้อนเกินไปได้ แต่ขึ้นอยู่กับว่าจะสร้างมันอย่างไร ส่วน standard library ก็มี built-in functions อยู่ ไม่เคยรู้สึกว่ามีข้อจำกัดมากเกินไป เลยคิดว่าไม่ใช่ target กับ dependency ชัดเจน จึงไม่ได้มีเวทมนตร์มากนัก แต่ syntax นั้นแน่นอนว่ายังปรับปรุงได้ และคิดว่าแค่เป็นไฟล์ JSON ธรรมดาก็น่าจะดีกว่าแล้ว
  • ผมชอบ Make มากจริง ๆ ตลอด 20 ปีที่ผ่านมา งานหรือโปรเจกต์ทุกอย่างที่ได้แตะต้อง ผมเขียนไว้อย่างน้อยหนึ่งไฟล์เสมอ
    ไม่มีอะไรฉลาดล้ำ เป็นแค่การรวบรวมชิ้นส่วนที่มีตัวแปรไม่กี่ตัวเท่านั้น เช่น make run, make test, make lint
    make recent จะ lint แล้วรันสคริปต์ที่แก้ไขล่าสุด จะทำด้วย Bash หรือ shell อื่นก็ได้ แต่ถ้าทำแบบนั้นก็จะถูกดูดเข้าไปในโลกของนักพัฒนา เพิ่มแต่ความซับซ้อนโดยไม่มีคุณค่าเพิ่ม Make เป็นเพียง ภาษาเฉพาะโดเมน ที่บอกว่า “ไฟล์แบบนี้สร้างจากไฟล์แบบนี้ และตอนนั้นให้รันคำสั่งหนึ่งหรือสองคำสั่งนี้” เท่านั้น เพราะอย่างนั้นมันจึงทรงพลังมาก

    • ชอบคำอธิบายว่า “ไฟล์แบบนี้สร้างจากไฟล์แบบนี้ และตอนนั้นให้รันคำสั่งหนึ่งหรือสองคำสั่งนี้”
      หลายสิบปีก่อนเคยสร้าง test framework ด้วย Java โดยให้ระบุ test และ class ที่พึ่งพาได้ด้วย syntax คล้าย make มีชุดของ test class ที่นิยาม “ชุดทดสอบมาตรฐาน” แล้วชั้นของ test class อื่น ๆ ที่พึ่งพาชุดนั้นจะรันได้ก็ต่อเมื่อส่วนหน้าสำเร็จก่อน
      ทุกวันนี้ไม่เข้าใจว่าทำไมต้องทำทุกอย่างให้ซับซ้อนขนาดนั้น ผมมักแนะนำเสมอให้ยึดกับเครื่องมือ Unix มาตรฐานและวิธีของมัน นั่นคือวิธีที่ผ่านการพิสูจน์ด้วยเวลา เว้นแต่จะเป็นสถานการณ์ที่ทำแบบนั้นไม่ได้จริง ๆ เวลาเรามีจำกัด จึงอยากใช้ไปกับแก่นของการแก้ปัญหาอย่างการออกแบบระบบ/โปรแกรม การทำโมเดล โครงสร้าง pattern ฯลฯ ส่วนที่เหลือเป็นเรื่องรอง
    • Makefile ทุกไฟล์ที่เคยเจอจริง ๆ ทำอะไรมากกว่านั้นมาก จนพูดว่า “มีแค่นั้น” ไม่ได้เลย
    • ผมก็คล้ายกัน เมื่อก่อนไม่ค่อยได้ใช้ แต่ที่ทำงานปัจจุบันแทบทุก repository มี Makefile
      การรู้ว่าไม่ว่า stack, ภาษา, หรือ repository จะเป็นอะไร แค่รัน make test แล้วมันทำงานได้ เป็นเรื่องที่ช่วยชีวิตมาก
    • ปัญหาของ Make ไม่ใช่ Make เอง แต่อยู่ที่สิ่งที่ Make เรียกใช้นั้นไม่ทำงานตามโมเดลนั้นอีกต่อไปแล้ว
      Makefile ในโปรเจกต์ก่อนมีคำสั่งหลักสี่อย่างคือ build, test, frontend, deploy โดย build กับ test เรียก maven, frontend เรียก npm, deploy เรียก docker กับ aws
      เครื่องมือเหล่านี้ล้วนมีการติดตามสถานะ caching และ incremental processing ของตัวเอง แต่ไม่รายงานว่าทำอะไรไปบ้าง ดังนั้นจึงเขียนกฎระดับโมเลกุลอย่าง “deploy เฉพาะเมื่อ build ถูกอัปเดตแล้ว” ไม่ได้ เพราะ maven/cargo/dotnet/npm/go ไม่เปิดเผยข้อมูลนั้น
  • ผู้เขียนไม่ได้ใช้แม้แต่ การติดตาม dependency ตาม mtime ด้วยซ้ำ target ต่าง ๆ ก็ควรเป็น .PHONY แต่ไม่ได้ระบุไว้ สามารถแทนที่ด้วยเชลล์สคริปต์ที่อ่าน $1 แล้ว match เพื่อกำหนดว่าจะทำอะไรได้เหมือนกัน

    • หรือจะใช้ตัวรันคำสั่งแบบง่ายอย่าง just แทนก็ได้
      https://just.systems/
    • มีเรื่องสยองขวัญบรรทัดเดียวจากโปรเจกต์ที่ใช้งานจริงอยู่: .PHONY: $(MAKECMDGOALS)
      ./build.sh dev อาจชัดเจนน้อยกว่า make dev ก็จริง อีกเหตุผลหนึ่งที่ใช้ Make แม้จะไม่มีขั้นตอนที่ไม่ใช่ phony เลย คือภายหลังถ้าจำเป็นก็สามารถเพิ่มขั้นตอนแบบนั้นได้ แน่นอนว่าผมเห็นด้วยว่าผู้เขียนควรระบุ {dev,build,deploy} ให้เป็น phony
    • ในเชิงเทคนิคก็ถูก แต่แก่นที่ได้จากบทความคือการได้ ความสม่ำเสมอของวิธีใช้งาน ไม่ใช่แค่ในโปรเจกต์ของตัวเอง แต่รวมถึงโปรเจกต์ third-party ด้วย
      Makefile ของตัวเองอาจปรับปรุงในเชิงเทคนิคได้ แต่สิ่งที่สำคัญกว่าคือประสบการณ์ “เริ่มใช้งาน” เมื่อเข้าไปในโปรเจกต์นั้นมีความสม่ำเสมอ
    • เราทุกคนก็เคยเป็นมือใหม่กันทั้งนั้น การจะเรียนรู้เครื่องมือ การได้ลองใช้จริงแม้จะเป็นการใช้งานแบบมือใหม่ที่ไม่สมบูรณ์แบบก็ช่วยได้ แล้วค่อย ๆ ปรับปรุงไประหว่างเรียนรู้ก็พอ
    • ความงดงามของ make และเชลล์คือมันทำตาม หลักการ Unix ที่เรียบง่ายและทำงานเดียวให้ดี
      ผู้คนอยากให้มันทำอย่างอื่นอีกมากมาย เช่นเป็นภาษา scripting, ตัวติดตาม dependency ฯลฯ แล้วก็ดึงเครื่องมืออ้วนเทอะทะเข้ามา ของใหม่ไม่ได้แปลว่าดีกว่าเสมอไป Autoconf กับ automake ไม่ใช่ make
  • ในเชิงเทคนิค target ของ make เหล่านี้ทั้งหมดจะมองหาไฟล์ที่มีชื่อเดียวกับ target แต่ละ target จึงควรถูกนิยามเป็น .PHONY จริง ๆ
    เมื่อก่อนผมก็ใช้ Makefile แบบนี้อยู่เสมอ แต่ช่วงไม่กี่ปีหลังย้ายไปใช้ just และ justfile แล้ว พฤติกรรมแบบนี้เป็นค่าเริ่มต้น และโดยรวมใช้ง่ายกว่า สิ่งอย่างพารามิเตอร์ก็ง่ายกว่าด้วย
    https://github.com/casey/just

    • ระบบที่คล้าย make แบบนี้ค่อนข้างดี แต่มีปัญหาอยู่อย่างหนึ่ง Make มีอยู่แล้วใน Linux และ Mac ทุกเครื่อง และบน Windows ก็หาได้ค่อนข้างง่าย น่าเสียดายมากที่มันไม่ได้รวมมากับ Git Bash ผมมองว่าการใช้ ตัวหารร่วมต่ำสุด นี่แหละเป็นข้อได้เปรียบใหญ่ของ Make
    • ความผิดพลาดใหญ่ของ Make คือการเอา phony target กับ file target มาปนกันใน namespace เดียวกัน ตัวอย่างเช่น phony target ควรขึ้นต้นด้วย : อะไรทำนองนี้ เพื่อให้แยกได้จากชื่อเลย
      แน่นอนว่าตอนนี้สายไปแล้ว
    • just ดีจริง แต่ค่อนข้างน่ารำคาญตรงที่มักไม่ได้ติดตั้งไว้
      ผมคิดว่าถึงเวลาแล้วที่จะมีดิสโทร coreutils แบบใหม่ที่รวมเครื่องมือใหม่ ๆ ที่มีประโยชน์อย่าง just, ripgrep ไว้ด้วยกัน แล้วติดตั้งได้ทุกที่ที่อยากใช้
  • น่าสนใจที่ make ก่อให้เกิดการถกเถียงรุนแรงได้ขนาดนี้ คล้ายสงครามกึ่งศาสนาในอดีตอย่าง vi ปะทะ emacs
    ผมเห็นด้วยกับต้นฉบับเต็มที่ และโดยเฉพาะมองว่าวิธีห่อ build tool ที่ซับซ้อนกว่าด้วย Makefile ระดับบนสุดเป็นแนวทางที่ฉลาด ข้อดีคือ การทำให้เป็นมาตรฐาน ไม่ต้องจำอะไรเลย ถ้าเป็นโปรเจกต์ที่ผมเขียนเอง ก็รู้ได้ว่าแค่พิมพ์ make แล้วมันจะทำงาน
    เช่น คนที่ใช้ C เมื่อจะคอมไพล์โปรเจกต์ Rust ก็ไม่ต้องไปค้นวิธีใช้ cargo แค่พิมพ์ make หรือ gmake ก็พอ ผมพยายามไม่ใช้ฟีเจอร์เฉพาะ GNU และให้สอดคล้องกับ POSIX แต่ก็จริงเหมือนกันว่า make ที่ใช้กันจริงแทบ 100% คือ gmake
    ข้อเสนอให้ใช้ make อันเหนือกาลเวลาเป็นตัวขับ build system ระดับบนสุดแบบหนึ่ง น่าจะยังใช้ได้แม้อีก 250 ปีข้างหน้า

    • น่าสนใจที่หัวข้อเรียบง่ายแบบนี้จุดชนวนดราม่าได้ ตัวบทความเองเป็น use case ของ make ที่เรียบง่ายสุด ๆ และเป็นเรื่องการใช้ gulp ในปี 2024 แต่เห็นได้ชัดว่าไม่มีใครอ่านกันเลย
    • cargo เป็นตัวอย่างที่ไม่ดี เพราะโดยทั่วไปก็ใช้ cargo build กันอยู่แล้ว
      ตัว Make เองยอดเยี่ยม แต่โปรเจกต์ C ส่วนใหญ่ที่ผมเคยทำงานด้วยคือการผสม cmake/autotools กับการติดตั้งแพ็กเกจแบบ global และเรื่องพวกนั้นต้องค้นดูบ่อย ๆ
    • ก่อนถึงตอนนั้น ถ้า bazel/blaze ครองตลาดได้ก็อาจไม่เป็นแบบนั้น ถ้าสงสัยให้ดู Chrome ปะทะ Firefox หรือ Kubernetes ปะทะ docker-compose
  • ผมเห็นด้วยกับความรู้สึกในนี้ แต่กำลังเขียนหลาย ๆ อย่างใหม่เป็น Justfile
    https://github.com/casey/just
    ช่วยหลีกเลี่ยงความแปลกเฉพาะตัวของ Makefile ได้เยอะ

    • GPT ช่วยเขียน Makefile ให้ได้ จึงมอบ ชีวิตที่สองให้ Makefile และเป็นความจริงที่มันทำให้การตายของ Makefile ช้าลง
    • ผมก็เหมือนกัน โบนัสคือไฟล์เดียวกันยังทำงานบน Windows ได้ด้วย
  • ใช่ Make ยอดเยี่ยมมาก ผมใช้กับงานสารพัดจริง ๆ เป็นแนวทางที่ดีสำหรับการทำงานอัตโนมัติ
    ตัวอย่างเช่นเว็บไซต์ส่วนตัวของผมใช้ Makefile เรียก bash script เพื่อสร้างหน้าเว็บที่อัปเดตใหม่ จากนั้น git push ไปยังเซิร์ฟเวอร์ แล้ว git hook บนเซิร์ฟเวอร์ก็เรียก Make เพื่อ deploy อย่างไรก็ตาม ผมไม่อยากเอาก้อน binary ที่เปลี่ยนบ่อยอย่าง PDF เอกสารประกอบการสอนไปใส่ใน Git repository ไม่เป็นไร ใน Makefile มี target uploads ที่อัปโหลดเฉพาะ PDF ที่แก้ไขแล้วขึ้นเซิร์ฟเวอร์ และ target นี้เป็น dependency ของ target deploy ที่ทำ git push ดังนั้นไม่ต้องคอยสนใจ
    อีกอย่าง PDF ที่อัปเดตสำหรับเอกสารประกอบการสอนก็ถูก Makefile อีกตัวใส่เข้าไปใน source tree ของเว็บไซต์ให้อัตโนมัติ ด้วย Makefile ตัวนั้น ผมสามารถสร้าง PDF ที่ใช้จากซอร์ส LaTeX หรือสร้างเวอร์ชันทางเลือกสำหรับนักศึกษาจากซอร์สเดียวกัน เช่น เวอร์ชันที่ไม่มีเฉลยแบบฝึกหัด แล้วเผยแพร่ลงเว็บไซต์เวอร์ชัน local อัตโนมัติ จากนั้นเมื่ออยากก็อัปโหลดไปยังเว็บไซต์ที่อัปเดตแล้วได้
    เป็นโครงสร้างแบบ Makefile ต่อกันไม่รู้จบ นั่นแหละ ผมชอบ Makefile

  • ในกรณีแบบนี้ สิ่งที่ผู้เขียนต้องการจริง ๆ ไม่ใช่ make แต่เป็น just
    https://just.systems/man/en/

    • just ก็เป็นแค่ dependency อีกตัวหนึ่งเท่านั้น make มีอยู่ทุกที่
  • ในฐานะ ตัวรันงาน make ก็ไม่ได้แย่ แต่สมัยนี้ก็มีทางเลือกที่ดีกว่าอย่าง just ตามที่คอมเมนต์อื่น ๆ พูดกัน
    ในฐานะระบบบิลด์ make ก็ใช้ได้อยู่จนกว่าจะไปเจอข้อเสียของมัน make/Makefile ไม่ได้ถูกทำให้เป็นมาตรฐาน จึงมี automake ขึ้นมา ดังนั้นจึงไม่ได้เขียน Makefile โดยตรง แต่เขียนเทมเพลตแล้วให้สร้าง makefile จริงออกมา ถ้าคุณเป็นเจ้าของ toolchain ทั้งหมดเองก็ไม่เป็นไร แต่ส่วนใหญ่ไม่ใช่แบบนั้น จึงทำแบบนี้เพื่อรับประกัน Makefile ที่พกพาได้
    make ไม่สามารถ resolve dependency ในรูปแบบใด ๆ ได้ และสมมติว่าสิ่งที่ต้องใช้มีอยู่ตรงนั้นเลย ดังนั้นจึงเกิดสคริปต์ configure ขึ้นมา และเพราะสคริปต์ configure ก็ไม่ใช่มาตรฐาน จึงต้องสร้างสคริปต์ configure ด้วย autoconf/autoreconf ก่อนรัน target ของ make
    make และเครื่องมือข้างเคียงอย่าง automake/autoconf/autoreconf ใช้ mtime ในการตัดสินว่าอินพุตล้าสมัยหรือไม่ ต่อให้รัน autoconf/autoreconf/automake/configure แล้ว ก็อาจตกอยู่ในสภาพที่อินพุตยังคงล้าสมัยอย่างถาวรจนบิลด์อะไรไม่ได้เลย หมายเหตุว่า build system จำนวนมากใช้ mtime ได้ไม่มีปัญหา หากมีการติดตาม dependency ที่เหมาะสม
    โดยรวมแล้ว ข้อบกพร่องเชิงการออกแบบพื้นฐานของ make คือมันยึดตามปรัชญา Unix คือ “ทำสิ่งเดียวให้ดี” สิ่งเดียวนั้นคือ “ถ้าอินพุตล้าสมัย ก็ rebuild target” แต่นี่เป็นเครื่องมือที่จำกัดมาก จน build system สมัยใหม่ต้องซ้อนงานจำนวนมากไว้บนมัน เพื่อให้มีประโยชน์มากกว่าตัวรันงานพื้นฐาน

    • make resolve dependency ไม่ได้งั้นหรือ?
      dependency:
      ...
      target: dependency
      ...
    • อาจเป็นคำถามที่ไม่รู้เรื่องก็ได้ แต่ การ resolve dependency ไม่ใช่หัวใจของ make หรอกหรือ? ตรงนี้หมายถึงอะไรกันแน่?
    • แนวปฏิบัติเก่า ๆ ทางฝั่งนั้นคือ make configure คำสั่งนี้จะตั้งค่าสิ่งที่จำเป็นสำหรับ make [build]
    • สิ่งที่น่าเสียดายจริง ๆ ใน make คือความสามารถในการตีความ mtime ด้วย เกณฑ์อื่นนอกจาก mtime เท่านั้น
      เลยต้องใช้ touchfile ซึ่งถึงจะรก แต่ก็ยังทำงานได้ดีกว่าอีกหลายอย่าง ผมพูดโดยเฉพาะเมื่อมองไปที่แคชของ docker build