Discuta este tópico no fórum

Se este conteúdo te ajudou, deixe um presente!

quinta-feira, 2 de abril de 2015

OpenWRT: Criando novos pacotes - construção de um pacote

Mais um artigo da série sobre o OpenWRT.
 artigo anterior, apresentei o problema da compilação de um novo programa para o OpenWRT. Neste vamos colocar a mão na massa.


Vou pegar um exemplo simples: o figlet. Ele faz isto:

$ figlet LuizLuca
 _          _     _                    
| |   _   _(_)___| |   _   _  ___ __ _ 
| |  | | | | |_  / |  | | | |/ __/ _` |
| |__| |_| | |/ /| |__| |_| | (_| (_| |
|_____\__,_|_/___|_____\__,_|\___\__,_|
                                       

Fantástico, não é? (nem sei como vivi sem ele até hoje) O que o programa faz não importa neste caso.

Você tem duas opções para montar o ambiente para gerar seus pacotes: recompilar todo o OpenWRT ou utilizar o SDK disponível no mesmo local onde estão as firmwares. O primeiro caso vai utilizar mais espaço e demorar mais mas traz a vantagem de isolar problemas do seu ambiente de problemas do seu pacote. Afinal de contas, se compilou todo o OpenWRT e não o meu pacote, o problema provavelmente é comigo. Fora isto, os fontes locais podem facilitar o uso destes como inspiração ou modelo para seu próprio pacote. Problemas de compilação cruzada se repetem aos montes e espiar o vizinho ajuda muito. Também, se seu desejo é integrar o pacote de volta ao OpenWRT para que outros usuários também possam aproveitar seu trabalho, o ideal é trabalhar em conjunto com todo o OpenWRT. 
A segunda opção é mais econômica. Serve mais para compilações isoladas. Como é o caso deste exemplo, foi optar por esta alternativa.

Vou pegar a família ar71xx, muito comum em roteadores, para este exemplo. O SDK desta família está aqui. Baixe de descompacte:

$ wget https://downloads...OpenWRT-SDK-ar71xx-for-linux-...tar.bz2
$ tar -xjvf OpenWRT-SDK...tar.bz2
$ cd OpenWrt-SDK-...

Tem um problema nesta versão do SDK. Ele está com o uso do ccache ativado. Caso isto seja um problema para você, desative-o editando o arquivo Config-build.in nesta parte:

config CCACHE
    bool
    default n

Estamos prontos para criar o primeiro pacote. crie um subdiretório em package. No caso, criarei o figlet (OpenWrt-SDK-.../package/figlet/. Dentro deste criaremos um arquivo Makefile descrevendo o pacote. Makefile? Mas isto não é usado depois na compilação? Sim, o OpenWRT (ab)usou o make para montar todo seu ambiente de compilação. Este Makefile não é o mesmo gerado pelo ./configure mas um padrão do OpenWRT que descreve as informações do seu pacote, como compilar, instalar e metainformações, assim como ocorre nos arquivos spec do RPM. Sim, pode ser feio, nem lembra um Makefile comum, mas funciona. Fora o Makefile existem outros arquivos opcionais. Abaixo do subdiretório "patches" ficam eventuais correções para consertar ou adaptar problemas nos fontes do programa. Caso necessite adicionar algum arquivo não gerado pelo ou presente nos fontes, como scripts de disparo de serviços, estes podem ficam em um outro subdiretório "files". E os fontes? Isto o OpenWRT baixa para você. Você só precisa dizer de onde no Makefile.

O arquivo Makefile do exemplo deste artigo ficaria em package/figlet/Makefile. Inicialmente é assim (em destaque o que alterei):

#
# Copyright (C) 2017 Luiz Angelo Daros de Luca
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=figlet
PKG_VERSION:=2.2.5
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=ftp://ftp.figlet.org/pub/figlet/program/unix/
PKG_MD5SUM:=d88cb33a14f1469fff975d021ae2858e
PKG_MAINTAINER:=Luiz Angelo Daros de Luca <luizluca@gmail.com>
PKG_LICENSE:=BSD-3-Clause
PKG_LICENSE_FILES:=LICENSE
PKG_INSTALL:=1

include $(INCLUDE_DIR)/package.mk

define Package/figlet
  SECTION:=xxx
  CATEGORY:=Utilities
  TITLE:=FIGlet
  URL:=http://www.figlet.org/
  MAINTAINER:=Claudio Matsuoka <cmatsuoka@gmail.com>
  DEPENDS:=
endef

define Package/figlet/description
  FIGlet is a program for making large letters out of ordinary text
endef

# Eh obrigatorio ser tabs antes das linhas abaixo
define Package/figlet/install
 $(INSTALL_DIR) $(1)/usr/bin
 $(CP) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/bin
 $(INSTALL_DIR) $(1)/usr/share/figlet/
 $(CP) $(PKG_INSTALL_DIR)/usr/share/figlet/* $(1)/usr/share/figlet/
endef

$(eval $(call BuildPackage,figlet))

O cabeçalho do arquivo, comentado, é meramente informativo. Os PKG_* descrevem dados para os pacotes gerados por este Makefile (sim, pode ser mais de um mas não é o caso deste exemplo). Os nomes são bem auto-explicativos. NAME é o nome do pacote, VERSION (do programa) e RELEASE (do pacote), em conjunto, formarão a versão do pacote. SOURCE_URL/SOURCE indicam de onde baixar e o MD5SUM o hash do arquivo. O campo MAINTAINER é o "dono" deste pacote. O PKG_INSTALL pede para que o "make install" seja executado após a compilação.

O "define Package/figlet" efetivamente declara o pacote. Em especial, observe o campo DEPENDS, que possui uma sintaxe própria e é usado para definir as dependências entre os pacotes. Este pacote de exemplo não tem dependências conhecidas (até este momento). "Package/figlet/description" é apenas um texto descritivo.

A compilação ocorre em uma série de etapas.
  • Build/Prepare:  descompacta os fontes, e aplica os patches (Build/Patches)
  • Build/Configure: roda o ./configure (ou equivalente)
  • Build/Compile: roda o make
  • Build/Install: se PKG_INSTALL for 1, roda o "make install", instalando em $(PKG_INSTALL_DIR)
Normalmente você não irá querer mudar essas etapas. Se não definidas, elas irão utilizar um valor padrão, de uma macro com o mesmo nome e sufixo /Default, que normalmente funcionará. Ex: se Build/Prepare não existir no pacote, será utilizado o Build/Prepare/Default. Você tem liberdade de mudar qualquer coisa dessas etapas, chamando ou não o respectivo valor /Default. Porém, normalmente os ajustes necessários são feitos mudando as variáveis usadas (ex: CONFIGURE_ARGS). Se quiser ver o conteúdo, olhe em https://github.com/openwrt/openwrt/blob/master/include/package-defaults.mk.

O "define Package/figlet/install" é um passo importante. Ele preenche a árvore que formará os arquivos do pacote. Este caminho é passado como argumento $(1). Então, se criar um $(1)/usr/yyy, o pacote será o conteúdo /usr/yyy. Normalmente o Package/xxx/install é uma sequência de chamadas ao comando install, que é equivalente uma composição dos comandos mkdir, cp, chmod, chown. Se o "make install" foi utilizado, o Package/xxx/install irá copiar arquivos de $(PKG_INSTALL_DIR) para $(1). Ao invés de usar diretamente o comando install ou ainda o cp/mkdir, o recomendado é utilizar o INSTALL_DIR, INSTALL_BIN, INSTALL_CONFIG, INSTALL_DATA (definidos em https://github.com/openwrt/openwrt/blob/master/rules.mk#L257)

E vamos a compilação:

$ make
Collecting package info: done
#
# configuration written to .config
#
 make[1] world
 make[2] package/compile
 make[3] -C package/figlet compile
make -r world: build failed. Please re-run make with V=s to see what's going on

Erro?! Mas o que aconteceu? O OpenWRT não mostra o erro por padrão. Como sugerido, rode novamente com o V=s para ver o que está acontecendo (vou omitir linhas não relevantes):

$ make V=s
...
mips-openwrt-linux-uclibc-ld  -o figlet figlet.o zipio.o crc.o inflate.o utf8.o
mips-openwrt-linux-uclibc-ld: warning: cannot find entry symbol __start; defaulting to 00000000004000b0
figlet.o:OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/figlet.c:293: undefined reference to `stderr'
figlet.o:OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/figlet.c:293: undefined reference to `stderr'
figlet.o:OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/figlet.c:293: undefined reference to `fprintf'
...

Não achando stderr? fprintf? __start? Isto é da libc! Quem já programou um pouco de C sabe que normalmente não precisamos indicar manualmente a libc para o ligador (linker). E por que ele não achou, então? Bem, o figlet é um dos exemplos onde o desenvolvedor optou por não usar o ./configure (autoconf). Ele escreveu o Makefile manualmente. O problema é que algumas coisas são classicamente configuradas no configure, como o prefixo do programa e parâmetros do compilador e do ligador. Outro problema é assumir coisas que nem sempre são verdades. O problema acima, por exemplo, ocorre porque o ligador (ld) não está ligando a libc ao programa caso isto não seja solicitado explicitamente. E por que funciona para o desenvolvedor? Como ele não viu isto? Na maioria dos casos, o ld disponível no Linux adiciona a libc por padrão e não é necessário explicitá-la. No caso do OpenWRT, o linker não faz isto. Se usasse o ./configure, provavelmente o ligador seria configurado apropiadamente (adicionando a libc). Como já enfrentei este problema anteriormente, conheço uma solução de contorno: usar o gcc como ligador. Isto pode ser feito substituindo a variável ambiente LD do make pelo compilador do alvo. Só adicionar isto ao Makefile anterior:

MAKE_FLAGS += \
    LD="$(TARGET_CC)"

E compilar novamente:

