Chapter 3. Packaging and Binding

In the last chapter I explained a lot of rules and conventions for writing a Scalable C project. It looks like a lot to remember. The good news is that if we are consistent, it pays off. For example if we always put our sources into src and our headers into include, it is easier to reuse build scripts between projects.

Speaking of build scripts...

Problem: Infinite Sucking Pits of Darkness

I'm speaking of Makefiles. Wikipedia tells us Make was invented by Bell Labs in 1976. Wikipedia lies! The real truth is that Makefiles are digital demon devisements from the darkest depths of Dis. Some say Bell Labs was the portal through which they clawed their way into our innocent world. We still don't know their true purpose. All we know is, they are eternal and cannot be killed. And we know the dying sound our soul makes as it leaves our body.

Makefiles spawned an entire legion of descendant demons called the autotools. There are said to be ancient scrolls that provide the incantations to tame autotools demons. A piece of one landed on my desk. This is what it said:

# Resolve issue #355, "client wants to replace me"
AC_ARG_WITH([pkgconfigdir],
    AS_HELP_STRING([--with-pkgconfigdir=PATH],
    [Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]),
    [pkgconfigdir="$withval"],
    [pkgconfigdir='${libdir}/pkgconfig'])
AC_SUBST([pkgconfigdir])

Makefiles and build systems that laughing call themselves "makefile generators" as if that made things simpler are inevitable. There is no escape to an alternate universe, if you are writing C code. Oh, please someone tell me how "you need make to reduce build times." I need a good laugh while Travis CI trundles through a fifteen-minute build, every time I push a commit to GitHub.

That being said...

Solution: make it someone else's problem.

Happily, this solution actually worked. It comes as close to killing makefiles as possible, after about 25 years of research. As often, the brilliance and genius comes from the collective mind.

What my team, at iMatix did, many years ago, was to build a way to generate code from high level models. We used this a lot and got good at it. Our gsl language makes it possible to develop DSLs (domain specific languages) quickly, and then build backends that turn these DSLs into code.

What the ZeroMQ community did, over about two years, was build a DSL for packaging, and write dozens of backends for it. This tool is called zproject, and it is what I'll explain in this chapter.

Remember this lesson:

Never give up. If you wait long enough, the ZeroMQ community may solve your problem for you.

Problem: I don't got zproject

Solution: get it from GitHub.

#   Install gsl first
git clone https://github.com/imatix/gsl
cd gsl/src
make -j 4 && sudo make install
cd ..
#   Install zproject
git clone https://github.com/zeromq/zproject
cd zproject
./autogen.sh && ./configure
make && make install

Remember this lesson:

Once you go master, you never go back.

Problem: we need an example

The fastest way to learn any new tool is by example. Let's make a minimal project by hand, then apply magic. Our project is called "Global Domination." Right now version 0.1 is small and modest. It is a skeleton project that fits the rules of Chapter 2, and does nothing more.

Solution: get the code from GitHub.

git clone https://github.com/scalable-c/globdom
cd globdom
git reset --hard version-0.1
cat README.md

The minimal project contains one empty class, and supporting files:

  • LICENSE -- MPLv2 license text
  • README.md -- this file
  • include/globaldom.h -- project public header
  • include/gdom_server.h -- Global Domination server API
  • src/gdom_server.c -- Global Domination server
  • src/gdom_classes.h -- project private header
  • src/gdom_selftest.c -- project selftest tool
  • build.sh -- build and test Global Domination
  • .gitignore -- tell git what files to ignore

To build and test GlobDom 0.1, run build.sh.

Remember this lesson:

Learn the basics of Bash, it will save your life many times.

Problem: people expect "./configure && make"

It is possible, and I've done this on real projects, to work without Makefiles. You compile stuff, chuck it into libraries, and link your executables. Yet it bounces off the wall of expectations. Also, the endless weirdness of the real world. Any real build process gets complex. And so people turn to Makefile generators, much like the victim of a street mugging turning to Somali warlords for help.

