6. Good development practice

Most of these are concerned with ensuring portability across all
POSIX and POSIX-like systems. Being widely portable is not just a worthy
form of professionalism and hackerly politeness, it's valuable insurance
against future changes in Linux.

Finally, other people will try to build your
code on non-Linux systems; portability reduces the amount of support
email you receive.

6.1. Choose the most portable language you can

Choose a development language which minimizes the differences of
the underlying environments in which it will run. C/C++ programs are
likely to be more portable than Fortran. Java is preferable to C/C++,
and a high-level scripting language such as Perl, Python or Ruby is the
best choice of all (since scripting languages have only one
cross-platform implementation).

Scripting languages that qualify include Python, Perl, Tcl, Emacs
Lisp, and PHP. Historic Bourne shell (/bin/sh) does
not qualify; there are too many different
implementations with subtle idiosyncrasies, and the shell environment
is subject to disruption by user customizations such as shell
aliases.

If you choose a compiled language such as C/C++, do not use
compiler extensions (such as allocating variable-length arrays on the
stack, or indirecting through void pointers). Regularly build using as
many different compilers and test machines as you can.

6.2. Don't rely on proprietary code

Don't rely on proprietary languages, libraries, or other code.
In the open-source community this is considered rude. Open-source
developers don't trust code for which they can't review the source.

6.3. Build systems

A significant advantage of open source distributions is they allow
each source package to adapt at compile-time to the environment it finds.
One of the choices you need to make is of a "build system", the toolkit you
(and other people) will rely on to transform your source into
executables.

One thing your build script cannot do is ask the user for system
information at compile-time. The user installing the package will not
necessarily know the answers to your questions, so this approach is doomed
from the start. Your software, calling the build system tools, must be able
to determine for itself any information that it may need at compile- or
install-time.

Community notions of best practice in build systems are now (rearly
2010) in a starte of some flux.

6.3.1. GNU autotools: fading but still standard

Previous versions of this HOWTO urged using GNU autotools to handle
portability issues, do system-configuration probes, and tailor your
makefiles. This is still the standard and most popular approach, but it is
becoming increasingly problematic because GNU autotools is showing its age.
Autotools was always a pile of crocks upon hacks upon kluges, implemented
in a messy mixture of languages and with some serious design flaws. The
resulting mess was tolerable for many years, but as projects become
more complex it is increasingly failing.

Still, people building from C sources expect to be able to type
"configure; make; make install" and get a clean build. Supposing you choose
a non-autotools system, you will probably want to emulate this behavior
(which should be easy).

6.3.2. SCons: leading a crowded field

The race to replace autotools does not yet have a winner, but it has
a front runner: SCons. SCons
abolishes makefiles; it combines the "configure" and "make" parts of the
autotools build sequence into one step. It offers cross-platfoem builds
with a single recipe on Unix/Linux, Maoc OS X, and Windows. It is
written in Python, is extensible in Python, and is to some extent riding
the increasing popularity of that language.

6.3.3. CMake and others

SCons is still a minority choice, and has stiff competition from
several others, of which CMake and WAF are probably the most
prominant. Fairly even-handed cross-comparisons, considering the source,
can be found
on the SCons wiki.

6.4. Test your code before release

A good test suite allows the team to buy inexpensive hardware for
testing and then easily run regression tests before releases. Create a
strong, usable test framework so that you can incrementally add tests
to your software without having to train developers up in the
intricacies of the test suite.

Distributing the test suite allows the community of users to test
their ports before contributing them back to the group.

Encourage your developers to use a wide variety of platforms as
their desktop and test machines so that code is continuously being
tested for portability flaws as part of normal development.

6.5. Sanity-check your code before release

If you're writing C/C++ using GCC, test-compile with -Wall and
clean up all warning messages before each release. Compile your code
with every compiler you can find — different compilers often find
different problems. Specifically, compile your software on true 64-bit
machine. Underlying data types can change on 64-bit machines, and you
will often find new problems there. Find a UNIX vendor's system and run
the lint utility over your software.

Run tools that for memory leaks and other run-time errors;
Electric Fence
and
Valgrind
are two good ones available in open source.

For Python projects, the
PyChecker
program can be a useful check. It's not out of beta yet, but nevertheless
often catches nontrivial errors.

6.6. Sanity-check your documentation and READMEs before release

Spell-check your documentation, README files and error messages
in your software. Sloppy code, code that produces warning messages when
compiled, and spelling errors in README files or error messages, leads
users to believe the engineering behind it is also haphazard and
sloppy.

6.7. Recommended C/C++ portability practices

If you are writing C, feel free to use the full ANSI features.
Specifically, do use function prototypes, which will help you spot
cross-module inconsistencies. The old-style K&R compilers are
history.

Do not assume compiler-specific features such as the GCC "-pipe"
option or nested functions are available. These will come around and
bite you the second somebody ports to a non-Linux, non-GCC system.

Code required for portability should be isolated to a single area
and a single set of source files (for example, an "os" subdirectory).
Compiler, library and operating system interfaces with portability
issues should be abstracted to files in this directory. This includes
variables such as "errno", library interfaces such as "malloc", and
operating system interfaces such as "mmap".

Portability layers make it easier to do new software ports. It is
often the case that no member of the development team knows the porting
platform (for example, there are literally hundreds of different
embedded operating systems, and nobody knows any significant fraction
of them). By creating a separate portability layer it is possible for
someone who knows the port platform to port your software without having
to understand it.

Portability layers simplify applications. Software rarely needs
the full functionality of more complex system calls such as mmap or
stat, and programmers commonly configure such complex interfaces
incorrectly. A portability layer with abstracted interfaces
("__file_exists" instead of a call to stat) allows you to export only
the limited, necessary functionality from the system, simplifying the
code in your application.

Always write your portability layer to select based on a feature,
never based on a platform. Trying to create a separate portability layer
for each supported platform results in a multiple update problem
maintenance nightmare. A "platform" is always selected on at least two
axes: the compiler and the library/operating system release. In some
cases there are three axes, as when Linux vendors select a C library
independently of the operating system release. With M vendors, N
compilers and O operating system releases, the number of "platforms"
quickly scales out of reach of any but the largest development teams.
By using language and systems standards such as ANSI and POSIX 1003.1,
the set of features is relatively constrained.

Portability choices can be made along either lines of code or
compiled files. It doesn't make a difference if you select alternate
lines of code on a platform, or one of a few different files. A rule of
thumb is to move portability code for different platforms into separate
files when the implementations diverged significantly (shared memory
mapping on UNIX vs. Windows), and leave portability code in a single
file when the differences are minimal (using gettimeofday,
clock_gettime, ftime or time to find out the current
time-of-day).

Avoid using complex types such as "off_t" and "size_t". They vary
in size from system to system, especially on 64-bit systems. Limit your
usage of "off_t" to the portability layer, and your usage of "size_t"
to mean only the length of a string in memory, and nothing else.

Never step on the namespace of any other part of the system,
(including file names, error return values and function names). Where
the namespace is shared, document the portion of the namespace that you
use.

Choose a coding standard. The debate over the choice of standard
can go on forever — regardless, it is too difficult and expensive to
maintain software built using multiple coding standards, and so some
coding standard must be chosen. Enforce your coding standard ruthlessly,
as consistency and cleanliness of the code are of the highest priority;
the details of the coding standard itself are a distant second.