next up previous
Next: Tarefas Up: Alguns Conceitos Relevantes Previous: Alguns Conceitos Relevantes

Estrutura Básica de uma Makefile

Um exemplo da estrutura básica da tríade alvo - dependência - regra de uma Makefile é dado a seguir. Note que vários destes blocos básicos podem ser encadeados para codificar relações mais complexas de dependência entre muitos arquivos diferentes. Basta que uma das dependências de uma regra seja, além disso, o alvo de uma outra regra. O make percorre recursivamente todas as relações de dependência contidas na Makefile, até poder decidir que arquivos devem ou não ser refeitos.





# Estrutura central básica de uma Makefile.
# Comentários são feitos como em shell scripts.

alvo: dependencia1 dependencia2
        regra1
        regra2





O que está antes do sinal ``:'' é o alvo, o que vem depois é uma lista de dependências separadas por espaços. As linhas de regra, contendo uma regra cada uma, vêm a seguir. Note o espaço em branco no início de cada uma destas linhas. Este é um importante elemento da sintaxe da Makefile: trata-se na realidade de um caracter ``TAB''. Linhas contendo regras devem sempre ser iniciadas por um TAB, que não pode ser substituído por um conjunto de 8 espaços em branco.

Cada linha de uma regra na Makefile será executada por uma shell, sendo que por default é usada a bash. Note que cada linha é executada por um processo de shell independente dos outros, de forma que o conjunto das regras que seguem uma linha de alvo e dependências não forma um shell script, pois neste todas as linhas são executadas pelo mesmo processo de shell. Se quisermos que um script longo e complexo seja executado como um bloco em uma regra, a opção mais apropriada é escrever o script em um arquivo executável separado e executá-lo como uma das linhas de regra. Só podemos incluir diretamente um script na Makefile se o escrevermos na forma de uma única linha, o que em geral não é muito conveniente.

A verificação da relação entre o alvo e as dependências é mais complexa do que aparenta até aqui, pois não é só a existência ou não dos arquivos que conta. Além disso o comando make verifica quais as datas em que foram criados ou modificados pela última vez os diversos arquivos envolvidos para decidir se realiza ou não as operações que recriam o alvo a partir das dependências. Assim, se a data da última modificação de algum dos arquivos que constam nas dependências for mais recente do que a data correspondente do alvo, o comando make repete as regras para a confecção deste. Se a data das dependências for anterior à do alvo, o comando make nada faz.

O comando make contém muitos elementos adicionais de sintaxe, que o tornam extremamente poderoso e flexível. Não pretendemos esgotar aqui todos estes elementos, mas vale a pena mencionar alguns dos mais importantes e mais utilizados. Um destes elementos é o uso de variáveis dentro da Makefile, as quais são também chamados de macros. Trata-se, na realidade, de variáveis de environment. O programa make herda da sessão que o inicia todo o conjunto de variáveis de environment, que ele pode utilizar internamente. Além disso, podemos acrescentar a este conjunto de variáveis herdadas novas variáveis que são definidas dentro da Makefile, sempre em letras maiúsculas, como é próprio para variáveis de environment. Estas variáveis são úteis para evitar repetições desnecessárias de conjuntos de nomes de arquivos ou comandos, à maneira do uso destas variáveis em shell scripts. Um exemplo simples da estrutura global de uma Makefile é dado abaixo, com os elementos mais comuns.





# Estrutura global de uma Makefile.

# Definicao de variaveis ou macros.
VARIAVEL1 = nome1 nome2 nome3
VARIAVEL2 = nome4 nome5 nome6

# Primeiro bloco de alvos, dependências e regras.
# Note a sintaxe específica para se fazer referência
# _ ao valor de uma variável, com os parentesis.
alvo1: dependencia1 dependencia2 $(VARIAVEL1)
        regra1
        regra2

