Makefile - Guia Rápido

Compilar os arquivos de código-fonte pode ser cansativo, especialmente quando você precisa incluir vários arquivos-fonte e digitar o comando de compilação toda vez que precisar compilar. Makefiles são a solução para simplificar essa tarefa.

Makefiles são arquivos de formato especial que ajudam a construir e gerenciar os projetos automaticamente.

Por exemplo, vamos supor que temos os seguintes arquivos de origem.

  • main.cpp
  • hello.cpp
  • factorial.cpp
  • functions.h

main.cpp

A seguir está o código para o arquivo de origem main.cpp -

#include <iostream>

using namespace std;

#include "functions.h"

int main(){
   print_hello();
   cout << endl;
   cout << "The factorial of 5 is " << factorial(5) << endl;
   return 0;
}

hello.cpp

O código fornecido a seguir é para o arquivo fonte hello.cpp -

#include <iostream>

using namespace std;

#include "functions.h"

void print_hello(){
   cout << "Hello World!";
}

factorial.cpp

O código para factorial.cpp é fornecido abaixo -

#include "functions.h"

int factorial(int n){
   
   if(n!=1){
      return(n * factorial(n-1));
   } else return 1;
}

functions.h

A seguir está o código para fnctions.h -

void print_hello();
int factorial(int n);

A maneira trivial de compilar os arquivos e obter um executável é executando o comando -

gcc  main.cpp hello.cpp factorial.cpp -o hello

Este comando gera o binário hello . Neste exemplo, temos apenas quatro arquivos e sabemos a sequência das chamadas de função. Portanto, é possível digitar o comando acima e preparar um binário final.

No entanto, para um grande projeto onde temos milhares de arquivos de código-fonte, torna-se difícil manter as compilações binárias.

o makecomando permite que você gerencie grandes programas ou grupos de programas. Ao começar a escrever programas grandes, você percebe que recompilar programas grandes leva mais tempo do que recompilar programas curtos. Além disso, você percebe que normalmente trabalha apenas em uma pequena seção do programa (como uma única função), e grande parte do programa restante permanece inalterado.

Na seção subsequente, veremos como preparar um makefile para nosso projeto.

o makeprograma permite que você use macros, que são semelhantes a variáveis. As macros são definidas em um Makefile como = pares. Um exemplo foi mostrado abaixo -

MACROS  = -me
PSROFF  = groff -Tps
DITROFF = groff -Tdvi
CFLAGS  = -O -systype bsd43
LIBS    = "-lncurses -lm -lsdl"
MYFACE  = ":*)"

Macros Especiais

Antes de emitir qualquer comando em um conjunto de regras de destino, existem certas macros especiais predefinidas -

  • $ @ é o nome do arquivo a ser feito.

  • $? são os nomes dos dependentes alterados.

Por exemplo, podemos usar uma regra da seguinte maneira -

hello: main.cpp hello.cpp factorial.cpp
   $(CC) $(CFLAGS) $? $(LDFLAGS) -o [email protected]

Alternatively:

hello: main.cpp hello.cpp factorial.cpp
   $(CC) $(CFLAGS) [email protected] $(LDFLAGS) -o [email protected]

Neste exemplo, $ @ representa hello e $? ou $ @. cpp pega todos os arquivos de origem alterados.

Existem mais duas macros especiais usadas nas regras implícitas. Eles são -

  • $ <o nome do arquivo relacionado que causou a ação.

  • $ * o prefixo compartilhado pelos arquivos de destino e dependentes.

A regra implícita comum é para a construção de arquivos .o (objeto) a partir de .cpp (arquivos fonte).

.cpp.o:
   $(CC) $(CFLAGS) -c $<

Alternatively:

.cpp.o:
   $(CC) $(CFLAGS) -c $*.c

Macros Convencionais

Existem várias macros padrão. Você pode vê-los digitando "make -p" para imprimir os padrões. A maioria é bastante óbvia pelas regras em que são usados.

Essas variáveis ​​predefinidas, ou seja, macros usadas em regras implícitas, se enquadram em duas classes. Eles são os seguintes -

  • Macros que são nomes de programas (como CC)

  • Macros que contêm argumentos dos programas (como CFLAGS).

