Examples Documentation Contact Downloads

Building Applications

Grace has a powerful application model that can take care of a lot of grind work. In cases where your application needs to carry a lot of internal resources, Grace can also build OSX-style .app bundles. This makes it easier for your application to refrain from leaving scent marks all over the filesystem.

Using mkproject

The grace mkproject command creates a project directory skeleton for new Grace projects. Here's how you use it:

$ grace mkproject
Application domain [nl.madscience.unregistered.apps]: com.openpanel.apps
Application name: storpelcount
Creating com.openpanel.apps.storpelcount
$ cd com.openpanel.apps.storpelcount/
$ ls -l
-rw-r--r--  1 pi  pi    471 Jun 26 22:38 Makefile
-rwxr-xr-x  1 pi  pi  18229 Jun 26 22:38 configure
-rw-r--r--  1 pi  pi     43 Jun 26 22:38 configure.in
-rw-r--r--  1 pi  pi    293 Jun 26 22:38 main.cpp
-rwxr-xr-x  1 pi  pi    126 Jun 26 22:38 makeinstall
drwxr-xr-x  6 pi  pi    204 Jun 26 22:38 rsrc
-rw-r--r--  1 pi  pi    485 Jun 26 22:38 storpelcount.h
$ 

A skeleton headerfile storpelcount.h has been created that will look like this:

#ifndef _storpelcount_H
#define _storpelcount_H 1
#include <grace/application.h>

//  -------------------------------------------------------------------------
/// Implementation template for application config.
//  -------------------------------------------------------------------------
typedef configdb<class storpelcountApp> appconfig;

//  -------------------------------------------------------------------------
/// Main application class.
//  -------------------------------------------------------------------------
class storpelcountApp : public application
{
public:
                 storpelcountApp (void) :
                    application ("com.openpanel.apps.storpelcount")
                 {
                 }
                ~storpelcountApp (void)
                 {
                 }

    int          main (void);

protected:
    appconfig    conf;
};

#endif

A skeleton implementation file main.cpp contains a stub application::main virtual method implementation:

#include "storpelcount.h"

$appobject(storpelcountApp);

//  =========================================================================
/// Main method.
//  =========================================================================
int storpelcountApp::main (void)
{
    string conferr;
    if (! conf.load ("com.openpanel.apps.storpelcount", conferr))
    {
        ferr.writeln ("%% Error loading configuration: %s" %format (conferr));
        return 1;
    }
    return 0;
}

Command Line Options

The application class takes care of a lot of initialization, including the command line argument parser. You can define command line flags by tweaking the application::opt value object. If your project builds ass an app-bundle, you can define further command line options in rsrc/grace.runoptions.xml. Through the mkapp tool, this file will get copied to Contents/Resources/ inside the app bundle. The application constructor will attempt to load "rsrc:grace.runoptions.xml". Here's a sample:


<?xml version="1.0"?>
<grace.runoptions>
    <grace.option id="-h">
        <grace.long>--help</grace.long>
    </grace.option>
    <grace.option id="-i">
        <grace.long>--input-file</grace.long>
    </grace.option>
    <grace.option id="--help">
        <grace.argc>0</grace.argc>
    </grace.option>
    <grace.option id="--input-file">
        <grace.argc>1</grace.argc>
        <grace.default>/var/db/storpels</grace.default>
        <grace.help>Storpel file to process</grace.help>
    </grace.option>
</grace.runoptions>

In the main method, we can access the parsed command line options through application::argv:

int storpelcountApp::main (void)
{
    string infile = argv["--input-file"];
    string conferr;
    
    if (! conf.load ("com.openpanel.apps.storpelcount", conferr))
    {
        ferr.writeln ("%% Error loading configuration: %s" %format (conferr));
        return 1;
    }
    
    if (! fs.exists (infile))
    {
        ferr.writeln ("File '%s' not found" %format (infile));
        return 1;
    }
    
    fout.writeln ("Processing '%s'" %format (infile));
    return 0;
}

Resources Inside the Application Bundle

If you look at the project Makefile, you will see that it goes through a somewhat more elaborate process to create your target application:

  1. Source files are turned into object files the usual way.
  2. The object files are linked into appname.exe.
  3. The grace mkapp tool is used to create a bundle named appname.app and a softlink appname that points to the executable inside the bundle directory.
Here is how it looks in action:

$ make
/usr/bin/g++  -I/sw/include -c main.cpp
/usr/bin/g++  -o storpelcount.exe main.o -L/sw/lib -lgrace     
grace mkapp storpelcount
* Building storpelcount for Darwin.powerpc:
> Creating ./storpelcount.app... OK
> Installing executable... OK
> Installing rsrc/com.openpanel.apps.storpelcount.conf.xml... OK
> Installing rsrc/com.openpanel.apps.storpelcount.schema.xml... OK
> Installing rsrc/com.openpanel.apps.storpelcount.validator.xml... OK
> Installing rsrc/grace.runoptions.xml... OK
$ ls -l
total 152
-rw-r--r--  1 pi  pi    471 Jun 26 22:57 Makefile
-rwxr-xr-x  1 pi  pi  18229 Jun 26 22:57 configure
-rw-r--r--  1 pi  pi     43 Jun 26 22:57 configure.in
-rw-r--r--  1 pi  pi    185 Jun 26 22:57 configure.paths
-rw-r--r--  1 pi  pi    293 Jun 26 22:57 main.cpp
-rw-r--r--  1 pi  pi   6496 Jun 26 22:57 main.o
-rw-r--r--  1 pi  pi    383 Jun 26 22:57 makeinclude
-rwxr-xr-x  1 pi  pi    126 Jun 26 22:57 makeinstall
-rw-r--r--  1 pi  pi     87 Jun 26 22:57 platform.h
drwxr-xr-x  6 pi  pi    204 Jun 26 22:57 rsrc
lrwxr-xr-x  1 pi  pi     21 Jun 26 22:57 storpelcount -> storpelcount.app/exec
drwxr-xr-x  4 pi  pi    136 Jun 26 22:57 storpelcount.app
-rwxr-xr-x  1 pi  pi  10472 Jun 26 22:57 storpelcount.exe
-rw-r--r--  1 pi  pi    485 Jun 26 22:57 storpelcount.h
 

Files in the rsrc directory are installed using the following rules:
File matches Description Installed to Alias Path
*.conf.xml Default configuration files Contents/Configuration Defaults conf:
*.schema.xml
*.validator.xml
XML Schema definitions Contents/Schemas schema:
*.thtml Scripted HTML templates Contents/Templates tmpl:
* Anything else Contents/Resources rsrc:
After grace mkapp did its magic you should end up with a situation like this:

$ cd storpelcount.app
$ cd Contents
$ find . -type f                  
./Configuration Defaults/com.openpanel.apps.storpelcount.conf.xml
./Darwin.powerpc/storpelcount.exe
./Resources/grace.runoptions.xml
./Schemas/com.openpanel.apps.storpelcount.schema.xml
./Schemas/com.openpanel.apps.storpelcount.validator.xml
$

The Configuration File

You my have noticed that the conf.xml got installed inside the application bundle. Obviously this isn't really useful for user configuration. This is where the conf: alias path comes to the rescue. The grace mkconf tool installs a copy of the default configuration into a suitable location:
Condition Install Location
If the current user is root: /etc/conf/$appid
If the platform is Mac OS X: home:Library/Preferences/$appid
The first of these locations if the parent folder exists: home:.conf/$appid
home:conf/$appid
home:etc/conf/$appid
If all else fails: home:.$appid
When the configuration file is loaded from conf: inside your application, Grace will go through a number of locations:

  1. A configuration inside the user's homedir
  2. A global configuration file in /etc/conf
  3. The Contents/Resources directory inside the application bundle.
So there's a fallback mechanism that ends up with your default conf.xml if nothing better was found. You can access configuration values in a thread-safe fashion by using the array index operator:

#include "myapp.h"
#include "storpelclient.h"

int myApp::main (void)
{
    string conferr;
    if (! conf.load ("com.openpanel.apps.myapp", conferr))
    {
        ferr.writeln ("%% Error loading config: %s" %format (conferr));
        return 1;
    }
    
    storpelclient cl;
    cl.sethost (conf["storpel"]["connecthost"]);
    cl.storpelize ();
    return 0;
}

The configdb has more powerful features that we will investigate when we're building a daemon.

Building Standalone Executables

You can also set up your project as a standalone executble using the --noapp option to grace mkproject. If your project is set like this, there is no grace.runoptions.xml file — only app-bundles support resource files. You can still influence the command line argument parsing by filling the application::opt object in your application's constructor:

class storpelcountApp : public application
{
public:
             storpelcountApp (void) :
                application ("com.openpanel.apps.storpelcount")
             {
                opt = $("-h", $("long", "--help")) ->
                      $("-i", $("long", "--input-file")) ->
                      $("--help",
                            $("argc", 0)
                       ) ->
                      $("--input-file",
                            $("argc", 1) ->
                            $("default", "/var/db/storpels") ->
                            $("help", "Storpel file to process")
                       );
             }
             ...
};

Standalone executables can have no resource files or default configuration. The grace mkproject template will assume configuration will be kept in an INI-style configuration file in /etc.

Initialization

Grace applications don't use the C standard main() function — this function is already provided by the libgrace shared object. The grace_init in the application's symbol table and, if found, call it. This function is useful if you want to override any of the normal defaults defined in the <grace/defaults.h> header. Let's say we have a compelling reason to override the default file readbuffer size:

void grace_init (void)
{
    defaults::sz::file::readbuf = 16384;
}

 

Previous Chapter Table of Contents Next Chapter