How to Create a Single Windows Executable from a Python and PyGame Project (Summary)
Here’s how you use PyInstaller and PyGame to create a single-file executable from a project that has a data
directory that contains resources like images, fonts, and music.
- Get PyInstaller.
- On Windows, you might also need pywin32 (and possibly MinGW if you don’t have Visual Studio).
- On Mac OS X, you will need XCode’s command line tools. To install the Command Line tools, first install XCode from the App Store, then go to Preferences – Downloads and there is an option to download them there.
- Modify your code so that whenever you refer to your
data
directory, you wrap it using the following function:def resource_path(relative): if hasattr(sys, "_MEIPASS"): return os.path.join(sys._MEIPASS, relative) return os.path.join(relative)
An example of usage would be
filename = 'freesansbold.ttf' myfontfile = resource_path(os.path.join(data_dir, filename)
This is mostly for convenience – it allows you to access your resources while developing, but then it’ll add the right prefix when it’s in the deployment environment.
- Specify exactly where your fonts are (and include them in the data directory). In other words, don’t use
font = Font(None, 26)
. Instead, use something likefont = Font(resource_path(os.path.join('data', 'freesansbold.ttf')), 14)
. - Generate the
.spec
file.- Windows: (You want a single EXE file with your data in it, hence
--onefile
).python pyinstaller.py --onefile your_main_file.py
- Mac OS X: (You want an App bundle with windowed output, hence
--windowed
).python pyinstaller.py --windowed your_main_file.py
- Windows: (You want a single EXE file with your data in it, hence
- Modify the
.spec
file so that you add yourdata
directory (note that these paths are relative paths to your main directory.- Windows: Modify the section where it says
exe EXE = (pyz,
and add on the next line:Tree('data', prefix='data'),
- Mac OS X: Modify the section where it says
app = BUNDLE(coll,
and add on the next line:Tree('data', prefix='data'),
- Windows: Modify the section where it says
- Rebuild your package.
python pyinstaller.py your_main_file.spec
- Look for your
.exe
or your.app
bundle in thedist
directory.
Phew! That took me a long time – the better part of a few hours to figure out. This post on the PyInstaller list really helped.
So why was I trying to package a Python executable file anyway? Read on…
Ludum Dare 26: 48-hour Game Design Compo
This weekend, I decided to participate in a 48-hour game design “competition”. Ludum Dare is a compo that asks you to create a video game from scratch in a 48-hour time period – you have to write your code and create all of your assets in that time period.
This means no reusing graphics, pictures, music, or sound from other projects, for example. You’re also not supposed to reuse code either. I decided to participate on the Thursday the day before. Most people use the previous weekend as a “warmup weekend” to test their tools, get some practice, and so forth. (My entry is located here, by the way).
I’ll do a more detailed compo writeup later, but I just want to concentrate on one thing that kept me up for hours after the competition: getting a Windows executable created from a Python project that uses PyGame and a data directory.
Python, Distribution, and You
I rather enjoy Python as a programming language. The syntax is reasonably concise, the language does a lot of things for you, and it’s well-laid out. There’s also a lot of good support in the form of third-party libraries. I’ve been using Python for various things for the past few years (usually small scripts for data extraction and analysis in research).
One thing I had never thought about before was distributing a Python project as an executable package, and while it was on my mind throughout the entire compo, I didn’t actually learn the process of creating the package until the last hour of the comp before submission. After you submit your primary platform, Ludum Dare allows you around 48 hours to compile for Windows, since the majority of reviewers use Windows.
The ideal submission is a single binary file (an .exe file for Windows) that doesn’t have to extract a lot of data, so that it’s easy for people to download and run your game.
PyInstaller vs. Py2exe vs. Py2app
I went on a wild goose chase trying to find out how to make a single executable file out of a Python project that would include all of my data assets. I first tried py2exe and py2app. py2app mostly worked all right, but py2exe was a pretty big mess.
The end story is that PyInstaller is newer and shinier than py2exe, and that you need to secret sauce code that someone out there on the Internet found before I did. PyInstaller basically runs EXE files by extracting the assets into a temporary data file that has a path _MEIPASS in it ((technical details here). Be sure that you check that every file is loaded in through that wrapper. The Tree() TOC syntax was also confusing, but basically, it’s the relative path of your data files and it will automatically load all of the files in that directory. Make sure it exists in the EXE portion (Windows) or the APP portion (Mac).
There’s a Make/Build cycle in PyInstaller to generate the spec file and build it in a single step as well – I find it easier to do that to generate the spec file and do an initial binary run, then to modify the spec and run PyInstaller again with the spec file as the argument. PyInstaller is pretty smart about rebuilding, and you save a lot of time.
I think in the long run, if you compare py2exe, py2app, and PyInstaller, PyInstaller is the program worth learning. It did have a pretty sharp curve for me – it didn’t help that I was trying to do this late at night after a challenging weekend!
If you do wish to use py2app to build your Mac OS X application bundle, then do keep in mind that you need to have a import pygame._view
because of some kind of obscure issue.
Anyway, that’s all there is to this post for now.
Appendix
Here’s the setup.py I used for py2app.
from setuptools import setup APP = ['painterscat.py'] DATA_FILES = ['data'] OPTIONS = { "argv_emulation": False, "compressed" : True, "optimize":2, # "iconfile":'data/game.icns', } setup( app=APP, data_files=DATA_FILES, options={'py2app': OPTIONS}, )
This is a really useful post on PyInstaller. Thanks a lot man!
Do you have to build Windows bundles on Windows and Mac bundles on Mac? Can I not make a Windows or Linux bundle from Mac as well?
HI, sorry for the late reply. As far as I know, you must have the operating system that you’re compiling for running. I believe that there are ways to do cross-compiles but that’s something that I haven’t tried figuring out.
I currently use Windows in Parallels for cross compilation.
On Mac OS X, I don’t see ‘ app = BUNDLE(coll,’ as you stated. Why is that?
Pingback: Working with python resource file | QueryPost.com
Adding the “Tree(…),” line within BUNDLE(…) does not seem to work. Instead, adding the line
a += Tree(“”, “”) after a = Analysis(…) did work for me. The function wrapping for including files within the source code was a very useful suggestions. Thank you!
Hi there.
This is a concise how to on the usage of PyInstaller.
I am currently scratching my head on how to modify Pyinstaller menubar in OSX: the about menu item. I would like to display more menu item there.
Do you have any idea on how to use this?
Thanks!
Unfortunately I do not.
Ey hi, thanks for the article.
What happen when i want to create two or more executable but sharing the same “dist” folder?
how can i do that :S
thanks