Lipat-lipat perintah yang sama diketik setiap hari: go run . saat mencoba, go test -v ./... sebelum commit, go build -o bin/aplikasi . saat rilis, plus gofmt dan go vet di sela-selanya. Setiap anggota tim mengingat urutannya masing-masing, dan yang baru bergabung harus menanya satu per satu. Makefile menyelesaikannya dengan satu pintu berbasis target: make test, make build, make lint — nama pendek yang sama untuk semua orang, didefinisikan sekali di dalam repositori. Pada chapter ini kita akan membaca anatomi Makefile untuk proyek Go, menjalankan seluruh target-nya sungguhan, lalu melihat alternatif task runner modern seperti Taskfile dan just.

Anatomi Makefile #

Make membaca berkas Makefile yang berisi aturan berbentuk target: kebutuhan diikuti recipe — baris perintah shell yang diawali satu karakter tab. Tiga elemen lain yang penting untuk proyek Go:

ElemenPeran
NAMA := nilaiVariabel; recipe memakainya sebagai $(NAMA)
.PHONY: targetMenandai target bukan berkas nyata sehingga selalu dieksekusi
Target pertamaMenjadi tujuan bawaan saat make dipanggil tanpa argumen

Penanda .PHONY krusial untuk target clean: tanpa penanda itu, bila suatu saat ada berkas bernama clean di direktori, make akan menganggap target sudah “selesai” dan menolak menjalankan recipe. Karena pengecualian ini mudah dilupakan, praktik amannya mendaftarkan seluruh target tugas ke .PHONY.

Makefile Lengkap untuk Proyek Go #

Chapter ini menyertakan module kecil example.com/aplikasikasir di bundle: program kasir pada main.go, test tabel pada main_test.go, dan go.mod sebagai penanda module. Seluruh tugas proyek dirangkum dalam satu Makefile:

make
# Makefile aplikasi kasir — otomasi tugas berulang.
# Jalankan "make <target>" dari direktori yang sama dengan berkas ini.

BINARY := bin/aplikasi
PACKAGES := ./...

# Target pertama menjadi tujuan default saat "make" dipanggil tanpa argumen.
.PHONY: run test build fmt lint clean

run:
	go run .

test:
	go test -v $(PACKAGES)

build:
	@mkdir -p bin
	go build -o $(BINARY) .

fmt:
	gofmt -l -w .

lint:
	go vet $(PACKAGES)

clean:
	rm -rf bin

Mari kita bedah Makefile baris demi baris:

  1. Baris BINARY := bin/aplikasi: variabel lokasi binary — mengubah nama program cukup mengubah satu baris.
  2. Baris PACKAGES := ./...: seluruh package di module; dipakai target test dan lint agar cakupannya identik.
  3. Baris .PHONY: run test build fmt lint clean: keenam target dinyatakan sebagai tugas, bukan berkas.
  4. Target run: menjalankan program lewat go run . — make meng-echo setiap baris recipe sebelum mengeksekusinya.
  5. Target build: baris @mkdir -p bin diawali @ sehingga perintahnya tidak di-echo; go build -o $(BINARY) . mengompilasi binary ke bin/.
  6. Target clean: menghapus hasil build agar gudang repositori tetap bersih.

Program utama module ini menghitung total belanja dengan potongan member:

go
package main

import "fmt"

func main() {
	total := hitungTotal([]int{15000, 25000}, true)
	fmt.Println("Total belanja:", total)
}

// hitungTotal menjumlahkan seluruh harga item, lalu memberi
// potongan 10 persen bila pembeli adalah member.
func hitungTotal(harga []int, member bool) int {
	total := 0
	for _, h := range harga {
		total += h
	}
	if member {
		total -= total / 10
	}
	return total
}

Program tersebut dapat dijalankan langsung tanpa make:

bash
go run main.go

Output yang Dihasilkan:

text
Total belanja: 36000

Test-nya memakai pola tabel yang telah dibahas pada chapter Unit Test :

go
package main

import "testing"

