Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

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

บทความเกี่ยวกับ Programming อื่นๆ

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

Makefile คืออะไร ? (What is a Makefile ?)

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

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์
ภาพจาก : https://www.youtube.com/watch?v=_r7i5X0rXJk

พูดง่าย ๆ คือ Make คือโปรแกรมที่ทำหน้าที่เป็น “ผู้สั่งการ” ส่วน Makefile คือ “แผนที่” หรือ “สูตร” ที่บอกให้ Make รู้ว่าต้องทำอย่างไร ? เพื่อให้โปรเจกต์นั้นถูกสร้างขึ้นอย่างถูกต้อง และเป็นอัตโนมัติ

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

ประวัติความเป็นมาของ Makefile (History and Origin of Makefile)

จุดเริ่มต้นของ Makefile นั้นกำเนิดมาจากเครื่องมือ Make ที่เราได้พูดถึงไป ซึ่งถูกสร้างขึ้นโดย Stuart Feldman ที่ Bell Labs ซึ่งเป็นห้องปฏิบัติการวิจัย และพัฒนาที่ตั้งอยู่ในประเทศสหรัฐอเมริกา ในปี ค.ศ. 1976 (พ.ศ. 2519) จุดเริ่มต้นของ Make มาจากปัญหาที่นักพัฒนาซอฟต์แวร์ในยุคนั้นต้องเผชิญอยู่บ่อยครั้ง นั่นคือการคอมไพล์โปรแกรมที่มีหลายไฟล์ซอร์สโค้ด ซึ่งต้องทำทีละไฟล์ด้วยตนเอง ทำให้เกิดความล่าช้า และอาจเกิดข้อผิดพลาดได้ง่ายนั่นเอง

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์
ภาพจาก : https://spectrum.ieee.org/bell-labs-100-birthday

เรื่องราวที่เป็นแรงบันดาลใจให้ Feldman สร้าง Make เกิดขึ้นเมื่อเพื่อนร่วมงานของเขาที่ชื่อ Steve Johnson ต้องเสียเวลาทั้งเช้าไปกับการ ดีบัก (Debug) โปรแกรมแก้ไขแล้วเรียบร้อยแล้ว แต่กลับลืมคอมไพล์ไฟล์ใหม่ ทำให้โค้ดยังไม่อัปเดตตามที่ควรจะเป็น Feldman เองก็เคยประสบปัญหาคล้ายกันในโปรเจกต์ที่เขาทำอยู่ จึงได้เกิดเป็นไอเดียสร้างเครื่องมือเพื่ออำนวยความสะดวกนี้ขึ้นมา

หลังจากที่ Make ถูกพัฒนาขึ้น มันก็ได้กลายเป็นเครื่องมือมาตรฐานที่รวมอยู่ในระบบ Unix ตั้งแต่เวอร์ชัน PWB/UNIX 1.0 ซึ่งมีชุดเครื่องมือพัฒนาซอฟต์แวร์ครบครัน และถูกใช้งานอย่างแพร่หลายจนถึงทุกวันนี้

โครงสร้างพื้นฐานของไฟล์ Makefile (Basic Structure of Makefile)

โครงสร้างของ Makefile เป็นหัวใจสำคัญที่ควบคุมการทำงานของ Make ให้เป็นไปอย่างถูกต้องโดยมีส่วนประกอบพื้นฐานดังนี้

1. กฏ (Rule)

กฏเป็นส่วนหลักของ Makefile ที่บอกว่าไฟล์เป้าหมาย ขึ้นอยู่กับไฟล์ต้นทาง (Dependencies) ใด ? และต้องใช้คำสั่งอะไรในการสร้าง ? หรืออัปเดตเป้าหมายนี้ โดยกฏแต่ละกฏจะเริ่มด้วยบรรทัดที่ระบุชื่อเป้าหมาย และไฟล์ที่เป้าหมายนี้ขึ้นอยู่ ตามด้วยบรรทัดคำสั่งที่ต้องขึ้นต้นด้วยการแท็บเว้นว่างเพื่อให้ Make สามารถแยกคำสั่งออกจากส่วนอื่น ๆ ได้

