***********************
Buildout Sandbox Recipe
***********************

.. contents ::

This is a recipe for use with Buildout. It is identical to Buildout's
zc.recipe.egg but has the following extra features:

* Can install the scripts from any dependent package
* Can setup an executable interpreter if you provide a suitable
  application for your platform

.. warning ::

    This is very much alpha software and has only been tested on Debian Etch.
    There are probably bugs on other platforms such as Windows. It is designed
    more as a proof-of-concept than a production-ready recipe.

To get started first download the Buildout bootstrap::

    wget "http://svn.zope.org/*checkout*/zc.buildout/trunk/bootstrap/bootstrap.py"

Then create a ``buildout.cfg`` file, replacing ``/path/to/pylons/app/src`` with
the path to your Pylons application and replacing ``PylonsApp`` with the name
of your application::

    [buildout]
    develop = /path/to/pylons/app/src
    parts = python

    [python]
    recipe = pylons_sandbox
    interpreter = python
    eggs = PylonsApp

You can now buildout your application::

    $ python bootstrap.py
    $ bin/buildout

So far the ``pylons_sandbox`` recipe has behaved *exaclty* the same as the
default ``zc.buildout.egg`` recipe, installing all the required dependencies
for your Pylons app to the local buildout sandbox. It has also set you up with
a ``bin/python`` script and a ``bin/buildout`` script which you can use to
buildout any future changes.

For Pylons use you should set the option ``dependent_scripts=True`` so that
scripts from packages such as ``Nose`` and ``PasteScript`` get created in the
``bin`` directoy::

    [buildout]
    develop = /path/to/pylons/app/src
    parts = python

    [python]
    recipe = pylons_sandbox
    interpreter = python
    eggs = PylonsApp
    dependent_scripts = True

Now run the following to re-buildout the directory::

    $ bin/buildout -N

The ``-N`` option means that buildout doesn't look for new dependencies if it
can meet them from files it already has installed. This means it is a bit
quicker to re-buildout the directory.

You should now have a ``bin/paster`` command you can use to serve your Pylons
application.

For the majority of users this set up will be fine but the ``pylons_sandbox``
recipe has one more feature, the ``launcher`` option.

If you want to treat your buildout setup as a true sandbox you will need a
Python interpreter which is an actual executable so that other scripts can use
your sandboxed Python interpreter in a #! line of in a script such as a CGI
script used by Apache. The ``python`` file generated by Buildout is actually
just a Python script itself so can't be used in this manner.

If you set the ``launcher`` option, the ``pylons_sandbox`` recipe will create a
new interpreter by appending ``.buildout`` to the name specified in the
``interpreter`` option and it will add a facility so that the directory of the
calling script is on sys.path. It will then copy the application specified by
the ``launcher`` option to the name specified in the ``interpreter`` option. In
our exampls so far this means the buildout ``python`` script would be in
``bin/python.buildout`` and the application to lauch it would be in
``bin/python`` and could now be used in a ``#!`` line.

This is all well and good but you need the application itself. Here is some C++
code which when compiled will create a suitable application. It has been
described as "gruesome" so I'm happy to accept a patch with some neater C++. 
Create a ``launcher.cc`` file with this content::

    /*
     * Buildout Launcher 
     * +++++++++++++++++
     *
     * This application excutes a python script in the same directory as the
     * application. This is useful because it effectively turns a Python script
     * into a real executable which you can use on the #! line of other scripts. 
     *
     * The script to be executed should have the same name as the the filename of
     * this compiled program but with a .py extension added to the end. The real
     * Python interpreter used to execute the script is dermined from the script's
     * #! line or /usr/bin/python is used as a fallback if no Python interpreter
     * can be found. 
     *
     * The Python interpreters generated by Buildout are actually just Python
     * scripts so this application allows them to be run from a real executable.
     *
     * Compile this file with the following command:
     *
     *     g++ launcher.cc -o launcher
     *
     * Copyright James Gardner. MIT license. No warranty to the maximum extent
     * possible under the law.
     *
     */
    
    #include <vector>
    #include <string>
    #include <unistd.h>
    #include <fstream>
    
    using namespace std;
    int main(int argc,char *argv[])
    {
        vector<string> args;
        int i;
        args.push_back("python");
        for (i=0;i<argc;i++) 
            args.push_back(argv[i]);
        args[1] = strcat(argv[0], ".buildout");
        char *new_argv[argc+1];
        for (int i=0 ; i<argc+1 ; i++)  {
            new_argv[i] = (char *)args[i].c_str();
        }
        new_argv[argc+1] = NULL;
        vector<string> text_file;
        ifstream ifs(new_argv[1]);
        string temp;
        string temp_short;
        getline(ifs, temp);
        if (strncmp((char *)temp.c_str(), "#!", 2)) {
            /* default to /usr/bin/python if no #! header */
            temp_short = "/usr/bin/python";
        } else {
            temp_short = temp.substr(2,(temp.length()-2));
        }
        char python[temp_short.length()];
        strcpy(python, (char *)temp_short.c_str());
        return execv(python, new_argv);
    }


Compile this with::

    $ g++ launcher.cc -o launcher

and place the ``launcher`` application in the same directory as your
``buildout.cfg``. You can then update your ``buildout.cfg`` to look like this::

    [buildout]
    develop = /path/to/pylons/app/src
    parts = python

    [python]
    recipe = pylons_sandbox
    interpreter = python
    eggs = PylonsApp
    dependent_scripts = True
    launcher = launcher

Now re-buildout again::

    $ bin/buildout -N
    
You should have a nicely working sandbox with a real Python executable as well
as all the other benefits of deploying using the Buildout system.

Test it out::

    $ bin/python
    >>> import pylons
    >>> 

As you can see all the module dependencies are present.

