(Muito breve) Introdução ao Make
- Introdução
- Make
- Makefiles
- Makefiles com múltiplos targets
- Estruturando a Makefile
- Como o Make adivinha o que tem de fazer
- Conclusão
- Recursos
Introdução
Neste post, iremos ver o que é o comando make
e algumas aplicações muito simples deste comando. Ao longo deste texto, abordaremos não só o significado e a função principal do comando make
, mas também exemplos práticos e simples de como ele pode ser utilizado.
Make
O make
é uma aplicação que permite automatizar a criação de código. É um programa já com alguma idade (foi criado em 1976), mas mantém-se ainda (muito) útil. Podem ver mais alguma informação na página da Wikipedia. Hoje em dia e no caso dos PCs, a versão mais comum do make
é da GNU, que é o GNU make, ou gmake.
Makefiles
A informação para o make
“fazer a sua magia” está nos ficheiros que contêm as regras (as instruções) para o make
saber o que tem a fazer. Estes ficheiros, que normalmente têm o nome de Makefile definem dependências entre ficheiros. Se, para ter um programa chamado hello
, eu preciso de compilar o ficheiro hello.c
, isso significa que o hello
depende de hello.c
. Neste caso, e em linguagem make
, o hello
é o target e o hello.c
é a dependency. As regras de uma Makefile destinam-se a definir os comandos que têm que ser executados para criar o target a partir das dependencies. Por exemplo, no caso anterior, a regra seria:
hello : hello.c
gcc -o hello hello.c
Com esta regra, o make
vai verificar se é preciso criar o target hello
. O target terá que ser criado se:
- Não existir o ficheiro
hello
. - Se o ficheiro
hello.c
for mais recente que ohello
(significa que ohello.c
já foi editado após se ter compilado ohello
pela última vez)
Se se verificar qualquer uma destas condições, o target tem de ser criado de novo e o make
lança os comandos respetivos.
Vamos supor que temos, numa determinada pasta, o ficheiro hello.c
e a respetiva Makefile, como está acima. Lançando o comando make
:
$ ls
hello.c Makefile
$ make
gcc -o hello hello.c
$ ls
hello hello.c Makefile
Se, imediatamente a seguir, lançarmos novamente o comando make
, ele vai verificar que não houve alteração do hello.c
e que não é preciso fazer nada:
$ make
make: 'hello' is up to date.
Makefiles com múltiplos targets
Uma Makefile não precisa de ter apenas um target (raramente tem). Podemos criar Makefiles com vários targets. Para selecionarmos o target a criar, damos o nome desse target como argumento ao make
. Por exemplo, podemos ter um target que apaga os ficheiros criados; neste caso, a Makefile ficaria:
hello : hello.c
gcc -o hello hello.c
clean :
rm -f hello
Se quisermos apagar os ficheiros através do make, damos o comando make clean
e o make fai executar as ações associadas ao target clean
:
$ make clean
rm -f hello
A grande vantagem do make
(e dos seus sucessores, como cmake e outros) está na gestão de projetos com um grande número de ficheiros. Consideremos o exemplo simples, de um programa (zcalc
) que serve para fazer operações com números complexos e que utiliza um módulo com funções para trabalhar com números complexos. Este módulo é composto por dois ficheiros, complex.c
e complex.h
.
Uma Makefile simples para compilar este programa seria:
zcalc : zcalc.c complex.c
gcc -o zcalc zcalc.c complex.c
O ficheiro zcalc_simples.zip contém uma estrutura de ficheiros com o exemplo apresentado aqui.
Estruturando a Makefile
Embora seja possível utilizar a estrutura acima, o que encontramos muitas vezes são Makefiles como a seguinte:
zcalc : zcalc.o complex.o
gcc -o zcalc zcalc.o complex.o
zcalc.o : zcalc.c
gcc -c zcalc.c
complex.o : complex.c complex.h
gcc -c complex.c
Ou seja, em vez de se obter diretamente o resultado final através dos ficheiros source (.c
), indica-se explicitamente os ficheiro objeto (.o
) de que depende o programa principal e indicamos a forma de obter esses ficheiro objeto intermédios.1
Porquê complicar, então? Num programa do tamanho do zcalc não faz muita diferença (falamos de pouco mais de 100 linhas de código); compilar tudo demora menos de 1/10 de segundo. Por isso, não nos custa passar todo o código pelo compilador C. Só que, em geral, os programas são muito maiores que isso. Indo para o outro extremo: o kernel do Linux tem cerca de 8 milhões de linhas de código, o Firefox são 21 milhões de linhas de código e o Windows 11, 50 milhões2. Se alteramos alguma coisa num destes programas, não queremos recompilar todo o código. Apenas a parte que for necessária. Por isso, as Makefiles (ou os seus equivalentes) desdobram o programa em partes, para só ter de passar pelo compilador o novo código e tudo aquilo (e apenas aquilo) que depende do código alterado. Desta forma, numa Makefile como a que está acima, se se alterar o ficheiro zcalc.c
, só há dois targets que têm de ser recriados: zcalc.o
e zcalc
. Não é necessário recriar o target complex.o
e utiliza-se o que já existe. É fácil de imaginar a diferença que isto pode fazer num programa com 100 ou mais targets diferentes (especialmente se considerarmos que uma boa prática, quando se está a programar, é compilar frequentemente).
O ficheiro zcalc_struct.zip contém os ficheiros com o exemplo agora apresentado.
Como o Make adivinha o que tem de fazer
Até agora, indicámos explicitamente ao make
quais os comandos a executar. No entanto, o make tem a capacidade de “adivinhar” o que há para ser feito a partir do que se chama regras implícitas. Há muita coisa a dizer sobre as regras implícitas do make, mas iremos focar apenas o essencial.
Consideremos o exemplo desta Makefile:
CC=gcc
CFLAGS=-Wall -Wextra -pedantic
zcalc : zcalc.o complex.o
gcc -o zcalc zcalc.o complex.o
clean:
rm -f *.o
cleanall: clean
rm -f zcalc
Esta estrutura de ficheiro é disponibilizada em zcalc_implicit.zip.
Em relação ao exemplo anterior, vemos algumas diferenças:
- apareceram umas linhas com
=
no começo da Makefile; - as regras para criar os ficheiros
.o
a partir dos ficheiros.c
desapareceram; - há algumas regras (
clean
ecleanall
) que servem, não para criar, mas para apagar ficheiros (fazer “limpezas”).
No entanto, embora faltem as regras para gerar os ficheiros objeto, esta Makefile continua a funcionar:
$ make
gcc -Wall -Wextra -pedantic -c -o zcalc.o zcalc.c
gcc -Wall -Wextra -pedantic -c -o complex.o complex.c
gcc -o zcalc zcalc.o complex.o
Como foi isto possível? Adivinhou o que era para fazer?
O ficheiro Makefile consegue resolver e determinar o que tem para fazer, neste caso, usando a primeira regra implícita do make:
Compiling C programs
n.o is made automatically from n.c with a recipe of the form ‘$(CC) $(CPPFLAGS) $(CFLAGS) -c’.
De acordo com esta regra e se nada for dito em contrário, um ficheiro .o
é gerado a partir de um ficheiro .c
que tenha o mesmo nome base. Neste caso, zcalc.o
é criado a partir de zcalc.c
e complex.o
a partir de complex.c
. Isto permite ao make saber como criar as dependências zcalc.o
e complex.o
para as quais não há regra explícita.
A receita acima também ajuda a explicar o significado das duas primeiras linhas da makefile: GCC
é a variável que guarda o nome do compilador de C (neste caso, gcc
) e CFLAGS
são as flags3 a dar ao compilador. Neste caso (info retirada do man gcc
):
-Wall
This enables all the warnings about constructions that some users
consider questionable, and that are easy to avoid (or modify to
prevent the warning), even in conjunction with macros. This also
enables some language-specific warnings described in C++ Dialect
Options and Objective-C and Objective-C++ Dialect Options.
-Wextra
This enables some extra warning flags that are not enabled by
-Wall. (This option used to be called -W. The older name is still
supported, but the newer name is more descriptive.)
-pedantic
Issue all the warnings demanded by strict ISO C and ISO C++; reject
all programs that use forbidden extensions, and some other programs
that do not follow ISO C and ISO C++. For ISO C, follows the
version of the ISO C standard specified by any -std option used.
A compilação de cada um dos ficheiros .c
para gerar o .o
é assim feita com o comando:
$ gcc -Wall -Wextra -pedantic -c -o <ficheiro>.o <ficheiro>.c
em que <ficheiro>
é o nome base do ficheiro. Reparem que os comandos até -o
resultam diretamente da regra enunciada acima.
Conclusão
Vimos aqui, brevemente, quais as capacidades do make. O make permite automatizar a compilação de programas e retirar do programador a necessidade de saber quais os comandos a correr para atualizar o código. Vimos também que a escrita das Makefiles pode ser feita usando regras explícitas ou implícitas.
Há muito mais de que se poderia falar sobre o comando make e Makefiles. O que está aqui é apenas uma breve apresentação. Para ir mais longe, há muita informação disponivel, como o manual do make da GNU.
Recursos
Os ficheiros seguintes contêm, em forma compactada, pastas com os exemplos referidos acima:
- zcalc_simples.zip: versão mais simples da Makefile
- zcalc_struct.zip: Makefile com targets separados por ficheiro fonte.
- zcalc_implicit.zip: Mekfile com targets implícitos.
-
Na realidade, em qualquer das alternativas, os ficheiros objeto são sempre criados. A diferença é que, na primeira opção (
gcc -o zcalc zcalc.c complex.c
), eles são criados como ficheiros temporários que são apagados assim que são utilizados e não ficam em disco. ↩ -
Embora neste último número esteja incluída muita coisa e muitos programas diferentes. Mas dá uma ideia… ↩
-
espero que compreendam que não dá para traduzir… ↩
Comments
Your comments are welcome. Feel free to leave here your remarks or your opinion!