Showing posts with label package. Show all posts
Showing posts with label package. Show all posts

Friday, December 28, 2012

From raw idea to useful source code

A couple months ago I had an Idea.

I even blogged about it: A lookup service for US National Weather Service codes. Those codes are necessary to access their machine-readable products.

In this post, I will show how I developed the idea into some code, how I grew the code into a project, added structure and version control, and finally moved the project onto online hosting.

This is not the only way to create a project.
This is probably not the best way for many projects.
It's just the way I did it, so you can avoid the most common mistakes.

You can see my final product hosted online at Launchpad.



From Idea to Code:

I know barely enough C to be able to ask there the bathroom is, so it's easier for me to use Python.

Code starts out as a single Python script:
- geolocation.py

As we add more features, a single script gets big and unwieldly, and we break it into smaller pieces.

For example, this structure easily allows more interfaces to be added.
- geolocation_service.py
- command_line_interface.py

Let's add a dbus interface, too. Dbus will semd messages to the interface if it knows about it. Let dbus know about it using a service file.
- dbus_interface.py
- dbus_service_file.service

Let's add an http interface, so not everyone in the world needs to download 5-6MB of mostly-unused lookup databases:
- http_interface.py
- specialized_webserver.py

Let's go back and formalize how we create the databases:
- database_creator.py

We have a lot of hard-coded variables in these scripts. Let's break them out into a config file.
- configfile.conf

There are other possible files, that we're not using. For example:
- Upstart config file (if we want the service to run/stop at boot or upon some system signal, lives in /etc/init)
- Udev rule file (if we want the service to run/stop when a device is plugged in, lives in /etc/udev/rules.d)

But that's a lot of files and scripts! 8 files, plus the databases.
 


Adding Version Control:

It's time to get serious about these eight files. We have invested a lot of time creating them, and it's time to start organizing the project so others can contribute, so we can track new bugs and features, and to protect all our invested work.

First, we need to introduce version control. Ideally, we would have done that from the start. But we didn't. So let's fix that.

Version control offers a lot of advantages:
    We can undo mistakes.
    It helps us package the software later.
    It helps us track bugs.
    It helps us apply patches.
    It helps us document changes.

There are plenty of good version control systems available. For this example, I'll use bazaar. The developers have a very good tutorial.

Installing bazaar:

$ sudo apt-get install bzr
$ bzr whoami "My Name "

Since we didn't start with proper version control, we need to create a new directory using version control, move our files into it, and add our files to version control.

