Saat proyek C tumbuh dari satu file menjadi belasan file, struktur direktori menjadi penentu apakah proyek tetap mudah dipelihara atau berubah jadi tumpukan file yang membingungkan. Struktur direktori yang baik memisahkan kode sumber dari header, artefak build dari kode sumber, dan program utama dari program uji. Pada Chapter ini kita akan merancang struktur direktori standar untuk proyek C, menulis Makefile sederhana yang mengelola kompilasi, dan mencoba perintah make yang sudah teruji.
Pola Direktori Standar #
Pola yang paling sering dipakai pada proyek C berukuran kecil hingga menengah adalah pembagian menjadi empat direktori:
src/— file sumber.cuntuk program utama.include/— header.hbuatan kita, dipakai oleh beberapa file.c.build/— tempat menyimpan object file dan executable akhir.tests/— program uji otomatis yang memeriksa perilaku fungsi-fungsi disrc/.
Struktur lengkap terlihat seperti ini:
proyek-c/
├── Makefile
├── include/
│ └── util.h
├── src/
│ ├── main.c
│ └── util.c
├── build/
└── tests/Mari kita buat struktur ini dengan satu perintah mkdir:
mkdir -p proyek-c/{src,include,build,tests}
cd proyek-c
find . -type d | sortOutput yang Dihasilkan:
.
./build
./include
./src
./testsIsi File Header dan Sumber #
Mari kita buat file sederhana yang menunjukkan interaksi antar direktori. File header include/util.h berisi deklarasi fungsi:
/* include/util.h */
#ifndef UTIL_H
#define UTIL_H
int tambah(int a, int b);
#endifFile implementasi src/util.c menyertakan header tersebut:
/* src/util.c */
#include "util.h"
int tambah(int a, int b) {
return a + b;
}File src/main.c adalah titik masuk program yang memakai fungsi tambah:
/* src/main.c */
#include <stdio.h>
#include "util.h"
int main(void) {
printf("1 + 2 = %d\n", tambah(1, 2));
return 0;
}- Tanda kutip ganda (
"util.h") menyuruh preprocessor mencari header pertama di direktori yang sama dengan file.c, lalu di direktori yang ditentukan flag-I. - Tanda kurung siku (
<stdio.h>) menyuruh preprocessor mencari header hanya di direktori sistem, bukan di direktori proyek. - Pemakaian tanda yang salah masih bisa berfungsi jika kebetulan ada header dengan nama yang sama, tetapi ini adalah praktik buruk yang sebaiknya dihindari.
Makefile Sederhana #
Makefile adalah file konfigurasi untuk make yang mengotomatiskan kompilasi. Berikut Makefile minimal yang sesuai dengan struktur direktori di atas:
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -Iinclude
SRCDIR = src
BUILD = build
SOURCES = $(wildcard $(SRCDIR)/*.c)
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(BUILD)/%.o,$(SOURCES))
TARGET = $(BUILD)/program
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) -o $@ $^
$(BUILD)/%.o: $(SRCDIR)/%.c | $(BUILD)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD):
mkdir -p $(BUILD)
clean:
rm -rf $(BUILD)
.PHONY: all cleanMari kita telaah baris-baris Makefile di atas:
- Baris 1 (
CC = gcc): variabelCCmenyimpan nama compiler; bisa diganti keclangtanpa mengubah baris lain. - Baris 2 (
CFLAGS = -std=c11 -Wall -Wextra -Iinclude): flag kompilasi;-Iincludememberitahu compiler untuk mencari header di direktoriinclude/. - Baris 4 (
SOURCES = $(wildcard $(SRCDIR)/*.c)): fungsiwildcardmengumpulkan semua file.cdisrc/tanpa harus menulis nama file satu per satu. - Baris 5 (
OBJECTS = $(patsubst ...)): fungsipatsubstmengganti polasrc/%.cmenjadibuild/%.o, sehingga daftar object file dihasilkan secara otomatis. - Baris 7 (
all: $(TARGET)): targetalladalah default target; menjalankanmaketanpa argumen akan membangun target ini. - Baris 9 (
$(TARGET): $(OBJECTS)): executable bergantung pada semua object file; bila ada object file berubah, linking diulang. - Baris 10 (
$(CC) $(CFLAGS) -o $@ $^): variabel otomatis$@menyimpan nama target dan$^menyimpan daftar prerequisite; perintah ini memanggil linker. - Baris 12 (
$(BUILD)/%.o: $(SRCDIR)/%.c | $(BUILD)): pattern rule; setiap object file bergantung pada file.cdengan nama yang sama dan pada direktoribuild/(prerequisite order-only). - Baris 16 (
$(BUILD):): target yang membuat direktoribuild/bila belum ada. - Baris 19 (
clean:): target yang menghapus direktoribuild/beserta seluruh isinya.
Karakter tab di awal baris perintah Makefile bersifat wajib. Bila editor mengganti tab dengan spasi, make akan memberi pesan galat missing separator. Selain itu, pola order-only prerequisite (| $(BUILD)) memastikan direktori build/ dibuat sebelum kompilasi, namun perubahan timestamp direktori tidak memicu kompilasi ulang.
Menjalankan make #
Setelah Makefile tersimpan di root proyek, kita bisa membangun seluruh proyek dengan satu perintah:
makeOutput yang Dihasilkan:
gcc -std=c11 -Wall -Wextra -Iinclude -c src/main.c -o build/main.o
gcc -std=c11 -Wall -Wextra -Iinclude -c src/util.c -o build/util.o
gcc -std=c11 -Wall -Wextra -Iinclude -o build/program build/main.o build/util.oPeriksa hasil build:
ls -l build
./build/programOutput yang Dihasilkan:
total 24
-rw-r--r-- 1 z z 1448 Oct 13 03:48 main.o
-rwxrwxr-x 1 z z 16016 Oct 13 03:48 program
-rw-r--r-- 1 z z 1104 Oct 13 03:48 util.o
1 + 2 = 3Mari kita telaah baris-baris output di atas:
makepertama: mengompilasisrc/main.cmenjadibuild/main.o,src/util.cmenjadibuild/util.o, lalu meng-link keduanya menjadibuild/program.ls -l build: menampilkan tiga file — dua object file dan satu executable. Karakterxpada izinprogrammenandakan file tersebut dapat dieksekusi../build/program: menjalankan executable; program mencetak1 + 2 = 3sesuai ekspektasi.
Jalankan make sekali lagi tanpa mengubah file:
makeOutput yang Dihasilkan:
make: Nothing to be done for 'all'.Pesan ini menunjukkan make sudah pintar — object file lebih baru daripada kode sumber, jadi tidak ada yang perlu dikompilasi ulang.
Perintah make clean menghapus seluruh artefak build:
make clean
ls build 2>&1 | head -3Output yang Dihasilkan:
rm -rf build
ls: cannot access 'build': No such file or directory- Selalu tambahkan
build/ke file.gitignoreagar artefak build tidak ikut ter-commit ke version control. - Jangan menyimpan file sumber
.cdi dalambuild/. Direktori ini boleh dihapus kapan saja; bila ada file sumber tersangkut di dalamnya, kita bisa kehilangan kode. - Bila proyek berkembang lebih besar (puluhan file, banyak target, dependensi pustaka eksternal), beralih ke CMake atau Meson yang lebih ekspresif daripada
Makefilemurni.