joliclic code

[version française]

Distribute your XULRunner app

2 - Linux

2.4 Package as rpm

2.4.1 Prerequisites

In this part we will create a rpm package, for rpm based distributions, like Red Hat, Fedora, Suse,... (I have tested only with Fedora, please let me know if there's any problems for others).

We can create this rpm from other Linux distributions as well, Debian based for example (that's what I do), we just need the rpmbuild executable, we have just to install rpm:

apt-get install rpm

Some documentation links:

I suppose we don't have any needed compilation, but if you have some, simply do it before packaging (despite the fact this is not recommended in this guide).

2.4.2 RPM files

To build a rpm, we're supposed to setup a user configuration for all rpm creation. But it is possible to do it in a local folder, that's what we will do.

We need to create the following folders, and a myapp.spec file:

    |- samples_chapter_2/
        |- myapp/
        |- rpmbuild/
            |- BUILD/
            |- RPMS/
            |- SOURCES/
            |- SPECS/
            |- SRPMS/
            |- myapp.spec

In fact this is the same structure than the required rpm environnement, but locally.

The .spec file is the 'script' used by rpmbuild to create the rpm. It is supposed to use a source tar.gz archive, that we will build especially later.

Here's the content of our spec:

Name:           myapp
Version:        1.0
Release:        1%{?dist}
Summary:        simple Hello World powered by XULRunner.

Group:          Development/Tools
License:        MPLv1.1 or GPLv2+ or LGPLv2+
URL:            http://example.com/myapp/
Source:         myapp-1.0.tar.gz
BuildArch:      noarch
BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id} -un)

#BuildRequires:  
Requires:       firefox >= 3.6

%description
simple Hello World
Powered by XULRunner.

%prep
%setup

%build

%install
rm -rf %{buildroot}
mkdir -p %{buildroot}%{_libdir}
cp -R myapp %{buildroot}%{_libdir}/
mkdir -p %{buildroot}%{_bindir}

ln -s %{_libdir}/myapp/myapp.sh %{buildroot}%{_bindir}/myapp

mkdir -p %{buildroot}%{_datadir}/applications
cp data/myapp.desktop %{buildroot}%{_datadir}/applications/

mkdir -p %{buildroot}%{_datadir}/icons/hicolor/16x16/apps
cp data/icons/icon16.png \
    %{buildroot}%{_datadir}/icons/hicolor/16x16/apps/myapp.png

mkdir -p %{buildroot}%{_datadir}/icons/hicolor/22x22/apps
cp data/icons/icon22.png \
    %{buildroot}%{_datadir}/icons/hicolor/22x22/apps/myapp.png

mkdir -p %{buildroot}%{_datadir}/icons/hicolor/32x32/apps
cp data/icons/icon32.png \
    %{buildroot}%{_datadir}/icons/hicolor/32x32/apps/myapp.png

mkdir -p %{buildroot}%{_datadir}/icons/hicolor/48x48/apps
cp data/icons/icon48.png \
    %{buildroot}%{_datadir}/icons/hicolor/48x48/apps/myapp.png

mkdir -p %{buildroot}%{_datadir}/icons/hicolor/scalable/apps
cp data/icons/icon48.svg \
    %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/myapp.svg

%post
touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :
if [ -x %{_bindir}/gtk-update-icon-cache ]; then
  %{_bindir}/gtk-update-icon-cache -q %{_datadir}/icons/hicolor;
fi

%postun
touch --no-create %{_datadir}/icons/hicolor &>/dev/null || :
if [ -x %{_bindir}/gtk-update-icon-cache ]; then
  %{_bindir}/gtk-update-icon-cache -q %{_datadir}/icons/hicolor;
fi


%clean
rm -rf $RPM_BUILD_ROOT


%files
%defattr(-,root,root,-)
%{_bindir}/myapp
%{_libdir}/myapp
%{_datadir}/applications/myapp.desktop
%{_datadir}/icons/hicolor/16x16/apps/myapp.png
%{_datadir}/icons/hicolor/22x22/apps/myapp.png
%{_datadir}/icons/hicolor/32x32/apps/myapp.png
%{_datadir}/icons/hicolor/48x48/apps/myapp.png
%{_datadir}/icons/hicolor/scalable/apps/myapp.svg
#%doc

%changelog
* Fri Oct  8 2010 John Doe <johndoe@example.com> 1.0-1
- first publication

Let see in detail some entries of this file:

The Name should not contain any whitespace characters.

The Version must contain only number and digit, no string like "beta".

The Group entry must be chosen in the list in the /usr/share/doc/rpm/GROUPS file. You should have it on your computer since you have installed rpm.

The Release is the version of the rpm for this version of the app, and %{?dist} will be replaced by rpmbuild by the current distribution. It will not be completed on a non rpm distribution, you can remove it if you want.

