Tutorial Autopackage GNOME Launch Box
Portabilidade do binário
Portabilidade do binário é garantir que seus programas compilados funcionem em diferentes computadores. Há coisas simples que você pode fazer para assegurar que a experiência de instalação é o mais próximo possível de "Apenas Funciona", não importa em que distribuição os usuários executem.
Vamos dar uma olhada em quantas bibliotecas Launch Box precisa.
[mike@redwood gnome-launch-box]$ ldd src/gnome-launch-box | wc -l 75
75?! Oh meu Deus, são muitas bibiotecas. Isto não pode estar possivelmente certo, um programa tão simples não deveria ter 75 dependências. Vamos cavar um pouco mais fundo. Olhando pela lista vemos muitas bibliotecas que a aplicação claramente não usa: por exemplo, libXrandR é ligada dentro do programa apesar do fato que Launch Box não modifica a resolução de sua tela. O que está acontecendo aqui?
Bem, há na verdade duas questões. A primeira é que o programa ldd mostra a você todas as bibliotecas carregadas na imagem do processo, não todas as bibliotecas que o programa pede diretamente. Nós não estamos vendo somente as dependências do programa, mas as dependências das dependências e assim por diante. Nós podemos ver somente o que o próprio Launch Box diretamente precisa com o seguinte comando:
[mike@redwood gnome-launch-box]$ objdump -x src/gnome-launch-box | grep NEEDED
Este é um comando tão útil que eu usualmente faço uma função dele no shell.
Ele lhe mostra as entradas ELF DT_NEEDED para o binário: estas são
as bibliotecas que são diretamente requisitadas e devem estar disponíveis em todos
os sistemas nos quais seu binário vai executar.
[mike@redwood gnome-launch-box]$ objdump -x src/gnome-launch-box | grep NEEDED NEEDED libgnome-menu.so.0 NEEDED libgnome-desktop-2.so.2 NEEDED libgnomeui-2.so.0 NEEDED libSM.so.6 NEEDED libICE.so.6 NEEDED libstartup-notification-1.so.0 NEEDED libbonoboui-2.so.0 NEEDED libgnomecanvas-2.so.0 NEEDED libart_lgpl_2.so.2 NEEDED libpangoft2-1.0.so.0 NEEDED libgtk-x11-2.0.so.0 NEEDED libgdk-x11-2.0.so.0 NEEDED libatk-1.0.so.0 NEEDED libgdk_pixbuf-2.0.so.0 NEEDED libpangoxft-1.0.so.0 NEEDED libpangox-1.0.so.0 NEEDED libpango-1.0.so.0 NEEDED libgobject-2.0.so.0 NEEDED libebook-1.2.so.3 NEEDED libedataserver-1.2.so.4 NEEDED libgnome-2.so.0 NEEDED libpopt.so.0 NEEDED libxml2.so.2 NEEDED libpthread.so.0 NEEDED libz.so.1 NEEDED libgnomevfs-2.so.0 NEEDED libbonobo-2.so.0 NEEDED libgconf-2.so.4 NEEDED libbonobo-activation.so.4 NEEDED libORBit-2.so.0 NEEDED libm.so.6 NEEDED libgmodule-2.0.so.0 NEEDED libdl.so.2 NEEDED libgthread-2.0.so.0 NEEDED libglib-2.0.so.0 NEEDED libc.so.6
Ouch - 36 bibliotecas é muito melhor do que 75, mas são muitas dependências. E elas ainda parecem incorretas: Launch Box utiliza seu próprio sistema IPC da libbacon, não Bonobo. Então por que libbonobo-activation estã sendo ligada? Está sequer sendo usada? Com "linkers" glibc modernos, você pode descobrir se há qualquer biblioteca na lista DT_NEEDED que não é utilizada:
[mike@redwood gnome-launch-box]$ ldd -u -r src/gnome-launch-box
Unused direct dependencies:
/usr/X11R6/lib/libSM.so.6
/usr/X11R6/lib/libICE.so.6
/usr/lib/libstartup-notification-1.so.0
/usr/lib/libbonoboui-2.so.0
/usr/lib/libgnomecanvas-2.so.0
/usr/lib/libart_lgpl_2.so.2
/usr/lib/libpangoft2-1.0.so.0
/usr/lib/libatk-1.0.so.0
/usr/lib/libpangoxft-1.0.so.0
/usr/lib/libpangox-1.0.so.0
/usr/lib/libpango-1.0.so.0
/usr/lib/libedataserver-1.2.so.4
/usr/lib/libgnome-2.so.0
/usr/lib/libpopt.so.0
/usr/lib/libz.so.1
/usr/lib/libbonobo-2.so.0
/usr/lib/libgconf-2.so.4
/usr/lib/libbonobo-activation.so.4
/usr/lib/libORBit-2.so.0
/lib/libm.so.6
/usr/lib/libgmodule-2.0.so.0
/lib/libdl.so.2
/usr/lib/libgthread-2.0.so.0
Interessante! 23 das 36 bibliotecas não são utilizadas! Elas estão sendo adicionadas por arquivos de configuração pkg e scripts similares que não listam somente a biblioteca na LDFLAGS, mas também as dependências das bibliotecas. Isto é necessário em alguns "linkers" não-GNU os quais não conseguem percorrer a árvore de dependências de bibliotecas, mas no Linux é apenas um aborrecimento.
Nós podemos aumentar imediatamente o número de sistemas em que o binário executa apenas eliminando essas chamadas dependências erradas. Por que devemos arriscar o usuário ver uma mensagem de erro sobre uma biblioteca que ele possui, mas que mesmo assim não precisa?
Nós podemos utilizar apbuild para consertar este problema de forma transparente. De fato, como veremos em um momento, apbuild pode automaticamente consertar muitos erros bobos de portabilidade de binário como este. Apbuild é um script encapsulador do GCC fácil de usar, que modifica os parâmetros passados para o GCC de tal forma que ele aumenta o número de sistemas Linux em que seus binários podem executar - de graça! Ele resolve muitos outros problemas, como versões de símbolo glibc, cabeçalhos não versionados, problemas com o tratamento de exceções GNU. Para binários em C++ ele pode até compilar duplamente o binário com dois compiladores diferentes que autopackage pode então usar para garantir que o usuário sempre pegará o binário da ABI certa.
É recomendado que você utilize apbuild com as últimas versões do conjunto de ferramentas GNU. Em particular, uma versão recente do binutils.
[mike@redwood gnome-launch-box]$ make CC=apgcc CXX=apg++
.....
[mike@redwood gnome-launch-box]$ objdump -x src/gnome-launch-box | grep NEEDED
NEEDED libpthread.so.0
NEEDED libgnome-menu.so.0
NEEDED libgnome-desktop-2.so.2
NEEDED libgnomeui-2.so.0
NEEDED libgtk-x11-2.0.so.0
NEEDED libgdk-x11-2.0.so.0
NEEDED libgdk_pixbuf-2.0.so.0
NEEDED libgobject-2.0.so.0
NEEDED libebook-1.2.so.3
NEEDED libxml2.so.2
NEEDED libgnomevfs-2.so.0
NEEDED libglib-2.0.so.0
NEEDED libc.so.6
Lidando com dependências
Vamos analizar cada biblioteca e descobrir quantos usuários terão elas.
- libpthread.so.0
- Está é parte da glibc, todos a terão.
- libgnome-menu.so.0
- Esta biblioteca vem com GNOME 2.10, mas é instável e pode quebrar compatibilidade a qualquer hora: mesmo em pontos de lançamento ou atualizações de segurança!
- libgnome-desktop-2.so.2
- Parte do GNOME 2.10 (a plataforma Desktop), e não é parte da plataforma de desenvolvimento GNOME. De novo, poderia quebrar compatibilidade com versões anteriores a qualquer hora. Não podemos confiar em ela estar presente.
- libgnomeui-2.so.0
- De acordo com Launch Boxes configure.ac, qualquer versão desta será suficiente. Também é parte de toda distribuição GNOME desde a versão 2.0, então está OK ligar com esta dinamicamente. É bem provável estar no sistema do usuário, ou pelo menos, muito fácil de conseguir.
- libgtk/libgdk/libgdk_pixbuf/libgobject/libglib
- Estas não são dependências separadas, mas uma única dependência: GTK+. É uma biblioteca difundida e quase todos a possuem. Porém, atualizar GTK+ para usuários finais é bastante difícil, realmente significa uma atualização da distrubuição e usuários (e até desenvolvedores) podem não possuir a última versão. Vale a pena tentar fazer sua aplicação executar com versões antigas do GTK+; desta forma você pode conseguir mais usuários (e como tal, mais desenvolvedores).
- libebook-1.2.so.3
- Este é um componente do Servidor Evolution Data. Você pode instalar s-e-d sem possuir Evolution realmente instalado, mas a maioria das pessoas dificilmente terão a não ser que também usem Evo. Launch Box é tão útil como uma forma rápida de executar aplicações que nós não queremos fazer esta uma dependência complicada. Eel deve funcionar corretamente mesmo se Evo não estiver instalado mas utilizar as funcionalidades extras se libebook estiver presente.
- libxml2.so.2
- Infelizmente, não há um configure.ac para registro desta biblioteca então nós não temos idéia de qual versão é necessária. Este tipo de coisa é o porquê autopackages devem ser escritos pelo mesmo desenvolvedor que escreveu o software original. Felizmente libxml2 é bem difundida, é seguro apostar que usuários a terão (assumindo que funcionalidades muito recentes não serão utilizadas).
- libgnomevfs-2.so.0
- O VFS vem com toda cópia de GNOME, então é provável que estará presente. É também bastante estável. Por outro lado, Launch Box precisa da versão GNOME 2.10, a qual é muito recente. Poucos usuários terão esta por enquanto. Ah bem, c'est la vie.
- libc.so.6
- A biblioteca GNU C é uma biblioteca muito especial. Você não pode dizer qual versão dela você precisa olhando somente as entradas DT_NEEDED, nem olhando o fonte. Algumas vezes você terá automaticamente dependências de versões muito novas da biblioteca C, mesmo que sem razão. Apbuild conserta isso automaticamente.
relaytool
Vamos fazer Launch Box funcionar mesmo que libebook-1.2.so esteja faltando.
A forma tradicional de fazer isso é utilizar dlopen() e dlsym(),
mas essa interface é um tanto embaraçosa. Porque "weak linking" é tão vital
para reduzir dependências e fazer o software mais fácil para instalar, o
projeto autopackage provê um programa que faz o uso de dlopen mais fácil.
Por trás da cena, relaytool gera e compila um arquivo C que pode representar os símbolos que você usa na biblioteca real. Quando seu programa inicia , uma função construtora ELF invoca dlopen na biblioteca e configura tudo para que então, quando você usar um símbolo da biblioteca, ele seja redirecionado para um stub gerado pelo relaytool (se a biblioteca está faltando) ou a real. A implementação atual envolve um pouco de assembly e eu não vou entrar em detalhes.
Primeiramente, nós gostaríamos de fazer isso opcional em tempo de compilação. Isto não é muito difícil, nós apenas adicionamos uma condicional do automake para alternar entre os dois arquivos lb-module-evolution, cercar o código de inicialização no gerenciador de módulo com um condicional do pré-processador C, e testar isso separadamente no script de configuração:
PKG_CHECK_MODULES(EVO, libebook-1.2, have_ebook=true, have_ebook=false) AM_CONDITIONAL(EBOOK, test x$have_ebook = xtrue) if test x$have_ebook = xtrue; then AC_DEFINE(HAVE_EBOOK, 1, [Have libebook-1.2.so]) fi
... então podemos separar os códigos fonte contendo o plugin Evo em um Makefile.am:
if EBOOK gnome_launch_box_SOURCES += lb-module-evolution.c lb-module-evolution.h endif
... e garantir que o plugin não vai ser carregado se estiver faltando:
#ifdef HAVE_EBOOK manager->modules = g_list_append (manager->modules, g_object_new (LB_TYPE_MODULE_EVOLUTION, NULL)); #endif
Até agora, não há nada especial. Agora se Evolution não estiver disponível em tempo de compilação, ainda será compilável. Mas tempo de compilação não é bom o bastante. Usuários querem binários flexíveis como os fontes, e isso é o que daremos a eles.
Relaytool é desenhado para funcionar em conjunto com pkg-config. Ele provê uma simples macro M4 que você pode usar em seu script de configuração. Uma vez que o apbuild foi instalado, você deve ser capaz de adicionar isso após a chamada PKG_CHECK_MODULES retornar com sucesso:
RELAYTOOL("ebook-1.2", LB_LIBS, LB_CFLAGS, )
O parâmetro final é algum código para executar se a ligação fraca for possível. No momento, isso simplesmente significa, se relaytool estiver instalado. Se não estiver então você terá um "hard link" como de costume. Os dois parâmetros do meio são os nomes das variáveis shell contendo o "linker" e comandos para o compilador, respectivamente. A macro RELAYTOOL irá modificá-los conforme necessário.
Agora, no código, você precisa encontrar aonde a biblioteca está
sendo utilizada e proteger quaisquer chamadas da biblioteca com
condicionais em tempo de execução. Nós podemos ter que reestruturar
o programa para que os codepaths que utilizam a biblioteca
sempre sejam compilados juntos. Neste caso, é fácil. Nós precisamos
colocar a macro RELAYTOOL_EBOOK_1_2 no fonte, protegida por um ifdef
para que se a biblioteca está faltando completamente seja ignorada.
O nome da macro é gerado através do nome da biblioteca. Em tempo de
compilação a macro será obtida da CFLAGS e será expandida para prover
libebook_1_2_is_present e libebook_1_2_symbol_is_present()
os quais você pode utilizar para evitar invocar os stubs acidentalmente.:
#ifdef HAVE_EBOOK /* the following line is a macro that provides the relaytool _is_present symbols */ RELAYTOOL_EBOOK_1_2 #endif .... #ifdef HAVE_EBOOK if (libebook_1_2_is_present) manager->modules = g_list_append (manager->modules, g_object_new (LB_TYPE_MODULE_EVOLUTION, NULL)); #endif
É isso! É tudo o que temos que fazer. Não há necessidade de modificar o código além de adicionar relaytool ao sistema de construção do programa e adicionar a verificação runtime, como mostrado. Se você chamar uma função em uma biblioteca que não está presente, então seu programa irá abortar com um erro identificando qual função foi chamada.
Relaytool funciona para variáveis também: se a biblioteca está faltando, o valor sempre será -1. É também capaz de "weak linking" com bibliotecas C++. Finalmente, você pode "confiar" um subconjunto de funcionalidades em uma biblioteca que você está "hard linking". Isto pode ser utilizado para facilmente utilizar novas APIs que podem ou não estar presentes na cópia que os usuários possuem de uma biblioteca (ou seja, o novo selecionador de arquivos GTK+).

