C Makefile

Na aula Vários Arquivos, a Ana aprendeu a compilar cada .c separado e a recompilar só o que mudou. Funciona, mas é chato: ela tem de lembrar o que mudou, que .c inclui qual .h, e digitar os comandos certos. O programa make faz isso por ela. Você escreve uma vez, num arquivo chamado Makefile, o que depende de quê e como se faz cada coisa; depois, é só digitar make. Ele olha a data de cada arquivo e roda apenas os comandos necessários.

Você vai aprender
  • A escrever uma regra: alvo, pré-requisitos e receita (que começa com TAB).
  • Como o make decide o que refazer, pela data dos arquivos.
  • A usar variáveis (CC, CFLAGS, LDLIBS) e as variáveis automáticas $@, $< e $^.
  • A regra de padrão %.o: %.c, o make clean e o .PHONY.
  • A não esquecer os .h, com -MMD -MP.
  • Os erros reais do make: faltando o separador, Sem regra para processar o alvo e Error 1.

Todos os comandos rodaram na máquina fictícia da Ana, na pasta ~/c/make, com o GNU Make 4.4 e o gcc 15.2. O sistema dela está em português, e o make fala português (o inglês, que aparece em fóruns e tutoriais, vai citado ao lado). O make não roda no editor do guia: lá, todas as abas .c são compiladas juntas a cada Rodar. Mas você vai ver o raciocínio dele num programa C. Para instalar no seu Linux: sudo apt install make (veja Instalar no Linux).


A ideia: a lista de tarefas da padaria

Na padaria, o bolo depende da massa e da cobertura; a massa depende da farinha e dos ovos. Se chegou cobertura nova, o padeiro refaz só a cobertura e monta o bolo de novo: a massa continua boa. Se nada mudou desde o último bolo, ele não faz nada. O make pensa assim:

Na padariaNo makeExemplo
O que se quer prontoO alvo (target)app, main.o
Do que ele dependeOs pré-requisitos (prerequisites)main.c, util.h
Como fazerA receita (recipe): os comandosgcc -c main.c
"Chegou ingrediente novo?"Algum pré-requisito é mais novo que o alvo?a data de util.c > a de util.o

A regra de ouro: o make refaz um alvo quando ele não existe ou quando algum pré-requisito foi modificado depois dele. Todo o resto é jeito de escrever menos.


1. O primeiro Makefile

O projeto é o da lojinha, da aula Vários Arquivos: main.c, util.c e util.h. Sem Makefile, o make não sabe o que fazer:

$ ls
main.c
util.c
util.h
$ make
make: *** Nenhum alvo indicado e nenhum arquivo make encontrado.  Pare.

("Nenhum alvo indicado e nenhum makefile encontrado"; em inglês, No targets specified and no makefile found. Stop.) A Ana cria, na mesma pasta, um arquivo chamado exatamente Makefile, sem extensão, com uma regra só:

app: main.c util.c util.h
	gcc -std=c23 -Wall -Wextra main.c util.c -o app
app
O alvo: o arquivo que esta regra produz. Vem antes dos dois-pontos.
main.c util.c util.h
Os pré-requisitos: se algum deles mudar, o app tem de ser refeito. O util.h entra porque o código dos dois .c depende dele.
(TAB) gcc …
A receita: o comando que faz o alvo. A linha tem de começar com um TAB (a tecla Tab), não com espaços. É a regra mais famosa e mais odiada do make; você vai ver o erro na próxima seção.
$ make
gcc -std=c23 -Wall -Wextra main.c util.c -o app
$ ./app
--------------------
Lojinha da Ana
--------------------
3 cadernos: R$ 36
5 lápis:    R$ 10
--------------------

O make leu o Makefile, viu que o app não existia, mostrou o comando da receita e o rodou. Agora rode de novo, sem mudar nada:

$ make
make: 'app' está atualizado.

(Em inglês: make: 'app' is up to date.) O app é mais novo que todos os pré-requisitos: não há o que fazer. O comando touch ("tocar") muda a data de um arquivo para agora, como se você o tivesse editado e salvado:

