From ddc68024fb5d1fffa36fe0983defd1804ff94f25 Mon Sep 17 00:00:00 2001 From: Mark Lodato Date: Thu, 6 Aug 2009 21:20:43 -0700 Subject: [PATCH] cython_freeze for making stand-alone programs --- Demos/freeze/Makefile | 33 ++++++++++++++ Demos/freeze/README.rst | 82 ++++++++++++++++++++++++++++++++++ Demos/freeze/cmath.pyx | 24 ++++++++++ Demos/freeze/combinatorics.pyx | 14 ++++++ bin/cython_freeze.py | 73 ++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+) create mode 100644 Demos/freeze/Makefile create mode 100644 Demos/freeze/README.rst create mode 100644 Demos/freeze/cmath.pyx create mode 100644 Demos/freeze/combinatorics.pyx create mode 100644 bin/cython_freeze.py diff --git a/Demos/freeze/Makefile b/Demos/freeze/Makefile new file mode 100644 index 00000000..dd511e08 --- /dev/null +++ b/Demos/freeze/Makefile @@ -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 index 00000000..cc81722f --- /dev/null +++ b/Demos/freeze/README.rst @@ -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 `_ +* `Cython `_ +* `freeze.py `_ diff --git a/Demos/freeze/cmath.pyx b/Demos/freeze/cmath.pyx new file mode 100644 index 00000000..cc3f0521 --- /dev/null +++ b/Demos/freeze/cmath.pyx @@ -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 index 00000000..f3a23ece --- /dev/null +++ b/Demos/freeze/combinatorics.pyx @@ -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 index 00000000..184c4a89 --- /dev/null +++ b/bin/cython_freeze.py @@ -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 +#include +#include + +#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]} -- 2.26.2