หลายคนที่เขียนโปรแกรมหลายคนก็คงต้องเคยเห็นเจ้าไฟล์ที่มีชื่อว่า 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 firstsecond:
@echo "create file name \"second\""
@touch secondthird:
@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 > file3file1:
@echo "create file1"
@echo "This from file1" > file1file2:
@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.omain.o:
gcc -c main.cclean:
rm main.o mainrun:
./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 จริงๆก็ยังมีฟีเจอร์อื่นอยู่อีกแต่วันนี้ก็ขอมาแนะนำแค่เบื้องต้นเท่านี้ก่อนละกันนะครับ