$ touch util.c
$ make
gcc -std=c23 -Wall -Wextra main.c util.c -o app

Sem argumento, o make faz o primeiro alvo do arquivo. Você também pode pedir um alvo pelo nome: make app é o mesmo que make aqui.

TAB, não espaços: faltando o separador

Muitos editores trocam o TAB por quatro espaços. Para o make, isso é outra coisa:

$ make
Makefile:2: *** faltando o separador.  Pare.

"Na linha 2 do Makefile, falta o separador; pare." Em inglês, a frase que você vai achar em todo fórum: Makefile:2: *** missing separator. Stop. O "separador" que falta é o TAB no começo da receita. Na tela, espaços e TAB parecem iguais. O comando cat -A mostra os caracteres invisíveis: o fim de cada linha aparece como $, e um TAB, como ^I. No Makefile quebrado, a receita começa com quatro espaços:

$ cat -A Makefile
app: main.c util.c util.h$
    gcc -std=c23 -Wall -Wextra main.c util.c -o app$

No Makefile que funciona, o começo da receita é um ^I:

$ cat -A Makefile
app: main.c util.c util.h$
^Igcc -std=c23 -Wall -Wextra main.c util.c -o app$

O conserto: apague os espaços e digite um TAB. No VS Code, o canto de baixo da janela mostra "Espaços: 4" ou "Tamanho da Tabulação"; nos arquivos chamados Makefile, ele já usa TAB sozinho.


2. Uma regra para cada .o

O primeiro Makefile recompila tudo quando qualquer arquivo muda. Para recompilar só o necessário, escreva uma regra para cada etapa da aula Vários Arquivos: uma para cada .o e uma para ligar.

app: main.o util.o
	gcc main.o util.o -o app

main.o: main.c util.h
	gcc -std=c23 -Wall -Wextra -c main.c

util.o: util.c util.h
	gcc -std=c23 -Wall -Wextra -c util.c

Leia de baixo para cima: o util.o depende do util.c e do util.h; o main.o, do main.c e do util.h; o app, dos dois .o. Quando a Ana pede o app, o make percebe que precisa dos .o antes, e os faz primeiro:

$ make
gcc -std=c23 -Wall -Wextra -c main.c
gcc -std=c23 -Wall -Wextra -c util.c
gcc main.o util.o -o app
$ make
make: 'app' está atualizado.

Agora o teste que importa. A Ana mexeu só no util.c:

$ touch util.c
$ make
gcc -std=c23 -Wall -Wextra -c util.c
gcc main.o util.o -o app
  1. main.o: o main.c e o util.h são mais velhos que ele. Nada a fazer.
  2. util.o: o util.c é mais novo. Recompila.
  3. app: o util.o acabou de ficar mais novo que ele. Liga de novo.

E se ela mexer no util.h, que os dois .c incluem? Os dois .o são refeitos, exatamente como a aula anterior mandava fazer à mão:

$ touch util.h
$ make
gcc -std=c23 -Wall -Wextra -c main.c
gcc -std=c23 -Wall -Wextra -c util.c
gcc main.o util.o -o app

Ver sem fazer: make -n

O -n (de no-op, "não faça") mostra os comandos que o make rodaria, sem rodá-los. Bom para conferir um Makefile novo:

$ touch main.c
$ make -n
gcc -std=c23 -Wall -Wextra -c main.c
gcc main.o util.o -o app

Experimente: o editor não roda o make, mas este programa C faz o mesmo raciocínio. Cada arquivo tem uma "hora" (número maior = mais novo), cada regra tem um alvo, dois pré-requisitos e uma receita, e a função make aplica a regra de ouro. Rode e compare com a saída real acima. Depois troque o touch("util.c") do main por touch("util.h") e por touch("main.c").

#include <stdio.h>
#include <string.h>

// "hora" da última modificação de cada arquivo: maior = mais novo
typedef struct {
    const char *nome;
    int hora;
} Arquivo;

Arquivo arquivos[] = {
    {"main.c", 10}, {"util.c", 10}, {"util.h", 10},
    {"main.o", 20}, {"util.o", 20}, {"app", 30},
};
#define N_ARQUIVOS 6