Abaixo está uma tabela de algumas das variáveis ​​comuns usadas como nomes de programas em regras embutidas de makefiles -

Sr. Não Variáveis ​​e descrição
1

AR

Programa de manutenção de arquivos; o padrão é `ar '.

2

AS

Programa para compilar arquivos de montagem; o padrão é `as '.

3

CC

Programa para compilar programas C; o padrão é `cc '.

4

CO

Programa para fazer check-out de arquivos do RCS; o padrão é `co '.

5

CXX

Programa para compilar programas C ++; o padrão é `g ++ '.

6

CPP

Programa para rodar o pré-processador C, com resultados para saída padrão; o padrão é `$ (CC) -E '.

7

FC

Programa de compilação ou pré-processamento de programas Fortran e Ratfor; o padrão é `f77 '.

8

GET

Programa para extrair um arquivo do SCCS; o padrão é `get '.

9

LEX

Programa a ser usado para transformar gramáticas Lex em código-fonte; o padrão é `lex '.

10

YACC

Programa a ser usado para transformar gramáticas Yacc em código-fonte; o padrão é `yacc '.

11

LINT

Programa a ser usado para executar o lint no código-fonte; o padrão é `lint '.

12

M2C

Programa a ser usado para compilar o código-fonte do Modula-2; o padrão é `m2c '.

13

PC

Programa para compilar programas Pascal; o padrão é `pc '.

14

MAKEINFO

Programa para converter um arquivo fonte Texinfo em um arquivo Info; o padrão é `makeinfo '.

15

TEX

Programa para criar arquivos dvi em TeX a partir de fontes TeX; o padrão é `tex '.

16

TEXI2DVI

Programa para criar arquivos dvi em TeX a partir de fontes Texinfo; o padrão é `texi2dvi '.

17

WEAVE

Programa para traduzir Web em TeX; o padrão é `weave '.

18

CWEAVE

Programa para traduzir C Web em TeX; o padrão é `cweave '.

19

TANGLE

Programa para traduzir Web em Pascal; o padrão é `tangle '.

20

CTANGLE

Programa para traduzir C Web em C; o padrão é `ctangle '.

21

RM

Comando para remover um arquivo; o padrão é `rm -f '.

Aqui está uma tabela de variáveis ​​cujos valores são argumentos adicionais para os programas acima. Os valores padrão para todos eles é a string vazia, a menos que seja indicado o contrário.

Sr. Não. Variáveis ​​e descrição
1

ARFLAGS

Sinalizadores para fornecer o programa de manutenção de arquivos; o padrão é `rv '.

2

ASFLAGS

Sinalizadores extras para fornecer ao montador quando explicitamente invocado em um arquivo `.s 'ou` .S'.

3

CFLAGS

Sinalizadores extras para fornecer ao compilador C.

4

CXXFLAGS

Sinalizadores extras para fornecer ao compilador C.

5

COFLAGS

Sinalizadores extras para fornecer ao programa RCS co.

6

CPPFLAGS

Sinalizadores extras para fornecer ao pré-processador C e aos programas que o utilizam (como compiladores C e Fortran).

7

FFLAGS

Sinalizadores extras para fornecer ao compilador Fortran.

8

GFLAGS

Sinalizadores extras para fornecer ao programa get do SCCS.

9

LDFLAGS

