faço

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 .

História

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.

Operação

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 A

O 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.

Makefile

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.

As regras

Um makefile é feito de regras . A forma mais simples de regra é:

cible [cible ...]: [composant ...] [tabulation] commande 1 . . . [tabulation] commande n

O “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.txt

A 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:

  • Um sinal de menos (-), especificando que os erros devem ser ignorados;
  • Uma arroba (@), especificando que o comando não deve ser exibido na saída padrão antes de ser executado;
  • Um sinal de mais (+), o comando é executado mesmo se make for chamado no modo "não execute".
Regra múltipla

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.txt

pode ser substituído por:

all: cible1 cible2 cible1 cible2: texte1.txt echo texte1.txt

Observe 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 .

Macros

Definição

Um makefile pode conter definições de macro . As macros são tradicionalmente definidas em letras maiúsculas:

MACRO = definition

As 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:

  • $ @  : refere-se ao alvo.
  • $?  : Contém os nomes de todos os componentes mais novos que o destino.
  • $ <  : contém o primeiro componente de uma regra.
  • $ ^  : contém todos os componentes de uma regra.


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.

Expansão

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 trabalho

Existem várias maneiras de definir uma macro:

  • O = é uma atribuição por referência (falamos de expansão recursiva )
  • O : = é uma atribuição por valor (falamos de expansão simples )
  • O ? = É uma atribuição condicional. Afeta apenas a macro se ainda não estiver afetada.
  • O + = é uma atribuição por concatenação. Ele pressupõe que a macro já existe.

As regras de sufixo

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_cible

Um 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.txt

O uso de regras de sufixo é considerado obsoleto porque é muito restritivo.

Exemplo de Makefile

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 $*.c

Neste 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.

Limitações

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.

  • No primeiro caso, há perda de eficiência.
  • No segundo caso, há perda de confiabilidade.

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:

  • O Make própria ignora completamente a semântica dos arquivos que processa, simplesmente ignora seu conteúdo. Por exemplo, nenhuma distinção é feita entre o código real e os comentários. Assim, no caso de adicionar ou mesmo apenas modificar um comentário dentro de um arquivo C, o make irá considerar que as bibliotecas ou programas alvo que dependem dele terão que ser reconstruídos.
  • Se o resultado final depende dos dados de entrada, também depende dos tratamentos aplicados. Esses tratamentos são descritos no arquivo makefile. Raras são as gravações de arquivos makefile que visam invalidar qualquer produção anteriormente realizada no caso da evolução do arquivo makefile; na verdade, para fazer isso, seria aconselhável colocar sistematicamente o arquivo makefile na dependência de todas as regras de produção que ele mesmo define. Embora não seja tecnicamente difícil imaginar que isso poderia ser feito diretamente por uma variante do make, teria o efeito colateral de atingir todos os alvos, mesmo se eles não fossem afetados pelas alterações feitas no makefile.
  • Uma variante do exemplo anterior é o caso em que as variáveis ​​que governam a produção são definidas por variáveis ​​de ambiente ou mesmo por meio da linha de comando. Aqui, novamente, o make não tem como detectar se essas variáveis ​​afetam a produção ou não, e em particular quando essas variáveis ​​designam opções e não arquivos.

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).

Alternativas

Existem várias alternativas para fazer:

  • makepp: um derivado do (GNU) make, mas que adicionalmente oferece um analisador extensível de comandos e arquivos incluídos para reconhecer automaticamente as dependências. As opções de controle alteradas e outras influências são reconhecidas. O grande problema de make recursivo pode ser facilmente evitado, para garantir a construção correta
  • clearmake é a versão integrada ao ClearCase . Fortemente acoplado ao gerenciamento de revisões de arquivos, realiza e armazena auditoria de toda a manufatura. Essas auditorias permitem superar o problema de datas, variáveis ​​de ambiente, dependências implícitas ou ocultas e também de parâmetros passados ​​na linha de comando (ver limitações ).
  • formiga  : bastante relacionado ao mundo Java .
  • rake  : um equivalente em Ruby .
  • SCons  : completamente diferente do make, inclui algumas das funções da ferramenta de compilação, como o autoconf. Você pode usar Python para estender a ferramenta.
  • Speedy Make usa XML para makefiles, muito fácil de escrever, oferece mais automação do que make.
  • mk: equivalente a make, originalmente projetado para o sistema Plan 9; é muito mais simples do que o make e a maneira como se comunica com o interpretador de comandos o torna muito mais limpo e poderoso; no entanto, tem a desvantagem de ser incompatível com o make.

Notas e referências

Notas

  1. Suponha que um usuário privilegiado seja responsável o suficiente para não fazer o downgrade imprudente da data de um arquivo.
  2. Por exemplo, com o GNU Make, que facilita a construção de listas dinâmicas, mesmo que não forneça tais analisadores.

Referências

  1. Doar 2005 , p.  94
  2. Página inicial do MakeppEsperantoEnglish

Apêndices

Bibliografia

  • [Doar 2005] (en) Matthew Doar, Practical Development Environments , O'Reilly Media ,2005( ISBN  978-0-596-00796-6 ).

Artigos relacionados

links externos