Utilizando Gnu/Autotools em Projetos

 

 

 

 

Na elaboração de projetos de software utilizamos várias formas de padronizar a compilação e instalação.

Neste artigo vou explicar como montar facilmente um autoconfigurador para sistemas GNU/Linux, onde podemos fazer a checagem das dependências necessárias para compilar nosso projeto.

Conteúdo:

Estrutura Padrão

Primeiramente vamos entender a estrutura básica do padrão GNU/Autotools.

  • raiz
    • src
      • Makefile.am
  • AUTHORS
  • ChangeLog
  • COPYING
  • configure.ac
  • INSTALL
  • Makefile.am
  • NEWS
  • README
  • CREDITS

Os arquivos AUTHORS, ChangeLog, NEWS e README devem ser criados mesmo que vazios, ou com as seguintes informações:
- AUTHORS -> Nomes dos autores do projeto.
- ChangeLog -> Log de alteração do projeto, desde o seu início.
- NEWS -> Novidades de uma versão para outro.
- README -> Informações necessárias para a utilização do sistema ou instalação.

Os arquivos COPYING e INSTALL são gerados na primeira execução da geração do configurador, e contêm as seguintes informações:
- COPYING -> Licença de utilização, no caso é uma cópia da GPL v3.
- INSTALL -> Instruções de configuração, compilação e instalação segundo o padrão GNU.

Os arquivos Makefile.am e configure.ac são os necessários para gerar o arquivo script configure que irá checar as dependências e gerar automaticamente o Makefile com o padrão de compilação e instalação do seu projeto.

Arquivo: configure.ac

Este arquivo é responsável por toda a checagem e nele é especificado o nome do projeto, versão, autor, e-mail para reportar bugs e página do desenvolvedor, além das checagem do compilador, de bibliotecas e arquivos instalados e as saídas que a configuração irá gerar.

Vamos entender sua composição através do exemplo abaixo:

