INTRODUZIONE & INVOCAZIONE

GNU Make è un build system che automatizza la compilazione leggendo un file chiamato Makefile (o makefile, GNUmakefile). Determina quali parti di un programma devono essere ricompilate ed esegue i comandi necessari basandosi su regole, dipendenze e timestamp dei file.

INVOCAZIONE DA RIGA DI COMANDO
ComandoDescrizione
makeEsegue il primo target del Makefile
make targetEsegue uno specifico target
make -f file.mkUsa un Makefile con nome diverso
make -j4Compilazione parallela (4 job)
make -jNN job paralleli (-j senza N = illimitati)
make -nDry run: mostra comandi senza eseguirli
make -BForza ricompilazione di tutto (unconditional)
make -kContinua anche dopo errori
make -sSilent: non stampa i comandi
make -C dirCambia directory prima di leggere il Makefile
make VAR=valueOverride di una variabile dalla command line
make -pStampa il database interno (regole e variabili)
make -dDebug mode: output dettagliato
make --debug=basicDebug selettivo (basic, verbose, implicit, jobs, all)
make -tTouch: aggiorna timestamp senza compilare
make -qQuery: exit 0 se il target è aggiornato, 1 altrimenti
ORDINE DI RICERCA DEL MAKEFILE
# Make cerca in questo ordine:
1. GNUmakefile
2. makefile
3. Makefile          # ← convenzione più usata
La convenzione è usare Makefile (con la M maiuscola) perché appare prima nei listing di directory ordinati (le maiuscole precedono le minuscole in ASCII). GNUmakefile è sconsigliato perché non portabile verso altre implementazioni di Make.
REGOLE (RULES)

Una regola è l'unità fondamentale di un Makefile. Definisce un target (cosa produrre), le sue dipendenze (prerequisiti) e una recipe (comandi da eseguire). Make ricompila un target solo se è più vecchio di almeno una delle sue dipendenze.

SINTASSI BASE
target: dipendenza1 dipendenza2 ...
	comando1
	comando2

# IMPORTANTE: i comandi DEVONO essere indentati con TAB, non spazi!

# Esempio concreto:
prog: main.o utils.o
	gcc main.o utils.o -o prog

main.o: main.c main.h
	gcc -c main.c

utils.o: utils.c utils.h
	gcc -c utils.c
TARGET MULTIPLI
# Più target con la stessa regola
file1.o file2.o: common.h

# Equivale a:
file1.o: common.h
file2.o: common.h

# Dipendenze aggiuntive (si accumulano)
main.o: main.c
main.o: config.h utils.h
# main.o dipende da main.c, config.h E utils.h
ORDER-ONLY PREREQUISITES
# I prerequisiti dopo | non influenzano il timestamp
# Usati tipicamente per creare directory
build/prog.o: prog.c | build
	gcc -c prog.c -o build/prog.o

build:
	mkdir -p build
# La directory viene creata se non esiste,
# ma modificarla non forza la ricompilazione
Il primo target del Makefile è il default goal: viene eseguito quando si invoca make senza argomenti. Convenzione: chiamarlo all.
PHONY TARGETS

Un target phony non corrisponde a un file reale. Senza .PHONY, se esistesse un file chiamato clean, Make lo considererebbe già aggiornato e non eseguirebbe la recipe.

DICHIARAZIONE E USO
.PHONY: all clean install uninstall rebuild test

all: prog

clean:
	rm -f *.o prog

install: prog
	install -m 755 prog /usr/local/bin/

uninstall:
	rm -f /usr/local/bin/prog

rebuild: clean all

test: prog
	./prog --test
PHONY TARGETS COMUNI
TargetConvenzione
allCompila tutto (default goal)
cleanRimuove file generati (.o, eseguibili)
installInstalla il programma nel sistema
uninstallRimuove l'installazione
distCrea un archivio per la distribuzione
distcleanclean + rimuove file di configurazione
check / testEsegue i test
helpMostra i target disponibili
VARIABILI

Le variabili rendono il Makefile flessibile e manutenibile. Si referenziano con $(VAR) o ${VAR}. Per convenzione i nomi sono in MAIUSCOLO.

