Make é um software que constrói automaticamente o arquivo , geralmente executável , ou bibliotecas a partir de elementos básicos, como o código-fonte . Ele usa arquivos chamados makefiles que especificam como construir os arquivos de destino. Ao contrário de um script de shell simples , o make executa comandos somente se eles forem necessários. O objetivo é chegar a um resultado (software compilado ou instalado, documentação criada, etc.) sem necessariamente refazer todas as etapas. make é particularmente usado em plataformas UNIX .
Na década de 1970, a compilação de programas tornou-se cada vez mais longa e complexa, exigindo muitas etapas inter-relacionadas. A maioria dos sistemas usados é baseada em scripts de shell , exigindo a repetição de todas as etapas para a menor correção. É neste contexto que o Make foi desenvolvido pelo Dr. Stuart Feldman (in) , em 1977 enquanto trabalhava para a Bell Labs . Ao gerenciar as dependências entre os arquivos fonte e os arquivos compilados, o Make permite que você compile apenas o que é necessário após a modificação de um arquivo fonte.
Desde o desenvolvimento original, o programa viu muitas variações. Os mais conhecidos são BSD's e GNU Project's - usados por padrão em sistemas Linux . Essas variantes fornecem novas funcionalidades e geralmente não são compatíveis entre si. Por exemplo, scripts destinados ao GNU Make podem não funcionar no BSD Make.
Posteriormente, outras ferramentas surgiram permitindo a geração automática de arquivos de configuração (Makefile) utilizados pelo make. Essas ferramentas permitem que você analise as dependências ( automake ) ou a configuração do sistema ( autoconf ) para gerar Makefiles complexos especificamente adaptados ao ambiente no qual as ações de compilação são executadas.
Desde então, Make inspirou uma variedade de softwares de gerenciamento de compilação, específicos para certas plataformas ( rake , ant ), ou generalistas como o Ninja nos anos 2010.
Em 2003, o D r Feldman recebeu o prêmio de ACM pelo desenvolvimento da marca.
O processo de compilação é dividido em regras elementares, como "O alvo A depende de B e C. Para fabricar A você tem que executar tal e tal série de comandos" . Todas essas regras são colocadas em um arquivo comumente chamado de Makefile. Os comandos consistem em ações elementares de compilação , edição de links , geração de código .
As dependências correspondem a arquivos, que podem ser arquivos de origem ou resultados intermediários no processo de construção. O destino geralmente é um arquivo, mas também pode ser abstrato.
A construção de A é realizada pelo comando:
make AO Make verificará então se os arquivos B e C estão disponíveis e atualizados, ou seja, não têm dependências alteradas após sua criação, e o Make, de outra forma, começará a construir recursivamente de B e C. O Make aplicará então o comandos de criação de A assim que nenhum arquivo com este nome existir, ou que o arquivo A for mais antigo que B ou C.
É comum usar um destino all resumindo todo o projeto, este destino abstrato tendo como dependência todos os arquivos a serem construídos. Outros alvos são de uso comum: instalar para executar os comandos de instalação do projeto, limpar para apagar todos os arquivos gerados produzidos.
Make permite o uso de regras de dependência explícitas, correspondendo aos nomes dos arquivos, ou implícitas, correspondendo aos padrões dos arquivos; por exemplo, qualquer arquivo com uma extensão .o pode ser construído a partir de um arquivo com o mesmo nome com uma extensão .c por um comando de compilação.
Make representa o projeto como uma árvore de dependências , e algumas variantes do programa permitem a construção de múltiplos destinos em paralelo quando não possuem dependências entre eles.
Faça pesquisas no diretório atual para o makefile a ser usado. Por exemplo, GNU faz buscas, em ordem, por um GNUmakefile , makefile , Makefile , então executa os destinos especificados (ou padrão) apenas para aquele arquivo.
A linguagem usada no makefile é a programação declarativa . Ao contrário da programação imperativa , isso significa que a ordem em que as instruções devem ser executadas não importa.
Um makefile é feito de regras . A forma mais simples de regra é:
cible [cible ...]: [composant ...] [tabulation] commande 1 . . . [tabulation] commande nO “destino” geralmente é um arquivo a ser construído, mas também pode definir uma ação (excluir, compilar, etc.). Os “componentes” são pré-requisitos necessários para a realização da ação definida pela norma. Em outras palavras, "componentes" são os alvos de outras regras que devem ser cumpridas antes que esta regra possa ser cumprida. A regra define uma ação por uma série de "comandos". Esses comandos definem como usar os componentes para produzir o alvo. Cada comando deve ser precedido por um caractere de tabulação.
Os comandos são executados por um shell separado ou por um interpretador de linha de comando .
Aqui está um exemplo de um makefile:
all: cible1 cible2 echo ''all : ok'' cible1: echo ''cible1 : ok'' cible2: echo ''cible2 : ok''Quando este makefile é executado através do comando make all ou do comando make , a regra 'all' é executada. Requer a criação dos componentes 'target1' e 'target2' que estão associados às regras 'target1' e 'target2'. Portanto, essas regras serão executadas automaticamente antes da regra 'todos'. Por outro lado, o comando make target1 só executará a regra 'target1'.
Para resolver a ordem em que as regras devem ser executadas, make usa uma classificação topológica .
Observe que uma regra não inclui necessariamente um comando.
Os componentes nem sempre são alvos de outras regras, eles também podem ser arquivos necessários para construir o arquivo de destino:
sortie.txt: fichier1.txt fichier2.txt cat fichier1.txt fichier2.txt > sortie.txtA regra de exemplo acima constrói o arquivo output.txt usando os arquivos file1.txt e file2.txt. Ao executar o makefile, verifique se o arquivo output.txt existe e, caso não exista, ele o construirá usando o comando definido na regra.
As linhas de comando podem ter um ou mais dos três prefixos a seguir:
Quando vários destinos requerem os mesmos componentes e são construídos pelos mesmos comandos, é possível definir uma regra múltipla. Por exemplo :
all: cible1 cible2 cible1: texte1.txt echo texte1.txt cible2: texte1.txt echo texte1.txtpode ser substituído por:
all: cible1 cible2 cible1 cible2: texte1.txt echo texte1.txtObserve que o destino all é obrigatório, caso contrário, apenas o destino target1 será executado.
Para descobrir o alvo em questão, você pode usar a variável $ @ , por exemplo:
all: cible1.txt cible2.txt cible1.txt cible2.txt: texte1.txt cat texte1.txt > $@criará dois arquivos, target1.txt e target2.txt , com o mesmo conteúdo de text1.txt .
Um makefile pode conter definições de macro . As macros são tradicionalmente definidas em letras maiúsculas:
MACRO = definitionAs macros são freqüentemente chamadas de variáveis quando contêm apenas definições de string simples, como CC = gcc . As variáveis de ambiente também estão disponíveis como macros. Macros em um makefile podem ser sobrescritas por argumentos passados para make. O comando é então:
make MACRO="valeur" [MACRO="valeur" ...] CIBLE [CIBLE ...]As macros podem ser compostas de comandos de shell usando o acento grave ( `):
DATE = ` date `Existem também 'macros internas' para fazer:
As macros permitem que os usuários especifiquem quais programas usar ou certas opções personalizadas durante o processo de construção. Por exemplo, a macro CC é freqüentemente usada em makefiles para especificar um compilador C a ser usado.
Para usar uma macro, você deve expandi-la encapsulando-a em $ () . Por exemplo, para usar a macro CC , você terá que escrever $ (CC) . Observe que também é possível usar $ {} . Por exemplo :
NOUVELLE_MACRO = $(MACRO)-${MACRO2}Esta linha cria uma nova macro NOUVELLE_MACRO subtraindo o conteúdo de MACRO2 do conteúdo de MACRO .
Tipos de trabalhoExistem várias maneiras de definir uma macro:
Essas regras permitem que você crie makefiles para um tipo de arquivo. Existem dois tipos de regras de sufixo: duplas e simples.
Uma regra de sufixo duplo é definida por dois sufixos: um sufixo de destino e um sufixo de origem . Esta regra reconhecerá qualquer arquivo do tipo "alvo". Por exemplo, se o sufixo de destino for '.txt' e o sufixo de origem for '.html', a regra será equivalente a '% .txt:% .html' (onde% significa qualquer nome de arquivo). Uma regra de sufixo simples precisa apenas de um sufixo de origem.
A sintaxe para definir uma regra de sufixo duplo é:
.suffixe_source.suffixe_cible :Observe que uma regra de sufixo não pode ter componentes adicionais.
A sintaxe para definir a lista de sufixos é:
.SUFFIXES: .suffixe_source .suffixe_cibleUm exemplo de uso de regras de sufixo:
.SUFFIXES: .txt .html # transforme .html en .txt .html.txt: lynx -dump $< > $@A seguinte linha de comando transformará o arquivo file.html em file.txt :
make -n fichier.txtO uso de regras de sufixo é considerado obsoleto porque é muito restritivo.
Aqui está um exemplo de Makefile:
# Indiquer quel compilateur est à utiliser CC ?= gcc # Spécifier les options du compilateur CFLAGS ?= -g LDFLAGS ?= -L/usr/openwin/lib LDLIBS ?= -lX11 -lXext # Reconnaître les extensions de nom de fichier *.c et *.o comme suffixes SUFFIXES ?= .c .o .SUFFIXES: $(SUFFIXES) . # Nom de l'exécutable PROG = life # Liste de fichiers objets nécessaires pour le programme final OBJS = main.o window.o Board.o Life.o BoundedBoard.o all: $(PROG) # Étape de compilation et d'éditions de liens # ATTENTION, les lignes suivantes contenant "$(CC)" commencent par un caractère TABULATION et non pas des espaces $(PROG): $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $(PROG) $(OBJS) .c.o: $(CC) $(CFLAGS) -c $*.cNeste exemplo, .c.oé uma regra implícita. Por padrão, os destinos são arquivos, mas quando é a justaposição de dois sufixos, é uma regra que deriva qualquer arquivo terminando com o segundo sufixo de um arquivo com o mesmo nome, mas terminando com o primeiro sufixo.
Para atingir este objetivo, deve-se executar a ação, o comando $(CC) $(CFLAGS) -c $*.c, onde $*representa o nome do arquivo sem sufixo.
Por outro lado, allé um destino que depende $(PROG)(e life, portanto , que é um arquivo).
$(PROG)- isto é life- é um destino que depende $(OBJS)(e, portanto, arquivos main.o window.o Board.o Life.oe BoundedBoard.o). Para fazer isso, makeexecute o comando $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $(PROG) $(OBJS).
A sintaxe CC ?= gcc, ou, mais geralmente <variable> ?= <valeur>, afeta <valeur>no <variable>somente se <variable>ainda não inicializado. Se <variable>já contém um valor, esta instrução não tem efeito.
As limitações do make decorrem diretamente da simplicidade dos conceitos que o popularizaram: arquivos e datas. Esses critérios são, de fato, insuficientes para garantir eficiência e confiabilidade.
O critério de data associado aos arquivos, por si só, combina as duas falhas. A menos que o arquivo resida em uma mídia de gravação única, não há garantia de que a data de um arquivo seja de fato a data de sua última modificação.
Se para um usuário não privilegiado, é garantido que os dados não podem ser posteriores à data indicada, a data exata de sua anterioridade não é garantida.
Assim, à menor alteração na data de um arquivo, toda uma produção pode ser considerada necessária se for uma fonte, mas ainda pior considerada inútil se, pelo contrário, for um destino.
Se a data e o arquivo permanecerem essencialmente necessários para qualquer mecanismo de produção desejado confiável e ideal, eles também não são suficientes e alguns exemplos específicos são suficientes para ilustrar isso:
Outra limitação do make é que ele não gera a lista de dependências e não é capaz de verificar se a lista fornecida está correta. Assim, o exemplo simples anterior que se baseia na regra .c.oé incorreto: na verdade, os arquivos-objeto não dependem apenas do arquivo-fonte .cassociado, mas também de todos os arquivos do projeto incluídos no arquivo .c. Uma lista mais realista de dependências seria:
$(PROG): $(OBJS) $(CC) $(CFLAGS) $(LDFLAGS) $(LDLIBS) -o $(PROG) $(OBJS) main.o: main.c Board.h BoundedBoard.h Life.h global.h $(CC) $(CFLAGS) -c main.c window.o: window.c window.h global.h $(CC) $(CFLAGS) -c window.c Board.o: Board.c Board.h window.h global.h $(CC) $(CFLAGS) -c Board.c Life.o: Life.c Life.h global.h $(CC) $(CFLAGS) -c Life.c BoundedBoard.o: BoundedBoard.c BoundedBoard.h Board.h global.h $(CC) $(CFLAGS) -c BoundedBoard.cÉ razoável supor que os arquivos de sistema incluídos (as stdio.h) não mudem e não listá-los como dependências. Ao custo de escrever analisadores capazes de produzir listas dinâmicas de dependências, algumas versões do make contornam esse problema.
É por essas razões que os motores de produção de nova geração se especializam no processamento de determinadas linguagens (semântica do conteúdo dos arquivos) ou também são acoplados a um banco de dados no qual todas as características de produção efetivas são registradas ( auditoria de produção ) alvos (rastreabilidade).
Existem várias alternativas para fazer: