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

.PHONY

แต่เดิม Make มีจุดประสงค์เดิมคือ run ชุดคำสั่งหรือ re-compile ส่วนของ code เมื่อมีการเปลี่ยนแปลงที่ file ที่กำหนดไว้เนื่องจาก code ใน file นั้นมี dependencies กับส่วนอื่น ๆ แต่เมื่อเวลาผ่านไปนักพัฒนาก็เอามาประยุกต์กับการ run ชุดคำสั่งโดยไม่ต้องขึ้นกับ file ใด ๆ จึงมีการกำหนด .PHONY หรือ PHONY ขึ้นมาเพื่อระบุว่าคำสั่งนี้ไม่ได้ขึ้นอยู่กับ file ใด ๆ ตัวอย่างเช่นเรามี Makefile หน้าตาประมาณนี้

serve:
	echo "Serving HTTP server at http://localhost:4000" 

แล้วเรามี project structure ที่มี file ชื่อว่า serve หน้าตาประมาณนี้

├── Makefile
└── serve

เมื่อเรา run คำสั่ง มันจะขึิ้นมาประมาณนี้

$ make serve

make: `serve' is up to date.

เพราะว่าเรามี file ชื่อว่า serve อยู่นั่นเอง แต่ว่าคำสั่งของเราจริง ๆ แล้วมันไม่ได้ขึ้นกับ file ที่ชื่อว่า serve เลย ดังนั้นเราก็เลยต้องใส่ .PHONY เข้าไปประมาณนี้

.PHONY: serve
serve:
	echo "Serving HTTP server at http://localhost:4000"

เมื่อเรา run คำสั่งใหม่ มันจะขึิ้นมาประมาณนี้

$ make serve

echo "Serving HTTP server at http://localhost:4000"
Serving HTTP server at http://localhost:4000

@

จากหัวข้อก่อนหน้าจะสังเกตเห็นว่า output ของ console มันถึงออกมา 2 บรรทัด เพราะโดยปกติแล้ว Make จะ print คำสั่งออกมาด้วย แต่เผอิญคำสั่งเราดันเป็น echo ไง เพื่อไม่ให้เกิดเหตุการณ์นี้ขึ้นเราก็จะใช้ syntax @ ในการปิดคำสั่งนั้นไม่ให้ print ออกมา ตัวอย่างเช่น

.PHONY: serve
serve:
	@echo "Serving HTTP server at http://localhost:4000"

เมื่อเรา run คำสั่งใหม่ มันจะขึิ้นมาแค่บรรทัดเดียวละ

$ make serve

Serving HTTP server at http://localhost:4000

Argument

ถ้าเราต้องการจะรับ argument เข้ามาจากคำสั่ง Make ก็รองรับนะ โดย Makefile จะออกมาหน้าตาประมาณนี้

.PHONY: serve
serve:
	@echo "Serving HTTP server at http://localhost:${port}"

เมื่อเรา run คำสั่งใหม่ ก็จะได้ผลลัพธ์ประมาณนี้

$ make serve port=9999

Serving HTTP server at http://localhost:9999

Variable

ถ้าเราต้องการจะประกาศ global varible เพื่อใช้ร่วมกันกับหลาย ๆ คำสั่งใน Make ก็สามารถทำได้ Makefile จะออกมาหน้าตาประมาณนี้

DATE := $(shell date +%Y-%m-%d)

.PHONY: serve
serve:
	@echo "Serving HTTP server at http://localhost:4000 at ${DATE}"

เมื่อเรา run คำสั่งใหม่ ก็จะได้ผลลัพธ์ประมาณนี้

$ make serve port=9999

Serving HTTP server at http://localhost:4000 at 2024-05-11

ปล. ตัวแปรจะตัวเล็กตัวใหญ่ก็ได้นะ ไม่ได้มีผลอะไร

Dependencies

ถ้าเรามีคำสั่งที่มี dependency เช่นต้อง run คำสั่งนึงก่อน-หลัง เราสามารถนำแนวคิดจากจุดประสงค์เดิมของ Make คือการ run คำสั่งของ file ที่มี dependencies กับอีก file นึงได้ เช่น คำสั่ง serve จะต้องรับ port argument มา แล้วเราอยากจะ check ว่า port มีค่าหรือเปล่า ใน Makefile ก็จะเขียนได้ประมาณนี้

.PHONY: serve
serve: validate-port
	@echo "Serving HTTP server at http://localhost:${port}"

.PHONY: validate-port
validate-port:
	@if [ -z "$(port)" ]; then \
		echo "Please provide a port for the server"; \
		exit 1; \
	fi

ถ้าเราลอง run คำสั่งโดยไม่ส่ง port อะไรไปเลย ก็จะขึ้น error ประมาณนี้

$ make serve

Please provide a port for the server
make: *** [validate-port] Error 1