sábado, 29 de enero de 2011

Creando binarios para linux con Open Suse Build Service. II Paquetes rpm

Después de ver de un modo general como funciona OBS intentaremos mostrar ahora el caso práctico de creación de RPMs para varias distribuciones.
Imaginemos que nuestra aplicación consta de un archivo binario y de un .desktop, es un caso sencillo ya que no contamos con archivos de traducción. Lo que tenemos que montar es un archivo de descripción .spec y un .tar.gz de nuestro código fuente.

Entorno de trabajo:

Si trabajamos con Subversion, o cualquier otro sistema de control de versiones, haremos un "commit" de todo nuestro proyecto antes de comenzar con la paquetización. Después de esto exportaremos una copia limpia de archivos ocultos a un directorio de trabajo diferente. Sobre esta nueva carpeta ya podremos crear nuestro tar.gz.
Tal como vimos en el capítulo anterior en nuestra cuenta en OBS habremos creado un proyecto, las distribuciones para las que se construirá  y el paquete correspondiente a la versión sobre la que estamos trabajando. Una vez hecho todo esto, para añadir nuestro código fuente navegamos hasta el paquete creado y seleccionamos la pestaña Sources, donde clicaremos sobre el botón Add files.


Se nos despliegará un formulario en el que podemos cargar, en este caso, nuestro código. Lo más cómodo es haber nombrado el tar.gz de la mejor manera posible, especificando la versión, y ahorrarnos el rellenar el campo nombre, de manera que OBS lo haga automáticamente. Más adelante podremos subir el .spec del mismo modo.


El archivo spec

Ahora toca montar el archivo spec con la información adecuada. Tenéis, a continuación,  todo el archivo apropiado para el proyecto de ejemplo. Después desgranaremos lo más importante a destacar:

%if 0%{?fedora_version}
%define breq qt4-devel
%define reqs qt qt-x11
%define qmake /usr/bin/qmake-qt4
%endif
%if 0%{?mandriva_version}
%define breq libqt4-devel qt4-linguist
%define reqs libqtcore4 libqtgui4
%define qmake /usr/lib/qt4/bin/qmake
%endif
%if 0%{?suse_version}
%define breq libqt4-devel
%define reqs libqt4 libqt4-x11
%define qmake /usr/bin/qmake
%endif
 
Name: RegExpTester
Summary: RegExpTester is an application that lets you test regular expresions
Version: 1.0
Release: 1
License: GPL v3
Group: Productivity/Utility/TextTools
BuildRoot: %{_tmppath}/build-root-%{name}
Source0: /usr/src/packages/SOURCES/RegExpTester-1.0.tar.gz
Packager: Guillermo Amat
Url: https://www.deuteros.es
Vendor: Guillermo Amat
 
BuildRequires: gcc-c++, make, %{breq}
Requires: %{reqs} libc.so.6 libgcc_s.so.1 libgcc_s.so.1(GCC_3.0) libm.so.6 libpthread.so.0 libstdc++.so.6 libstdc++.so.6(CXXABI_1.3) libstdc++.so.6(GLIBCXX_3.4)
%description
RegExpTester is an application that lets you test regular expresions
 
RegExpTester is an application that lets you test regular expresions
 
%prep
rm -rf %{buildroot}
mkdir %{buildroot}
 
%setup -q
 
%build
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_datadir}/%{name}
mkdir -p %{buildroot}%{_datadir}/applications
%{qmake} %{name}.pro
CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" \
make
 
%install
cp %{name} %{buildroot}%{_bindir}
cp %{name}.png %{buildroot}%{_datadir}/%{name}/
 
cat > $RPM_BUILD_ROOT%{_datadir}/applications/%{name}.desktop << EOF
[Desktop Entry]
Encoding=UTF-8
Name=%{name}
GenericName=%{name}
GenericName[de]=%{name}
Comment=Synchronize files between computers
Exec=%{_bindir}/regexptester
Icon=%{_datadir}/%{name}/%{name}.png
Terminal=false
Type=Application
StartupNotify=false
Categories=Qt;KDE;Utility;TextEditor;
EOF
 
%clean
rm -rf %{buildroot}
 
%files
%defattr(-,root,root)
%dir %{_datadir}/RegExpTester
%{_datadir}/applications/RegExpTester.desktop
%{_bindir}/RegExpTester
%{_datadir}/%{name}/%{name}.png
 
%changelog


Menudo rollo ¿no? Pues vamos a destriparlo paso a paso para que se entienda mejor:
El primer bloque se corresponde con una serie de declaraciones condicionales que dependerán de la maquina virtual en la que OBS ejecute la construcción del rpm.