func TestHitungTotal(t *testing.T) {
	uji := []struct {
		nama   string
		harga  []int
		member bool
		ingin  int
	}{
		{"member", []int{15000, 25000}, true, 36000},
		{"bukan member", []int{15000, 25000}, false, 40000},
		{"tanpa item", nil, true, 0},
	}
	for _, u := range uji {
		dapat := hitungTotal(u.harga, u.member)
		if dapat != u.ingin {
			t.Errorf("%s: hitungTotal(%v, %v) = %d; ingin %d", u.nama, u.harga, u.member, dapat, u.ingin)
		}
	}
}

Menjalankan Target #

Seluruh perintah berikut dijalankan nyata pada bundle chapter ini memakai GNU Make 4.4. Target paling sering dipakai saat pengembangan:

bash
make run

Output yang Dihasilkan:

text
go run .
Total belanja: 36000

Baris pertama adalah echo recipe dari make; baris kedua keluaran programnya. Pemeriksaan sebelum commit dijahit dalam satu kata:

bash
make test

Output yang Dihasilkan:

text
go test -v ./...
=== RUN   TestHitungTotal
--- PASS: TestHitungTotal (0.00s)
PASS

Target lint menjalankan go vet — recipe-nya tetap di-echo, tetapi karena tidak ada temuan, tidak ada baris lain yang tercetak dan status keluar 0:

bash
make lint

Output yang Dihasilkan:

text
go vet ./...

Pemformatan dijaga oleh target fmt — recipe-nya di-echo, lalu tidak ada berkas lain yang dicetak karena seluruh kode sudah terformat:

bash
make fmt

Output yang Dihasilkan:

text
gofmt -l -w .

Rangkaian rilis ditutup build dan bersih-bersih:

bash
make build
ls bin
make clean

Output yang Dihasilkan:

text
go build -o bin/aplikasi .
aplikasi
rm -rf bin

Setelah make build, binary aplikasi hadir di direktori bin (terbukti dari keluaran ls), lalu make clean menghapusnya. Terakhir, pemanggilan tanpa argumen menjalankan target pertama — run:

bash
make

Output yang Dihasilkan:

text
go run .
Total belanja: 36000

Alternatif Task Runner Modern #

Make tersedia hampir di semua lingkungan Unix, tetapi sintaksnya tua: tanda $ harus di-escape untuk dipakai dalam recipe, dan penanganan parameter cukup kaku. Dua alternatif populer di komunitas Go:

AlatKonfigurasiKeunggulan
Task (go-task)Taskfile.yml berbasis YAMLSintaks modern, dukungan variable dan watch bawaan
justjustfileRingan, recipe mirip make tanpa kebutuhan berkas nyata

Keduanya diinstal sebagai binary tunggal dan menawarkan pengalaman task test atau just test yang setara. Pilihan alat tidaklah sepenting kesepakatannya: selama seluruh tim memanggil pintu yang sama, tugas berulang tidak lagi bergantung pada ingatan masing-masing orang. Pintu berikutnya yang perlu dibangun adalah menjalankan make test dan make lint secara otomatis di server — topik chapter Continuous Integration.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

main_test.go

Unduh berkas
go
package main

import "testing"

func TestHitungTotal(t *testing.T) {
	uji := []struct {
		nama   string
		harga  []int
		member bool
		ingin  int
	}{
		{"member", []int{15000, 25000}, true, 36000},
		{"bukan member", []int{15000, 25000}, false, 40000},
		{"tanpa item", nil, true, 0},
	}
	for _, u := range uji {
		dapat := hitungTotal(u.harga, u.member)
		if dapat != u.ingin {
			t.Errorf("%s: hitungTotal(%v, %v) = %d; ingin %d", u.nama, u.harga, u.member, dapat, u.ingin)
		}
	}
}

main.go

Unduh berkas
go
package main

import "fmt"

func main() {
	total := hitungTotal([]int{15000, 25000}, true)
	fmt.Println("Total belanja:", total)
}

// hitungTotal menjumlahkan seluruh harga item, lalu memberi
// potongan 10 persen bila pembeli adalah member.
func hitungTotal(harga []int, member bool) int {
	total := 0
	for _, h := range harga {
		total += h
	}
	if member {
		total -= total / 10
	}
	return total
}