Olá pessoal, a muito tempo eu queria aprender como funciona o tão famoso autotools. Então eu decidi pegar um projeto pessoal meu e converter meu Makefile, feito na mão, por um script do autotools. Neste tutorial vou mostrar o que eu precisei fazer para a criação do script do zero e como ele funciona, incluindo checagens por bibliotecas necessárias para compilação e outras coisas interessantes.
O autotools, ou o GNU Build System, é um projeto da Free Software Foundation (FSF) e tem como propósito facilitar a compilação de um projeto em diferentes ambientes UNIX-like. O autotools é um pacote com 3 ferramentas principais: autoconf, automake e libtool. Para mais informações sobre o autotools dê uma olhada na Wikipédia aqui.
Se você já precisou compilar um código fonte no Linux, talvez você já tenha utilizado o autotools sem saber! Se você já teve de executar os seguintes passos:
./configure
make
make install
então o autotools não é mais um ser desconhecido para você.
Enquanto escrevo este artigo, encontrei este link da FSF com um hello world do autotools que parece ser muito bom também.
Aviso: os conhecimentos passados neste artigo foram retirados da minha experiência em aprende-lo. Se algo estiver errado, ou algo puder ser melhor explicado, por favor, escreva nos comentários, pois o adendo ajudará outras pessoas também.
Porque usar o autotools? Ele facilita muito na compilação em ambientes diferentes do ambiente do desenvolvedor original do projeto. Ele contém macros onde é possível verificar se existem as bibliotecas necessárias para compilação no seu ambiente, é possível habilitar/desabilitar opções de compilação, facilidade em dizer ao desenvolvedor que está compilando o software quais as bibliotecas faltantes em seu ambiente, criação automática do arquivo Makefile entre vários outros motivos. Grandes projetos de software livre utilizam o autotools, como por exemplo o LibreOffice e o git. Imagine criar um Makefile para o LibreOffice, sendo que este projeto tem inúmeras possíveis configurações para serem ativadas ou desativadas em tempo de compilação. Para se ter uma ideia do tamanho do LibreOffice, seu arquivo configure.ac(que é o arquivo criado com macros do autoconf e automake) tem quase 13 mil linhas. Tive muitas vezes de verificar como o LibreOffice usava algumas macros do autoconf para poder converter meu projeto pessoal para o autotools, já que o LibreOffice abrange muitas opções da ferramenta.
Para iniciar nosso artigo, este era o arquivo Makefile original, feito do zero, para meu projeto pessoal:
CC=g++
CXXFLAGS=-Wall -Wextra -g -lpthread -std=c++0x
# just use in case of debug
#CXXFLAGS += -DCHAT_VERBOSE
all:
$(CC) server.cxx $(CXXFLAGS) -o server
$(CC) client.cxx screen.c $(CXXFLAGS) -o client -lncurses
clean:
rm server client
Pode-se notar como ele é simples e objetivo. Sem verificação de bibliotecas disponíveis no ambiente, sem verificar se existe um compilador instalado. Neste exemplo, se quisermos habilitar o modo “verbose” no projeto temos de ir no arquivo Makefile e descomentar a linha que faz um append no CXXFLAGS.
Para iniciar a usar o autotools é necessário instalar este do repositório de sua distribuição Linux. Após instalado, é necessário criar um arquivo chamado configure.ac. O conteúdo deste arquivo são as configurações gerais do projeto, como por exemplo a verificação da existência de um compilador, existência de bibliotecas necessárias para compilar e possíveis parâmetros que podem ser passados ao projeto antes deste ser compilado. Estas definições são criadas utilizando macros do próprio autotools. Este arquivo será responsável pela geração do script configure com base nas macros definidas no arquivo configure.ac. Ao executar o script configure, esta irá executar as checagens de ambiente, para verificar se é possível compilar o projeto ou não. Após a execução do script configure sem erros então é gerado o arquivo Makefile para compilação do projeto.
O arquivo configure.ac que foi criado para substituir o antigo Makefile é este:
Explicando cada linha do arquivo configure.ac:
AC_INIT: Inicia o autoconf, que é a ferramenta que verifica os pré-requisitos para a compilação do projeto. Os parâmetros desta macro são o nome do projeto, versão do projeto, bugtracker do projeto (vazio neste caso), nome do tarball a ser gerado (também vazio neste caso) e página do projeto.
AC_CHECK_LIB: Faz a verificação de uma biblioteca no sistema que está compilando o projeto. Os parâmetros são o nome da biblioteca, função a ser testada nesta biblioteca, ação se encontrar a biblioteca(vazio neste caso) e ação se não encontrar a biblioteca. Neste ultimo parâmetro foi utilizada outra macro AC_MSG_ERROR. Esta macro termina a execução do script configure e mostra a mensagem de erro especificada.
AC_ARG_ENABLE: Adiciona parâmetro ao script. O parâmetro adicionado neste arquivo é para verificar se irá ser habilitado o modo verbose da aplicação. O parâmetro pode ser passado como –verbose, –enable-verbose=yes ou –enable-verbose=no. Isso tudo gerado automaticamente pelo autoconf.
AC_MSG_CHECKING: Mensagem mostrada ao usuário na execução script configure. Esta mensagem geralmente é utilizada para checar algum possível parâmetro passado ao script configure. Neste caso estamos habilitando um parâmetro “–verbose” que pode ser especificado ao executar o script configure. Os parâmetros usados são o nome do parâmetro e a mensagem de help. Esta mensagem é mostrada quando o programador executa “./configure –help”
AC_MSG_RESULT: Somente complementar a mensagem AC_MSG_CHECK, informando ao programador se a checagem encontrou ou não a configuração.
AC_DEFINE: Define uma variável de ambiente. No contexto do arquivo configure.ac em que se encontra esta macro, é verificado se foi ou não informado o parâmetro de modo “verbose”.
AM_INIT_AUTOMAKE: Inicia o automake, que irá criar os arquivos Makefile para compilação do projeto. O parâmetro passado nesta macro é a versão mínima do automake aceita pelo script.
AC_CONFIG_FILES: Arquivos que serão gerados pelo automake. Neste caso terá um Makefile no mesmo nível do arquivo configure.ac, e outro dentro da estrutura de arquivos fonte.
AC_PRO_CXX: Busca um compilador CXX no ambiente.
AC_OUTPUT: Finaliza o script configure.ac. Neste ponto o script configure é gerado e está pronto para ser executado.
Quando dizemos que os arquivos serão criados pela macro, esta criação acontece somente quando o script configure é executado. O arquivo configure.ac é, como falado antes, somente uma coleção de macros para ser gerado um script configure.
Segundo meu entendimento, o prefixo AC se refere ao autoconf e o prefixo AM se refere ao automake.
Como foi informado na macro AC_CONFIG_FILES dois arquivos Makefile, precisamos criar dois arquivos Makefile.am. O primeiro deles é mostrado abaixo:
A definição SUBIRS informação que existe um outro Makefile dentro da pasta src. A definição EXTRA_DIST é basicamente para incluir arquivos que não gerados pela build do projeto no tarball resultante. Este será explicado posteriormente.
O arquivo Makefile.am responsável pela compilação é mostrado abaixo:
Explicando as definições deste novo artigo:
bin_PROGRAMS: Os arquivos binários que serão gerados no final da build. Neste exemplo, o nome dos binários são server e client, e estes nomes serão utilizados para especificar o que compõe cada binário.
server_SOURCES: Fontes que compõe o binário server. server_LDFLAGS: Flags para o linker encontrar as bibliotecas que o binário server necessita. server_CXXFLAGS: Flags de compilação para o binário server
O binário cliente tem exatamente as mesmas configurações e com as mesmas opções.
Alguns arquivos precisam ser criados para ser possível executar o autotools. Estes arquivos são somente para fins informativos do projeto caso ele seja distribuído.
O comando a seguir cria os arquivos necessários:
touch NEWS README AUTHORS COPYING INSTALL ChangeLog
Após os arquivos criados, necessitamos executar o comando que realmente utiliza o configure.ac e cria o script configure que será executado pelo desenvolvedor. O arquivo autogen.sh foi criado para facilitar o desenvolvedor que altera o script configure.ac e precisa recriar e executar o script configure:
Após a execução do arquivo autogen.sh com sucesso é possível verificar que um arquivo Makefile foi criado. Executando este, será compilado os fontes dentro da pasta src e os binários server e client estarão dentro da pasta src.
Algumas coisas a mais que o autotools faz: existe um target no Makefile chamado dist. Este target pega seus arquivos fontes, todos aqueles arquivos relacionados a READ e demais, e cria um arquivo chat-0.01.tar.gz (neste exemplo). Você pode distribuir este arquivo compactado para outras pessoas para que estas possam compilar e usar o seu projeto. Como o arquivo autogen.sh não tem relação com o projeto, é necessário colocar a definição EXTRA_DIST no Makefile.am no mesmo nível do script configure. Explicado como prometido anteriormente!
Abaixo está a saída de uma execução com sucesso do script configure:
[marcos@xfiles chat]$ ./configure
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking ncurses.h usability... yes
checking ncurses.h presence... yes
checking for ncurses.h... yes
checking for initscr in -lncurses... yes
checking for pthread_create in -lpthread... yes
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for style of include used by make... GNU
checking whether make supports nested variables... yes
checking dependency style of gcc... gcc3
checking for g++... g++
checking whether we are using the GNU C++ compiler... yes
checking whether g++ accepts -g... yes
checking dependency style of g++... gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: executing depfiles commands
E a execução do Makefile:
[marcos@xfiles chat]$ make
Making all in src/
make[1]: Entrando no diretório `/mnt/data/gitroot/chat/src'
g++ -DPACKAGE_NAME="chat" -DPACKAGE_TARNAME="chat" -DPACKAGE_VERSION="0.01" -DPACKAGE_STRING="chat 0.01" -DPACKAGE_BUGREPORT="" -DPACKAGE_URL="" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DPACKAGE="chat" -DVERSION="0.01" -I. -std=c++0x -g -O2 -MT server-server.o -MD -MP -MF .deps/server-server.Tpo -c -o server-server.o `test -f 'server.cxx' || echo './'`server.cxx
mv -f .deps/server-server.Tpo .deps/server-server.Po
g++ -std=c++0x -g -O2 -lpthread -o server server-server.o
g++ -DPACKAGE_NAME="chat" -DPACKAGE_TARNAME="chat" -DPACKAGE_VERSION="0.01" -DPACKAGE_STRING="chat 0.01" -DPACKAGE_BUGREPORT="" -DPACKAGE_URL="" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DPACKAGE="chat" -DVERSION="0.01" -I. -std=c++0x -g -O2 -MT client-client.o -MD -MP -MF .deps/client-client.Tpo -c -o client-client.o `test -f 'client.cxx' || echo './'`client.cxx
mv -f .deps/client-client.Tpo .deps/client-client.Po
g++ -DPACKAGE_NAME="chat" -DPACKAGE_TARNAME="chat" -DPACKAGE_VERSION="0.01" -DPACKAGE_STRING="chat 0.01" -DPACKAGE_BUGREPORT="" -DPACKAGE_URL="" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DPACKAGE="chat" -DVERSION="0.01" -I. -std=c++0x -g -O2 -MT client-screen.o -MD -MP -MF .deps/client-screen.Tpo -c -o client-screen.o `test -f 'screen.cxx' || echo './'`screen.cxx
mv -f .deps/client-screen.Tpo .deps/client-screen.Po
g++ -std=c++0x -g -O2 -lncurses -lpthread -o client client-client.o client-screen.o
make[1]: Saindo do diretório `/mnt/data/gitroot/chat/src'
make[1]: Entrando no diretório `/mnt/data/gitroot/chat'
make[1]: Nada a ser feito para `all-am'.
make[1]: Saindo do diretório `/mnt/data/gitroot/chat'
Então pessoal, como foi possível verificar, o autotools no início parece ser complicado, mas não é. Após começar a entender como ele funciona, todo o resto acaba sendo implícito e ao finalizar seu primeiro script você se sentirá mais a vontade com esta ferramenta ao ver outros projetos a utilizando.
O autotools tem muitas outras opções para resolver muitos outros problemas, mas para o caso apresentado as macros utilizadas no arquivo configure.ac foram suficientes.
Espero que vocês tenham curtido o aprendizado do autotools tanto quando eu! Não se esqueça de se inscrever no nosso feed para ter em primeira mão nossos outros artigos! Um abraço!