%if 0%{?fedora_version}
%define breq qt4-devel
%define reqs qt qt-x11
%define qmake /usr/bin/qmake-qt4
%endif
%if 0%{?mandriva_version}
%define breq libqt4-devel qt4-linguist
%define reqs libqtcore4 libqtgui4
%define qmake /usr/lib/qt4/bin/qmake
%endif
%if 0%{?suse_version}
%define breq libqt4-devel
%define reqs libqt4 libqt4-x11
%define qmake /usr/bin/qmake
%endif


Por ejemplo, en los requisitos de construcción (breq) para Mandriva y Opensuse se necesitará el paquete libqt4-devel, mientras que el equivalente en Fedora recibe el nombre qt4-devel. OBS instalara el paquete adecuado en cada máquina virtual gracias a eso. En todo caso para cada una de las distribuciones definimos tres variables que usaremos mas tarde:
  • breq: requisitos para la construcción
  • reqs: dependencias para la ejecución de nuestro programa en el PC del usuario que se lo instale
  • qmake: ruta al ejecutable qmake
El siguiente bloque que nos encontramos es el de la información general de nuestra aplicación.  Aquí y en todo el fichero spec hay que tener cuidado con los saltos de línea y en concreto aquí  con no equivocarse con el valor del campo source0. Además, parte de esta información será mostrada al usuario en el gestor de paquetes de su distribución:
Name: RegExpTester
Summary: RegExpTester is an application that lets you test regular expresions
Version: 1.0
Release: 1
License: GPL v3
Group: Productivity/Utility/TextTools
BuildRoot: %{_tmppath}/build-root-%{name}
Source0: /usr/src/packages/SOURCES/RegExpTester-1.0.tar.gz
Packager: Guillermo Amat
Url: https://www.deuteros.es
Vendor: Guillermo Amat
BuildRequires: gcc-c++, make, %{breq}
Requires: %{reqs} libc.so.6 libgcc_s.so.1 libgcc_s.so.1(GCC_3.0) libm.so.6 libpthread.so.0 libstdc++.so.6 libstdc++.so.6(CXXABI_1.3) libstdc++.so.6(GLIBCXX_3.4)
%description
RegExpTester is an application that lets you test regular expresions
 
RegExpTester is an application that lets you test regular expresions  

En el bloque anterior también se aprecia la aparición de las variables que hemos definido con anterioridad. Su valor se concatena al de los nombres de los paquetes que aparecen definidos. Más claro: BuildRequires contiene gcc-c++ y make porque se llaman así en todas las distribuciones (menos mal) y el contenido de la variable breq que cambia en función de la máquina virtual.
En description tenemos una descripción corta y otra que puede ser más extensa. La linea en blanco que separa a ambas es necesaria.
Vamos con el siguiente trozo. Esto ya tiene que ver con la construcción del paquete y en el aparecen nuevas variables. La diferencia es que estas no son nuestras, sino predefinidas por el sistema:
  •  buildroot: el directorio donde se construirá el paquete en la MV
  • _bindir: el directorio de los ejecutables
  • _datadir: directorio de datos
  • name: es el nombre que aparece en el campo Name (linea 17 del .spec)
Así pues lo que se hace en este bloque es preparar todos los directorios necesarios. Después se ejecuta qmake sobre el archivo .pro cosa que creará el Makefile en la MV. La siguiente linea contiene opciones de compilación específicas para la creación de rpms que, a su vez, son usadas por el comando make.
Después de ejecutar make se procede con la sección install que, en este caso, copia un par de archivos en sus lugares correspondientes.

%prep
rm -rf %{buildroot}
mkdir %{buildroot}

%setup -q

%build
mkdir -p %{buildroot}%{_bindir}
mkdir -p %{buildroot}%{_datadir}/%{name}
mkdir -p %{buildroot}%{_datadir}/applications
%{qmake} %{name}.pro
CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" \
make

%install
cp %{name} %{buildroot}%{_bindir}
cp %{name}.png %{buildroot}%{_datadir}/%{name}/


 En el siguiente bloque se crea el archivo .desktop que hará que nuestro programa aparezca en el menú de inicio de KDE. No es necesario crearlo así, podríamos haberlo escrito en un editor, empaquetado en el tar.gz y por último tratarlo en el .spec de la misma forma que al png. En todo caso, se debe saber que durante la construcción se comprobará este archivo y que es muy importante que el contenido de Categories siga las definiciones del estándar de freedesktop.org 
cat > $RPM_BUILD_ROOT%{_datadir}/applications/%{name}.desktop << EOF
[Desktop Entry]
Encoding=UTF-8
Name=%{name}
GenericName=%{name}
GenericName[de]=%{name}
Comment=Synchronize files between computers
Exec=%{_bindir}/regexptester
Icon=%{_datadir}/%{name}/%{name}.png
Terminal=false
Type=Application
StartupNotify=false
Categories=Qt;KDE;Utility;TextEditor;
EOF 