typedef struct {
    const char *alvo;
    const char *deps[2];
    const char *receita;
} Regra;

// em ordem: primeiro os .o, depois o programa
Regra regras[] = {
    {"main.o", {"main.c", "util.h"}, "gcc -std=c23 -Wall -Wextra -c main.c"},
    {"util.o", {"util.c", "util.h"}, "gcc -std=c23 -Wall -Wextra -c util.c"},
    {"app",    {"main.o", "util.o"}, "gcc main.o util.o -o app"},
};
#define N_REGRAS 3

int relogio = 100;

Arquivo *achar(const char *nome) {
    for (int i = 0; i < N_ARQUIVOS; i++) {
        if (strcmp(arquivos[i].nome, nome) == 0) {
            return &arquivos[i];
        }
    }
    return nullptr;
}

void touch(const char *nome) {
    relogio++;
    achar(nome)->hora = relogio;
}

void make(void) {
    bool fez_algo = false;
    for (int i = 0; i < N_REGRAS; i++) {
        Regra *r = &regras[i];
        bool refazer = false;
        for (int j = 0; j < 2; j++) {
            if (achar(r->deps[j])->hora > achar(r->alvo)->hora) {
                refazer = true;      // a dependência é mais nova que o alvo
            }
        }
        if (refazer) {
            printf("%s\n", r->receita);
            touch(r->alvo);          // o alvo acabou de ser refeito
            fez_algo = true;
        }
    }
    if (!fez_algo) {
        printf("make: 'app' está atualizado.\n");
    }
}

int main(void) {
    printf("$ make\n");
    make();
    printf("$ touch util.c\n");
    touch("util.c");
    printf("$ make\n");
    make();
    return 0;
}

(O make de verdade lê a data de cada arquivo no disco, em vez de uma tabela, e descobre sozinho a ordem das regras; o programa acima as percorre na ordem em que foram escritas.)


3. Variáveis, $@, $< e clean

O Makefile da seção 2 repete -std=c23 -Wall -Wextra em cada regra. Para trocar uma opção, a Ana teria de mudar várias linhas. As variáveis resolvem isso:

CC = gcc
CFLAGS = -std=c23 -Wall -Wextra
OBJS = main.o util.o

app: $(OBJS)
	$(CC) $(OBJS) -o $@

main.o: main.c util.h
	$(CC) $(CFLAGS) -c $< -o $@

util.o: util.c util.h
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f app $(OBJS)

.PHONY: clean
CC = gcc
Cria a variável CC (de C compiler, "compilador C"). $(CC) é trocado por gcc onde aparecer. Os nomes CC, CFLAGS e LDLIBS são uma convenção que todo mundo segue.
CFLAGS
"C flags", as opções de compilação.
OBJS
Um nome inventado pela Ana para a lista dos .o.
$@
Variável automática: o alvo da regra. Na regra do util.o, $@ vale util.o.
$<
O primeiro pré-requisito. Na regra do util.o, vale util.c. (Por isso o .c vem antes do .h na lista.)
clean:
Um alvo que não é arquivo: uma "tarefa" que apaga o que o make gerou. Não tem pré-requisitos.
.PHONY: clean
"clean é um alvo falso (phony)": nunca procure um arquivo com esse nome. Você vai ver por que ele importa logo abaixo.
$ make clean
rm -f app main.o util.o
$ make
gcc -std=c23 -Wall -Wextra -c main.c -o main.o
gcc -std=c23 -Wall -Wextra -c util.c -o util.o
gcc main.o util.o -o app

Repare que o make mostra os comandos já com as variáveis trocadas: $(CFLAGS) virou -std=c23 -Wall -Wextra, $< virou main.c e $@ virou main.o. O make clean é o jeito de começar do zero (por exemplo, depois de mudar o CFLAGS, para que tudo seja recompilado com as opções novas).

Por que o .PHONY

Sem a linha .PHONY: clean, o make trata clean como um arquivo. Na maior parte do tempo dá certo, porque esse arquivo não existe. Mas se um dia aparecer um arquivo chamado clean na pasta (aqui, a Ana o cria com touch):