AC_INIT(Projeto, 1.0.0, sombra@alcionesytes.net , projeto , http://www.alcionesytes.net/)
AC_PREREQ([2.50])
AM_INIT_AUTOMAKE([gnu 1.11 -Wall])
AC_PROG_CXX
AC_LANG([C++]
AC_HEADER_STDC
AC_CHECK_HEADERS([iostream string fstream vector])
PKG_CHECK_MODULES([GTKMM], [gtkmm-2.4 >= 2.10])
AC_CHECK_PROGS([PG_CONFIG], [pg_config])
if test x"$PG_CONFIG" = x; then
AC_MSG_ERROR([$PACKAGE requires pg_config])
fi
AC_SUBST(PG_LIBS, [`$PG_CONFIG –libdir`])
AC_SUBST(PG_CPPFLAGS, [`$PG_CONFIG –cppflags`])
AC_SUBST(PG_LDFLAGS, [`$PG_CONFIG –ldflags`])
AC_DEFINE([CONF_FILE],["conf/db.conf"],[Arquivo de Configuração])
AC_TYPE_SIZE_T
AC_PREFIX_DEFAULT([~/bin])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT

AC_INIT
Define o cabeçalho do arquivo e é onde informamos o nome do projeto, versão, e-mail para reportar bugs, nome do arquivo tar e site do desenvolvedor.

AC_PREREQ
Informamos a versão do autoconf utilizada.

AM_INIT_AUTOMAKE
Iniciamos a checagem do automake, os parametros são:

  • gnu padrão GNU/Linux de pojetos
  • 1.11 versão do automake necessária
  • -Wall flag de compilação para o gcc ou g++

AC_PROG_CXX
Checa se o compilador do C++ está instalado no sistema, para checar o compilador do C retiramos o XX.

AC_LANG
Checa se determinada linguagem de programação está presente no sistema no caso do exemplo a linguagem C++.

AC_HEADER_STDC
Checa se os cabeçalhos da Standard C estão presentes, esta checagem pode ser omitida se não for utilizar headers que são presentes em C e C++.

AC_CHECK_HEADERS
Checa as headers necessárias para o seu programa, nesse ponto você deve listar todos as headers que deseja incluir no seu projeto.

PKG_CHECK_MODULES
Esse macro é utilizado para checar a instalação de uma determinada biblioteca, que é mantida em uma lista pelo comando 'pkg-config'. Após checado ele gera variáveis internas para serem utilizadas nos 'Makefile.am' utilizando o prefixo informado, no exemplo o GTKMM, seguido dos sufixos _CPPFLAGS, _LDFLAGS.

AC_CHECK_PROGS
Esse macro serve para testar a existência de um programa que será utilizado, veja que sua estrutura é formada por duas partes, a primeira você nomeia a variável local que irá armazear o caminho do programa, e a segunda é o nome do programa que irá ser checado no PATH padrão do seu sistema.
Usando o 'if', observe que essa estrutura condicional está utilizando o padrão BASH, você pode testar se obteve sucesso na checagem da existência do programa testado, e enviar uma mensagem de erro caso o mesmo não foi encontrado.

AC_SUBST
Esse macro serve para você declarar variáveis que podem ser utilizadas nos Makefile.am para auxiliar na compilação do programa.

AC_DEFINE
Utilizamos esse macro para definir 'macros' da linguagem C/C++ para checagem do preprocessador.

AC_TYPE_SIZE_T
Se estiver utilizando a biblioteca time.h é recomendado utilizar esse macro para checar a declaração do size_t na linguagem.

AC_PREFIX_DEFAULT
Aqui nos definimos o diretório onde será instalado o programa após compilado, ou seja, quando executarmos o 'make install', ele irá utilizar a informação contida nessa linha para determinar onde será instalado o programa, porém se omitido podemos modificá-lo na hora do './configure' utilizando o argumento '--prefix='.

AC_CONFIG_HEADERS
Esse macro é utilizado para definirmos o nome do arquivo headers que será gerado ao final de toda a checagem feita, ou seja, a listagem da existências dos headers checados, dos defines criados e além disso o nome do projeto, juntamente com as informações passadas no ínicio do 'configure.ac'.

AC_CONFIG_FILES
Aqui nos colocamos os arquivos que serão gerados, geralmente os caminhos dos Makefiles, utilizando as informações preprocessadas.

AC_OUTPUT
Finalizamos com esse macro para dizer para o script (configure) que já pode gerar os outputs necessários que já foram definidos acima.

Arquivo: Makefile.am

Nesse arquivo especificaremos quais serão as pastas compiladas e quais os arquivos que pertencem ao projeto.

Vejamos um exemplo básico:

SUBDIRS = src

Projetodir = $(prefix)
Projeto_DATA = README\
COPYING\
AUTHORS\
ChangeLog\
INSTALL\
NEWS\
CREDITS
EXTRA_DIST = $(Projeto_DATA)

Vamos observar alguns detalhes importantes nesse arquivo:

SUBDIRS
Essa variável vai receber a lista de todos os diretórios que tem outros Makefile.am, cada diretório é separado por um espaço, podendo existir mais de um diretório.

Os outros rótulos são formados com o nome de um macro, definido a critério, seguido de um sufixo, cada sufixo tem uma função, vejamos para que servem cada um:

dir
Nesse rótulo informamos onde serão instalados os arquivos listados no rótulo _DATA, geralmente utilizamos a variável já definida $(prefix).

_DATA
Nesse rótulo listamos os arquivos que serão direcionados para o diretório informado no rótulo anterior, podemos separá-los por espaço ou infomar um por linha conforme o exemplo.

EXTRA_DIST
Essa variável vai receber a lista de os arquivos que serão incluídos no projeto quando formos gerar o arquivo compactado para envio, podemos observar que podemos utilizar variáveis já definidas anteriormente.

Arquivo: src/Makefile.am

Diferente do Makefile.am da raiz o que está nesta pasta será um exemplo de montagem para a compilação, ou seja, aqui definimos qual será o nome do executável e suas dependências.

Todas as checagens e variáveis criadas no 'configure.ac' podem ser utilizadas, vejamos o exemplo.

bin_PROGRAMS = Exemplo

Exemplodir = $(includedir)

Exemplo_SOURCES = main.cpp
Exemplo_HEADERS = bibliotecas.h

Exemplo_CPPFLAGS = $(CPPFLAGS) $(GTKMM_CFLAGS) $(PG_CPPFLGAS)
Exemplo_LDFLAGS = $(LIBS) $(LDFLAGS) $(GTKMM_LIBS) -L$(PG_LIBS) -lpq $(PG_LDFLAGS)
Exemplo_LDADD = $(LIBS) $(LDFLAGS) $(GTKMM_LIBS) -L$(PG_LIBS) -lpq $(PG_LDFLAGS)

bin_PROGRAMS
Nesse rótulo definimos o nome do executável que será gerado, podem ser gerados mais de um executável, fazemos isso separando os nomes com espaço.

Os outros rótulos são formados com o nome do executável seguido se um sufixo, cada sufixo tem uma função, vejamos para que servem cada um:

dir
Nesse rótulo informamos onde serão instalados os cabeçalhos (.h), geralmente utilizamos a variável já definida $(includedir).

_SOURCES
Nesse rótulo listamos os arquivos-fonte da linguagem que serão compilados, cada arquivo deve ser separado do outro utilizando espaços.

_HEADERS
Nesse rótulo listamos os arquivos cabeçalho (.h) que fazem parte do projeto, para serem incluídos no pacote e quando for instalado serão copiados para o caminho especificado no rótulo dir.

_CPPFLAGS
Nesse rótulo listamos todas as variáveis checadas que tem informações para a compilação do objeto (.o), a principal é a $(CPPFLAGS), mas também devemos colocar as geradas pelo pkg-config, no nosso exemplo é a $(GTKMM_CFLGAS), as definidas pelo AC_SUBST também devem ser listadas, exemplo $(PG_CPPFLGAS).

_LDFLAGS e _LDADD
Nesses dois rótulos listamos todas as variáveis checadas que tem informações para a linkagem do objeto gerando o executável, as principais são a $(LIBS) e $(LDFLAGS), mas também, como fizemos anteriormente, colocar as geradas pelo pkg-config e as definidas pelo AC_SUBST, no nosso exemplo $(GTKMM_LIBS), $(PG_LIBS) e $(PG_LDFLAGS).

Arquivo: src/main.cpp

Vamos utilizar para exemplificar nosso projeto um exemplo retirado do site oficial do GTKmm.org que é a API para C++ que utilizo no desenvolvimento de sistemas desktop.

/**
* main.cpp
* Qua outubro 14 19:32:22 2015
* Copyright 2015 Alcione Ferreira
*
*/
#include <gtkmm.h>
#include <iostream>

using namespace std;

int main (int argc, char **argv)
{
Gtk::Main kit(&argc, &argv);
Gtk::Window *janela;
Gtk::Label *texto;

janela = new Gtk::Window();
janela->set_title(Glib::ustring("Olá Mundo!"));
janela->set_size_request (200,200);
texto = new Gtk::Label(Glib::ustring("Programa Exemplo!"));
janela->add(*texto);
janela->show_all();

Gtk::Main::run(*janela);

return (0);
}

Gerando script 'configure'

Agora executaremos os passos para gerar o script de configuração, na documentação eles passam uma sequência de comandos que podem ser executados, porém podemos abreviar essa sequência utilizando o seguinte comando:
autoreconf -f -i
As opções -f e -i significam, respectivamente, forçado e instalação, que quer dizer para fazer a reconfiguração do projeto de forma a ignorar versões obsoletas e para copiar as informações auxiliares de instalação, onde será colocado o texto base da documentação GNU para projetos OpenSource.

Vejamos o resultado dessa execução:

alcione@sombra:~/Documentos/Artigo-Gnu-Autotools$ autoreconf -f -i
configure.ac:5: installing './install-sh'
configure.ac:5: installing './missing'
src/Makefile.am: installing './depcomp'

Pronto nosso projeto já tem o seu script de configuração pronto, agora iremos executá-lo para gerar o Makefile adaptado ao nosso Sistema Operacional, para posterior geração do executável, vejamos a seguir.

Executando './configure'

Ao executar o script de configuração o sistema será checado em todas as partes que especificamos no 'configure.ac', vamos rodá-lo para ver o resultado.

alcione@sombra:~/Documentos/Artigo-Gnu-Autotools$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for g++... g++
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 g++ accepts -g... yes
checking for style of include used by make... GNU
checking dependency style of g++... gcc3
checking how to run the C++ preprocessor... g++ -E
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /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 iostream usability... yes
checking iostream presence... yes
checking for iostream... yes
checking string usability... yes
checking string presence... yes
checking for string... yes
checking fstream usability... yes
checking fstream presence... yes
checking for fstream... yes
checking vector usability... yes
checking vector presence... yes
checking for vector... yes
checking for pkg-config... /usr/bin/pkg-config
checking pkg-config is at least version 0.9.0... yes
checking for GTKMM... yes
checking for pg_config... pg_config
checking for size_t... yes
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating config.h
config.status: executing depfiles commands

Pronto os Makefile's já estão gerados e aptos para compilar o fonte e gerar o executável, se houvesse algum problema, e não conseguisse checar alguma especificação teríamos alguma mensagem de erro, mas como não foi o caso, vamos para a próxima etapa que é a geração do nosso programa.

Compilando com o 'make'

Para gerar nosso programa (executável), utilizaremos o comando 'make', que irá, recursivamente, executar as instruções passadas nos Makefile.am que fizemos, e que geraram os Makefile correspondentes, vejamos agora o comportamento no projeto:

alcione@sombra:~/Documentos/Artigo-Gnu-Autotools$ make
make all-recursive
make[1]: Entering directory '/home/alcione/Documentos/Artigo-Gnu-Autotools'
Making all in src
make[2]: Entering directory '/home/alcione/Documentos/Artigo-Gnu-Autotools/src'
g++ -DHAVE_CONFIG_H -I. -I.. -pthread -I/usr/include/cairomm-1.0 -I/usr/lib/cairomm-1.0/include -I/usr/include/gtkmm-2.4 -I/usr/lib/x86_64-linux-gnu/gtkmm-2.4/include -I/usr/include/atkmm-1.6 -I/usr/include/gtk-unix-print-2.0 -I/usr/include/gtk-2.0 -I/usr/include/gdkmm-2.4 -I/usr/lib/x86_64-linux-gnu/gdkmm-2.4/include -I/usr/include/giomm-2.4 -I/usr/lib/x86_64-linux-gnu/giomm-2.4/include -I/usr/include/pangomm-1.4 -I/usr/lib/x86_64-linux-gnu/pangomm-1.4/include -I/usr/include/glibmm-2.4 -I/usr/lib/x86_64-linux-gnu/glibmm-2.4/include -I/usr/include/sigc++-2.0 -I/usr/lib/x86_64-linux-gnu/sigc++-2.0/include -I/usr/include/gtk-2.0 -I/usr/lib/x86_64-linux-gnu/gtk-2.0/include -I/usr/include/gio-unix-2.0/ -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/libpng12 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng12 -I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/freetype2 -g -O2 -MT Exemplo-main.o -MD -MP -MF .deps/Exemplo-main.Tpo -c -o Exemplo-main.o `test -f 'main.cpp' || echo './'`main.cpp
mv -f .deps/Exemplo-main.Tpo .deps/Exemplo-main.Po
g++ -g -O2 -lgtkmm-2.4 -latkmm-1.6 -lgtk-x11-2.0 -lgdkmm-2.4 -lgiomm-2.4 -lpangomm-1.4 -lglibmm-2.4 -lcairomm-1.0 -lsigc-2.0 -lgtk-x11-2.0 -lgdk-x11-2.0 -lpangocairo-1.0 -latk-1.0 -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lpangoft2-1.0 -lpango-1.0 -lgobject-2.0 -lglib-2.0 -lfontconfig -lfreetype -L/usr/pgsql/lib -lpq -Wl,--as-needed -o Exemplo Exemplo-main.o -lgtkmm-2.4 -latkmm-1.6 -lgtk-x11-2.0 -lgdkmm-2.4 -lgiomm-2.4 -lpangomm-1.4 -lglibmm-2.4 -lcairomm-1.0 -lsigc-2.0 -lgtk-x11-2.0 -lgdk-x11-2.0 -lpangocairo-1.0 -latk-1.0 -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lpangoft2-1.0 -lpango-1.0 -lgobject-2.0 -lglib-2.0 -lfontconfig -lfreetype -L/usr/pgsql/lib -lpq -Wl,--as-needed
make[2]: Leaving directory '/home/alcione/Documentos/Artigo-Gnu-Autotools/src'
make[2]: Entering directory '/home/alcione/Documentos/Artigo-Gnu-Autotools'
make[2]: Leaving directory '/home/alcione/Documentos/Artigo-Gnu-Autotools'
make[1]: Leaving directory '/home/alcione/Documentos/Artigo-Gnu-Autotools'

Pronto nosso programa já está compilado, observem que a quantidade de informação que é criada é muito maior que a que a configuramos. Essa é a mágica do autotools, você especifica o que precisa e o script inclui o necessário.

Vamos ver o resultado desse trabalho.

Testando execução './src/Exemplo'

Para chamar o executável que criamos, partindo da raiz do nosso projeto, digitamos ./src/Exemplo, lembrando que esse exemplo foi o que definimos no src/Makefile.am.

Captura de tela de 2022-10-20 21-08-56

Pronto! Com isso encerramos esse artigo, espero que tenham aproveitado mais esse conhecimento!

Vou disponibilizar o projeto que trabalhamos gerado pela execução do comando 'make dist' na pasta raiz do projeto: projeto-1.0.0.tar.gz

Até o próximo artigo e viva o conhecimento!

Alcione Ferreira <sombra@alcionesytes.net>
https://alcionesytes.net / http://ensino.alcionesytes.net/

Referências:

Gtkmm - C++ Interfaces for GTK+ and GNOME – https://www.gtkmm.org
Automake – Gnu Project – Free Software Foundantion (FSF) - https://www.gnu.org/software/automake/
Autoconf - GNU Project - Free Software Foundation (FSF) - http://www.gnu.org/software/autoconf/autoconf.html
Gnu Autoconf, Automake and Libtool - https://www.sourceware.org/autobook/

 

Share this content: