quinta-feira, 19 de abril de 2007

Makefiles

O que vem a ser Make?

Make é um programa que trabalha com uma arquivo chamado makefile ou Makefile. Dentro deste arquivo são declaradas variáveis, dependências e regras que servirão para o propósito de compilação de vários tipos diferentes de arquivos (C, C++, Phyton, PostScript, etc). A utilização do make traz grandes vantagens para o programador, tais como:
  • Evitar a digitação de comandos longos toda vez que um projeto tiver que ser recompilado;
  • Evitar o esquecimento de parâmetros utilizados raramente na compilação e que possivelmente podem ser esquecidos;
  • Manter o ambiente de desenvolvimento consistente;
  • Automatizar o processo de construção/compilação.
Formato dos Makefiles - Variáveis

Para efetuar a declaração de variáveis dentro de um makefile, simplesmente deve-se colocar o nome desejado para ela em letras maiúsculas seguido do sinal de igualdade e posteriormente o valor desejado

NOMEVAR = Valor

Assim é feita a atribuição de Valor à variável NOMEVAR. Pode-se definir, ainda, como valores de variáveis parâmetros de compilação ou mesmo o nome do compilador escolhido para ser utilizado.

PARAM = -o
CXX = g++

Isto atribui o parâmetro de compilação -o à variável PARAM e o nome do compilador g++ à variável CXX. Para acessar o conteúdo de uma variável, colocasse um cifrão($) seguido do nome da variável entre parentesis.

$(NOMEVAR)

Retornando, assim, o valor Valor. Da mesma maneira pode-se accesar o valor da variável PARAM e do compilador escolhido.

$(PARAM)
$(CXX)

Formato dos Makefiles - Dependências

Dependencias são o "coração" dos makefiles. Sem eles nada será feito. O formato geral para declaração de dependências é como se segue:

dependencia: dependencia1 dependencia2 ... dependenciaN
          comandos para a dependencia

É importante ressaltar que antes dos comandos para a dependencia é necessária a colocação de uma tabulação(tab), e não espaços, como pode parecer. A forma de leitura desse arquivo é exatamente verificar cada dependência e as dependências que ele possui. O que o make faz é verificar dependencia e perceber que ela é dependende de dependencia1, dependencia2 ... dependenciaN. Após isso ele vai atras da definição de cada uma dessas dependências para poder, ao final, executar os comandos do alvo1. Por exemplo:

main: programa.cc
          g++ programa.cc -o programa

Este é um exemplo bem simples, onde pode-se perceber a maneira que o make atua. Ao chamar o make ele automaticamente compila as dependências de main, por que é a primeira dependencia contido no arquivo makefile. Com isso, ele checa programa.cc para verificar quando se deu a última modificação nesse arquivo e determinar se é necessário executar os comandos correspondentes (g++ programa.cc -o programa). Se o arquivo não tiver sido modificado após a última compilação, nada será executado.

Um outro exemplo, onde pode-se perceber essas dependências mais claramente é no desenvolvimento de vários arquivos, onde o principal depende de vários outros. Sendo assim, na declaração da primeira dependência coloca-se como dependentes os outros arquivos objeto que são gerados dos outros arquivos, e na dependência desses respectivos arquivos os comandos de compilação para eles. Fazendo como a seguir:

programa: main.cc subarquivo.o classe.o
          g++ main.cc subarquivo.o classe.o

subarquivo.o: subarquivo.cc classe.o
          g++ -c main.cc subarquivo.o classe.o

classe.o: classe.cc
          g++ -c classe.cc

Obervemos as dependências aqui. Quando o make é executado, a dependência programa é a primeira a ser chamada. Então, a dependência subarquivo.o é encontrada, logo após, classe.o é encontrada dentro de subarquivo.o. Classe.cc é, então, compilada, a seguir subarquivo.o é compilado, então, finalmente podemos retornar à dependência programa. Todas as três dependências estão compiladas e, finalmente, os comandos de programa são efetuados para criar a.out.

Amarrando variáveis e dependências

Podemos usar valores de variáveis dentro de dependências muito facilmente. No próximo exemplo, utilizamos duas variáveis, uma para definir o compilador e outra para definir as opções de compilação. Isto facilita muito a vida quando queremos, por exemplo, habilitar o depurador para todo o código ou desabilitá-lo, se assim preferirmos.

CXX = g++
FLAGS = -g

program: main.cc subarquivo.o classe.o
          $(CXX) $(FLAGS) main.cc subarquivo.o classe.o

subarquivo.o: subarquivo.cc classe.o
          $(CXX) $(FLAGS) -c main.cc subarquivo.o classe.o

classe.o: classe.cc
          $(CXX) $(FLAGS) -c classe.cc

Surge a pergunta: como faremos para limpar isto? A resposta é simples, basta adicionar ao makefile uma nova dependencia chamada "clean", a qual se encarregará de limpar todo nosso código e recomeçar do que tinhamos antes, ou seja, somente os arquivos fonte. A dependência é feita como se segue:

CXX = g++
FLAGS = -g

program: main.cc subarquivo.o classe.o
          $(CXX) $(FLAGS) main.cc subarquivo.o classe.o

subarquivo.o: subarquivo.cc classe.o
          $(CXX) $(FLAGS) -c main.cc subarquivo.o classe.o

classe.o: classe.cc
          $(CXX) $(FLAGS) -c classe.cc

clean:
          rm -rf *.o a.out

Assim, quando digitamos “make clean” na linha de comando, ele removerá todas os arquivos .o e o executável a.out que nós temos.

Conclusões

Os Makefiles são muito muito úteis ao tratar dos projetos. A primeira coisa que você deve fazer quando fazer um projeto é críar um makefile, ele torna sua vida muito mais fácil.

Referências

[1] Criando makefiles - Gazeta do linux
[2] Makefile Getting Started

quarta-feira, 18 de abril de 2007

Iniciando

Primeiramente é preciso escolher a plataforma de desenvolvimento, que passa pelas seguintes questões:
  • Sistema Operacional (Windows, Linux, MacOS, etc);
  • Ferramentas de desenvolvimento:
    • Linguagem (C, C++, Phyton, Pascal, Fortran, Java, etc);
    • IDE's (Eclipse, Anjuta, Dev-XXX, Visual-XXX(Microsoft), XXX-Builder(Borland), etc) ou;
    • Editores de texto (Kate, Xemacs, Gedit, Kwrite, Bloco de notas, etc)
    • Compiladores (gcc, g++, g77, etc)
    • Paradigma (Imperativa, Funcional, Declarativa)
    • Disciplina de programação (OO, Estruturada, etc)
    • Bibliotecas (runtime, glib, STL, etc)
Rodar exemplos é uma boa maneira de iniciar, pegar coisas prontas e tentar entender, ou olhar apostilas, tutoriais e documentações sobre os componentes escolhidos para a plataforma de desenvolvimento. Estudar também as opções que o compilador lhe ofereçe é uma boa, tais como: somente compilar, gerar executável, otimizar o código, depurar, etc.
Procurar dominar mais de uma tecnologia é sempre importante, para não ficar bitolado e saber se virar nas mais diferentes situações.