next up previous
Next: Problemas e dicas Up: FMA 215 Aula 16: Previous: Estrutura Básica de uma

Tarefas

Nestas tarefas vamos comparar a utilidade do make com a de um shell script convencional, cuja estrutura já conhecemos. Construiremos uma Makefile para a compilação de um programa escrito em Fortran, começando com uma de estrutura bem simples e incluindo nela, aos poucos, várias das estruturas mais importantes suportadas pelo comando make.

  1. Vamos inicialmente criar um ``shell script'' capaz de compilar um programa escrito em várias subrotinas, separadas em arquivos diferentes. Para isso, use um browser para copiar o arquivo

    http://latt.if.usp.br/fma215/materiais/aula-16/decmat.tgz

    dentro de um diretório de trabalho de temporário com o nome, digamos, makeaula. Entre no diretório makeaula e dê o comando

    tar xzvf decmat.tgz

    Dê um ls -l e verifique que foi criado o seguinte conjunto de arquivos:

    -rw-rw-r--  1 <nome> <nome> 7739 Oct 30 21:47 Decompmat.f
    -rw-rw-r--  1 <nome> <nome>  439 Oct 30 21:47 cb_gen.f
    -rw-rw-r--  1 <nome> <nome> 1525 Oct 30 21:47 checkmats.f
    -rw-rw-r--  1 <nome> <nome>  278 Oct 31 15:05 fp_def.f
    -rw-rw-r--  1 <nome> <nome> 1427 Oct 30 21:47 getseed.f
    -rw-rw-r--  1 <nome> <nome> 2179 Oct 30 21:47 ranr.f
    -rw-rw-r--  1 <nome> <nome> 1082 Oct 30 21:47 writemat.f
    

  2. Crie o script comp.tcsh, dado abaixo. Esse script verifica que existem os arquivos Fortran, compila cada arquivo *.f gerando seu correspondente objeto binário *.o e, então, cria o executável.





    #!/bin/tcsh -f
    #
    # Script para compilar um programa em Fortran.
    #
    # Define algumas variaveis de shell.
    set progfiles = ( Decompmat checkmats getseed \
                      ranr writemat writemats     )
    set files = ( $progfiles cb_gen fp_def )
    #
    # Verifica a existencia dos arquivos necessarios.
    #
    # Loop sobre os arquivos.
    foreach file ( $files )
        if ( ! -f "$file.f" ) then
            echo "Erro: arquivo $file.f não encontrado"
            exit 1
        endif
    end
    #
    # Gera os objetos binarios e cria uma lista deles.
    #
    # Inicializa uma variável vetorial.
    set ofiles
    #
    # Loop sobre os arquivos.
    foreach progfile ( $progfiles )
        #
        # Gera o objeto binario.
        echo -n "Compilando $progfile.f... "
        g77 -O -c -o $progfile.o $progfile.f
        #
        # Acrescenta o objeto a lista.
        set ofiles = ( $ofiles $progfile.o )
        echo " feito."
    end
    #
    # Cria o executável efetuando a linkagem dos objetos.
    echo -n "Gerando executável... "
    g77 -o decmat $ofiles
    echo " feito."
    





    Dê um ls -l, verifique que o script pode ser executado, altere as autorizações do arquivo com chmod se necessário e rode-o. Dê o comando file * para identificar cada arquivo criado e verificar que são o que esperamos.

    Observe que todas as operações são efetuadas toda vez que o script for executado, independentemente de existir ou não o executável, o que poderia tomar muito tempo caso o programa contivesse muitos módulos. Para eliminar essa redundância de operações, seria necessário acrescentar-se ao script uma série de ``ifs'' condicionais que verificassem quais módulos já foram compilados e quais deveriam ser refeitos, o que aumentaria muito a complexidade do script.

  3. Vamos criar agora uma Makefile bastante simples, com as regras necessárias apenas para a compilação de nosso programa, como no script acima. Crie o arquivo abaixo (sem autorização de execução pois não se trata de um executável) com o nome Makefile.





    # Makefile para compilacao de programa em Fortran.
    
    # Primeiro alvo (default target).
    decmat: Decompmat.o checkmats.o getseed.o \
    ranr.o writemat.o writemats.o
            g77 -o decmat Decompmat.o checkmats.o getseed.o \
                ranr.o writemat.o writemats.o
    
    # Demais alvos (targets).
    Decompmat.o: Decompmat.f fp_def.f cb_gen.f
            g77 -O -c -o Decompmat.o Decompmat.f
    
    checkmats.o: checkmats.f fp_def.f cb_gen.f
            g77 -O -c -o checkmats.o checkmats.f 
    
    writemats.o: writemats.f fp_def.f cb_gen.f
            g77 -O -c -o writemats.o writemats.f
    





    Para efetuar a compilação, simplesmente execute o comando make. Esse comando procurará automaticamente um arquivo Makefile no diretório corrente. Se não houver tal arquivo, o comando terminará com uma mensagem de erro. Se o arquivo tiver outro nome, rode o comando

    make -f <nome_da_makefile>

    Como padrão, o make busca o primeiro alvo (``target''), determina as suas dependências e tenta efetuar as suas regras. No caso acima, há uma série de arquivos que devem existir antes de se executar a compilação final, que faz a linkagem. A barra invertida (\), no final da linha, indica que a linha seguinte é uma continuação. Se os arquivos *.o não existem, o comando make entende que deve procurar regras para criá-los, ou seja, procurará alvos cujos nomes sejam essas dependências. Assim, as regras do segundo, terceiro e quarto ``targets'' serão efetuadas antes do primeiro. Se isso não for possível por qualquer motivo, make interromperá seu procedimento e apresentará uma mensagem de erro.

    Agora, rode

    ./decmat

    para verificar que o programa gerado funciona.

  4. Vamos agora observar como o comando make se comporta com a alteração de algum arquivo. Edite, por exemplo, o arquivo fp_def.f e altere o valor do parâmetro ndim para $2$. Rode novamente o comando make. Quais compilações foram efetuadas?

  5. Mova o arquivo checkmats.f para checkmats.f.OLD. Edite novamente o arquivo fp_def.f, recoloque o valor anterior do parâmetro modificado e rode make. Houve compilação? Qual? Agora, mova checkmats.f.OLD de volta para checkmats.f e rode

    make checkmats.o

    o que deve gerar o objeto checkmats.o. Como se vê, é possível ordenar ao make que execute qualquer um dos alvos existentes na Makefile, o primeiro é apenas o default. Repita o comando acima. Por que não houve compilação? Agora, rode make sem argumentos novamente e analise as mensagens apresentadas. Quais arquivos foram compilados e por quê? Mais uma vez, rode o programa decmat criado para confirmar que ele funciona como antes.

  6. Até aqui, procuramos entender qual a estrutura básica de uma Makefile e como funciona o processo de execução das regras segundo alterações nas dependências. O passo seguinte é sofisticar um pouco a Makefile, usando variáveis. Edite a Makefile e modifique-a para que fique como segue:





    # Makefile para compilacao de programa em Fortran.
    
    # Definicao dos objetos binarios.
    OBJ =           \
    Decompmat.o     \
    checkmats.o     \
    writemat.o      \
    getseed.o       \
    ranr.o
    
    # Primeiro alvo. Observe que trata-se de um alvo
    # _ virtual, que não corresponde a um arquivo.
    all: decmat
    
    # A regra de linkagem.
    decmat: $(OBJ)
            g77 -o decmat $(OBJ)
    
    # Demais dependencias.
    Decompmat.o: Decompmat.f fp_def.f cb_gen.f
    checkmats.o: checkmats.f fp_def.f cb_gen.f
    writemats.o: writemats.f fp_def.f cb_gen.f
    
    # Regra geral de compilacao de objetos.
    %.o: %.f
            g77 -O -c -o $@ $<
    





    Dê um

    touch Decompmat.f

    e rode novamente make para verificar que tudo funciona perfeitamente. Você sabe dizer porque foi necessário o touch?

    Analisemos a nova forma da Makefile. Primeiramente, definimos uma variável OBJ cujo valor é o conjunto de objetos a serem produzidos na compilação. A seguir, criamos um alvo ``default'', com o nome all; neste caso, esse ``target'' não possui nenhuma regra para ser executada, pois ele apenas depende de outro alvo, o decmat. Assim, all funciona apenas como um apelido ou ``alias'' para a compilação. Depois, informamos na Makefile como gerar o executável a partir dos objetos definidos por OBJ. Finalmente, dizemos quais objetos dependem de que arquivos, sem especificar as regras para produzi-los.

    Nesse ponto, lançamos mão de ``pattern rules'', isto é, regras do tipo ``pattern'', que são regras comuns a vários alvos. Estas, por sua vez, fazem uso de variáveis automáticas, definidas por símbolos especiais como % e @.

    O símbolo de percentagem (%) num ``target'' indica o ``pattern'' a ser considerado como nome. A repetição de % na dependência indica que o mesmo nome deve ser considerado na dependência, como um curinga repetido. Logo,

    %.o: %.f

    diz que todo arquivo cujo nome termina com .o depende de um arquivo com a mesma parte inicial do nome-base mas cujo nome termina com .f. A regra de nossa Makefile para efetuar a compilação apresenta duas variáveis:

    $@ (cifrão, arroba) - representa o nome completo do ``target''; em nosso caso, podem ser os objetos Decompmat.o, checkmats.o, writemat.o,
    getseed.o ou ranr.o;

    $< (cifrão, menor) - representa a primeira dependência apenas, o que é apropriado em nosso caso pois as outras são arquivos incluídos na primeira.

  7. Há vários outros tipos de variáveis automáticas compreendidas pelo comando make. Como exercício, execute o comando

    info make

    e procure por ``automatic variables''. Encontre uma segunda variável que substitui todo o conjunto de dependências. Qual variável substitui apenas o nome do diretório dado no alvo? E o nome do arquivo?

  8. Já vimos toda a estrutura básica da Makefile incluindo o uso de algumas variáveis. No entanto, essa ferramenta é bastante robusta, apresentando ainda muitos recursos. Para dar um exemplo de como podem ser as relações entre os vários alvos, criaremos uma estrutura de compilação um pouco mais sofisticada, com maior nível de organização e, consequentemente, mais complexa.

    Na nova estrutura, a compilação será feita dentro de sub-diretórios, e tentaremos automatizar a Makefile ao máximo. Doravante os arquivos fonte deverão permanecer dentro de um diretório src e os objetos, num diretório lib. Se os diretórios não existem, deverão ser criados; logo, temos dois alvos com suas respectivas regras:





    # Variaveis: nomes dos diretorios.
    S = src
    L = lib
    
    # Regras para criar os sub-diretorios.
    $(S):
            mkdir $@
    
    $(L):
            mkdir $@
    





    Tudo foi feito com variáveis para evitar repetições, pois passaremos a nos referir aos objetos e às fontes sempre acompanhados de seus respectivos diretórios.

  9. Com isso, a estrutura de dependências e as regras para compilação mudam. A nova Makefile deve ter o aspecto que é dado a seguir.





    # Makefile para compilacao de programa em Fortran.
    
    # Variaveis: nomes dos diretorios.
    S = src
    L = lib
    
    # Definicao dos objetos.
    OBJ =             \
    $(L)/Decompmat.o  \
    $(L)/checkmats.o  \
    $(L)/writemat.o   \
    $(L)/getseed.o    \
    $(L)/ranr.o
    
    # Primeiro alvo.
    all: decmat
    
    # Regra de linkagem.
    decmat: $(OBJ)
            g77 -o decmat $(OBJ)
    
    # Demais dependencias.
    $(L)/Decompmat.o: $(S)/Decompmat.f \
        $(S)/fp_def.f $(S)/cb_gen.f
    $(L)/checkmats.o: $(S)/checkmats.f \
        $(S)/fp_def.f $(S)/cb_gen.f
    $(L)/writemats.o: $(S)/writemats.f \
        $(S)/fp_def.f $(S)/cb_gen.f
    
    # Regra geral de compilacao de objetos.
    $(L)/%.o: $(S)/%.f $(L)
            g77 -O -c -o $@ $<
    
    # Regras para criar os sub-diretorios.
    $(S):
            mkdir $@
    
    $(L):
            mkdir $@
    





    Observe que a própria regra de compilação exige a existência do diretório lib para que possa ser executada.

    Dê um touch em algum arquivo fonte e rode make.

    Por que não é efetuada a compilação? A mensagem de erro deve dizer algo como que não encontra uma regra para criar um ``target'' src/Decompmat.f, exigido por lib/Decompmat.o. Isso ocorre porque informamos na Makefile que os novos arquivos fonte estão dentro de um diretório src, mas não definimos uma regra para colocá-los ali. Assim, devemos introduzir uma regra que diz como extrair arquivos *.f de decmat.tgz dentro do diretório src:





    # Regra para extrair arquivos de um tar.
    $(S)/%.f: decmat.tgz $(S)
            tar xmzvf $< -C $(S) $*.f
    





    Nessa estrutura, todo arquivo *.f dentro de src depende da existência de decmat.tgz e do próprio diretório src. A variável $* carrega o valor da parte do ``pattern'' definido por ``%'' que é comum ao alvo e às dependências. A opção m no comando tar não preserva as datas originais dos arquivos; isso é feito para se evitar problemas de comparação das datas dos arquivos pelo make em caso de termos uma Makefile mais complexa, em que são efetuadas alterações recorrentes dos arquivos extraídos.

    Rode make e, então, decmat. Dê ls -l * para certificar-se de que toda a estrutura está correta.

  10. Agora você pode remover os arquivos *.f, *.o e decmat do diretório corrente. Para esse tipo de tarefa, é usual e conveniente criar-se uma regra que ``limpa'' a área de trabalho, eliminando todos os arquivos desnecessários e, eventualmente, até os ``targets'' da Makefile. Acrescente à sua Makefile a seguinte estrutura:





    # Regra para limpar a area.
    clean:
            rm -f *~ core $(S)/*~ $(L)/*.o
    
    # Regra para limpar mais a area.
    veryclean: clean
            rm -f decmat
            rm -fr $(L) $(S)
    
    # Regra para _realmente_ limpar a area.
    verycleanindeed: veryclean
            rm -f decmat.tgz
    





    Observe que o alvo verycleanindeed depende de veryclean, que por sua vez depende de clean; por outro lado, este último é efetuado sempre que se roda o comando

    make clean

    pois clean não tem dependências. Logo, todos são sempre efetuados sempre que se roda make verycleanindeed; tome bastante cuidado com estruturas desse tipo, pois, apesar de úteis, podem, também, apagar os dados inadvertidamente em certas circunstâncias.

    Execute o alvo clean e rode make novamente. O que acontece? Como você modificaria os alvos acima para que make clean não force a recompilação de decmat?

  11. Vale aqui uma observação. Depois de um veryclean seguido de make, os alvos são feitos e decmat é gerado. Repare na última linha das mensagens; deve haver um comando para remover uma série de arquivos *.f. Como isso é possível se não demos tal ordem?

    Quando colocamos regras gerais (``pattern rules'') na Makefile, ela interpreta que certos arquivos, gerados em passos intermediários por essas regras e que não foram explicitados, são dispensáveis. Assim, elimina-os depois que termina suas tarefas. Esse é o motivo pelo qual alguns passos são repetidos se executamos make seguidamente, ou seja, mesmo que os alvos já estejam atualizados. Para evitarmos esse tipo de conduta ou se quisermos preservar alguns dos arquivos gerados nos passos intermediários, devemos classificar esses alvos segundo uma classe especial, que corresponde a um grupo especial de nomes reservados para casos como esse, os ``Special Built-in Target Names'' (execute info make e procure esse ítem). Isso é feito com o alvo ``.PRECIOUS'', colocando-se na Makefile a linha

    .PRECIOUS: arquivo1 arquivo2 etc.

    Introduza uma variável SRC em sua Makefile, cujo valor são todos os arquivos src/*.f (escreva-os explicitamente). Depois, defina $(SRC) como ``.PRECIOUS''.

  12. Vale citar outro caso de ``Special Built-in Target Names''. O alvo clean e seus semelhantes não correspondem a arquivos, bem como o alvo all; são apenas nomes de alvos para indicar certas regras. É possível que existam arquivos com esses mesmos nomes, por coincidência ou incidentalmente, o que interferiria na execução dos alvos. Ou, ainda, é possível que queiramos, por algum motivo excêntrico, manter arquivos com o mesmo nome do alvo, mas que sejam independentes deste. Para desvincular nomes de alvos dos respectivos arquivos, devemos caracterizar esses alvos, segundo a classe especial citada acima, como ``phony targets''. Isso é feito introduzindo na Makefile a linha

    .PHONY: all clean veryclean verycleanindeed

  13. Suponhamos que você estivesse satisfeito e executado

    make verycleanindeed

    o que apaga tudo exceto a Makefile. No entanto, você verifica que faltaram alguns testes com o programa decmat e você precisa baixá-lo novamente. Para evitar esse transtorno toda vez, vamos adicionar a estrutura abaixo:





    # Regra para obter arquivo tar pela rede.
    decmat.tgz:
            wget -qN \
        http://latt.if.usp.br/fma215/materiais/aula-16/decmat.tgz
    





    O comando wget puxa o arquivo pela rede, segundo o protocolo http. A opção -q corresponde a ``quiet'', isto é, sem avisos. A opção -N faz com que o arquivo só seja puxado pelo wget caso não exista ou seja mais antigo do que o arquivo que está nas páginas da disciplina, bem no espírito do make. Assim, nossa Makefile está completa e bastante automatizada. Execute o alvo verycleanindeed, dê um ls -l para verificar que a área está vazia (exceto pela Makefile) e, então, make. Finalmente, execute novamente ls -lR e rode decmat para confirmar que a operação foi bem sucedida.


next up previous
Next: Problemas e dicas Up: FMA 215 Aula 16: Previous: Estrutura Básica de uma