$ touch clean
$ make clean
make: 'clean' está atualizado.

O alvo clean "existe" e não tem pré-requisitos, então está sempre atualizado, e o rm nunca roda. Com o .PHONY: clean de volta, o make ignora o arquivo e roda a receita:

$ make clean
rm -f app main.o util.o

Ponha no .PHONY todo alvo que é uma tarefa, e não um arquivo (.PHONY: clean all teste).


4. A regra de padrão %.o: %.c

As regras do main.o e do util.o são iguais, só muda o nome. Com dez arquivos, seriam dez regras iguais. Uma regra de padrão diz de uma vez como fazer qualquer .o a partir do .c de mesmo nome. A Ana aproveitou para acrescentar um módulo novo, frete.c, que calcula o frete com a função ceil do math.h (arredondar para cima: R$ 5 a cada 10 km começados):

#include <math.h>
#include "frete.h"

double frete(double km) {
    return ceil(km / 10.0) * 5.0;
}

O frete.h tem o protótipo double frete(double km);, e o main.c ganhou a linha printf("Frete (23 km): R$ %.2f\n", frete(23.0));. O Makefile ficou assim:

CC = gcc
CFLAGS = -std=c23 -Wall -Wextra
OBJS = main.o util.o frete.o

app: $(OBJS)
	$(CC) $^ -o $@ $(LDLIBS)

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

clean:
	rm -f app $(OBJS)

.PHONY: clean
%.o: %.c
"Para fazer um .o qualquer, use o .c de mesmo nome." O % é o pedaço que casa: para frete.o, ele vale frete, e o pré-requisito é frete.c.
$^
Todos os pré-requisitos. Na regra do app, vale main.o util.o frete.o.
$(LDLIBS)
As bibliotecas para ligar (de LD libraries; ld é o ligador). Vai no fim do comando. Por enquanto, a variável não existe e vale vazio.

Para acrescentar um módulo, agora basta pôr o .o na lista OBJS. O make descobre sozinho como fazer o frete.o. Mas a ligação falha:

$ make
gcc -std=c23 -Wall -Wextra -c main.c -o main.o
gcc -std=c23 -Wall -Wextra -c util.c -o util.o
gcc -std=c23 -Wall -Wextra -c frete.c -o frete.o
gcc main.o util.o frete.o -o app
/usr/bin/x86_64-linux-gnu-ld.bfd: frete.o: na função "frete":
frete.c:(.text+0x2d): referência não definida para "ceil"
collect2: error: ld returned 1 exit status
make: *** [Makefile:6: app] Error 1
referência não definida para "ceil"
O erro de verdade, do ligador: a função ceil mora na biblioteca de matemática, que o gcc só liga com -lm (aulas Matemática e Bibliotecas).
make: *** [Makefile:6: app] Error 1
O recado do make: "a receita do alvo app, na linha 6 do Makefile, terminou com erro (código 1)". O make para no primeiro comando que falha. Este recado sai em inglês mesmo no sistema em português; ele só diz onde parou, e o motivo está nas linhas de cima.

A Ana cria a variável LDLIBS = -lm logo abaixo do CFLAGS. Os três .o já estão prontos; só falta ligar:

$ make
gcc main.o util.o frete.o -o app -lm
$ ./app
--------------------
Lojinha da Ana
--------------------
3 cadernos: R$ 36
Frete (23 km): R$ 15.00
--------------------

O perigo: o make não lê os seus #include

A regra %.o: %.c só fala do .c. Veja o que acontece quando a Ana muda o util.h:

$ touch util.h
$ make
make: 'app' está atualizado.

Errado! O util.h mudou, e os .o que o incluem estão velhos. Como a aula Vários Arquivos avisou, um .o compilado com a promessa antiga pode fazer o programa ligar e se comportar mal. O make não abre o seu código para ver os #include: ele só sabe o que está escrito no Makefile. O conserto é dizer quem depende de quem, em linhas sem receita (a receita continua vindo da regra de padrão):

main.o util.o: util.h
main.o frete.o: frete.h
$ make
gcc -std=c23 -Wall -Wextra -c main.c -o main.o
gcc -std=c23 -Wall -Wextra -c util.c -o util.o
gcc main.o util.o frete.o -o app -lm

