logo Autopackage - Easy Linux Software Installation

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

Agora está melhor: nós podemos ver quais bibliotecas nosso programa realmente precisa.
Agora é hora de uma auditoria de dependências.

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+).

Sumário

E agora terminamos! Vamos dar uma olhada em todas as coisas que fizemos:
  • Audiência de dependências: nós determinamos o que o programa realmente precisa, e as especificamos como dependências obrigatórias ou opcionais.
  • "Soft linking": utilizando relaytool, nós transformamos libebook em uma dependência opcional em tempo de execução. Isto torna a vida mais fácil para usuários finais!

Uma nota final: dependência glibc

Uma coisa que não lidamos neste tutorial foi a dependência glibc. Você alguma vez já viu erros nas linhas "undefined symbol GLIBC_2.3 in /lib/libc.so" ("símbolo indefinido GLIBC_2.3 in /lib/libc.so") quando tenta executar um programa? Este problema (e o conserto) são explicados pela página APbuild. Os projetos autoconf/automake que utilizam a função prepareBuild (isto será explicado no passo 4) serão automaticamente compilados utilizando APBuild. Mas se seu projeto não utilizar autoconf/automake (logo não utiliza prepareBuild) enão não esqueça de compilar seu fonte utilizando APBuild!

Tutorial Autopackage GNOME Launch Box