ทำความรู้จักกับ Makefile เครื่องมือในการรัน command

Snoy My
4 min readApr 30, 2022

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

ในบทความนี้ผมก็จะมาแนะนำและสอนการใช้งานเบื่องต้นของ Makefile กันครับ

Makefile คืออะไร?

Makefile เป็นโปรแกรมที่บนใน OS ตระกูล Unix (Linux, MacOS เป็นต้น) แต่บน Windows ก็สามารถติดตั้งเพิ่มได้เช่นกัน

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

ก่อนจะเริ่มขอแจ้งว่าตัวอย่างทั้งหมดนี้ใช้ command ของ linux ทั้งหมดหากใครใช้บน windows อาจต้องมีการดัดแปลงบางคำสั่งบ้าง

Syntax ของ Makefile

ก่อนอื่นเรามาทำความเข้าใจ syntax ของมันก่อน โดยหน้าตาของ Makefile จะอยู่ในลักษณะนี้

target: prerequisites
command
command
...
  • target — ตรงนี้จะเป็นชื่อไฟล์โดย Makefile จะทำการตรวจสอบไฟล์ในโฟลเดอร์ว่ามีไฟล์ตามชื่อที่เรากำหนดใน target หรือยัง หากไม่มีก็จะรัน command ที่อยู่ใน target หรือถ้ามีไฟล์อยู่แล้วก็จะข้าม target นี้ไป
  • prerequisites — หรือจะเรียกว่า dependencies ก็ได้ เป็นชื่อ target อื่นๆที่จะให้ทำก่อน target หลัก ในส่วนของ prerequisites เป็น optional จะมีหรือไม่มีก็ได้
  • command — เป็นคำสั่งที่เราต้องการให้ทำงาน

เขียน Makefile ตัวแรก

หัวข้อนี้เราจะมาทำความเข้าใจการสร้างและการทำงานของ Makefile กันนะครับ

ขั้นตอนแรกให้เราทำการสร้างไฟล์ที่มีชื่อว่า Makefile ขึ้นมาก่อน

ต่อไปเปิดไฟล์ที่เราเพิ่งสร้างด้วย text editor ตัวไหนก็ได้ที่ถนัดครับ จากนั้นเราจะเขียน Makefile ตัวแรกโดยให้ลองพิมพ์ตามนี้

hello:
echo "Hello, World!"

ตรงนี้เราจะมี

  • target ชื่อว่า hello
  • command ว่า echo “Hello, World!”

เสร็จแล้วก็บันทึกไฟล์แล้วเปิด terminal ขึ้นมาในโฟลเดอร์เดียวกันกับที่เราสร้างไฟล์ Makefile ไว้ครับ จากนั้นพิมพ์คำสั่ง

make

จากตัวอย่างเราก็จะได้ผลลัพธ์

echo "Hello, World!" 
Hello, World!

แต่ผลลัพธ์ที่แสดงออกมาจะมี echo "Hello, World!" ของเราติดออกมาด้วย ถ้าเราไม่อยากให้ command ของเราแสดงออกมาให้เราใส่เครื่องหมาย @ ไว้หน้า command ใน Makefile ของเรา

hello:
@echo "Hello, World!"

ผลลัพธ์ที่ได้ก็จะไม่มี echo "Hello, World!" ติดออกมาแล้ว

เขียน Makefile ที่มี target หลายตัว

หัวข้อนี้เราจะมาทำความเข้าใจลำดับการทำงานและเรื่อง prerequisites หรืออีกชื่อคือ dependencies กันครับ

โดยเริ่มต้นให้ลองพิมพ์ตามนี้ครับ

first: second
@echo "first"
second:
@echo "second"
third:
@echo "third"

แล้วลองรัน make

ผลที่ได้จะมีแค่ first ที่ถูกแสดงออกมา นั่นเป็นเพราะว่าถ้าเรารัน make เปล่าๆ เจ้าตัว Makefile ก็จะทำงานเฉพาะ target แรกที่อยู่บนสุด

ซึ่งเราสามารถกำหนด target ที่เราต้องการรันได้ โดยใส่ชื่อ target หลังคำสั่ง make

make <target>

เช่น make first make second make third

ถ้าเราอยากให้มันทำอันใดอันหนึ่งก่อนล่ะ? เราก็สามารถเอา target ที่ชื่อ second ไปใส่ในส่วนของ prerequisites ของ target ที่ชื่อว่า first