Agora sim: o util.h era mais novo que o main.o e o util.o, e os dois foram refeitos. O frete.o, que não inclui o util.h, ficou como estava. Escrever essas linhas à mão funciona, mas é fácil esquecer uma quando você acrescenta um #include. A seção 5 mostra como deixar o gcc escrevê-las.

Mudar uma variável na hora: make CC=clang

Uma variável escrita na linha de comando vale mais que a do Makefile. Para compilar o projeto inteiro com o Clang, sem mudar o arquivo:

$ make clean
rm -f app main.o util.o frete.o
$ make CC=clang
clang -std=c23 -Wall -Wextra -c main.c -o main.o
clang -std=c23 -Wall -Wextra -c util.c -o util.o
clang -std=c23 -Wall -Wextra -c frete.c -o frete.o
clang main.o util.o frete.o -o app -lm
$ ./app | tail -2
Frete (23 km): R$ 15.00
--------------------

5. Dependências automáticas: -MMD -MP

O gcc sabe exatamente quais .h cada .c inclui: afinal, foi ele que os leu. Com as opções -MMD -MP, ele grava essa lista num arquivo .d (de dependências) ao lado de cada .o, já no formato do make. Este é o Makefile completo da Ana, um bom modelo para os seus projetos:

CC = gcc
CFLAGS = -std=c23 -Wall -Wextra -MMD -MP
LDLIBS = -lm
OBJS = main.o util.o frete.o
DEPS = $(OBJS:.o=.d)

app: $(OBJS)
	$(CC) $^ -o $@ $(LDLIBS)

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

clean:
	rm -f app $(OBJS) $(DEPS)

.PHONY: clean

-include $(DEPS)
-MMD
Opção do gcc: enquanto compila, grave no .d a lista dos .h que este .c incluiu (só os seus, não os do sistema, como o stdio.h).
-MP
Acrescente ao .d uma regra vazia para cada .h. Assim, se você apagar ou renomear um .h, o make não para com erro.
DEPS = $(OBJS:.o=.d)
"A lista OBJS, trocando .o por .d no fim de cada nome": main.d util.d frete.d.
-include $(DEPS)
Leia esses arquivos como se fizessem parte do Makefile. O - na frente quer dizer "se não existirem, tudo bem" (na primeira vez, ainda não existem).
$ make
gcc -std=c23 -Wall -Wextra -MMD -MP -c main.c -o main.o
gcc -std=c23 -Wall -Wextra -MMD -MP -c util.c -o util.o
gcc -std=c23 -Wall -Wextra -MMD -MP -c frete.c -o frete.o
gcc main.o util.o frete.o -o app -lm
$ cat main.d
main.o: main.c util.h frete.h
util.h:
frete.h:

O main.d é a linha que a Ana escrevia à mão, agora escrita pelo gcc (mais as duas regras vazias do -MP). Teste com o frete.h, que só o main.c e o frete.c incluem:

$ touch frete.h
$ make
gcc -std=c23 -Wall -Wextra -MMD -MP -c main.c -o main.o
gcc -std=c23 -Wall -Wextra -MMD -MP -c frete.c -o frete.o
gcc main.o util.o frete.o -o app -lm
$ make
make: 'app' está atualizado.

Exatamente os dois certos. Se amanhã o util.c passar a incluir o frete.h, o util.d é reescrito na próxima compilação, sem a Ana mexer no Makefile.

Compilar em paralelo: make -j

Os três .o não dependem uns dos outros. O -j4 (de jobs, "tarefas") deixa o make rodar até quatro comandos ao mesmo tempo, um por núcleo do processador:

$ make clean
rm -f app main.o util.o frete.o main.d util.d frete.d
$ make -j4
gcc -std=c23 -Wall -Wextra -MMD -MP -c main.c -o main.o
gcc -std=c23 -Wall -Wextra -MMD -MP -c util.c -o util.o
gcc -std=c23 -Wall -Wextra -MMD -MP -c frete.c -o frete.o
gcc main.o util.o frete.o -o app -lm