Sinalizadores extras para fornecer aos compiladores quando eles deveriam invocar o linker, `ld '.

10

LFLAGS

Bandeiras extras para dar a Lex.

11

YFLAGS

Bandeiras extras para dar ao Yacc.

12

PFLAGS

Sinalizadores extras para fornecer ao compilador Pascal.

13

RFLAGS

Sinalizadores extras para fornecer ao compilador Fortran para programas Ratfor.

14

LINTFLAGS

Sinalizadores extras para dar ao fiapo.

NOTE - Você pode cancelar todas as variáveis ​​usadas por regras implícitas com a opção '-R' ou '--no-builtin-variables'.

Você também pode definir macros na linha de comando, conforme mostrado abaixo -

make CPP = /home/courses/cop4530/spring02

É muito comum que um binário final dependa de vários códigos-fonte e arquivos de cabeçalho de origem. Dependências são importantes porque permitem quemakeConhecido sobre a origem de qualquer destino. Considere o seguinte exemplo -

hello: main.o factorial.o hello.o
   $(CC) main.o factorial.o hello.o -o hello

Aqui, nós dizemos o makeque hello depende dos arquivos main.o, factorial.o e hello.o. Portanto, sempre que houver uma alteração em qualquer um desses arquivos-objeto,make entrará em ação.

Ao mesmo tempo, precisamos dizer ao makecomo preparar arquivos .o. Portanto, precisamos definir essas dependências também da seguinte maneira -

main.o: main.cpp functions.h
   $(CC) -c main.cpp

factorial.o: factorial.cpp functions.h
   $(CC) -c factorial.cpp

hello.o: hello.cpp functions.h
   $(CC) -c hello.cpp

Vamos agora aprender as regras do Makefile.

A sintaxe geral de uma regra de destino Makefile é -

target [target...] : [dependent ....]
[ command ...]

No código acima, os argumentos entre colchetes são opcionais e reticências significam um ou mais. Aqui, observe que a guia para iniciar cada comando é necessária.

Um exemplo simples é fornecido abaixo, onde você define uma regra para tornar seu alô de destino de três outros arquivos.

hello: main.o factorial.o hello.o
   $(CC) main.o factorial.o hello.o -o hello

NOTE - Neste exemplo, você teria que fornecer regras para fazer todos os arquivos de objeto dos arquivos de origem.

A semântica é muito simples. Quando você diz "fazer alvo", omakeencontra a regra de destino que se aplica; e, se algum dos dependentes for mais novo que o destino,makeexecuta os comandos um de cada vez (após a substituição da macro). Se algum dependente tiver que ser feito, isso acontece primeiro (então você tem uma recursão).

Maketermina se algum comando retornar um status de falha. A seguinte regra será mostrada em tal caso -

clean:
   -rm *.o *~ core paper

Makeignora o status retornado nas linhas de comando que começam com um travessão. Por exemplo, quem se importa se não houver um arquivo principal?

Makeecoa os comandos, após a substituição da macro, para mostrar o que está acontecendo. Às vezes, você pode querer desligar isso. Por exemplo -

install:
   @echo You must be root to install

As pessoas esperam certos alvos nos Makefiles. Você deve sempre navegar primeiro. No entanto, é razoável esperar que os destinos all (ou apenas make), install e clean sejam encontrados.

  • make all - Compila tudo para que você possa fazer testes locais antes de instalar os aplicativos.

  • make install - Ele instala aplicativos nos lugares certos.

  • make clean - Limpa aplicativos, elimina os executáveis, quaisquer arquivos temporários, arquivos de objetos, etc.

Regras implícitas do Makefile

O comando é aquele que deve funcionar em todos os casos em que construímos um executável x a partir do código-fonte x.cpp. Isso pode ser declarado como uma regra implícita -

.cpp:
   $(CC) $(CFLAGS) [email protected] $(LDFLAGS) -o [email protected]

Esta regra implícita diz como fazer x de xc - execute cc em xc e chame a saída x. A regra está implícita porque nenhum alvo específico é mencionado. Pode ser usado em todos os casos.

Outra regra implícita comum é para a construção de arquivos .o (objeto) a partir de .cpp (arquivos de origem).

.cpp.o:
   $(CC) $(CFLAGS) -c $<

alternatively

.cpp.o:
   $(CC) $(CFLAGS) -c $*.cpp

Makepode criar o arquivo AO automaticamente, usando cc -c no arquivo .c correspondente. Essas regras são integradas aomake, e você pode aproveitar essa vantagem para encurtar seu Makefile. Se você indicar apenas os arquivos .h na linha de dependência do Makefile do qual o destino atual depende,makesaberá que o arquivo .c correspondente já é necessário. Você não precisa incluir o comando para o compilador.

Isso reduz o Makefile ainda mais, como mostrado abaixo -

OBJECTS = main.o hello.o factorial.o
hello: $(OBJECTS)
   cc $(OBJECTS) -o hello
hellp.o: functions.h

main.o: functions.h 
factorial.o: functions.h

Makeusa um destino especial, denominado .SUFFIXES para permitir que você defina seus próprios sufixos. Por exemplo, consulte a linha de dependência fornecida abaixo -

.SUFFIXES: .foo .bar

Informa make que você usará esses sufixos especiais para fazer suas próprias regras.

Semelhante a como makejá sabe como fazer um arquivo .o a partir de um arquivo .c , você pode definir regras da seguinte maneira -

.foo.bar:
   tr '[A-Z][a-z]' '[N-Z][A-M][n-z][a-m]' < $< > [email protected]
.c.o:
   $(CC) $(CFLAGS) -c $<

A primeira regra permite que você crie um arquivo .bar a partir de um arquivo .foo . Basicamente, ele embaralha o arquivo. A segunda regra é a regra padrão usada pormakepara criar um arquivo .o a partir de um arquivo .c .

Existem várias diretivas disponíveis em várias formas. omakeprograma em seu sistema pode não suportar todas as diretivas. Portanto, verifique se o seumake apoia as diretrizes que estamos explicando aqui. GNU make suporta essas diretivas.

Diretivas Condicionais

As diretivas condicionais são -

  • o ifeqdiretiva começa a condicional e especifica a condição. Ele contém dois argumentos, separados por vírgula e entre parênteses. A substituição de variável é realizada em ambos os argumentos e, em seguida, eles são comparados. As linhas do makefile após o ifeq são obedecidas se os dois argumentos coincidirem; caso contrário, eles são ignorados.

  • o ifneqdiretiva começa a condicional e especifica a condição. Ele contém dois argumentos, separados por vírgula e entre parênteses. A substituição de variável é realizada em ambos os argumentos e, em seguida, eles são comparados. As linhas do makefile após o ifneq serão obedecidas se os dois argumentos não corresponderem; caso contrário, eles são ignorados.

  • o ifdefdiretiva começa a condicional e especifica a condição. Ele contém um único argumento. Se o argumento fornecido for verdadeiro, a condição se torna verdadeira.

  • o ifndefdiretiva começa a condicional e especifica a condição. Ele contém um único argumento. Se o argumento fornecido for falso, a condição se tornará verdadeira.

  • o elsea diretiva faz com que as seguintes linhas sejam obedecidas se a condicional anterior falhar. No exemplo acima, isso significa que o segundo comando alternativo de vinculação é usado sempre que a primeira alternativa não é usada. É opcional ter um else em uma condicional.

  • o endifdiretiva termina o condicional. Cada condicional deve terminar com um endif.

Sintaxe das diretivas condicionais

A sintaxe de uma condicional simples sem outra é a seguinte -

conditional-directive
   text-if-true
endif

O text-if-true pode ser qualquer linha de texto, a ser considerada como parte do makefile se a condição for verdadeira. Se a condição for falsa, nenhum texto será usado.

A sintaxe de uma condicional complexa é a seguinte -

conditional-directive
   text-if-true
else
   text-if-false
endif

Se a condição for verdadeira, text-if-true será usado; caso contrário, text-if-false é usado. O text-if-false pode ser qualquer número de linhas de texto.

A sintaxe da diretiva condicional é a mesma, seja a condicional simples ou complexa. Existem quatro diretivas diferentes que testam várias condições. Eles são como dados -

ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2"

As diretivas opostas das condições acima são as seguintes -

ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq "arg1" 'arg2'
ifneq 'arg1' "arg2"

Exemplo de diretivas condicionais

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
   $(CC) -o foo $(objects) $(libs_for_gcc)
else
   $(CC) -o foo $(objects) $(normal_libs)
endif

A incluir diretiva

o include directive permite makepara suspender a leitura do makefile atual e ler um ou mais outros makefiles antes de continuar. A diretiva é uma linha no makefile que parece a seguir -

include filenames...

Os nomes de arquivo podem conter padrões de nome de arquivo shell. Espaços extras são permitidos e ignorados no início da linha, mas uma tabulação não é permitida. Por exemplo, se você tiver três arquivos `.mk ', ou seja,` a.mk', `b.mk 'e` c.mk', e $ (bar), então ele se expande para bish bash, e então expressão.