เมื่อโปรแกรมทำงาน มันจะตรวจสอบว่าไฟล์ต้นทางนั้นใหม่กว่าไฟล์เป้าหมาย หรือไม่ ? หรืออาจตรวจสอบแล้วพบว่าเป้าหมายยังไม่มีอยู่ ถ้าใช่ Make จะรันคำสั่งที่อยู่ในกฏนั้น เพื่ออัปเดตไฟล์เป้าหมายนั่นเอง

ตัวอย่าง

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

ในที่นี้ “Program” คือไฟล์เป้าหมายที่ต้องการสร้าง โดยขึ้นอยู่กับไฟล์ main.o และ utils.o ถ้าไฟล์ใดไฟล์หนึ่งใหม่กว่า หรือไฟล์ Program ยังไม่มี Make ก็จะสั่งให้รันคำสั่งคอมไพล์เพื่อนำไฟล์มาต่อกันเป็นโปรแกรม

2. ตัวแปร (Variable)

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

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

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

3. คำสั่งพิเศษ และคอมเมนต์ (Directive and Comment)

นอกจากกฏ และตัวแปรแล้ว Makefile ยังรองรับคำสั่งพิเศษ เช่น รวมไฟล์ Makefile อื่น ๆ เพื่อแบ่งงานออกเป็นส่วน ๆ หรือตั้งค่าเฉพาะสิ่งที่ต้องทำก่อนเริ่มทำงาน นอกจากนี้การเขียนคอมเมนต์ด้วยเครื่องหมาย “#” ยังช่วยให้คนที่อ่าน Makefile เข้าใจว่าส่วนใดทำหน้าที่อะไร ? คล้ายกับภาษาอื่น ๆ เลย

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

4. กฏทั่วไป (Implicit Rule)

Makefile มีฟีเจอร์ที่ช่วยให้กำหนดกฏสำหรับไฟล์หลาย ๆ ไฟล์ได้ โดยไม่ต้องเขียนซ้ำกัน กฏทั่วไป (Implicit Rule) คือกฏที่ Make รู้จัก และสามารถใช้ได้เลย เช่น กฏที่บอกว่าไฟล์ .c (ภาษา C) จะถูกแปลงเป็นไฟล์ .o  (ภาษาเครื่อง) โดยอัตโนมัติ ทำให้ไม่ต้องเขียนคำสั่งซ้ำ ๆ สำหรับแต่ละไฟล์

ตัวอย่างเช่น กฏนี้

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

หมายความว่า ไฟล์ .o ใด ๆ จะถูกสร้างจากไฟล์ .c ที่ชื่อเดียวกัน โดยที่ $

คำสั่งที่สำคัญใน Makefile (Key Commands of Makefile)

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

1. Make

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

2. Make [Target]

ใช้ระบุเป้าหมาย (Target) ที่ต้องการรันโดยเฉพาะ เช่น ถ้าใน Makefile ของเรามีเป้าหมายชื่อ Clean, Install, หรือ Test ก็สามารถเรียกใช้แยกได้

คำสั่งนี้มักถูกใช้เพื่อ “ล้าง” ไฟล์ที่ถูกคอมไพล์ออกไปก่อน เช่น .o หรือ ไฟล์ประเภท .exe เพื่อเตรียมคอมไพล์ใหม่ หรือป้องกันปัญหาโปรแกรมค้าง ส่วน Install ใช้หลังจาก Build เสร็จ เพื่อคัดลอกไฟล์ไปไว้ในที่ที่เราจะใช้งาน เช่น /usr/local/bin เป็นต้น

3. Make -n

ใช้เพื่อจำลองการทำงาน โดย ยังไม่รันจริง เหมาะกับการตรวจสอบว่า Make จะรันคำสั่งอะไรบ้างก่อนที่เราจะลงมือจริง ช่วยป้องกันข้อผิดพลาดที่อาจเกิดขึ้น เช่น ลบไฟล์ผิด หรือลิงก์ผิดที่ โดยเฉพาะในโปรเจกต์ขนาดใหญ่ที่มีคำสั่งหลายขั้นตอน

4. Make -f

ในกรณีที่เราไม่ได้ตั้งชื่อไฟล์ว่า Makefile หรือมีหลายไฟล์ Makefile เช่น แยกตามโมดูล ก็ใช้คำสั่งนี้

โปรแกรมก็จะเลือกไฟล์ที่จะรันได้ ซึ่งอันนี้จะใช้ Makefile ที่ชื่อ DebugMakefile แทนค่าเริ่มต้น