Com três arquivos pequenos, não se nota diferença (e a ordem das linhas pode mudar de uma vez para outra). Num projeto com centenas de .c, o -j divide o tempo de compilação pelo número de núcleos. A ligação sempre espera todos os .o ficarem prontos.


Formas diferentes e qual usar

FormaBom paraRuim porque
Uma regra só, com o gcc de todos os .c (seção 1)Dois ou três arquivos; não ter de lembrar o comandoRecompila tudo quando qualquer coisa muda
Uma regra por .o, escrita à mão (seção 2)Entender como o make pensaRepetição; cada #include novo tem de ir para o Makefile
Variáveis + %.o: %.c + -MMD -MP (seção 5)Qualquer projeto C no Linux, pequeno ou médioMais coisas para entender na primeira vez
CMakeProjetos grandes, que compilam também no Windows e no macOSMais uma ferramenta para aprender; ele próprio gera um Makefile

Qual usar? Copie o Makefile da seção 5 e troque a lista OBJS e o nome app. Ele serve para quase todo projeto de estudo. Quando o projeto precisar compilar em outros sistemas, passe para o CMake.


Quando dá errado

Makefile:2: *** faltando o separador. Pare. (missing separator. Stop.)

O que significa: a receita na linha 2 começa com espaços em vez de TAB (ou a linha não é nem regra, nem variável, nem receita).

Como consertar: confira com cat -A Makefile: toda receita começa com ^I. Apague os espaços e digite um TAB.

make: *** Nenhum alvo indicado e nenhum arquivo make encontrado. Pare.

O que significa: não há Makefile na pasta atual (em inglês: No targets specified and no makefile found. Stop.).

Como consertar: entre na pasta do projeto (cd) e confira o nome: Makefile (ou makefile), sem extensão. Um Makefile.txt salvo pelo editor de texto não vale.

make: *** Sem regra para processar o alvo 'fretes.o', necessário por 'app'. Pare.

O que significa: o app precisa de um fretes.o, e o make não sabe fazê-lo: não há regra para ele, e a regra %.o: %.c não serve, porque não existe fretes.c. Em inglês: No rule to make target 'fretes.o', needed by 'app'. Stop. Com um .h que não existe, a frase é a mesma: Sem regra para processar o alvo 'fretes.h', necessário por 'main.o'.

Como consertar: quase sempre é um nome digitado errado no Makefile (aqui, fretes em vez de frete) ou um arquivo que ficou em outra pasta.

make: *** [Makefile:10: main.o] Error 1

O que significa: o comando da receita do main.o (linha 10 do Makefile da seção 4, a da regra %.o: %.c) falhou. O make não erra: ele só repassa o erro do gcc e para. Por exemplo, com o ; faltando depois das três chamadas linha() do main.c:

$ make
gcc -std=c23 -Wall -Wextra -c main.c -o main.o
main.c: In function ‘main’:
main.c:6:12: error: expected ‘;’ before ‘printf’
    6 |     linha()
      |            ^
      |            ;
    7 |     printf("Lojinha da Ana\n");
      |     ~~~~~~
main.c:8:12: error: expected ‘;’ before ‘printf’
    8 |     linha()
      |            ^
      |            ;
    9 |     printf("3 cadernos: R$ %d\n", total_compra(12, 3));
      |     ~~~~~~
main.c:11:12: error: expected ‘;’ before ‘return’
   11 |     linha()
      |            ^
      |            ;
   12 |     return 0;
      |     ~~~~~~
make: *** [Makefile:10: main.o] Error 1

Como consertar: leia as linhas de cima, que são do gcc (aqui, um erro de sintaxe no main.c), conserte e rode o make de novo. O que já tinha ficado pronto não é refeito.

make: 'app' está atualizado., mas você mudou um .h

O que significa: o .h não aparece como pré-requisito de nenhum .o. O make não lê os #include.

Como consertar: use -MMD -MP e o -include $(DEPS) da seção 5. Na hora do aperto, um make clean seguido de make recompila tudo.

make: 'clean' está atualizado.

O que significa: existe um arquivo chamado clean na pasta, e o alvo clean não foi declarado em .PHONY.

