====== O sistema Make ======
{{ progc_make.mkv |Video desta aula}}
O ''make'' é um programa utilitário Unix ([[https://en.wikipedia.org/wiki/Make_%28software%29|criado em 1976]]) responsável por organizar o processo de compilação e ligação do código-fonte de um projeto, para a geração do programa executável.
O Make é uma ferramenta essencial em grandes projetos, quando há muitos arquivos envolvidos, ou quando a compilação usa muitos //flags// e opções.
===== O makefile =====
Para compilar um projeto, o programa ''make'' lê um arquivo ''Makefile'' ou ''makefile'', que contém as definições de comandos de compilação/ligação e as regras de dependência entre os diversos arquivos do projeto.
Um arquivo ''Makefile'' é composto de uma ou mais regras, cuja estrutura básica é a seguinte:
# comentário
alvo: dependência dependência dependência ...
receita
receita
...
Onde:
* **alvo**: nome do objeto a ser construído (geralmente um nome de arquivo ou uma ação sobre os arquivos)
* **dependência**: alvos (ou arquivos) dos quais este alvo depende para ser construído
* **receita**: comandos necessários para compilar/construir/gerar o arquivo alvo
A endentação das linhas de comando abaixo de cada regra deve ser feita com **tabulação** (TAB) e nunca com espaços em branco.
===== Exemplo =====
Considere um programa C composto pelos seguintes arquivos:
#ifndef __ESCREVA__
#define __ESCREVA__
void escreva (char *msg) ;
#endif
#include
void escreva (char *msg)
{
printf ("%s", msg) ;
}
#include "escreva.h"
int main ()
{
escreva ("Hello, world!\n") ;
return (0) ;
}
O arquivo a seguir define uma regra mínima para compilar o programa e gerar o executável ''hello'':
hello: hello.c escreva.c escreva.h
gcc -Wall hello.c escreva.c -o hello
A regra acima significa, literalmente:
* ''hello'' depende de ''hello.c'', ''escreva.c'' e ''escreva.h'': se algum destes tiver sido modificado, o alvo ''hello'' deve ser recompilado;
* para (re)compilar o alvo ''hello'', deve-se executar o comando ''gcc -Wall hello.c escreva.c -o hello''.
Ao executar o comando ''make hello'' será gerado o executável ''hello'':
$ ls
escreva.c escreva.h hello.c Makefile
$ make
gcc hello.c escreva.c -o hello -Wall
$ ls
escreva.c escreva.h hello hello.c Makefile
Caso nenhum alvo seja indicado na linha de comando do ''make'', por default o **primeiro alvo** definido no arquivo ''Makefile'' é ativado. Assim, ''make'' e ''make hello'' irão gerar o mesmo resultado, no exemplo anterior.
Uma característica essencial do sistema ''make'' é a verificação automática da necessidade de reconstruir os alvos, através da análise das datas dos arquivos declarados como dependências: um alvo só é reconstruído se alguma de suas dependências tiver sido modificada (ou seja, se a data de algum dos arquivos correspondentes for mais recente que a data do arquivo-alvo).
===== Regras de compilação e de ligação =====
Podem ser definidas regras separadas para compilação e ligação, agilizando a construção de sistemas com muitos arquivos:
# regras de ligação
hello: hello.o escreva.o
gcc hello.o escreva.o -o hello
# regras de compilação
hello.o: hello.c escreva.h
gcc -c hello.c -Wall
escreva.o: escreva.c escreva.h
gcc -c escreva.c -Wall
Assim, como as regras do arquivo ''Makefile'' acima definem que ''hello'' depende de ''hello.o'' e ''escreva.o''; assim a receita da regra ''hello'' só será ativada caso os arquivos ''hello.o'' ou ''escreva.o'' sejam **mais recentes** que o arquivo ''hello'' (ou não existam). O mesmo ocorre entre ''hello.o'' e ''hello.c'', e assim por diante.
Exemplo (o comando ''touch'' atualiza a data de um arquivo):
$ make
make: Nada a ser feito para `hello'.
$ touch hello.c
$ make
gcc -c hello.c -Wall
gcc hello.o escreva.o -o hello -Wall
$ touch escreva.c
$ make
gcc -c escreva.c -Wall
gcc hello.o escreva.o -o hello -Wall
$ touch escreva.h
$ make
gcc -c hello.c -Wall
gcc -c escreva.c -Wall
gcc hello.o escreva.o -o hello -Wall
===== Regras usuais =====
As seguintes regras são usualmente encontradas em arquivos ''Makefile'':
* ''all'': constrói todos os alvos definidos no ''Makefile''; costuma ser a primeira regra, ativada por default.
* ''clean'': remove arquivos temporários como os arquivos objeto (''.o''), backups de editor, etc.
* ''purge'': remove os arquivos temporários e os alvos construídos, preservando somente os arquivos-fonte do projeto (também são usados os nomes ''mrproper'' e ''distclean'').
* ''debug'': mesmo que ''all'', mas incluindo //flags// de depuração, como ''-g'' e ''-DDEBUG'', por exemplo.
* ''test'': compila e executa procedimentos de teste.
# regra default (1a)
all: hello
...
# remove arquivos temporários
clean:
-rm -f *~ *.o
# remove tudo o que não for o código-fonte original
purge: clean
-rm -f hello
Por default, o ''make'' interrompe a execução quando uma receita resulta em erro; para evitar esse comportamento, pode-se adicionar o caractere "-" no início da receita cujo erro se deseja ignorar (como nas receitas das regras ''clean'' e ''purge'' acima).
===== Variáveis =====
Variáveis podem ser criadas dentro do ''Makefile'' para facilitar a escrita de regras envolvendo muitos arquivos. O exemplo a seguir ilustra a sintaxe de criação e uso de variáveis:
# Makefile de exemplo (Manual do GNU Make)
CFLAGS = -Wall # flags de compilacao
LDLIBS = -lm # bibliotecas a ligar
# arquivos-objeto
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects) $(LDLIBS)
main.o : main.c defs.h
cc -c main.c $(CFLAGS)
kbd.o : kbd.c defs.h command.h
cc -c kbd.c $(CFLAGS)
command.o : command.c defs.h command.h
cc -c command.c $(CFLAGS)
display.o : display.c defs.h buffer.h
cc -c display.c $(CFLAGS)
insert.o : insert.c defs.h buffer.h
cc -c insert.c $(CFLAGS)
search.o : search.c defs.h buffer.h
cc -c search.c $(CFLAGS)
files.o : files.c defs.h buffer.h command.h
cc -c files.c $(CFLAGS)
utils.o : utils.c defs.h
cc -c utils.c $(CFLAGS)
clean :
-rm -f edit $(objects)
Na declaração da variável ''objects'', observe como quebrar linhas muito longas!
Regras podem ser usadas para alterar o valor de variáveis. No exemplo abaixo, a regra ''debug'' adiciona alguns flags à variável ''CFLAGS'' e em seguida chama a regra ''all'':
# compila com flags de depuração
debug: CFLAGS += -DDEBUG -g
debug: all
===== Regras implícitas =====
O sistema ''Make'' possui um conjunto de receitas e regras implícitas para as operações mais usuais. Receitas e regras implícitas não precisam ser declaradas, o que simplifica o arquivo ''Makefile''.
A geração de arquivo-objeto (''.o'') a partir do código-fonte C correspondente (''.c'') usa esta receita implícita:
$(CC) $(CPPFLAGS) $(CFLAGS) -c fonte.c
A ligação de arquivos-objeto (''.o'') para a geração do executável usa esta receita implícita:
$(CC) $(LDFLAGS) arq1.o arq2.o ... $(LDLIBS) -o executavel
As variáveis usadas nessas regras implícitas têm os seguintes significados:
^ variável ^ significado ^ exemplo ^
| ''CC'' | compilador a ser usado (por default ''cc'') | ''CC = clang'' |
| ''CPPFLAGS'' | opções para o preprocessador | ''CPPFLAGS = -DDEBUG -I/opt/opencv/include''|
| ''CFLAGS'' | opções para o compilador | ''CFLAGS = -std=c99''|
| ''LDFLAGS'' | opções para o ligador | ''LDFLAGS = -static -L/opt/opencv/lib'' |
| ''LDLIBS'' | bibliotecas a ligar ao código gerado | ''LDLIBS = -lpthreads -lm'' |
Eis como ficaria nosso ''Makefile'' com variáveis e receitas implícitas:
CFLAGS = -Wall -g # gerar "warnings" detalhados e infos de depuração
objs = hello.o escreva.o
# regra default (primeira regra)
all: hello
# regras de ligacao
hello: $(objs)
# regras de compilação
hello.o: hello.c escreva.h
escreva.o: escreva.c escreva.h
# remove arquivos temporários
clean:
-rm -f $(objs) *~
# remove tudo o que não for o código-fonte
purge: clean
-rm -f hello
Uma lista de receitas e regras implícitas para várias linguagens pode ser encontrada [[http://www.gnu.org/software/make/manual/make.html#Implicit-Rules|nesta página]].
===== Tópicos avançados =====
Este pequeno tutorial apenas "arranha a superfície" do sistema ''Make'', mas é suficiente para o uso em projetos mais simples. Contudo, existem muitos tópicos avançados não tratados aqui, como:
* Variáveis automáticas
* Regras com padrões
* Condicionais
* //Wildcards//
* Makefiles recursivos
* Avaliação de regras em paralelo
* Geração automática de dependências
* ...
Esses e outros tópicos podem ser estudados em maior profundidade no [[http://www.gnu.org/software/make/manual/make.html|Manual do GNU Make]].