$ make V=s
...
cp figlet chkfont figlist showfigfonts /home/luizluca/Downloads/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/ipkg-install/usr/local/bin
...
cp: cannot stat '/home/luizluca/Downloads/OpenWrt-SDK-ar71xx-for-linux-x86_64-gcc-4.8-linaro_uClibc-0.9.33.2/build_dir/target-mips_34kc_uClibc-0.9.33.2/figlet-2.2.5/ipkg-install/usr/bin/*': No such file or directory

Erro novamente?! Ao menos ele compilou. Por que ele não achou os arquivos? Porque ele instalou o figlet abaixo de /usr/local e não de /usr, como é o padrão do OpenWRT (e da maioria das distribuições). Esta configuração do prefixo é, na maioria dos casos, configurada pelo script ./configure. Se quisesse, poderia alterar o nosso Makefile para usar o /usr/local. Porém, este não é o caminho padrão para este tipo de arquivo. Novamente, a opção por criar manualmente o Makefile prejudicou a compilação cruzada. Caso usasse o ./configure, o OpenWRT iria definir o prefixo automaticamente. Solução? A mesma usada anteriormente: outra variável ambiente para reconfigurar o Makefile do figlet.

MAKE_FLAGS += \
    LD="$(TARGET_CC)" \
    prefix="$(CONFIGURE_PREFIX)"

E agora sim:

$ make
Collecting package info: done
#
# configuration written to .config
#
 make[1] world
 make[2] package/compile
 make[3] -C package/figlet compile
 make[2] package/index

Sucesso! Seu pacote estará em bin/ar71xx/packages/base/figlet_2.2.5-1_ar71xx.ipk.

Copiando para um roteador e instalado. Não é que ele funciona?

$ scp bin/ar71xx/packages/base/figlet_2.2.5-1_ar71xx.ipk root@router:/tmp 
$ ssh root@router
root@router:~# opkg install /tmp/figlet_2.2.5-1_ar71xx.ipk 
Installing figlet (2.2.5-1) to root...
Configuring figlet.
root@router:~# figlet "Funciona!"
 _____                 _                   _ 
|  ___|   _ _ __   ___(_) ___  _ __   __ _| |
| |_ | | | | '_ \ / __| |/ _ \| '_ \ / _` | |
|  _|| |_| | | | | (__| | (_) | | | | (_| |_|
|_|   \__,_|_| |_|\___|_|\___/|_| |_|\__,_(_)
                                             

O Makefile final ficou:


#
# Copyright (C) 2017 Luiz Angelo Daros de Luca
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=figlet
PKG_VERSION:=2.2.5
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=ftp://ftp.figlet.org/pub/figlet/program/unix/
PKG_MD5SUM:=d88cb33a14f1469fff975d021ae2858e
PKG_MAINTAINER:=Luiz Angelo Daros de Luca <luizluca@gmail.com>
PKG_LICENSE:=BSD-3-Clause
PKG_LICENSE_FILES:=LICENSE
PKG_INSTALL:=1

include $(INCLUDE_DIR)/package.mk

MAKE_FLAGS += \
   LD="$(TARGET_CC)" \
   prefix="$(CONFIGURE_PREFIX)"

define Package/figlet
 SECTION:=xxx
 CATEGORY:=Utilities
 TITLE:=FIGlet
 URL:=http://www.figlet.org/
 MAINTAINER:=Claudio Matsuoka <cmatsuoka@gmail.com>
 DEPENDS:=
endef

define Package/figlet/description
 FIGlet is a program for making large letters out of ordinary text
endef

# Eh obrigatorio ser tabs antes das linhas abaixo
define Package/figlet/install
   $(INSTALL_DIR) $(1)/usr/bin
   $(CP) $(PKG_INSTALL_DIR)/usr/bin/* $(1)/usr/bin
   $(INSTALL_DIR) $(1)/usr/share/figlet/
   $(CP) $(PKG_INSTALL_DIR)/usr/share/figlet/* $(1)/usr/share/figlet/
endef

$(eval $(call BuildPackage,figle
t))


Nem sempre preparar um pacote de algo mais simples será mais fácil. Às vezes, a simplificação do programa pode levar o desenvolvedor a não utilizar práticas padrão de desenvolvimento. Nem cheguei a tratar dependências de bibliotecas. Quando o programa fonte é bem feito, normalmente é questão de adicionar a biblioteca necessária na lista de dependências e pronto. O OpenWRT, inclusive, verifica automaticamente se você não esqueceu de uma delas.

Se fizer algo interessante para o OpenWRT e quiser compartilhar, o desenvolvimento dos pacotes "extras" do OpenWRT está bem produtivo. Ele migrou para https://github.com/openwrt/packages.

Se precisar de uma ajuda, crie um tópico no fórum do blog ou no tópico deste artigo.
Até a próxima.

Atualização em 2017-05-11: adicionado informações sobre etapas de compilação e mais informação sobre o Package/xxx/install; Adicionado o PKG_INSTALL para evitar chamada dentro  do Package/xxx/install.