Como consertar: acrescente .PHONY: clean (e os outros alvos que não são arquivos).

referência não definida para "ceil" (ou sqrt, pow) dentro do make

O que significa: faltou ligar a biblioteca de matemática.

Como consertar: LDLIBS = -lm, e o $(LDLIBS) no fim da receita de ligação.


Referência

No MakefileO que quer dizer
alvo: pré-requisitos + receita com TABUma regra: faça o alvo com a receita se ele não existir ou se algum pré-requisito for mais novo
NOME = valor e $(NOME)Criar e usar uma variável
CC, CFLAGS, LDLIBSCompilador, opções de compilação, bibliotecas (-lm)
$@O alvo da regra
$<O primeiro pré-requisito
$^Todos os pré-requisitos
%.o: %.cRegra de padrão: qualquer .o a partir do .c de mesmo nome
main.o: util.h (sem receita)Só acrescenta um pré-requisito
.PHONY: cleanclean não é arquivo
$(OBJS:.o=.d)A lista, trocando .o por .d
-include arquivosLer outros arquivos de regras, sem erro se não existirem
No terminalO que faz
makeFaz o primeiro alvo do Makefile
make util.oFaz só esse alvo (e o que ele precisar)
make cleanRoda a tarefa clean
make -nMostra os comandos, sem rodar
make -j4Até 4 comandos ao mesmo tempo
make CC=clangTroca uma variável só desta vez
touch arquivoMuda a data do arquivo para agora (como se tivesse sido editado)
cat -A MakefileMostra os TABs (^I)

Exercícios

1. O que começa a receita

Que caractere tem de vir no começo de cada linha de receita? Escreva o nome da tecla:

2. O alvo da regra

Complete a receita para que o .o gerado tenha o nome do alvo, sem escrever o nome:

estoque.o: estoque.c estoque.h
    $(CC) $(CFLAGS) -c estoque.c -o

3. A biblioteca de matemática

Complete a variável que faz o make ligar a biblioteca do math.h:

LDLIBS =

4. No Makefile da seção 2, a Ana mudou só o main.c. O que o make roda?

5. Na regra util.o: util.c util.h, quanto vale $<?

Missão final: escreva a regra de ouro

Missão: o mini-make abaixo está quase pronto, mas a função precisa_refazer sempre responde false. Escreva o corpo dela: devolva true se algum pré-requisito da regra tiver hora maior que a do alvo (use a hora, que já está pronta). O main roda make (nada a fazer), depois touch util.h e make de novo. A saída deve ser igual à do make de verdade, na seção 2:

$ make
make: 'app' está atualizado.
$ touch util.h
$ make
gcc -std=c23 -Wall -Wextra -c main.c
gcc -std=c23 -Wall -Wextra -c util.c
gcc main.o util.o -o app
#include <stdio.h>
#include <string.h>

typedef struct {
    const char *nome;
    int hora;
} Arquivo;

Arquivo arquivos[] = {
    {"main.c", 10}, {"util.c", 10}, {"util.h", 10},
    {"main.o", 20}, {"util.o", 20}, {"app", 30},
};
#define N_ARQUIVOS 6

typedef struct {
    const char *alvo;
    const char *deps[2];
    const char *receita;
} Regra;

Regra regras[] = {
    {"main.o", {"main.c", "util.h"}, "gcc -std=c23 -Wall -Wextra -c main.c"},
    {"util.o", {"util.c", "util.h"}, "gcc -std=c23 -Wall -Wextra -c util.c"},
    {"app",    {"main.o", "util.o"}, "gcc main.o util.o -o app"},
};
#define N_REGRAS 3

int relogio = 100;

int hora(const char *nome) {
    for (int i = 0; i < N_ARQUIVOS; i++) {
        if (strcmp(arquivos[i].nome, nome) == 0) {
            return arquivos[i].hora;
        }
    }
    return 0;   // arquivo que não existe: "hora zero", mais velho que tudo
}

void touch(const char *nome) {
    relogio++;
    for (int i = 0; i < N_ARQUIVOS; i++) {
        if (strcmp(arquivos[i].nome, nome) == 0) {
            arquivos[i].hora = relogio;
        }
    }
}