Let me be frank, for a change. I do not like the GNU build system, even after mastering it. It uses a flat yet vast macro language to generate Makefiles by sheer brute force. It may be powerful, yet so are the technicals those Somali warlords like to drive to work. The only good thing about autotools is that if (and this is a large if) you can master it, or find someone who's done this, then it is solid.

Happily we figured out how to use autotools' considerable power from a position of blissful ignorance. Let me show you how.

Solution: tell zproject what your project looks like.

First, create a file project.xml in the globdom root directory, like this:

<project
    name = "Global Domination"
    script = "zproject.gsl"
    prefix = "gdom" >
    <use project = "czmq" />
    <class name = "gdom_server" />
</project>

This should be self-explanatory. If you've heard bad things about XML, or been traumatized by it in the past, my sympathies. Give some people a set of hammers, and they think they're rock star drummers. XML is not a programming language. It is however great for writing models to generate code from. You'll learn the fun and profit in this.

Second, run the gsl command to build the project model:

globdom> gsl project.xml
GSL/4.1c Copyright (c) 1996-2016 iMatix Corporation
gsl/4 I: Processing project.xml...
gsl/4 M: Building GNU build system (autotools)
gsl/4 M: Building CMake build system (cmake)

And now, the "configure/make" thing works. Run "./autogen.sh" first, as that produces the configure script:

./autogen.sh
./configure
make -j 4
make check

Remember this lesson:

If everyone expects cake, give them cake.

Problem: sorry, I meant "cmake..."

No problems. As you saw, zproject supports both CMake and the GNU build system. So:

cmake .
make -j 4
make test

Solution: zproject targets both autotools and CMake.

If autotools are the Somali warlords of build systems, then CMake is the Texas politician who promises wealth and power. CMake still wants your soul, yet it is has much more charisma. "I can take care of Visual Studio for you!" it says, smiling, playing off our inner fears.

My main gripe with CMake is that it is just a better build scripting system. It doesn't change the basic fact that I don't want to write build scripts because they're always doing the same bloody work!

Solution: don't script when you can model.

With zproject we don't write scripts. Instead we document our projects as abstract XML models. Then, we can run arbitrary backends on this model, each doing what it must. It is a profound and valuable shift.

Just to finish my complaints about CMake, it lacks a "clean" command. And since it leaves trash lying all over the place, and since that trash really gets in the way sometimes, this is bad. The solution people use is to build in a temporary directory. It isn't great.

Still, since we get CMake support for free, why complain.

Remember this lesson:

Even if you hate a particular build system, someone out there is addicted to it.

Problem: what do I add to git?

Ah, our directory is now a mess of different files. We have some made by hand, some produced by autotools, some by CMake, and some by the compiler and linker.

We cannot add everything to git, because many of these files change every time we compile, and do not belong in the repository. Yet we need the basic build scripts in git, otherwise no-one will know how to use our code.

Solution: put the output of zproject in git.

Let's rewind. First, save project.xml. Then reset the clock using git clean. Then run zproject again:

mv project.xml ..
git clean -d -f -x
mv ../project.xml .
gsl project.xml

Let's look at what zproject actually generated for us. Run git status to see all new and changed files:

globdom> git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   src/gdom_classes.h
    modified:   src/gdom_selftest.c

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    CMakeLists.txt
    Findczmq.cmake
    Findlibsodium.cmake
    Findlibzmq.cmake
    Makefile.am
    autogen.sh
    configure.ac
    doc/
    include/gdom_library.h
    include/global_domination.h
    project.xml
    src/.valgrind.supp
    src/Makemodule.am
    src/libgdom.pc.in
    version.sh

Two of our hand-written files got smashed by generated versions. That's intentional. We won't modify these by hand ever again, as they track the project. Then we got a lot of new files, for autotools and for CMake. And then we got a project header called include/global_domination.h. Cute! But useless! Let's tolerate that for now, and fix it later.

Add these files to git and commit:

git add .
git commit -m "Problem: git repo doesn't contain build scripts