include foo *.mk $(bar)

is equivalent to:

include foo a.mk b.mk c.mk bish bash

Quando o makeprocessa uma diretiva de inclusão, ele suspende a leitura do makefile e lê cada arquivo listado por vez. Quando terminar,make continua lendo o makefile no qual a diretiva aparece.

A diretiva de substituição

Se uma variável foi definida com um argumento de comando, então as atribuições comuns no makefile são ignoradas. Se você quiser definir a variável no makefile mesmo que tenha sido definida com um argumento de comando, você pode usar uma diretiva de substituição, que é uma linha que parece a seguir -

override variable = value

or

override variable := value

o makeO programa é um utilitário inteligente e funciona com base nas alterações que você faz nos arquivos de origem. Se você tiver quatro arquivos main.cpp, hello.cpp, factorial.cpp e functions.h, todos os arquivos restantes serão dependentes de functions.h, e main.cpp será dependente de hello.cpp e factorial.cpp. Portanto, se você fizer alguma alteração em functions.h, então omakerecompila todos os arquivos de origem para gerar novos arquivos de objeto. No entanto, se você fizer qualquer alteração em main.cpp, já que ele não depende de nenhum outro arquivo, apenas o arquivo main.cpp é recompilado, e help.cpp e factorial.cpp não são.