For the License entry, you must choose a short keyword from this list.

The Source entry must match exactly the tar.gz that we will create later.

The BuildArch: noarch line specify that our app is not architecture specific. If your app contains some compiled code, you must remove this line, and a rpm specific to the current architecture will be build.

The BuildRoot value is a usual one.

The BuildRequires is commented (with a #), as we have no compilations.

Finally, the Requires entry contains the other packages that are needed by our app. You could add others, separated by comma (,). Note that you can't declare alternative dependencies like with deb. That's why we declare Firefox and not XULRunner as dependencies, in the eventual case where the Firefox of the distribution is not XULRunner based.

Later we will copy the tar.gz archive of our app into the SOURCES folder.
Then the %prep section and the %setup instruction will unpack it into the BUILD folder.
The %build section is empty, because we have no compilation to do.

In the %install section, we install our app.
These are some shell commands, the current working directory is the unpacked source tar.gz. %{buildroot} is the root of the local installation of our app, this will be the computer root / in the real installation.
%{_libdir}, %{_bindir}, and %{_datadir} corresponds respectively to /usr/lib/, /usr/bin/, and /usr/share/. I guess that's the case for main rpm based distros, If I'm wrong, please correct me, it means that some these variables should be redefined.
So in this section, we re-create the initial buildroot folder. Then we create the link to our launcher in /usr/bin/, copy our app in /usr/lib/, and copy the desktop file and our icons into the right location in /usr/share/.

The %post and %postun sections declare some actions after that our app will be installed and uninstalled. We update the icons database for the windows manager.
If the app handle some mime types, i.e. can open some type of files, this should be specified in the .desktop file. And you must add the following code in these %post and %postun sections:

if [ -x /usr/bin/update-desktop-database ]; then
    update-desktop-database &> /dev/null || :
fi

The %files section is required, and contains the complete list of files/folders installed by our app.

Finally, the format of the %changelog section, is described in this doc.

2.4.3 Create the rpm

So now that we have all that we need, we will create a script to create a fake source archive for rpmbuild, which contains our app, our desktop file, and our icons. We will place this archive in the SOURCES folder, then invoke rpmbuild locally with our spec file. And in fact, we will doing all of this in a temporary folder, to let our sources clean.

Note: rmpbuild seems to bug if there's some whitespace in the path of the working folder, so be careful with the name of the (sub)directories where you place your sources.
The following script will abort if this is the case.

Here this script:

#!/bin/bash

# exit the script on errors
set -e

TMP_DIR=./tmp
SRC_VERSION=1.0

# get the absolute path of our TMP_DIR folder
TMP_DIR=$(readlink -f -- "$TMP_DIR")

# re-create the TMP_DIR folder
rm -rfv "$TMP_DIR"
mkdir -v "$TMP_DIR"

# copy the rmpbuild tree folder
cp -rv rpmbuild/* "$TMP_DIR/"

# create a source tar.gz archive for rpmbuild into SOURCES
mkdir -v "$TMP_DIR/myapp-$SRC_VERSION"
cp -rv myapp "$TMP_DIR/myapp-$SRC_VERSION/"
cp -rv data "$TMP_DIR/myapp-$SRC_VERSION/"
cd "$TMP_DIR"
# clean eventual tempory files
find . -type f -name *.*~ -exec rm {} \;
tar -zcvf  "$TMP_DIR/SOURCES/myapp-$SRC_VERSION.tar.gz" \
    "myapp-$SRC_VERSION"
cd -
rm -rv "$TMP_DIR/myapp-$SRC_VERSION"

# rmpbuild seems to have a bug, it can't handle a _topdir with whitespace
if [ -n "$(echo "$TMP_DIR" | grep '[ ]')" ]; then
    echo "the path '$TMP_DIR' contains whitespace, rpmbuild will failed. script aborted!"
    exit 1
fi

# build the rpm from our spec, specify our local build tree
rpmbuild --define="_topdir $TMP_DIR" -bb "$TMP_DIR/myapp.spec"

# and finally copy our created rpm in to into the current folder
cp -v "$TMP_DIR/RPMS/noarch/myapp-$SRC_VERSION-1.noarch.rpm" ./

To create our rpm, in a terminal:

And as result we have finally the file myapp-1.0-1.noarch.rpm in the myapp-src folder :) .

2011-06-15 - Nicolas Martin

You can download all the samples of this chapter 2 (Linux) in the samples_chapter_2.tar.gz archive.

The myapp application, from developer.mozilla.org is in the Public Domain.

The icon used is from the Tango Desktop Project, and is in the Public Domain.

All added data, and sample files, of this chapter 2, are in the Public Domain too.