# Segundo bloco de alvos, dependências e regras.
# Note que o primeiro alvo é uma das dependências, o que
# _ faz com que, neste caso, a inclusão da VARIAVEL1 nas
# _ dependências seja redundante.
alvo2: dependencia3 alvo1 $(VARIAVEL1) $(VARIAVEL2)
        regra3
        regra4





Inicialmente, definimos as variáveis auxiliares VARIAVEL1 e VARIAVEL2. A seguir, são definidos os alvos alvo1 e alvo2, separados por dois pontos (``:'') de suas respectivas dependências; algumas dessas dependências são definidas pelas variáveis previamente definidas. Observe que, nesse exemplo, tanto o alvo1 quanto o alvo2 dependem do conteúdo definido pela VARIAVEL1. Na linha seguinte a cada alvo, são definidas as regras; é importante lembrar que o espaço entre o começo da linha e cada regra é dado por um caracter de tabulação, isto é, deve-se sempre digitar um TAB no início da linha de definição de cada regra.

Outro conceito muito útil é o de variáveis automáticas. Estas são variáveis especiais da Makefile, com sua sintaxe própria, que permitem que o comando make reconheça certos padrões gerais de nomes que estão envolvidos numa determinada regra como alvos ou dependências. São úteis em particular a variável $@, que representa o alvo envolvido na regra, a variável $< que representa o primeiro elemento da lista de dependências, bem como a variável $+ que representa o conjunto das dependências. Estas estruturas tornam mais sintética a descrição das regras, como é ilustrado abaixo num caso um tanto trivial.





# Uma forma um tanto complicada de se copiar arquivos.

arquivo2: arquivo1
        cp -pf $< $@

arquivo4: arquivo3
        cp -pf $< $@





Além disso, há também algumas estruturas de controle que podem ser usadas dentro da Makefile, bem como uma sintaxe especial usando o símbolo % dentro dos alvos e das dependências, o que permite a criação de regras especiais chamadas ``pattern rules''. Estas regras especiais baseadas no reconhecimento de ``patterns'' têm uma aplicação muito geral, ampla e flexível. Elas tornam a escrita da Makefile muito mais simples e sintética, pois codificam em uma única regra um grande conjunto de regras que satisfazem a um ``pattern'' definido, como veremos nas tarefas. Elas são de particular utilidade se combinadas com as variáveis especiais que mencionamos acima. Há inclusive uma variável especial de grande utilidade que é específica para este tipo de regra, a variável $* que representa a parte do ``pattern'' comum entre o alvo e as dependências.

Observe que, apesar de que o comando make foi criado no Unix para auxiliar no desenvolvimento e manutenção de projetos grandes e complexos de software, geralmente utilizando a linguagem C, a sua utilidade não se limita, em absoluto, a este tipo de uso. Só para dar alguns outros exemplos de nossa experiência imediata: estas apostilas são desenvolvidas e mantidas através do uso do make, inclusive para manter atualizada a versão em HTML que se encontra na home-page da disciplina; toda a pesquisa de nosso pequeno grupo de pesquisa é baseada em uma coleção de programas, escritos primordialmente em Fortran, que são mantidos e desenvolvidos através do make; o livro-texto sobre Teoria Quântica de Campos na Rede, que pode ser encontrado na home-page de nosso grupo, é escrito e mantido através de uma estrutura bastante complexa de make.

Pode-se entender uma Makefile como uma forma de registro para a solução de um problema. Quando tentamos resolver o problema de como fazer uma determinada coisa no sistema, vamos descobrindo por tentativa e erro as relações relevantes e as operações necessárias para resolver cada parte do problema. A Makefile funciona como um registro ativo das relações de dependência e da sequência de operações que resolve o problema. Mesmo muito tempo depois da sua formulação, a leitura de uma Makefile nos recorda rapidamente e com precisão da solução que havíamos encontrado anteriormente para resolver o problema de como fazer uma determinada coisa no sistema.


next up previous
Next: Tarefas Up: Alguns Conceitos Relevantes Previous: Alguns Conceitos Relevantes