Y para acabar se define el paso de limpieza y se enumeran los archivos generados para la creación del paquete. Cualquier inconsistencia entre esta lista y lo que se encuentre realmente generará un error, por ejemplo: si nuestro tar.gz contiene un segundo png que no hayamos nombrado aquí.


%clean
rm -rf %{buildroot}

%files
%defattr(-,root,root)
%dir %{_datadir}/RegExpTester
%{_datadir}/applications/RegExpTester.desktop
%{_bindir}/RegExpTester
%{_datadir}/%{name}/%{name}.png

%changelog

El archivo tar.gz

Como se ha dicho antes, lo mejor es generar el tar.gz desde una copia limpia de nuestro código fuente. Si trabajamos con Subversion haremos un "svn export" y después comprimiremos el resultado.
A parte de eso, habrá que llevar cuidado con incluir todo lo necesario y especificar correctamente las rutas de todo lo que aparezca en el .spec

El resultado

Si todo va bien en nuestro panel de resumen del paquete tendremos indicado que el proceso ha finalizado con éxito y podremos navegar hasta el repositorio de descarga de cada una de las distribuciones.


 Si por ejemplo pulsamos sobre openSuse_11_3 llegaremos a la página donde tenemos todos sus binarios disponibles:


Si algo falla, desde la pantalla de resumen veremos que se nos indica un error (failed en vez de success) y haciendo clic sobre la palabra failed accederemos al log de la máquina virtual donde veremos el error.

Una imagen vale más que mil palabras

Y un par de enalces y de archivos de ejemplo también, así que allá va:

jueves, 20 de enero de 2011

Un tutorial rápido de QT: evaluador de expresiones regulares

Estaba dándome cabezazos contra la mesa el otro día porque no conseguía hacer funcionar una expresión regular en el código de Acid Rain sin encontrar la explicación. La situación me llevaba casi a la decepción por haber olvidado toda esa teoría cuando se me ocurrió buscar alguna web donde probar lo que hacía. Al encontrarla me di cuenta de que no era tan mentecato, algo fallaba ya que la expresión regular funcionaba. Tan loco me estaba volviendo que decidí hacer una aplicación en QT en donde testar de forma rápida las expresiones, ya que con la que estaba trabajando realmente me costaba un poco llegar al punto donde se evaluaba la expresión.


Y así fue que me puse a hacer una aplicación muy parecida a la web que he comentado antes. En poco más de cinco minutos la tenía hecha y se me ocurrió que sería un buen ejemplo para iniciarse en QT. Es tan sólo un poco más complicado que un "Hola mundo" y de paso se ve como utilizar aspectos esenciales de QT Creator, signals/slots y cómo trabajar con la disposición de los elementos en un formulario. ¡Vamos al tema!

Creando el proyecto

 Lo primero será crear el proyecto eligiendo "QT C++ Project" y como subopción  "QT GUI Application"


 Ahora le damos un nombre apropiado a la aplicación y apretamos sobre "Next"


 Como se nos crea un formulario, debemos definir su nombre y el de la clase asociada al mismo


En cuanto a la creación del proyecto sólo nos queda aceptar el resumen.


Lo que queremos hacer

Lo que nos interesa es tener un texto sobre el que queremos hacer alguna prueba, usando una expresión regular para reemplazar parte del él por otro texto determinado. Así pues necesitaremos unos cuantos campos etiqueta, tres campos de entrada y uno de salida.
Los campos de de entrada serán: el de la expresión regular, el de la cadena que queremos introducir y el del texto inicial sobre el que trabaja. Para los dos primeros campos nos bastará con un "line edit" mientras que para el texto usaremos un "text edit". El resultado lo mandaremos a otra caja de texto ("text edit") de salida que tendrá marcada la propiedad "read only".
En cuanto le demos al botón "Test"  se evaluará la expresión regular y, para hacerlo mas emocionante y poder probar más rápido nuestras expresiones, también se evaluará con cada cambio que hagamos en el campo de entrada de la expresión, con lo que veremos los resultados al vuelo mientras la escribimos.
En cuanto a la disposición de los elementos, usaremos dos frames para agrupar por un lado los campos del formulario y por otro el botón "Test".

Insertando los elementos de la ventana

Lo primero que haremos será insertar los dos frames, arrastrándolos a la ventana.


Lo segundo debe ser elegir la disposición de la ventana (hay que asegurarse de que está seleccionada), en este caso le daremos una disposición vertical "Layout vertically"


Es el momento de darle vida a todo esto. En el marco superior dejamos caer cuatro campos "label", los dos "line edit" y los dos "text edit"