$ bzr init-repo My_NEW_project_directory
$ bzr init My_NEW_project_directory
$ mv My_OLD_project_directory/* My_NEW_project_directory/
$ cd My_NEW_project_directory
$ bzr add *

Finally, we need to clean up the old directory, and commit the changes.

$ rm ../My_OLD_project_directory
$ bzr commit -m "Initial setup"


Organizing the code

My project directory is starting to get disorganized, with eight scripts and files, plus six database files, plus version control, plus more to come. I'm going to restructure my project folder like this:

My_project_directory
  +-- data   (all the database files)
  +-- src    (all the python scripts and other non-data files)
  +-- .bzr   (bzr's version control tracking)


Once version control is active, we cannot just move things around. We need to use the version control tools so it can keep tracking the right files.

$ bzr mkdir data src
$ bzr mv *.gz data/
$ bzr mv *.py src/
$ bzr mv dbus_service_file.service src/
$ bzr mv configfile.conf src/

See how bazaar adds the directories and performs the moves?

Now My_project_directory should be empty of files. Once reorganization is complete, remember to commit the change:

$ bzr commit -m "Reorganize the files to a better project structure"




Integrating into the system:

We have a problem with our eight files. They run beautifully, but only if they are in our home directory.

That won't work in the long run. A server should not be run as a user with shell access - that's a security hole. Nor should it be run out of a user's /home. Nor should it be run as root. Also, other applications that are looking for our server won't find it - all the files are in the wrong places.

So we need to put our files into the right places. And often that means fixing the scripts to replace hard-coded temporary paths (like '~/server/foo') with the proper locations ('/usr/lib/foo-server/foo').

Where are the right places?

The Linux Filesystem Hierarchy Standard (FHS) is used by Debian to define the right places.

Two files are directly user-launched in regular use:
- specialized_webserver.py: /usr/bin
- command_line_interface.py: /usr/bin

The database files are read-only and available to any application:
- database files: /usr/shared

Three files are launched or imported by other applications or scripts:
- geolocation_service.py: /usr/lib
- dbus_interface.py: /usr/lib
- http_interface.py: /usr/lib

One file is very rarely user-launched under unusual circumstances:
- database_creator.py

The  dbus service file will be looked for by dbus in a specific location:
- geolocation_dbus.service: /usr/share/dbus-1/services

Config files belong in /etc
- geolocation.conf: /etc

Makefiles make organization easier:

Now that we know where the right places are, let's create a Makefile that will install and uninstall the files to the right place. Our originals stay where they are - the makefile copies them during the install, and deletes the copies during uninstall.

Makefiles are really config files for the make application (included in the build-essential metapackage). Makefiles tell make which files depend upon which, which files to compile (we won't be compiling), and where the installed application files should be located, and how to remove the application.

Here is a sample makefile for my project (wbs-server):
DATADIR = $(DESTDIR)/usr/share/weather-location
LIBDIR  = $(DESTDIR)/usr/lib/wbs-server
BINDIR  = $(DESTDIR)/usr/bin
DBUSDIR = $(DESTDIR)/usr/share/dbus-1/services
CONFDIR = $(DESTDIR)/etc
CACHDIR = $(DESTDIR)/var/cache/wbs-webserver

install: 
 # Indents use TABS, not SPACES! Space indents will cause make to fail
 mkdir -p $(DATADIR)
 cp data/*.gz $(DATADIR)/

 mkdir -p $(LIBDIR)
 cp src/geolocation.py $(LIBDIR)/
 cp src/wbs_dbus_api.py $(LIBDIR)/
 cp src/wbs_http_api.py $(LIBDIR)/
 cp src/wbs_database_creator.py $(LIBDIR)/

 cp src/wbs_cli_api.py $(BINDIR)/
 cp src/wbs_webserver.py $(BINDIR)/
 cp src/wbs-server.service $(DBUSDIR)/
 cp src/confile.conf $(CONFDIR)/wbs-server.conf
 mkdir -p $(CACHDIR)

uninstall:
 rm -rf $(DATADIR)
 rm -rf $(LIBDIR)

 rm -f $(BINDIR)/wbs_cli_api.py
 rm -f $(BINDIR)/wbs_webserver.py
 rm -f $(DBUSDIR)/wbs-server.service
 rm -f $(CONFDIR)/wbs-server.conf
 rm -rf $(CACHDIR)

Let's save the makefile as Makefile, and run it using sudo make install and sudo make uninstall.

We run a test:

$ sudo make install
$ /usr/bin/wbs_cli_api.py zipcode 43210
bash: /usr/lib/wbs-server/wbs_cli_api.py: Permission denied

Uh-oh. Let's investigate:

$ ls -l /usr/lib/wbs-server/wbs_cli_api.py 
-rw-r--r-- 1 root root 3287 Dec 23 20:46 /usr/lib/wbs-server/wbs_cli_api.py

Aha. Permissions are correct, but the executable flag is not set. Let's uninstall the application so we can fix the makefile.

$ sudo make uninstall

In the makefile, we can make a few changes if we wish. We can set the executable flag. We can also create links or symlinks, or rename the copy.

For example, wbs_cli_api.py is a rather obtuse name for a command-line executable. Instead of copying it to /usr/bin, let's copy it to /usr/lib with its fellow scripts, make it executable, and create a symlink to /usr/bin with a better name like 'weather-lookup'

install:
        ...
 cp src/wbs_cli_api.py $(LIBDIR)/
 chmod +x $(LIBDIR)/wbs_cli_api.py
 ln -s $(LIBDIR)/wbs_cli_api.py $(BINDIR)/weather-lookup
        ...

uninstall:
        ...
 rm -f $(BINDIR)/weather-lookup
        ...


Another example: It's a bad idea to run a webserver as root. So let's add a couple lines to the makefile to create (and delete) a separate system user to run the webserver.

USERNAM = wbserver
        ...
install:
        ...
 adduser --system --group --no-create-home --shell /bin/false $(USERNAM)
 cp chgrp $(USERNAM) $(LIBDIR)/*
 cp chgrp $(USERNAM) $(CACHDIR)
 # Launch the webserver using the command 'sudo -u wbserver wbs-server'
        ...

uninstall:
        ...
 deluser --system --quiet $(USERNAM)
        ...




Sharing the code

Now we have a complete application, ready to distribute.

Thanks to the Makefile, we include a way to install and uninstall.

It's not a package yet. It's not even a source package yet. It's just source code and an install/uninstall script.

We can add a README file, a brief description of how to install and use the application.

We can also add an INSTALL file, detailed instructions on how to unpack (if necessary) and install the application.

It would be very very wise to add a copyright and/or license file, so other people know how they can distribute the code.

After all that, remember to add those files to version control! And to finally commit the changes:

bzr commit -m "Initial code upload. Add README, INSTALL, copyright, and license files."

Finally, we need a place to host the code online. Since I already have a Launchpad account and use bzr, I can easily create a new project on bzr.

And then uploading the version-controlled files is as simple as:

bzr launchpad-login my-launchpad-name
bzr push lp:~my-launchpad-name/my-project/trunk 

You can see my online code hosted at Launchpad.


Next time, we'll get into how to package this source code.

Tuesday, May 1, 2012

How to build a Simutrans .deb package manually

Simutrans, as I've written before, is a fun game.

Currently it's in active development, with new features and bug fixes arriving several times each year.  But Ubuntu's repository system is based on six-month snapshots; it cannot keep up with the pace of Simutrans releases.

That causes a big problem for multiplayer games: The server requires clients to be on a similar pakset and release, and the server operators usually keep up with the releases. So Ubuntu users are locked out of multiplayer games - the versions in the Ubuntu repos are simply too old.

So I want roll my own .deb of the latest version of Simutrans. This is the first step - eventually a PPA of the backported latest release would be cool.



1) Create a VM for the build environment. For this, I used a VM of my current install, Ubuntu 11.10. But you can use anything back to Debian 6, of course.

The VM serves several purposes. It provides the appropriate build environment for each version of Ubuntu. It also provides a safe place to test the final package installation. And, of course, it protects my main system.

Beyond the basic setup, you should probably clean up the desktop shortcut bar; you don't need Software Center or LibreOffice, but you do need Terminal on there. You should also set Software Sources to automatically download-and-apply security updates, you can safely ignore all non-security updates.

Also, install the following packages needed for a useful Simutrans build environment:
$ sudo apt-get install build-essential devscripts debhelper libsdl1.2-dev 
libsdl-mixer1.2-dev zlib1g-dev imagemagick libpng-dev libbz2-dev libssl-dev subversion

Do NOT install the simutrans package from the Ubuntu repositories. If you do install it, for example to confirm that it works, remove it.


2) Prepare the working directory for Simutrans and download the sources

First, let's set up the Simutrans working directory

~$ mkdir Simutrans_Development
~$ cd Simutrans_Development
~/Simutrans_Development$ wget http://ftp.us.debian.org/debian/pool/main/s/simutrans/simutrans_111.2.2-1.debian.tar.xz 
~/Simutrans_Development$ tar -xvf simutrans_111.2.2-1.debian.tar.xz 

Increment the version number by editing the changelog in each Debian directory.

~/Pak64_Development$ cd ~/Simutrans_Development
~/Simutrans_Development$ dch -v 111.2.2-0ppa1

simutrans (111.2.1-0ppa1) oneiric; urgency=low

  * New upstream release

 -- me <me@example.com>  Wed, 25 Apr 2012 07:23:32 -0500

Edit the Debian rules file to reflect the most recent upstream release, then download a new source code tarball, uncompress the tarball, and copy the debian folder into the new folder.

~/Pak64_Development$ cd ~/Simutrans_Development
~/Simutrans_Developmen$ nano debian/rules

SVNREV  = 5583
VERSION = 111.2.2


~/Simutrans_Development$ make -f debian/rules get-orig-source
tar -xvf simutrans_111.2.2.orig.tar.zx
cp -r debian/ simutrans-111.2.2/

Finally, a progress check: The directory should look something like this:

~$ ls Simutrans_Development/
debian             simutrans_111.2.2-1.debian.tar.xz
simutrans-111.2.2  simutrans_111.2.2-1.orig.tar.xz


3) Prepare the working directory for Pak64 and download the sources

~$ mkdir Pak64_Development
~/Simutrans_Development$ cd ~/Pak64_Development
~/Pak64_Development$ wget http://ftp.us.debian.org/debian/pool/main/s/simutrans-pak64/simutrans-pak64_111.2-1.debian.tar.xz
~/Pak64_Development$ tar -xvf simutrans_111.2-1.debian.tar.xz

Increment the version number by editing the changelog in each Debian directory.

~/Simutrans_Development$ cd ~/Pak64_Development
~/Pak64_Development$ dch -v 111.2-0ppa1

simutrans-pak64 (111.2-0ppa1) oneiric; urgency=low

  * New upstream release

 -- me <me@example.com>  Wed, 25 Apr 2012 07:23:32 -0500

Edit the Debian rules file to reflect the most recent upstream release, then download a new source code tarball, uncompress the tarball, and copy the debian folder into the new folder.

~/Simutrans_Development$ cd ~/Pak64_Development
~/Pak64_Development$ nano debian/rules

SVNREV  = 811
VERSION = 111.2
UPSTREAM_SVNREV = 810M # see README.source

~/Pak64_Development$ make -f debian/rules get-orig-source
tar -xvf simutrans-pak64_111.2.orig.tar.xz
cp -r debian/ simutrans-pak64-111.2/

Finally, a progress check: The directory should look something like this:

~$ ls Pak64_Development/
debian                                 simutrans-pak64-111.2
simutrans-pak64_111.2-1.debian.tar.xz  simutrans-pak64_111.2.orig.tar.xz


4) Build Simutrans

We're ready to try building the package. This is where the troubleshooting really starts.

~$ cd Simutrans_Development/simutrans-111.2.2
~/Simutrans_Development/simutrans-111.2.2$ debuild -us -uc

First  Problem: debhelper 9.
 
dpkg-checkbuilddeps: Unmet build dependencies: debhelper (>= 9)
dpkg-buildpackage: warning: Build dependencies/conflicts unsatisfied; aborting.
dpkg-buildpackage: warning: (Use -d flag to override.)
debuild: fatal error at line 1348:
dpkg-buildpackage -rfakeroot -D -us -uc failed

The debhelper package was originally version 7.x. According to the git log, it incremented up to 9 in March 2012, long after the build environment (that's why we review the log!). We can try the -h flag to avoid this...or unapply the patch that incremented debhelper.
 
~/Simutrans_Development/simutrans-111.2.1$ debuild -us -uc -d

Second Problem: Lintian errors.
 
Now running lintian...
W: simutrans source: newer-standards-version 3.9.3 (current is 3.9.2)
W: simutrans: binary-without-manpage usr/games/simutrans-nettool
Finished running lintian.

Lintian errors are a good sign - it means everything else worked! Both warnings in this case can be safely ignored...though a good citizen would also file bugs with patches to fix those warnings.

Take a look at the Simutrans_Development directory now. See all those ready-to-install deb packages?
 
ian@VM-11-10:~/Simutrans_Development$ ls
debian                                simutrans_111.2.2-0ppa1_i386.deb
simutrans-111.2.2                     simutrans_111.2.2.debian.tar.gz
simutrans_111.2.2-1.debian.tar.xz     simutrans_111.2.2.orig.tar.xz
simutrans_111.2.2-1.dsc               simutrans-data_111.2.2-0ppa1_all.deb
simutrans_111.2.2-0ppa1_i386.build    simutrans-makeobj_111.2.2-0ppa1_i386.deb
simutrans_111.2.2-0ppa1_i386.changes

Do not install the simutrans_111.2.1-1_i386.deb or simutrans-data_111.2.1-1_all.deb packages yet. If you try, they will install a pak64 package from the repositories instead of the one you are about to build.

5) Install makeobj

makeobj is a package (simutrans-makeobj_111.2.1-1_i386.deb) that is built with the Simutrans game, and used in turn to create the paks. Install the deb that was just created before building pak64.

~/Simutrans_Development$ sudo dpkg -i simutrans-makeobj_111.2.2-0ppa1_i386.deb 

6) Build Pak64


We're ready to try building the pak package.

~$ cd Pak64_Development/simupak-64
~/Pak64_Development/simupak64$ debuild -us -uc

Problem: Makefile patches failed

The file debian/patches/buildsystem has several patches that get applied to the makefile. As the makefile changes over time, the patch program may not find the right place to apply some patches. Those changes to the patch must be applied manually.

patching file Makefile
Hunk #2 FAILED at 46.
Hunk #3 succeeded at 65 (offset 2 lines).
Hunk #4 FAILED at 84.
2 out of 4 hunks FAILED -- saving rejects to file Makefile.rej

Hunk #2 was a straightforward change to one line (adding the word "zip").
Hunk #4 had the old version number instead of the new.

Upon the second try, the pak64 build was successful. You can see the deb package in the directory listing:

~$ ls Pak64_Development/
debian                                     simutrans-pak64_111.2.1-1.dsc
simutrans-pak64_111.2-0ppa1.debian.tar.xz  simutrans-pak64_111.2.1-1_i386.build
simutrans-pak64-111.2                      simutrans-pak64_111.2.1-1_i386.changes
simutrans-pak64_111.2-0ppa1_all.deb        simutrans-pak64_111.2-1.orig.tar.xz
simutrans-pak64_111.2-1.debian.tar.xz      simutrans-pak64_111.2-1.orig.tar.xz

7) Test Install

When installing package-by-package, make sure to install in the right order!

sudo dpkg -i Pak64_Development/simutrans-pak64_111.2-0ppa1_all.deb
sudo dpkg -i Simutrans_Development/simutrans-data_111.2.2-0ppa1_all.deb 
sudo dpkg -i Simutrans_Development/simutrans_111.2.2-0ppa1_i386.deb


I fire up Simutrans...and it works!

8) Removing Simutrans

When the test is complete, and I want to remove simutrans:
sudo dpkg -r simutrans
sudo dpkg -r simutrans-data
sudo dpkg -r simutrans-pak64