first: second
@echo "first"
second:
@echo "second"
third:
@echo "third"

เมื่อรัน make เปล่าๆก็จะแสดงทั้งสอง target โดยจะทำงานใน second ก่อนแล้วมาทำ first

เราสามารถเอา third ไปใส่ของ second เพื่อให้ทำงานพ่วงต่อไปกันได้

first: second
@echo "first"
second: third
@echo "second"
third:
@echo "third"

อีกวิธีหนึ่งที่สามารถใช้กำหนดลำดับการทำงานคือการสร้าง target ใหม่ขึ้นมาโดยผมจะใช้ชื่อว่า all ไว้บนสุดแล้วใส่ target ตามลำดับที่เราต้องการ

all: first second thirdfirst:
@echo "first"
second:
@echo "second"
third:
@echo "third"

Makefile ก็จะทำตามลำดับใน all ที่กำหนดไว้

การใช้ Makefile กับการสร้างไฟล์

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

โดยเริ่มต้นในโฟลเดอร์เราจะยังไม่มีอะไรนอกจากไฟล์ Makefile

ให้ลองพิมพ์ตามนี้

all: first second thirdfirst:
@echo "create file name \"first\""
@touch first
second:
@echo "create file name \"second\""
@touch second
third:
@echo "create file name \"third\""
@touch third

แล้วรัน make

ผลที่ได้คือการสร้างสามไฟล์ first, second, third ขึ้นมา

หากเราลองรัน make ซ้ำมันก็จะไม่ทำอะไร

คราวนี้ลองลบไฟล์ second ออกไป

แล้วลองรัน make อีกที

มันก็จะทำงานใน target ที่ชื่อ second เพียงอย่างเดียว

อีกตัวอย่างสุดท้าย

file3: file1 file2
@echo "merge file1 and file2"
@cat file1 file2 > file3
file1:
@echo "create file1"
@echo "This from file1" > file1
file2:
@echo "create file2"
@echo "This from file2" > file2

ผมต้องการสร้าง file3 โดยรวม file1 และ file2 เข้าด้วยกัน ซึ่ง target ชื่อ file3 นั้นมี dependencies คือ file1 และ file2

หาก file1 หรือ file2 ยังไม่ได้ถูกสร้างมันก็จะทำ target ที่ยังไม่มีไฟล์

กรณีที่ไม่มี file1 และ file2 เมื่อรันคำสั่ง make

Makefile ก็จะทำ target file1 และ file2 ก่อนแล้วถึงทำ target file3

กรณีเราสร้าง file1 ก่อนด้วยคำสั่ง make file1 จากนั้นค่อยรันคำสั่ง make

Makefile ก็จะทำแค่ target file2 แล้วค่อยทำ target file3 เพราะมี file1 อยู่แล้ว

สุดท้ายถ้าเรามี file1 และ file2 อยู่แล้ว เมื่อรัน make

ก็จะทำแค่ target file3

ตัวอย่างการใช้งานกับการ compile ภาษา C

ผมจะทำการสร้างไฟล์ main.c ขึ้นมาก่อนโดยมีโค้ด

#include <stdio.h>int main()
{
printf("Hello, from C!!\n");
return 0;
}

จากนั้นเราก็จะมาเขียน Makefile เพื่อ compile โปรแกรม

build: main.o
gcc -o main main.o
main.o:
gcc -c main.c
clean:
rm main.o main
run:
./main

build — เป็น target หลักที่เอาไว้ compile โปรแกรมโดยมี dependencies เป็นไฟล์ main.o

main.o — เป็น target ที่เอาไว้ compile ไฟล์ main.c เป็น object file

clean — เป็น target ที่เอาไว้ลบไฟล์ที่ได้มาจากการ compile

run — เป็น target ที่เอารันไฟล์ binary

เมื่อรันคำสั่ง make

Makefile ก็จะทำการ compile ไฟล์ main.c เป็น main.o ก่อนแล้วจึง compile main.o ไปเป็น binary file ตามขั้นตอนที่เราได้เขียนไว้

และหากลองรัน target ที่เหลือตามที่เขียนไว้เช่น

make run และ make clean

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

เจ้า Makefile จริงๆก็ยังมีฟีเจอร์อื่นอยู่อีกแต่วันนี้ก็ขอมาแนะนำแค่เบื้องต้นเท่านี้ก่อนละกันนะครับ

--

--