Solution: add everything that zproject generates"

Now run ./autogen.sh && ./configure && make check again. You should lots of output, ending like this:

/bin/bash ./libtool --mode=execute ./src/gdom_selftest
Running global domination selftests...
 * gdom_server: OK
Tests passed OK
...

Remember this lesson:

When using git clean, save any hand-written files first.

Problem: git status shows lots of junk

Building your project will produce lots of files and directories scattered around. Running git clean too often is a bad idea, as it will wipe any new files you've written.

Solution: use a more complete .gitignore file.

It gets tedious to write a complete .gitignore file. Happily we have a tool whose intention is precisely to do the tedious things involved in building Scalable C projects. Here is how we get a complete .gitignore file:

rm .gitignore
gsl project.xml

You'll see this in the output:

gsl/4 M: Generating initial .gitignore file

Now type git status and the image comes in focus:

git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   .gitignore

no changes added to commit (use "git add" and/or "git commit -a")

Let's add this and commit:

git add .
git commit -m "Problem: .gitignore needs more beef

Solution: delete and regenerate via zproject"

Remember this lesson:

zproject generates .gitignore for us, if we don't have it.

Problem: I need to make a new class

Global domination is well on the way! Now we'd like to add a client class. We'll offer two APIs. One is for those who wish to run the GlobDom server in their code. The second is for those who want to access it, over the network. So let's make a client class. Like our server class, it'll do nothing, yet. First draw the outline, then fill it in.

Solution: add new classes to project.xml.

Here is how we define the client class in project.xml:

<project
    ...
    <class name = "gdom_client" />
</project>

Then we run gsl project.xml again and see what git status gives us. There's a few changes to build scripts, then two new files:

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    include/gdom_client.h
    src/gdom_client.c

Take a quick look at these generated files. The include/gdom_client.h header defines a bare minimum for a typical class:

/*  =========================================================================
    gdom_client - class description

    Copyright (c) the Authors
    =========================================================================
*/

#ifndef GDOM_CLIENT_H_INCLUDED
#define GDOM_CLIENT_H_INCLUDED

#ifdef __cplusplus
extern "C" {
#endif

//  @interface
//  Create a new gdom_client
GDOM_EXPORT gdom_client_t *
    gdom_client_new (void);

//  Destroy the gdom_client
GDOM_EXPORT void
    gdom_client_destroy (gdom_client_t **self_p);

//  Print properties of object
GDOM_EXPORT void
    gdom_client_print (gdom_client_t *self);

//  Self test of this class
GDOM_EXPORT void
    gdom_client_test (bool verbose);
//  @end

#ifdef __cplusplus
}
#endif

#endif

And src/gdom_client.c implements these three methods with little more than air and used chewing gum. Let's do the usual sanity check:

make check

Remember this lesson:

zproject generates classes for you if you need them.

Problem: generated sources don't have a license

These two new files need a license blurb at their start. Now, we could add the license blurb by hand. zproject does not touch the class header or source once those files exist. Plus, there are other files that zproject gives us, like gdom_selftest.c, which also need a license blurb.

Solution: specify a blurb in our project model.

We'll put the blurb into a separate XML file so it doesn't clutter our project model. Create a file license.xml with this content:

<license>
Copyright (c) the Contributors as noted in the AUTHORS file.
This file is part of Global Domination. Resistance is useless.

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
</license>

And now include that into your project.xml thus:

<project ...>
    <include filename = "license.xml" />
    ...
</project>

Now, let's check that this works:

rm src/gdom_client.c
rm include/gdom_client.h
gsl project.xml

If gsl complains, fix the XML syntax and try again. Now look at the src/gdom_client.c file. It should start like this:

