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.
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
#!/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.
# 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.
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.
# 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.
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?
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.
# 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.
# 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?
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''.
.PHONY: all clean veryclean verycleanindeed
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.