5. Make -B หรือ Make –always-make

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

6. Make -j 

สั่งให้ Make ทำหลายอย่างพร้อมกัน (Parallel Build) โดยใช้ หน่วยประมวลผลกลาง (CPU) หลาย คอร์ (Core) เพื่อเร่งความเร็ว เช่น 

จะสั่งให้ทำพร้อมกันสูงสุด 4 งาน เหมาะกับโปรเจกต์ใหญ่ ๆ ที่คอมไพล์หลายไฟล์ แยกกันได้

ตัวอย่างการใช้งาน Makefile เบื้องต้น (Basic Examples of using Makefile)

การใช้งาน Makefile จริง ๆ แล้วไม่ซับซ้อน ขอแค่เครื่อง คอมพิวเตอร์ (PC) นั้นมีเครื่องมือ Make ติดตั้งอยู่ ก็สามารถรัน Makefile ได้ทันที  สิ่งที่สำคัญคือ ไม่ว่าเราจะอยู่ในระบบปฏิบัติการใด ลีนุกซ์ (Linux), macOS, หรือแม้แต่ วินโดวส์ (Windows) ที่ติดตั้งเครื่องมือ Build เสริมอย่าง Git Bash หรือ Windows Subsystem for Linux (WSL) ถ้าหากว่าเราเรียก Make ในไดเรกทอรีที่มี Makefile อยู่ คำสั่งในนั้นก็จะทำงานโดยอัตโนมัติตามที่เรากำหนดไว้

ตัวอย่างที่ 1 คำสั่งง่ายที่สุดใน Makefile

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

  • Say_hello  คือชื่อของ เป้าหมาย (Target) ในที่นี้ เปรียบได้เหมือนฟังก์ชันที่เราจะเรียกใช้งานในภายหลัง
  • บรรทัดถัดมา @echo “Hello World” คือ คำสั่ง (Recipe) ที่ Make จะรันเมื่อเราเรียก Target นี้
  • เครื่องหมาย @ หน้าคำสั่ง ทำให้ไม่แสดงคำสั่งก่อนรัน แสดงเฉพาะผลลัพธ์อย่างเดียว
  • หากไม่มี @ เวลา Make ทำงานจะเห็นทั้งคำสั่ง และผลลัพธ์

เมื่อมีคนสั่งให้รัน say_hello Make จะพิมพ์คำว่า Hello World ออกมาโดยไม่แสดงคำสั่ง Echo เอง

ตัวอย่างที่ 2 สร้างไฟล์อัตโนมัติ + ล้างไฟล์

Makefile คืออะไร ? รู้จักไฟล์เล็ก ๆ ที่ควบคุมการคอมไพล์ได้ทั้งโปรเจกต์

  • .PHONY เป็นการบอก Make ว่า target ที่ระบุไว้ไม่ใช่ไฟล์จริง ๆ เป็นแค่ชื่อกลุ่มคำสั่งเฉย ๆ เช่น Clean, Generate ถ้าเราไม่ใส่ .PHONY แล้วบังเอิญมีไฟล์ชื่อ Clean อยู่ Make จะไม่รันคำสั่ง Clean ให้
  • All คือ Target หลักของ Makefile (มักเป็น default) โดยจะไปรัน greet และ generate ตามลำดับ หรือก็คือ เมื่อเราสั่ง make เฉย ๆ มันจะทำ all และ all จะไปเรียก target อื่น
  • Greet คือ Target ที่พิมพ์ข้อความเริ่มต้น
  • @echo “เริ่มต้นการทำงาน” คือคำสั่งแสดงข้อความธรรมดา
  • Generate  ใช้สำหรับสร้างไฟล์ 5 ไฟล์ โดยใช้คำสั่ง touch file-{1..5}.txt ซึ่งก็คือสร้าง file-1.txt, file-2.txt, …, file-5.txt
  • Clean: ใช้สำหรับลบไฟล์ที่สร้างขึ้น โดยใช้คำสั่ง rm -f file-*.txt
  • Rm -f เป็นคำสั่งลบแบบไม่ถาม และไม่แจ้ง Error ถ้าหาไฟล์ไม่เจอ