/*  ===================================================================
    gdom_client - class description

    Copyright (c) the Contributors as noted in the AUTHORS file.
    This file is part of Global Domination. Resistance is useless.

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/.
    ===================================================================

Take a peek at src/gdom_selftest.c and you will see the same blurb.

Remember this lesson:

Stop deleting src/gdom_client.c. From now you, you edit this by hand.

Problem: the client wants to run a server

As luck would have it, Global Domination's sales team already has a potential customer, who's offering good money for a server. You just need a demo, and the sooner the better.

Solution: write a server program.

What the server program does is create a gdom_server instance, print an optimistic message, wait for Ctrl-C, and then exit. How hard can it be?

Let's start by adding a 'main' element to project.xml:

<main name = "gdomd">Global Domination Demon</main>

Now regenerate the project with the usual gsl project.xml. You'll notice that zproject tells you:

gsl/4 M: Generating skeleton for src/gdomd.c

The skeleton src/gdomd.c doesn't do anything useful, or even pretend to. Let's open it up in an editor and add some body to it:

    ...
    //  Insert main code here
    if (verbose)
        zsys_info ("Welcome to Global Domination v0.1");
    zsys_info ("starting Global Domination server...");
    gdom_server_t *server = gdom_server_new ();
    assert (server);
    while (!zsys_interrupted) {
        sleep (1);
    }
    zsys_info ("terminating Global Domination server...");
    gdom_server_destroy (&server);
    return 0;
}

The zsys_info method is one of a bunch that do system logging. The others are:

  • zsys_error - log error condition - highest priority.
  • zsys_warning - log warning condition - high priority.
  • zsys_notice - log normal condition - normal priority.
  • zsys_info - log information message - low priority.

Run man zsys to see some of the other methods in the CZMQ zsys class.

Let's build and test our new server:

make
src/gdomd -v

It should say:

gdomd - Global Domination Demon
I: 16-01-20 18:11:07 Welcome to Global Domination v0.1
I: 16-01-20 18:11:07 starting Global Domination server...

Remember this lesson:

Better a hundred small steps than one giant leap.

Problem: update the version number

After such a lot of work we should release a new version. We defined the version number in our public header file, include/globdom.h. It should be simple to bump it:

  • Edit include/globdom.h to say #define GLOBDOM_VERSION_MINOR 2
  • Print the version number from the header:
zsys_info ("Welcome to Global Domination v%d.%d",
            GLOBDOM_VERSION_MAJOR, GLOBDOM_VERSION_MINOR);

Run make and... we get compile errors like this:

gdomd.c: In function ‘main’:
gdomd.c:46:21: error: ‘GLOBDOM_VERSION_MAJOR’ undeclared

The reason is that our project header files are confused. We have a mix of hand-written files (the original include/globdom.h) and generated files doing the same thing. Take a look at include/gdom_library.h and you'll see it does the same (and more) as we did by hand.

Solution: define the version and header in our project model.

First, let's fix the header mismatch. Add this header attribute to the project item:

<project
    name = "Global Domination"
    script = "zproject.gsl"
    prefix = "gdom"
    header = "globaldom.h">
    ...

Now delete these two files:

rm include/global_domination.h
rm include/globaldom.h

The first is junk and has to go. The second is our hand-written project header. When we delete it, and then build the project again, zproject gives us a new skeleton header. It does nothing except pull in gdom_library.h, which has all of our public API. This split lets us add hand-written code (to globaldom.h) while also generating the public API (in gdom_library.h).

It sounds tricky yet once things are working, you can more or less forget about it. Rebuild the project with gsl project.xml as usual. You should see this output:

gsl/4 M: Generating skeleton for include/globaldom.h

Now look at the version macros in include/gdom_library.h:

#define GDOM_VERSION_MAJOR 0
#define GDOM_VERSION_MINOR 0
#define GDOM_VERSION_PATCH 0

To define a version in the project, we add a 'version' item like this:

<version major = "0" minor = "2" />

Run gsl project.xml again and see how include/gdom_library.h changes. Now we can fix our main program, and make the project:

zsys_info ("Welcome to Global Domination v%d.%d",
            GDOM_VERSION_MAJOR, GDOM_VERSION_MINOR);
make
src/gdomd -v

Remember this lesson:

Simple is hard.

Problem: someone's patch broke my code

Congratulations, you got a contributor! It is a precious thing, someone joining your project. To criticize their work is clumsy, and foolish. Yet most open source projects treat patches like lollipops offered by strangers in a park. I've already discussed why optimistic merging is sane and effective.

Yet, it's nice to give people rapid feedback on their work. It might take us a day to see someone's patch and spot a mistake. We have computers to do this kind of stuff.

Solution: enable Travis CI on your project.

"CI" is short for "build everything and run make check, and see if it works." I'm sure there are other CI systems yet Travis wins for being simple, fast, and reliable.

To enable Travis, add this line to your project.xml:

<target name = "travis" />

Run gsl project.xml again and you'll see this output:

gsl/4 I: Processing project.xml...
gsl/4 M: Building GNU build system (autotools)
gsl/4 M: Building CMake build system (cmake)
gsl/4 M: Building Travis CI scripts (travis)
gsl/4 M: Generating skeleton .travis.yml script

In all cases, zproject builds the autotools and cmake targets. We've now added one more target, travis. The .travis.yml script is where we tell Travis what to do.

Do git status and you'll see two new files:

Untracked files:
    .travis.yml
    ci_build.sh

The ci_build.sh file is always generated, whereas we will modify .travis.yml by hand, as we decide to expand our test cover. Here is what the skeleton Travis script looks like:

# Travis CI script
language: c

os:
- linux

sudo: false

env:
- BUILD_TYPE=default
#- BUILD_TYPE=android
#- BUILD_TYPE=check-py
#- BUILD_TYPE=cmake

addons:
  apt:
    packages:
    - valgrind

before_install:
- if [ $TRAVIS_OS_NAME == "osx" ] ; then stuff

# Hand off to generated script for each BUILD_TYPE
script: ./ci_build.sh

You can, if this is your kind of soup, go and learn Travis' scripting language.

  • Go to travis-ci.org (not .com!).
  • Click "Log in with GitHub" at the top right.
  • Authorize Travis for your organizations.
  • When you return to Travis, click your user icon at the top right.
  • Click "Sync account" so Travis knows what repositories you have.
  • Find your Global Domination project.
  • Click the toggle to enable Travis.

Nothing happens until you push the .travis.yml file to your repository. So:

git add .
git commit -m "Problem: someone's patch broke my code

Solution: enable Travis CI"
git push origin master

Now Travis should start building and testing your project. Every time you push commits, it will run the .travis.yml script and report any errors.

Remember this lesson:

Travis is there to help you, not add more stress.

Problem: Travis isn't building my project :(

The first time you try to use Travis it's not obvious. Just enabling builds on your repo does not start a build. So if you carefully pushed your new .travis.yml file, and then enabled builds, nothing happened.

You need to push a commit before Travis wakes up. You don't need to change anything. Git lets you create empty commits.

Solution: push an empty commit.

Run these commands:

git commit --allow-empty -m "Problem: Travis isn't building my project

Solution: push an empty commit"
git push origin master

Remember this lesson:

With enough empty commits you can draw pictures on your GitHub profile.

Problem: my client is using Windows

Consider this an opportunity, not a problem. "We need support for Visual Studio 2010," says your client. "Hmm, that could take a week or more. Is that OK?" you reply. The client doesn't blink. "We could make it work with VS2015 too, that way you're compatible with the next Windows 10 service pack," you continue. The client still doesn't blink. "We put our best people on it... it'll cost more but you'll get it faster." The client finally blinks. "OK, make it happen," they say.

Solution: zproject does Visual Studio.

Here's a way to see all the targets that zproject supports. This is a growing list:

gsl -target:? project.xml

Here is the kind of thing that zproject will report:

Valid targets are:
    android             Native shared library for Android
    autotools           GNU build system
    cmake               CMake build system
    cygwin              Cygwin build system
    debian              packaging for Debian
    docker              packaging for Docker
    java                Java JNI binding
    java-msvc           MSVC builds for Java JNI binding
    mingw32             Mingw32 build system
    nuget               Packaging for NuGet
    python              Python binding
    qml                 QML binding
    qt                  Qt binding
    redhat              Packaging for RedHat
    ruby                Ruby binding
    travis              Travis CI scripts
    vs2008              Microsoft Visual Studio 2008
    vs2010              Microsoft Visual Studio 2010
    vs2012              Microsoft Visual Studio 2012
    vs2013              Microsoft Visual Studio 2013
    vs2015              Microsoft Visual Studio 2015

So to support VS2010 and VS2015, we add these two targets to our project.xml:

<target name = "vs2010" />
<target name = "vs2015" />

And then we regenerate the project packaging with gsl project.xml. Here is what zproject reports now:

globdom> gsl project.xml
GSL/4.1c Copyright (c) 1996-2016 iMatix Corporation
gsl/4 I: Processing project.xml...
gsl/4 M: Building GNU build system (autotools)
gsl/4 M: Building CMake build system (cmake)
gsl/4 M: Building Travis CI scripts (travis)
gsl/4 M: Building Microsoft Visual Studio 2010 (vs2010)
gsl/4 M: Building Microsoft Visual Studio 2015 (vs2015)

You can generate a single target using the -target command line switch. E.g.:

gsl -target:vs2010 project.xml

When you run git status you will now see a new subdirectory called builds. Add this to your repository, and commit it:

git add builds
git add -u
git commit -m "Problem: my client is using Windows

Solution: add Visual Studio targets"
git push origin master

Your repository now holds project files for two versions of Visual Studio.

Remember this lesson:

zproject supports a lot of targets.

Problem: my client wants a stable release

Hold on to your horses, cowboy! One thing at a time. What the client wants and what the client needs are often two different things. Don't throw the word "stable" around lightly. It means different things to different people:

  • It means stuff that is firm, robust, and won't crash.
  • It means stuff that is resistant to change.
  • It means stuff that isn't screamingly insane.
  • It means stuff you put your horses in.

Our code is definitely one of these, and definitely not one of these. The other two, meh. As a compromise, let's "tag" the release. This is somewhat like shooting an orange dart, labeled "Little Buffy", into a wild buffalo. The tag is a short way of saying "that raging mountain of anger heading straight for you," or as we say in the trade, commit #bbc2c1.

Solution: tag this version.

Here is how we tag the current commit and send that off to GitHub:

git tag -m "This is version 0.2" version-0.2
git push --tags origin master

Here's a subtle yet important detail. We change the version number, then we do work, and then we tag it. And then we change the version number again. That means after making our "stable release", we right away update the version number, then regenerate everything, then push it to GitHub again.

In project.xml:

<version major = "0" minor = "3" />

And then:

gsl project.xml
git add -u
git commit -m "Problem: code version needs updating

Solution: update to version 0.3"
git push origin master

Remember this lesson:

The version in git master is always the next version you intend to release.

Problem: how does my client get a tagged release?

Solution: use GitHub's release page.

GitHub offers one-click downloads of the tarballs and zip files for any given release of a project. There really isn't a better way of distributing tarballs.The ZeroMQ project uses a download server, yet that's an historical artifact, not a preference over GitHub.

If you want a specific tagged release from git, there are two ways. Either way you first clone the git repository. Then you can do either of these:

git checkout version-0.2
git reset --hard version-0.2

I tend to use the second form, as it leaves me with a clean state. It is exactly as if I rewound history. The first form leaves the repository in a "detached head" state. Unless you are a masochist, forget it. The hard reset is clean and effective. You can make commits, and they'll be in the right place. Just do git pull origin master before sending your commits back to GitHub.

Remember this lesson:

When it comes to git, ignorance is bliss.

Problem: how do I build my project on Windows?

If you use Linux in your work, then Windows can be a bit of a mystery. I especially hate the Visual Studio user interface. It greeted me by asking me to register a new user account. It then tried to show me "cool" stuff about my project. The only cool stuff in a C project is the code, which seemed impossible to find. Windows 1.0 introduced flat frames without overlap, and Visual Studio has been trying to get back there ever since.

Happily you can entirely ignore Visual Studio's aspirations to be an "environment", and treat it just like any other C/C++ compiler.

Solution: Windows has a command line.

I'll summarize what you need to have, know, and do in order to build your project via the command line:

  • You need a PC running Windows 8 or 10 with Internet connection.

  • You need a Visual Studio compiler. You can use the Community Edition for free.

  • Ensure the {[programfiles}} environment variable is set properly. Use the command line (or PowerShell! if you want a Kung Fury-style retro experience).

  • You need to install git and/or unzip for the command line. I'd recommend to build from git.

  • Using git, clone Global Domination plus all dependent projects:

git clone https://github.com/scalable-c/globdom
git clone https://github.com/zeromq/czmq
git clone https://github.com/zeromq/libzmq
  • Build the projects in order:
cd libzmq\builds\msvc\vs2010
.\build.bat
cd ..\..\..\..
cd czmq\builds\msvc\vs2010
.\build.bat
cd ..\..\..\..
cd globdom\builds\msvc\vs2010
.\build.bat
cd ..\..\..\..
  • Laugh with glee as you realize you just accomplished months of work in a few hours.

Remember this lesson:

It is already amazing the horse can dance. Don't expect it to also keep a rhythm.

Problem: Java

Ah, thither yon sweet Java! Thy perfumed voice lulls my dreams with paths untold! Let me count the ways I love thee... OK, done.

The main problem with Java is that people use the language. And not just a few people either. It is the COBOL of the 21st Century, built by Big Software and sold to managers. If C++ suffers from "hey, how abstract can we make this stuff before our brains strangle themselves?" syndrome, then Java suffers from "just one more path, I promise!" pain.

Inevitably, a quiet and yet determined character will approach you on a dark street corner and offer you money. "Just give me Global Domination in Java," they'll say. "Just once," they'll insist, "How bad can it be?"

The answer is, pretty bad. Java took the traditional way of integrating native C libraries into "higher-level" languages, which is to say "pain and grief." The official name for this torture chamber is JNI, which stands for "Just Nasty, Innit?" Like many parts of the thankless job of programming, it seems OK once you're used to it.

Yet writing JNI code is a mind-numbing exercise that belies the delicacy of it. You need all the grace of an elephant hopping from rock to rock in a molten lava pool. Get it wrong and badness happens. Here's a slice of JNI:

JNIEXPORT jstring JNICALL
Java_org_zeromq_zyre_Zyre__1_1name (JNIEnv *env, jclass c, jlong self)
{
    char *name_ = (char *) zyre_name ((zyre_t *) (intptr_t) self);
    jstring return_string_ = (*env)->NewStringUTF (env, name_);
    return return_string_;
}

I'm not going to explain this. You can find enough JNI tutorials on-line to decipher it. What I'm going to do is explain how to generate everything you need to make it Someone Else's Problem.

Solution: use the 'java' target.

Add this to project.xml:

<target name = "java" />

And then gsl project.xml as usual. You'll see this output from zproject:

gsl/4 M: Building Java JNI binding (java)

And when you type git status, you will only project.xml changed, and nothing else. There is a new directory bindings/jni and it is empty.

Problem: bindings/jni is empty

To generate a binding we need a little more than just the class name. We need a description of the methods in the class. We call this the "API model" and it sits in a subdirectory called "api."

Solution: write an API model for gdom_client.

Create a new file api/gdom_client.xml with this content:

<class name = "gdom_client">
    Global Domination client API
    <constructor>
        Create a new Global Domination client
    </constructor>
    <destructor>
        Destroy a Global Domination client
    </destructor>
</class>

And then run gsl project.xml as usual. It looks just like before, yet take a look at bindings/jni now. It's full of life. Let's quickly add the newly generated files to our repository:

git add api bindings
git add -u
git status