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.
- A escrever uma regra: alvo, pré-requisitos e receita (que começa com TAB).
- Como o
makedecide 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, omake cleane o.PHONY. - A não esquecer os
.h, com-MMD -MP. - Os erros reais do
make:faltando o separador,Sem regra para processar o alvoeError 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 padaria | No make | Exemplo |
|---|---|---|
| O que se quer pronto | O alvo (target) | app, main.o |
| Do que ele depende | Os pré-requisitos (prerequisites) | main.c, util.h |
| Como fazer | A receita (recipe): os comandos | gcc -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
apptem de ser refeito. Outil.hentra porque o código dos dois.cdepende 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
main.o: omain.ce outil.hsão mais velhos que ele. Nada a fazer.util.o: outil.cé mais novo. Recompila.app: outil.oacabou 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 = ®ras[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 porgcconde aparecer. Os nomesCC,CFLAGSeLDLIBSsã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,$@valeutil.o. - $<
- O primeiro pré-requisito. Na regra do
util.o, valeutil.c. (Por isso o.cvem antes do.hna lista.) - clean:
- Um alvo que não é arquivo: uma "tarefa" que apaga o que o
makegerou. 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
.oqualquer, use o.cde mesmo nome." O%é o pedaço que casa: parafrete.o, ele valefrete, e o pré-requisito éfrete.c. - $^
- Todos os pré-requisitos. Na regra do
app, valemain.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
ceilmora 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 alvoapp, na linha 6 doMakefile, terminou com erro (código 1)". Omakepara 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
.da lista dos.hque este.cincluiu (só os seus, não os do sistema, como ostdio.h). - -MP
- Acrescente ao
.duma regra vazia para cada.h. Assim, se você apagar ou renomear um.h, omakenão para com erro. - DEPS = $(OBJS:.o=.d)
- "A lista
OBJS, trocando.opor.dno 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
| Forma | Bom para | Ruim 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 comando | Recompila tudo quando qualquer coisa muda |
Uma regra por .o, escrita à mão (seção 2) | Entender como o make pensa | Repetiçã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édio | Mais coisas para entender na primeira vez |
| CMake | Projetos grandes, que compilam também no Windows e no macOS | Mais 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 Makefile | O que quer dizer |
|---|---|
alvo: pré-requisitos + receita com TAB | Uma 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, LDLIBS | Compilador, opções de compilação, bibliotecas (-lm) |
$@ | O alvo da regra |
$< | O primeiro pré-requisito |
$^ | Todos os pré-requisitos |
%.o: %.c | Regra de padrão: qualquer .o a partir do .c de mesmo nome |
main.o: util.h (sem receita) | Só acrescenta um pré-requisito |
.PHONY: clean | clean não é arquivo |
$(OBJS:.o=.d) | A lista, trocando .o por .d |
-include arquivos | Ler outros arquivos de regras, sem erro se não existirem |
| No terminal | O que faz |
|---|---|
make | Faz o primeiro alvo do Makefile |
make util.o | Faz só esse alvo (e o que ele precisar) |
make clean | Roda a tarefa clean |
make -n | Mostra os comandos, sem rodar |
make -j4 | Até 4 comandos ao mesmo tempo |
make CC=clang | Troca uma variável só desta vez |
touch arquivo | Muda a data do arquivo para agora (como se tivesse sido editado) |
cat -A Makefile | Mostra 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:
$(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:
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(®ras[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(®ras[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
makelê oMakefilee 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é-requisitose, embaixo, a receita, que começa com TAB. Com espaços:faltando o separador(missing separator). - Uma regra por
.ofaz omakerecompilar só o que mudou;make -nmostra o que ele faria. - Variáveis:
CC,CFLAGS,LDLIBS = -lm. Automáticas:$@(alvo),$<(primeiro pré-requisito),$^(todos).%.o: %.cvale para todos os.o. cleanapaga o que foi gerado;.PHONY: cleandiz que ele não é arquivo.- O
makenão lê os#include: use-MMD -MPe-include $(DEPS)para o gcc escrever as dependências dos.h. make: *** [Makefile:N: alvo] Error 1só 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.