cython_freeze for making stand-alone programs
authorMark Lodato <lodatom@gmail.com>
Fri, 7 Aug 2009 04:20:43 +0000 (21:20 -0700)
committerMark Lodato <lodatom@gmail.com>
Fri, 7 Aug 2009 04:20:43 +0000 (21:20 -0700)
Demos/freeze/Makefile [new file with mode: 0644]
Demos/freeze/README.rst [new file with mode: 0644]
Demos/freeze/cmath.pyx [new file with mode: 0644]
Demos/freeze/combinatorics.pyx [new file with mode: 0644]
bin/cython_freeze.py [new file with mode: 0644]

diff --git a/Demos/freeze/Makefile b/Demos/freeze/Makefile
new file mode 100644 (file)
index 0000000..dd511e0
--- /dev/null
@@ -0,0 +1,33 @@
+CC = gcc
+CYTHON = ./../bin/cython
+CYTHON_FREEZE = ../../bin/cython_freeze.py
+
+CFLAGS = -fPIC -g -O2 -Wall -Wextra
+CPPFLAGS = -I /usr/include/python2.6
+LDFLAGS = -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions
+LDLIBS = /usr/lib/python2.6/config/libpython2.6.a \
+       -lm -ldl -pthread -lutil -lz
+
+
+# Name of executable
+TARGET = nCr
+
+# List of Cython source files, with main module first.
+CYTHON_SOURCE = combinatorics.pyx cmath.pyx
+
+
+all : $(TARGET)
+
+$(TARGET) : $(TARGET).o $(CYTHON_SOURCE:.pyx=.o)
+
+$(TARGET).c :
+       $(CYTHON_FREEZE) $(CYTHON_SOURCE:.pyx=) > $@
+
+%.c : %.pyx
+       $(CYTHON) $(CYTHONFLAGS) $^
+
+clean:
+       $(RM) *.o *.c $(TARGET)
+
+.PHONY: clean
+.SECONDARY: $(CYTHON_SOURCE:.pyx=.c)
diff --git a/Demos/freeze/README.rst b/Demos/freeze/README.rst
new file mode 100644 (file)
index 0000000..cc81722
--- /dev/null
@@ -0,0 +1,82 @@
+NAME
+====
+
+cython_freeze.py - create a C file for embedding Cython modules
+
+
+SYNOPSIS
+========
+
+cython_freeze.py module [...]
+
+
+DESCRIPTION
+===========
+
+**cython_freeze.py** generates a C source file to embed a Python interpreter
+with one or more Cython modules built in.  This allows one to create a single
+executable from Cython code, without having to have separate shared objects
+for each Cython module.
+
+A major advantage of this approach is that it allows debuging with gprof(1),
+which does not work with shared objects.
+
+Note that this method differs from ``cython --embed``.  The ``--embed`` options
+modifies the resulting C source file to include a ``main()`` function, so it
+can only be used on a single Cython module.  The advantage ``--embed`` is
+simplicity.  This module, on the other hand, can be used with multiple
+modules, but it requires another C source file to be created.
+
+
+EXAMPLE
+=======
+
+In the example directory, there exist two Cython modules:
+
+cmath.pyx
+    A module that interfaces with the -lm library.
+
+combinatorics.pyx
+    A module that implements n-choose-r using cmath.
+
+Both modules have the Python idiom ``if __name__ == "__main__"``, which only
+execute if that module is the "main" module.  If run as main, cmath prints the
+factorial of the argument, while combinatorics prints n-choose-r.
+
+The provided Makefile creates an executable, *nCr*, using combinatorics as the
+"main" module.  It basically performs the following (ignoring the compiler
+flags)::
+
+    $ cython_freeze.py combintorics cmath > nCr.c
+    $ cython combinatorics.pyx
+    $ cython cmath.pyx
+    $ gcc nCr.c -o nCr.o
+    $ gcc combinatorics.c -o combinatorics.o
+    $ gcc cmath.c -o cmath.o
+    $ gcc nCr.o combinatorics.o cmath.o -o nCr
+
+Because the combinatorics module was listed first, its ``__name__`` is set
+to ``"__main__"``, while cmath's is set to ``"cmath"``.  The executable now
+contains a Python interpreter and both Cython modules. ::
+
+    $ ./nCr
+    USAGE: ./nCr n r
+    Prints n-choose-r.
+    $ ./nCr 15812351235 12
+    5.10028093999e+113
+
+
+
+
+PREREQUISITES
+=============
+
+Cython 0.11.2 (or newer, assuming the API does not change)
+
+
+SEE ALSO
+========
+
+* `Python <http://www.python.org>`_
+* `Cython <http://www.cython.org>`_
+* `freeze.py <http://wiki.python.org/moin/Freeze>`_
diff --git a/Demos/freeze/cmath.pyx b/Demos/freeze/cmath.pyx
new file mode 100644 (file)
index 0000000..cc3f052
--- /dev/null
@@ -0,0 +1,24 @@
+cdef extern from "math.h":
+    double c_lgamma "lgamma" (double)
+    double c_exp "exp" (double)
+
+def exp(n):
+    """Return e**n."""
+    return c_exp(n)
+
+def lfactorial(n):
+    """Return an estimate of the log factorial of n."""
+    return c_lgamma(n+1)
+
+def factorial(n):
+    """Return an estimate of the factorial of n."""
+    return c_exp( c_lgamma(n+1) )
+
+
+if __name__ == "__main__":
+    import sys
+    if len(sys.argv) != 2:
+        sys.stderr.write("USAGE: %s n\nPrints n!.\n" % sys.argv[0])
+        sys.exit(1)
+    n = map(float, sys.argv[1:])
+    print factorial(n)
diff --git a/Demos/freeze/combinatorics.pyx b/Demos/freeze/combinatorics.pyx
new file mode 100644 (file)
index 0000000..f3a23ec
--- /dev/null
@@ -0,0 +1,14 @@
+import cmath
+
+def nCr(n, r):
+    """Return the number of ways to choose r elements of a set of n."""
+    return cmath.exp( cmath.lfactorial(n) - cmath.lfactorial(r)
+                      - cmath.lfactorial(n-r) )
+
+if __name__ == "__main__":
+    import sys
+    if len(sys.argv) != 3:
+        sys.stderr.write("USAGE: %s n r\nPrints n-choose-r.\n" % sys.argv[0])
+        sys.exit(1)
+    n, r = map(float, sys.argv[1:])
+    print nCr(n, r)
diff --git a/bin/cython_freeze.py b/bin/cython_freeze.py
new file mode 100644 (file)
index 0000000..184c4a8
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+"""
+Create a C file for embedding one or more Cython source files.
+Requires Cython 0.11.2 (or perhaps newer).
+
+See README.rst for more details.
+"""
+
+import sys
+
+if len(sys.argv) < 2:
+    print >>sys.stderr, "USAGE: %s module [module ...]" % sys.argv[0]
+    sys.exit(1)
+
+def format_modname(name):
+    if name.endswith('.pyx'):
+        name = name[:-4]
+    return name.replace('.','_')
+
+modules = [format_modname(x) for x in sys.argv[1:]]
+
+print """
+#include <Python.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if PY_MAJOR_VERSION < 3
+# define MODINIT(name)  init ## name
+#else
+# define MODINIT(name)  PyInit_ ## name
+#endif
+"""
+
+for name in modules:
+    print "PyMODINIT_FUNC MODINIT(%s) (void);" % name
+
+print """
+static struct _inittab inittab[] = {"""
+
+for name in modules:
+    print '    {"%(name)s", MODINIT(%(name)s)},' % {'name' : name}
+
+print """    {NULL, NULL}
+};
+
+extern int __pyx_module_is_main_%(main)s;
+
+#if PY_MAJOR_VERSION < 3 || (!defined(WIN32) && !defined(MS_WINDOWS))
+int main(int argc, char** argv) {
+#else
+int wmain(int argc, wchar_t **argv) {
+#endif
+    int r = 0;
+    PyObject *m = NULL;
+    if (PyImport_ExtendInittab(inittab)) {
+        fprintf(stderr, "No memory\\n");
+        exit(1);
+    }
+    Py_SetProgramName(argv[0]);
+    Py_Initialize();
+    PySys_SetArgv(argc, argv);
+    __pyx_module_is_main_%(main)s = 1;
+    m = PyImport_ImportModule(inittab[0].name);
+    if (!m) {
+        r = 1;
+        PyErr_Print(); /* This exits with the right code if SystemExit. */
+        if (Py_FlushLine()); PyErr_Clear();
+    }
+    Py_XDECREF(m);
+    Py_Finalize();
+    return r;
+}
+""" % {'main' : modules[0]}