Ao compilar um arquivo, o makeverifica seu arquivo de objeto e compara os carimbos de hora. Se o arquivo de origem tiver um registro de data e hora mais recente do que o arquivo de objeto, ele gerará um novo arquivo de objeto, assumindo que o arquivo de origem foi alterado.

Evitando recompilação

Pode haver um projeto que consiste em milhares de arquivos. Às vezes, você pode ter alterado um arquivo de origem, mas pode não querer recompilar todos os arquivos que dependem dele. Por exemplo, suponha que você adicione uma macro ou uma declaração a um arquivo de cabeçalho, do qual os outros arquivos dependem. Sendo conservador,make assume que qualquer mudança no arquivo de cabeçalho requer a recompilação de todos os arquivos dependentes, mas você sabe que eles não precisam ser recompilados e você prefere não perder seu tempo esperando que eles sejam compilados.

Se você antecipar o problema antes de alterar o arquivo de cabeçalho, pode usar a opção `-t '. Esta bandeira dizmakenão para executar os comandos nas regras, mas sim para marcar o destino atualizado, alterando sua data de última modificação. Você precisa seguir este procedimento -

  • Use o comando `make 'para recompilar os arquivos fonte que realmente precisam de recompilação.

  • Faça as alterações nos arquivos de cabeçalho.

  • Use o comando `make -t 'para marcar todos os arquivos objeto como atualizados. Na próxima vez que você executar o make, as alterações nos arquivos de cabeçalho não causarão nenhuma recompilação.

Se você já alterou o arquivo de cabeçalho em um momento em que alguns arquivos precisam ser recompilados, é tarde demais para fazer isso. Em vez disso, você pode usar o sinalizador `-o file ', que marca um arquivo especificado como" antigo ". Isso significa que o próprio arquivo não será refeito e nada mais será refeito por sua conta. você precisa seguir este procedimento -

  • Recompile os arquivos fonte que precisam de compilação por razões independentes do arquivo de cabeçalho particular, com `make -o header file '. Se vários arquivos de cabeçalho estiverem envolvidos, use uma opção `-o 'separada para cada arquivo de cabeçalho.

  • Atualize todos os arquivos de objetos com `make -t '.

Neste capítulo, examinaremos vários outros recursos do Makefile.

Uso Recursivo de Make

Uso recursivo de make significa usar makecomo um comando em um makefile. Esta técnica é útil quando você deseja makefiles separados para vários subsistemas que compõem um sistema maior. Por exemplo, suponha que você tenha um subdiretório chamado `subdir 'que tem seu próprio makefile, e você gostaria que o makefile do diretório que o contém seja executadomakeno subdiretório. Você pode fazer isso escrevendo o código abaixo -

subsystem:
   cd subdir && $(MAKE)

or, equivalently:
 	
subsystem:
   $(MAKE) -C subdir

Você pode escrever recursivo makecomandos apenas copiando este exemplo. No entanto, você precisa saber como eles funcionam e por que, e como o submarcar se relaciona com o fabricante de nível superior.

Comunicando Variáveis ​​para uma Sub-Marca