Ahora hay que seleccionar el marco superior y después pinchar sobre "Lay out in a form layout". Todavía no quedará muy bonito asi que habrá que corregir  la posición de los elementos. También podemos redimensionar un poco el formulario haciendo uso del ratón para que no quede todo tan ahogado en cuanto espacio.
Vamos con el marco inferior: hay que añadir un "Push Button",  después seleccionar "layout horizontally" para el marco y finalmente añadirle un espaciador horizontal.


El siguiente paso es cambiar el texto que muestran las etiquetas y el botón y darle un nombre correcto a cada uno de ellos para que sea más intuitivo utilizarlos desde el código. Los nombres podrían ser estos (según el orden del formualrio):
lblRegEx | leRegEx 
lblReplace | leReplace 
lblText | teText
lblResult | teResult
         pbTest
Aprovecharemos para seleccionar teResult y marcar la propiedad "read only"


Jugando con los slots

No voy a entrar mucho en esto, tan solo una breve explicación. En QT la comunicación entre objetos se hace mediante signals  y slots. Por un lado hay determinados eventos de los objetos que lanzan señales (al hacer clic sobre un botón, al cambiar el texto en un campo de texto, etc) Por otro lado los objetos también tienen asociadas una serie de funciones llamadas slots cuya finalidad es ser receptoras de señales de otros objetos ( por ejemplo setText(QString) en un campo text edit). Uniendo ambas cosas podemos hacer que al hacer clic sobre un boton "clear" se borre todo el contenido de una caja de texto.
Volvamos a lo nuestro. Seleccionando el botón "Test", apretamos el botón derecho de nuestro ratón para obtener el menú contextual y buscamos la opción "Go to slot..."


Se nos abrirá una ventana en la que elegiremos la señal clciked() y se nos creará automáticamente la cabecera y el cuerpo vacío de un slot llamado "on_pbTest_clicked". Internamente ya se ha relacionado la señal "clicked" con el nuevo slot, así que sólo tendremos que añadir el código que queramos al mismo (cosa que haremos luego) y este se ejecutará al pinchar el botón en nuestra aplicación.


Lo anterior sirve cuando es un objeto  llamándose a si mismo, pero ¿cómo hacemos algo similar entre dos objetos distintos? Pues ahora lo vemos, recordemos antes que queríamos evaluar la expresión al vuelo mientras la escribíamos así que vamos a relacionar la señal "textChanged" del campo leRegExp con la ranura "clicked" del botón pbTest. Para ello nos vamos al modo de edición "edit signals/slots" mediante f4 o haciendo uso del botón de la parte superior de QT Creator. Una vez cambiado el modo, arrastramos el ratón desde el campo de entrada hasta el botón y se nos abrirá una nueva ventana de selección. En la izquierda escogemos la señal "textChanged" y en la derecha el slot "click", aceptamos y los tendremos ya relacionados. Esto quiere decir que cuando haya cualquier cambio en la expresión regular será a todos los efectos, como si pulsásemos el botón "Test" con lo que se irá evaluando al vuelo.


Un poquito de código

Bueno, ya tenemos todo relacionado pero esto todavía no hace nade de nada. Vamos a insertar el código, para ello abrimos el archivo "regextextwindow.cpp" y buscamos la función que QT Creator nos ha insertado. Bastará con dejarla así:

void RegExTestWindow::on_pbTest_clicked()
{
 ui->teResult->setText(ui->teText->toPlainText().replace(QRegExp(ui->leRegExp->text()),ui->leReplace->text()));
}

Lo que significa que asigne como texto del campo "teResult" lo obtenido al reemplazar en el texto de prueba que hemos dejado en "teText" todo lo que encaje con la expresión regular contenida en "leRegExp". Todo lo que encaje con la expresión se sustituye por el valor de "leReplace"
¡Ya está!
Pues si todo ha ido bien  le damos al botón de ejecución y se nos lanzará la apliación. Sólo nos queda desengrasar nuestros conocimientos en expresiones regulares :) Cuando tenga un rato colgaré el código fuente y la aplicación en esta web para los más perezosos.

Sobre mi estupidez

Tal vez os hayáis quedado pensando en lo que contaba al principio del post. Resulta que cuando probé la expresión en la aplicación test la cosa funcionaba. Así que tenía lo siguiente: funcionaba en la web, funcionaba en el testador y fallaba en mi código. Cuando pasan cosas así siempre piensas lo mismo "tiene que ser una estupidez como un piano" Efectivamente así era, no se me había pasado por la cabeza el escapar el carácter de escape, que perogrullada más infernal.
Una expresión valida como \s\d+/\d+\s escrita en código es \\s\\d+/\\d+\\s
Perder tanto tiempo con una tontería semejante suele llevarme, cuando encuentro la solución, al segundo pensamiento típico: "que empanado que estoy"