Makefile นี้ประกอบด้วย Target ที่แยกกันชัดเจน Greet, Generate และ Clean ซึ่งสามารถเลือกเรียกใช้ได้แบบเฉพาะเจาะจง หรือเรียกรวมกันผ่าน Target all ได้ โดยไม่ต้องเขียนคำสั่งซ้ำ ๆ ทีละคำสั่งด้วยมือ

ประโยชน์ และ ข้อควรระวังของการใช้ Makefile (Benefits and Caveats of using Makefile)

ประโยชน์ของ Makefile

1. ลดงานซ้ำซ้อน

เขียนคำสั่งไว้เพียงครั้งเดียว แล้วเรียกใช้งานได้ทุกครั้ง ไม่ต้องมานั่งพิมพ์คำสั่งยาว ๆ ซ้ำ ๆ เอง

2. ควบคุมการ Build ได้เป็นระบบ

จัดลำดับขั้นตอน เช่น คอมไพล์ – ลิงก์ไฟล์ – ทำความสะอาดไฟล์เก่า ได้แบบอัตโนมัติ

3. เพิ่มความเร็วในการทำงาน

บางการใช้งานจะให้ Make คอมไพล์เฉพาะไฟล์ที่มีการเปลี่ยนแปลงเท่านั้น ไม่ทำซ้ำทั้งหมดทุกครั้งซึ่ง ช่วยประหยัดเวลาได้มาก

4. เหมาะกับโปรเจกต์ร่วมกันหลายคน

ทุกคนสามารถใช้ Makefile เดียวกันได้ ทำให้การทำงานทีมมีมาตรฐานเดียวกัน

5. ขยายง่าย ปรับปรุงง่าย

ใช้ตัวแปรแทนค่าต่าง ๆ ทำให้เปลี่ยนค่าครั้งเดียวมีผลทั่วทั้งไฟล์

6. ใช้งานได้หลากหลาย

นอกจากคอมไพล์โค้ด ยังสามารถใช้ Makefile สำหรับสร้างไฟล์, รันเทส, ย้ายไฟล์, ทำความสะอาดไดเรกทอรีเป็นต้น

ข้อควรระวังในการใช้ Makefile

1. Syntax ต้องเป๊ะ

บรรทัดคำสั่งในแต่ละ target ต้องขึ้นต้นด้วย Tab (เว้นว่าง) เท่านั้น ไม่ใช่กด Space Bar ธรรมดา ผิดนิดเดียวก็ไม่ทำงาน

2. ไม่ใช่เครื่องมือที่เข้าใจง่ายในครั้งแรก

สำหรับมือใหม่ อาจต้องใช้เวลาเรียนรู้คำสั่ง, รูปแบบ และแนวคิดของ Target, Dependency และ Rule

3. ทำงานแบบ Text-Based ล้วน ๆ

ถ้าเขียนผิด ไม่มีตัวช่วยตรวจ หรือไกด์ ต้องไล่ดูด้วยสายตาเอง ซึ่งอาจเกิดความผิดพลาดได้ง่าย

4. ไม่เหมาะกับโครงการที่ซับซ้อนมาก

ถ้าโครงสร้างโปรเจกต์มีหลาย Platform หรือ Dependency ซับซ้อน อาจต้องใช้เครื่องมือ Build System ขั้นสูง เช่น CMake หรือ Meson ร่วมด้วย

5. ใช้ไฟล์เดียวควบคุมทั้งระบบ อาจทำให้รก ถ้าไม่จัดหมวดหมู่ให้ดี

ควรแยก Makefile ออกเป็นหลายไฟล์ แล้วเรียกใช้ผ่าน Include เพื่อให้ดูแลง่ายขึ้นเมื่อโปรเจกต์ขยายตัว

บทสรุปของ Makefile (Makefile Summary)

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

แม้ว่าการเริ่มต้นอาจจะดูยุ่งยากไปบ้าง เพราะต้องรู้จักโครงสร้างที่ดูซับซ้อนของมัน เช่น Target, Dependency และคำสั่งต่าง ๆ แต่ถ้าเข้าใจพื้นฐานแล้ว มันจะกลายเป็นผู้ช่วยที่ทำให้งานเขียนโค้ดลื่นไหล และเป็นระเบียบขึ้นอย่างเห็นได้ชัดเลย

ที่มา : www.linkedin.com , opensource.com , en.wikipedia.org

Leave a comment