Reputation: 57
I have a simple hello world script and if I wanted to compile it to become a binary file executed on my Linux machine what can I use to achieve this? It also has to be possible to execute the same file on a different machine, even with a different architecture
This has to compile python3.6 files
I have tried these:
Pyinstaller. 2 problems, too big when compiled and won't run on different archs to the compiled one
Nuitka. Size is perfectly fine but it won't run on machines with a different arch type to the original one that compiled it
Cython. Same problem as Nuitka
Any other suggestions?
The result should be:
python3 hello.py
Hello World
--compiled--
./hello
Hello World
Upvotes: 0
Views: 3082
Reputation: 565
I began expanding this answer today to provide sample code. In the course of doing so I stumbled over a recent blog post by Gregory Szorc for his PyOxidizer tool, which on the surface appears to do more of what you were looking for in the OP. It uses Rust to boil Python down to machine code, and while it still technically requires a runtime, it embeds that runtime in the executable so that you don't need to bundle files. I'd be interested in your thoughts if you try this out.
Since I still think my previous answer is potentially useful, here's the update:
I don't know of a 100% coverage solution for this. I think it depends a lot on your code, complexity, dependencies, and goals.
You mentioned trying Cython. I think there's a little more to it than what you're seeing. I'm not familiar with Cython; it's just an option that comes up when I read about the topic. But I gather that you can use it to build a native executable, subject to runtime requirements.
See Is it feasible to compile Python to machine code? for some other options.
You also can build an excutable yourself, by writing a small C program that instantiates a python interpreter and loads compiled python bytecode from a separate file or a compiled-in bytestring. Details for this are buried in the Python embedding/extending documentation.
It is easy to build Python code into a native machine executable (this is called embedding), but you're right (in your comment) that you still need a Python runtime. But this is a complexity problem. For a small enough use case, e.g. your simple hello world, you can bundle the elements that you actually depend on into your code. (For example, link statically to the python library.)
If you're looking for an arbitrarily-scalable solution, then one common solution — I've seen this from commercial games to backup software — is to bundle a sufficient python binary runtime pre-compiled for your target platform, with an installation prefix that you can predict or control (i.e. not /usr
). This bundle usually includes any machine code (shared libraries) and compiled python bytecode as needed (.pyc
or .pyo
), without any .py
files. This doesn't necessarily include the totality of the python installation — just the machine code and the bytecode required for the application.
Here's a really simple hello world, written in Python but embedded into a C skeleton:
#include <Python.h>
int
main(int argc, char *argv[])
{
wchar_t *name = Py_DecodeLocale(argv[0], NULL);
if (name == NULL) {
fprintf(stderr, "error: cannot decode program name\n");
exit(1);
}
/* Setup */
Py_SetProgramName(name);
Py_Initialize();
/* Load and run code */
if (argc > 1)
PyRun_SimpleString(argv[1]);
else
PyRun_SimpleString("print('Hello, world')");
/* Importing and running code from a file is also possible.
* See https://docs.python.org/3/c-api/veryhigh.html
* and https://docs.python.org/3/c-api/import.html
*/
/* Clean up */
Py_Finalize();
PyMem_RawFree(name);
/* Ideally we should get a result from the interpreter
* and evaluate it, returning conditionally. Py_FinalizeEx()
* is a good way but it's only in Python 3.6+, so I'm leaving
* it out of this example. */
return 0;
}
A makefile
for this could go as follows:
PY=python3
PC=$(PY)-config
all: py
py: py.o
$(CC) $$($(PC) --ldflags) -o $@ $< $$($(PC) --libs)
py.o: py.c
$(CC) $$($(PC) --cflags) -c $<
Build and run:
$ make
gcc $(python3-config --cflags) -c py.c
gcc $(python3-config --ldflags) -o py py.o $(python3-config --libs)
$ ./py
Hello, world
$ ./py 'print("hi")'
hi
You can run this under strace
to see what the runtime requirements are:
$ strace -e trace=open ./py 2>&1 | grep py
... and then you know what files need to be bundled. If you were to pick up each of those in a cpio file, you'd have a portable application. (It just would need to be installed in the same location. Workarounds for that include building python from source with --prefix
as mentioned above, and playing odd games with ldconfig and PYTHON_PATH.)
Upvotes: 3