bool precisa_refazer(const Regra *r) {
    // escreva aqui: percorra r->deps[0] e r->deps[1]
    // e compare a hora de cada um com hora(r->alvo)
    (void)r;   // (esta linha só cala o aviso de parâmetro não usado; pode apagar)
    return false;
}

void make(void) {
    bool fez_algo = false;
    for (int i = 0; i < N_REGRAS; i++) {
        if (precisa_refazer(&regras[i])) {
            printf("%s\n", regras[i].receita);
            touch(regras[i].alvo);
            fez_algo = true;
        }
    }
    if (!fez_algo) {
        printf("make: 'app' está atualizado.\n");
    }
}

int main(void) {
    printf("$ make\n");
    make();
    printf("$ touch util.h\n");
    touch("util.h");
    printf("$ make\n");
    make();
    return 0;
}
#include <stdio.h>
#include <string.h>

typedef struct {
    const char *nome;
    int hora;
} Arquivo;

Arquivo arquivos[] = {
    {"main.c", 10}, {"util.c", 10}, {"util.h", 10},
    {"main.o", 20}, {"util.o", 20}, {"app", 30},
};
#define N_ARQUIVOS 6

typedef struct {
    const char *alvo;
    const char *deps[2];
    const char *receita;
} Regra;

Regra regras[] = {
    {"main.o", {"main.c", "util.h"}, "gcc -std=c23 -Wall -Wextra -c main.c"},
    {"util.o", {"util.c", "util.h"}, "gcc -std=c23 -Wall -Wextra -c util.c"},
    {"app",    {"main.o", "util.o"}, "gcc main.o util.o -o app"},
};
#define N_REGRAS 3

int relogio = 100;

int hora(const char *nome) {
    for (int i = 0; i < N_ARQUIVOS; i++) {
        if (strcmp(arquivos[i].nome, nome) == 0) {
            return arquivos[i].hora;
        }
    }
    return 0;   // arquivo que não existe: "hora zero", mais velho que tudo
}

void touch(const char *nome) {
    relogio++;
    for (int i = 0; i < N_ARQUIVOS; i++) {
        if (strcmp(arquivos[i].nome, nome) == 0) {
            arquivos[i].hora = relogio;
        }
    }
}

bool precisa_refazer(const Regra *r) {
    for (int j = 0; j < 2; j++) {
        if (hora(r->deps[j]) > hora(r->alvo)) {
            return true;
        }
    }
    return false;
}

void make(void) {
    bool fez_algo = false;
    for (int i = 0; i < N_REGRAS; i++) {
        if (precisa_refazer(&regras[i])) {
            printf("%s\n", regras[i].receita);
            touch(regras[i].alvo);
            fez_algo = true;
        }
    }
    if (!fez_algo) {
        printf("make: 'app' está atualizado.\n");
    }
}

int main(void) {
    printf("$ make\n");
    make();
    printf("$ touch util.h\n");
    touch("util.h");
    printf("$ make\n");
    make();
    return 0;
}

Resumo

  • O make lê o Makefile e refaz um alvo quando ele não existe ou quando um pré-requisito é mais novo. Sem argumento, faz o primeiro alvo.
  • Uma regra é alvo: pré-requisitos e, embaixo, a receita, que começa com TAB. Com espaços: faltando o separador (missing separator).
  • Uma regra por .o faz o make recompilar só o que mudou; make -n mostra o que ele faria.
  • Variáveis: CC, CFLAGS, LDLIBS = -lm. Automáticas: $@ (alvo), $< (primeiro pré-requisito), $^ (todos). %.o: %.c vale para todos os .o.
  • clean apaga o que foi gerado; .PHONY: clean diz que ele não é arquivo.
  • O make não lê os #include: use -MMD -MP e -include $(DEPS) para o gcc escrever as dependências dos .h.
  • make: *** [Makefile:N: alvo] Error 1 só diz onde parou; o erro de verdade está nas linhas de cima. Para projetos que compilam em vários sistemas, o próximo passo é o CMake.