Valores variáveis ​​de nível superior makepode ser passado para a submarca por meio do ambiente por solicitação explícita. Essas variáveis ​​são definidas no submarque como padrões. Você não pode sobrescrever o que é especificado no makefile usado pelo sub-make makefile a menos que use a opção `-e '.

Para transmitir ou exportar uma variável, makeadiciona a variável e seu valor ao ambiente para executar cada comando. O sub-make, por sua vez, usa o ambiente para inicializar sua tabela de valores de variáveis.

As variáveis ​​especiais SHELL e MAKEFLAGS são sempre exportadas (a menos que você as cancele). MAKEFILES é exportado se você definir qualquer coisa.

Se você deseja exportar variáveis ​​específicas para um sub-make, use a diretiva de exportação, conforme mostrado abaixo -

export variable ...

Se você quiser evitar que uma variável seja exportada, use a diretiva não exportar, como mostrado abaixo -

unexport variable ...

A Variável MAKEFILES

Se a variável de ambiente MAKEFILES for definida, makeconsidera seu valor como uma lista de nomes (separados por espaço em branco) de makefiles adicionais a serem lidos antes dos outros. Isso funciona de forma muito semelhante à diretiva include: vários diretórios são pesquisados ​​para esses arquivos.

O principal uso de MAKEFILES é na comunicação entre invocações recursivas do make.

Incluindo arquivo de cabeçalho de diferentes diretórios

Se você colocou os arquivos de cabeçalho em diretórios diferentes e está executando makeem um diretório diferente, é necessário fornecer o caminho dos arquivos de cabeçalho. Isso pode ser feito usando a opção -I no makefile. Supondo que o arquivo functions.h esteja disponível na pasta / home / tutorialspoint / header e o restante dos arquivos esteja disponível na pasta / home / tutorialspoint / src /, então o makefile seria escrito da seguinte maneira -

INCLUDES = -I "/home/tutorialspoint/header"
CC = gcc
LIBS =  -lm
CFLAGS = -g -Wall
OBJ =  main.o factorial.o hello.o

hello: ${OBJ}
   ${CC} ${CFLAGS} ${INCLUDES} -o [email protected] ${OBJS} ${LIBS}
.cpp.o:
   ${CC} ${CFLAGS} ${INCLUDES} -c $<

Anexando mais texto às variáveis

Freqüentemente, é útil adicionar mais texto ao valor de uma variável já definida. Você faz isso com uma linha contendo `+ = ', como mostrado -

objects += another.o

Ele pega o valor dos objetos variáveis ​​e adiciona o texto `outro.o 'a ele, precedido por um único espaço como mostrado abaixo.

objects = main.o hello.o factorial.o
objects += another.o

O código acima define objetos para `main.o hello.o factorial.o another.o '.

Usar `+ = 'é semelhante a:

objects = main.o hello.o factorial.o
objects := $(objects) another.o

Linha de continuação em Makefile

Se você não gosta de linhas muito grandes em seu Makefile, você pode quebrar sua linha usando uma barra invertida "\" conforme mostrado abaixo -

OBJ =  main.o factorial.o \
   hello.o

is equivalent to

OBJ =  main.o factorial.o hello.o

Executando Makefile do Prompt de Comando

Se você preparou o Makefile com o nome "Makefile", simplesmente escreva make no prompt de comando e ele executará o arquivo Makefile. Mas se você deu qualquer outro nome para o Makefile, use o seguinte comando -

make -f your-makefile-name

Este é um exemplo do Makefile para compilar o programa hello. Este programa consiste em três arquivos main.cpp , factorial.cpp e hello.cpp .

# Define required macros here
SHELL = /bin/sh

OBJS =  main.o factorial.o hello.o
CFLAG = -Wall -g
CC = gcc
INCLUDE =
LIBS = -lm

hello:${OBJ}
   ${CC} ${CFLAGS} ${INCLUDES} -o [email protected] ${OBJS} ${LIBS}

clean:
   -rm -f *.o core *.core

.cpp.o:
   ${CC} ${CFLAGS} ${INCLUDES} -c $<

Agora você pode construir seu programa hello usando o make. Se você vai emitir um comandomake clean em seguida, remove todos os arquivos-objeto e arquivos principais disponíveis no diretório atual.