VARIABILI FONDAMENTALI
CC      = gcc                 # compilatore C
CXX     = g++                 # compilatore C++
CFLAGS  = -Wall -Wextra -g    # flag compilazione C
CXXFLAGS= -Wall -Wextra -g    # flag compilazione C++
LDFLAGS = -L/usr/local/lib    # flag linker (percorsi)
LDLIBS  = -lm -lpthread      # librerie da linkare
CPPFLAGS= -I./include -DDEBUG # flag preprocessore
AR      = ar                  # archiviatore
ARFLAGS = rcs                 # flag per ar

TARGET  = prog
SRC     = main.c utils.c io.c
OBJ     = $(SRC:.c=.o)        # main.o utils.o io.o

$(TARGET): $(OBJ)
	$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)
VARIABILI PREDEFINITE IMPORTANTI
VariabileDefaultUso
CCccCompilatore C
CXXg++Compilatore C++
CFLAGS(vuoto)Flag per $(CC)
CXXFLAGS(vuoto)Flag per $(CXX)
CPPFLAGS(vuoto)Flag preprocessore (-I, -D)
LDFLAGS(vuoto)Flag linker (-L)
LDLIBS(vuoto)Librerie (-l)
ARarArchiviatore
RMrm -fComando di rimozione
MAKEmakeRiferimento ricorsivo a make
SHELL/bin/shShell usata per le recipe
MAKEFLAGS(vuoto)Flag passate a sub-make
Le regole implicite di Make usano queste variabili. Ad esempio, la regola implicita per .c.o esegue: $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@. Basta definire le variabili e Make sa già come compilare.
VARIABILI AUTOMATICHE

Le variabili automatiche sono settate da Make per ogni regola eseguita. Disponibili solo all'interno delle recipe.

RIFERIMENTO COMPLETO
VariabileSignificatoEsempio
$@Il targetprog
$<La prima dipendenzamain.c
$^Tutte le dipendenze (senza duplicati)main.o utils.o
$+Tutte le dipendenze (con duplicati)main.o utils.o main.o
$?Dipendenze più recenti del targetfile modificati dopo l'ultimo build
$*Lo stem del pattern matchmain in %.o: %.c
$(@D)Directory del targetbuild in build/prog
$(@F)Filename del targetprog in build/prog
$(<D)Directory della prima dipendenzasrc in src/main.c
$(<F)Filename della prima dipendenzamain.c in src/main.c
ESEMPIO PRATICO
# Senza variabili automatiche (ripetitivo)
prog: main.o utils.o
	gcc main.o utils.o -o prog

# Con variabili automatiche (generico e manutenibile)
prog: main.o utils.o
	$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)

# $@ = prog, $^ = main.o utils.o

# Pattern rule con $< e $@
%.o: %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# $< = il file .c corrispondente, $@ = il file .o target
TIPI DI ASSEGNAZIONE
OPERATORI DI ASSEGNAZIONE
OperatoreNomeComportamento
=RecursiveEspansione ritardata (lazy): il valore viene espanso ogni volta che la variabile è usata
:=SimpleEspansione immediata: il valore viene espanso al momento dell'assegnazione
::=Simple (POSIX)Identico a :=, sintassi POSIX standard
?=ConditionalAssegna solo se la variabile non è già definita
+=AppendAggiunge al valore esistente (separato da spazio)
!=ShellAssegna l'output di un comando shell (GNU Make 4.0+)
DIFFERENZE TRA = E :=
# Recursive (=) — espansione ritardata
A = $(B)
B = hello
# $(A) vale "hello" — B viene espanso quando si usa A

# Simple (:=) — espansione immediata
A := $(B)
B := hello
# $(A) vale "" — B era vuoto quando A è stato assegnato

# Conditional (?=)
CC ?= gcc
# CC = gcc solo se non già definito (es. da ambiente o command line)

# Append (+=)
CFLAGS  = -Wall
CFLAGS += -Wextra
CFLAGS += -g
# CFLAGS = "-Wall -Wextra -g"

# Shell (!=)
GIT_HASH != git rev-parse --short HEAD
# Equivalente a: GIT_HASH := $(shell git rev-parse --short HEAD)
Regola pratica: usare := per la maggior parte delle variabili (più prevedibile e veloce). Usare = solo quando serve espansione ritardata. Usare ?= per dare default sovrascrivibili dall'utente.
OVERRIDE & AMBIENTE
PRIORITÀ DELLE VARIABILI (dalla più alta)
#OrigineEsempio
1override nel Makefileoverride CFLAGS += -g
2Command linemake CFLAGS="-O2"
3MakefileCFLAGS = -Wall
4Ambienteexport CFLAGS="-O3"
5Default di MakeCC = cc
DIRETTIVA OVERRIDE
# Senza override: "make CFLAGS=-O2" sovrascrive il Makefile
CFLAGS = -Wall -g

# Con override: -g viene SEMPRE aggiunto, anche con override da CLI
override CFLAGS += -g

# Esportare variabili ai sub-make
export CC CFLAGS LDFLAGS

# Esportare tutto
export

# Non esportare una variabile specifica
unexport SECRET_KEY
VARIABILI TARGET-SPECIFIC
# Variabili che valgono solo per un target (e le sue dipendenze)
debug: CFLAGS += -DDEBUG -O0
debug: prog

release: CFLAGS += -O2 -DNDEBUG
release: prog

# Ora: "make debug" compila con -DDEBUG -O0
#      "make release" compila con -O2 -DNDEBUG
PATTERN RULES

Le pattern rules usano % come wildcard per definire regole generiche applicabili a più file. Sostituiscono le vecchie suffix rules (.c.o:).

PATTERN RULES BASE
# Regola generica: qualsiasi .c → .o
%.o: %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# Regola generica: qualsiasi .cpp → .o
%.o: %.cpp
	$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

# Con directory di output
build/%.o: src/%.c | build
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
REGOLE IMPLICITE BUILT-IN
PatternComando implicito
%.o da %.c$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
%.o da %.cpp$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
% da %.o$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
%.o da %.s$(AS) $(ASFLAGS) $< -o $@
STATIC PATTERN RULES
# Applica il pattern solo a un elenco specifico di target
OBJS := main.o utils.o io.o

$(OBJS): %.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# Sintassi: targets: target-pattern: prereq-pattern
# Utile quando hai più pattern rules e vuoi evitare ambiguità
CANCELLARE REGOLE IMPLICITE
# Disabilitare una regola implicita specifica
%.o: %.s

# Disabilitare TUTTE le regole implicite
MAKEFLAGS += --no-builtin-rules
.SUFFIXES:
FUNZIONI BUILT-IN

GNU Make offre numerose funzioni richiamabili con $(funzione argomenti) o ${funzione argomenti}.

FUNZIONI SU STRINGHE
FunzioneDescrizioneEsempio
$(subst da,a,testo)Sostituzione testuale$(subst .c,.o,main.c)main.o
$(patsubst pat,repl,testo)Sostituzione con pattern$(patsubst %.c,%.o,main.c utils.c)
$(strip testo)Rimuove spazi iniziali/finali e duplicati$(strip   a   b  )a b
$(findstring needle,testo)Cerca una stringaVuoto se non trovata
$(filter pattern,testo)Filtra parole che matchano$(filter %.c,main.c lib.h)main.c
$(filter-out pattern,testo)Esclude parole che matchano$(filter-out %.h,main.c lib.h)main.c
$(sort lista)Ordina e rimuove duplicati$(sort c a b a)a b c
$(word n,testo)Ennesima parola (1-based)$(word 2,a b c)b
$(words testo)Numero di parole$(words a b c)3
$(firstword testo)Prima parola$(firstword a b)a
$(lastword testo)Ultima parola$(lastword a b c)c
FUNZIONI SU NOMI DI FILE
FunzioneDescrizioneEsempio
$(dir nomi)Parte directory$(dir src/main.c)src/
$(notdir nomi)Parte filename$(notdir src/main.c)main.c
$(suffix nomi)Estensione$(suffix main.c).c
$(basename nomi)Senza estensione$(basename main.c)main
$(addsuffix sfx,nomi)Aggiunge suffisso$(addsuffix .c,main utils)main.c utils.c
$(addprefix pfx,nomi)Aggiunge prefisso$(addprefix src/,main.c)src/main.c
$(join lista1,lista2)Concatena parola per parola$(join a b,1 2)a1 b2
$(wildcard pattern)Glob sul filesystem$(wildcard src/*.c)
$(realpath nomi)Percorso assoluto canonicoRisolve symlink
$(abspath nomi)Percorso assolutoNon risolve symlink
FUNZIONI DI CONTROLLO
# foreach
DIRS := src lib test
SRCS := $(foreach d,$(DIRS),$(wildcard $(d)/*.c))

# if (espande then se condizione non vuota)
DEBUG_FLAGS := $(if $(DEBUG),-g -O0,-O2)

# or (primo valore non vuoto)
CC := $(or $(CUSTOM_CC),gcc)

# and (ultimo valore se tutti non vuoti, altrimenti vuoto)
RESULT := $(and $(CC),$(CFLAGS),ok)

# call (chiama una funzione definita dall'utente)
to_upper = $(shell echo $(1) | tr a-z A-Z)
NAME := $(call to_upper,hello)   # HELLO

# shell (esegue un comando e cattura stdout)
DATE  := $(shell date +%Y-%m-%d)
FILES := $(shell find src -name '*.c')

# value (valore non espanso di una variabile)
RAW := $(value CC)

# eval (espande e valuta come sintassi Makefile)
$(eval $(call genera_regola,modulo1))

# error / warning / info
$(error Messaggio fatale: build interrotto)
$(warning Attenzione: variabile non settata)
$(info Build avviato alle $(DATE))
DIRETTIVE CONDIZIONALI
SINTASSI CONDIZIONALE
# ifeq / ifneq — confronto tra stringhe
ifeq ($(CC),gcc)
  CFLAGS += -Wno-unused-result
else ifeq ($(CC),clang)
  CFLAGS += -Wno-unused-command-line-argument
else
  CFLAGS += -W
endif

# ifdef / ifndef — verifica se la variabile è definita
ifdef DEBUG
  CFLAGS += -g -O0 -DDEBUG
else
  CFLAGS += -O2 -DNDEBUG
endif

# Uso dalla riga di comando: make DEBUG=1

# Confronto con stringa vuota
ifeq ($(VERBOSE),)
  Q := @           # silenzia i comandi
else
  Q :=             # mostra i comandi
endif

%.o: %.c
	$(Q)$(CC) $(CFLAGS) -c $< -o $@
RILEVAMENTO OS
UNAME_S := $(shell uname -s)

ifeq ($(UNAME_S),Linux)
  LDLIBS += -lrt -lpthread
endif
ifeq ($(UNAME_S),Darwin)
  CC := clang
  LDLIBS += -framework CoreFoundation
endif

# Rilevamento Windows (MinGW / MSYS)
ifdef COMSPEC
  RM = del /Q
  EXE = .exe
else
  RM = rm -f
  EXE =
endif
INCLUDE
DIRETTIVA INCLUDE
# Include un altro Makefile
include config.mk
include $(wildcard deps/*.d)

# -include (o sinclude): non dà errore se il file non esiste
-include $(OBJ:.o=.d)

# Uso tipico: file .d per dipendenze automatiche (vedi sezione dedicata)
# Uso tipico: config.mk per variabili specifiche dell'ambiente
PROGETTI MULTI-DIRECTORY
APPROCCIO RICORSIVO (RECURSIVE MAKE)
# Makefile principale che invoca sub-make
SUBDIRS := src lib tests

.PHONY: all clean $(SUBDIRS)

all: $(SUBDIRS)

$(SUBDIRS):
	$(MAKE) -C $@

# Dipendenze tra sottodirectory
src: lib
tests: src

clean:
	for dir in $(SUBDIRS); do $(MAKE) -C $$dir clean; done
APPROCCIO NON RICORSIVO (SINGOLO MAKEFILE)
# Struttura:
#   src/main.c src/utils.c
#   lib/parser.c lib/lexer.c
#   build/  (output)

SRCDIR  := src
LIBDIR  := lib
BUILDDIR:= build

SRC     := $(wildcard $(SRCDIR)/*.c) $(wildcard $(LIBDIR)/*.c)
OBJ     := $(patsubst %.c,$(BUILDDIR)/%.o,$(notdir $(SRC)))
TARGET  := $(BUILDDIR)/prog

VPATH   := $(SRCDIR) $(LIBDIR)

$(TARGET): $(OBJ) | $(BUILDDIR)
	$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)

$(BUILDDIR)/%.o: %.c | $(BUILDDIR)
	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

$(BUILDDIR):
	mkdir -p $@
VPATH — RICERCA SORGENTI
# VPATH: lista di directory dove cercare i prerequisiti
VPATH = src:lib:include

# vpath (minuscolo): più selettivo, per pattern
vpath %.c src lib
vpath %.h include

# Con vpath Make cerca automaticamente i .c nelle directory specificate
# quando un prerequisito non viene trovato nella directory corrente
Recursive vs non-recursive make: il recursive make ($(MAKE) -C subdir) è più semplice ma può avere problemi con le dipendenze tra directory. Il non-recursive (singolo Makefile) è più efficiente e corretto ma più complesso da gestire.
RECIPE AVANZATE
PREFISSI DELLE RECIPE
PrefissoSignificato
@Silenzia il comando (non viene stampato)
-Ignora errori (continua anche se il comando fallisce)
+Esegue anche con -n (dry run) — usato per sub-make
ESEMPI PREFISSI
clean:
	@echo "Pulizia in corso..."    # @ = non stampare il comando
	-rm -f *.o                      # - = non fallire se non ci sono .o
	@echo "Fatto."

submake:
	+$(MAKE) -C subdir              # + = esegui anche con make -n
MULTI-LINE & SHELL
# Ogni riga della recipe è eseguita in una shell separata!
bad:
	cd subdir
	ls            # NON lista subdir! Il cd si è perso

# Soluzione 1: una riga sola con ;
good1:
	cd subdir; ls

# Soluzione 2: backslash per continuazione
good2:
	cd subdir && \
	ls && \
	pwd

# Soluzione 3: .ONESHELL (GNU Make 3.82+)
.ONESHELL:
good3:
	cd subdir
	ls
	pwd
VARIABILI SHELL VS MAKE
# $ in una recipe: bisogna raddoppiare per la shell
list:
	for f in *.c; do echo $$f; done
# $$ diventa $ per la shell, $f è la variabile shell
# Un singolo $ verrebbe interpretato da Make

# Accedere a variabili d'ambiente nella recipe
info:
	@echo "User: $$USER"
	@echo "CC:   $(CC)"
DIPENDENZE AUTOMATICHE

Generare manualmente le dipendenze degli header è fragile e tedioso. GCC/Clang possono generare file .d con le dipendenze, che Make include automaticamente.

METODO CLASSICO (FILE .d)
CC      := gcc
CFLAGS  := -Wall -g
DEPFLAGS= -MMD -MP

SRC     := $(wildcard *.c)
OBJ     := $(SRC:.c=.o)
DEP     := $(SRC:.c=.d)

prog: $(OBJ)
	$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)

%.o: %.c
	$(CC) $(CPPFLAGS) $(CFLAGS) $(DEPFLAGS) -c $< -o $@

-include $(DEP)

clean:
	$(RM) $(OBJ) $(DEP) prog
FLAG PER LE DIPENDENZE
FlagDescrizione
-MGenera tutte le dipendenze (incluse system headers)
-MMGenera dipendenze escludendo system headers
-MD-M + compila normalmente + scrive in .d
-MMD-MM + compila normalmente + scrive in .d
-MPAggiunge target phony per ogni header (evita errori se l'header viene rimosso)
-MF fileScrive le dipendenze nel file specificato
-MT targetSpecifica il target nella regola generata
CONTENUTO DI UN FILE .d
# main.d generato da gcc -MMD -MP main.c
main.o: main.c main.h utils.h config.h

main.h:
utils.h:
config.h:
# Le regole vuote (-MP) evitano errori se un header viene cancellato
Il flag -MP è importante: senza di esso, se si cancella un header file referenziato nel .d, Make dà errore perché non trova il prerequisito. Con -MP, ogni header ha un target phony che evita l'errore.
DEBUG & TROUBLESHOOTING
COMANDI DI DEBUG
ComandoDescrizione
make -nDry run: mostra cosa farebbe senza eseguire
make -dDebug completo (molto verboso)
make --debug=basicDebug leggero: mostra quali target vengono ricompilati e perché
make --debug=verboseCome basic + file cercati per regole implicite
make --debug=implicitSolo ricerca regole implicite
make --debug=jobsInformazioni sui job paralleli
make -pStampa database interno (tutte le regole e variabili)
make -p -f /dev/nullSolo regole built-in (senza leggere il Makefile)
make --warn-undefined-variablesAvvisa se una variabile non è definita
FUNZIONI DI DEBUG NEL MAKEFILE
# Stampare il valore di una variabile
$(info CFLAGS = $(CFLAGS))
$(warning SRC = $(SRC))            # come info ma con file:linea

# Target di debug
print-%:
	@echo '$* = $($*)'
# Uso: make print-CFLAGS  make print-SRC  make print-OBJ

# Ispezionare l'origine di una variabile
$(info origin CC = $(origin CC))
# Possibili valori: undefined, default, environment,
# file, command line, override, automatic

# Ispezionare il flavor di una variabile
$(info flavor CFLAGS = $(flavor CFLAGS))
# Possibili valori: undefined, recursive, simple
ERRORI COMUNI
# ERRORE: *** missing separator.  Stop.
# Causa: spazi invece di TAB nella recipe
# Fix: usare TAB reali (non soft-tab del tuo editor)

# ERRORE: No rule to make target 'file.h'
# Causa: file .d obsoleto riferisce un header cancellato
# Fix: aggiungere -MP alle DEPFLAGS, oppure make clean

# ERRORE: circular dependency dropped
# Causa: un target dipende (direttamente o indirettamente) da sé stesso

# Il target non viene ricompilato nonostante modifiche
# Causa: dipendenze incomplete (manca un header nelle dipendenze)
# Fix: usare le dipendenze automatiche (-MMD -MP)
MAKEFILE COMPLETO DI RIFERIMENTO
PROGETTO C STANDARD
# ── Progetto C con dipendenze automatiche, build dir, debug/release ──

# Compilatore e flag
CC       := gcc
CFLAGS   := -Wall -Wextra -Wpedantic -std=c17
CPPFLAGS := -I./include
LDFLAGS  :=
LDLIBS   := -lm
DEPFLAGS  = -MMD -MP -MF $(DEPDIR)/$*.d

# Directory
SRCDIR   := src
BUILDDIR := build
DEPDIR   := $(BUILDDIR)/deps

# File
SRC      := $(wildcard $(SRCDIR)/*.c)
OBJ      := $(patsubst $(SRCDIR)/%.c,$(BUILDDIR)/%.o,$(SRC))
DEP      := $(patsubst $(SRCDIR)/%.c,$(DEPDIR)/%.d,$(SRC))
TARGET   := $(BUILDDIR)/prog

# Debug/Release
ifdef DEBUG
  CFLAGS += -g3 -O0 -DDEBUG
else
  CFLAGS += -O2 -DNDEBUG
endif

# ── Target principali ──
.PHONY: all clean rebuild help

all: $(TARGET)

$(TARGET): $(OBJ) | $(BUILDDIR)
	$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)
	@echo "Build completato: $@"

$(BUILDDIR)/%.o: $(SRCDIR)/%.c | $(BUILDDIR) $(DEPDIR)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(DEPFLAGS) -c $< -o $@

# ── Directory ──
$(BUILDDIR) $(DEPDIR):
	mkdir -p $@

# ── Pulizia ──
clean:
	$(RM) -r $(BUILDDIR)

rebuild: clean all

# ── Help ──
help:
	@echo "Target disponibili:"
	@echo "  all      - Compila il progetto (default)"
	@echo "  clean    - Rimuove i file generati"
	@echo "  rebuild  - Clean + build"
	@echo "  help     - Mostra questo messaggio"
	@echo ""
	@echo "Variabili:"
	@echo "  DEBUG=1  - Compila con simboli di debug"
	@echo "  CC=clang - Usa un compilatore diverso"

# ── Dipendenze automatiche ──
-include $(DEP)
LIBRERIA STATICA
LIB     := libutils.a
LIB_SRC := $(wildcard lib/*.c)
LIB_OBJ := $(LIB_SRC:.c=.o)

$(LIB): $(LIB_OBJ)
	$(AR) $(ARFLAGS) $@ $^

# Linkare il programma alla libreria
prog: main.o $(LIB)
	$(CC) $(LDFLAGS) main.o -L. -lutils -o $@
LIBRERIA DINAMICA (SHARED)
SHLIB     := libutils.so
SHLIB_SRC := $(wildcard lib/*.c)
SHLIB_OBJ := $(SHLIB_SRC:.c=.o)

# Compilare con -fPIC per shared library
lib/%.o: lib/%.c
	$(CC) $(CFLAGS) -fPIC -c $< -o $@

$(SHLIB): $(SHLIB_OBJ)
	$(CC) -shared $^ -o $@

# Linkare ed eseguire
prog: main.o $(SHLIB)
	$(CC) $(LDFLAGS) main.o -L. -lutils -o $@
# Esecuzione: LD_LIBRARY_PATH=. ./prog
TIPS & TRICKS
SPECIAL TARGETS
TargetDescrizione
.PHONYI target elencati non corrispondono a file reali
.SUFFIXESDefinisce le suffix rules (svuotare per disabilitare: .SUFFIXES:)
.DEFAULTRecipe per target senza regola
.PRECIOUSNon cancellare i file elencati in caso di errore o interruzione
.INTERMEDIATEI file elencati vengono cancellati automaticamente dopo l'uso
.SECONDARYCome .INTERMEDIATE ma non vengono cancellati
.DELETE_ON_ERRORCancella il target se la recipe fallisce
.ONESHELLEsegue tutta la recipe in una singola shell
.SILENTNon stampare i comandi (come @ su ogni riga)
.EXPORT_ALL_VARIABLESEsporta tutte le variabili ai sub-processi
TRUCCHI UTILI
# Sopprimere "Nothing to be done" e "is up to date"
all: prog
	@:                          # : = comando nullo della shell

# Forzare la ricompilazione di un target
.PHONY: FORCE
FORCE:
version.o: FORCE            # sempre ricompilato

# Stampare un separatore colorato
build: $(TARGET)
	@printf '\033[32m=== Build OK ===\033[0m\n'

# Creare automaticamente le directory per gli output
$(BUILDDIR)/%.o: %.c
	@mkdir -p $(@D)
	$(CC) $(CFLAGS) -c $< -o $@

# Target help auto-documentante
help: ## Mostra questo messaggio
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
	  awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[36m%-15s\033[0m %s\n", $$1, $$2}'

# Timestamp di build
CPPFLAGS += -DBUILD_DATE='"$(shell date)"'
CPPFLAGS += -DGIT_HASH='"$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)"'

# Check se un programma è installato
check-deps:
	@command -v $(CC) >/dev/null 2>&1 || { echo "$(CC) non trovato"; exit 1; }

# Compilazione condizionale con feature flags
ifdef WITH_SSL
  CPPFLAGS += -DWITH_SSL
  LDLIBS   += -lssl -lcrypto
endif
CHEAT SHEET RAPIDO
CosaComando / Sintassi
Compilare tuttomake oppure make all
Puliremake clean
Build parallelomake -j$(nproc)
Dry runmake -n
Debug buildmake DEBUG=1
Altro compilatoremake CC=clang
Verbosomake VERBOSE=1 o make V=1
Valore variabilemake print-CFLAGS (con target print-%)
Rebuild forzatomake -B