Compile-time constants with Pyinstaller spec files
Insert version number and build time directly into your code!
When I was developing LIFX-Control-Panel, I was constantly running into
the problem of keeping my version numbers straight. Namely, I was keeping my version number in two separate places; one
in the top of my settings.py
file, and the other in the default.ini
config file. The idea was, the binary would compare
its version string to config.ini
s on startup, and if the binaries version was greater than the existing config, it
overwrite it with the newer default.ini
. A brilliant, game-changing idea on it’s own (if I do say so myself), but the problem was, I had to
manually set the values every time I upped the version number or rebuilt the binaries. Naturally, I forgot to keep the
version numbers in lock-step, and much confusion ensued. How could I keep these constant values in some singular place,
where I only have to change them once and it will propagate through the rest of my code?
The best way I’ve come up with involves a _constants.py
file, and some changes to the beginning of my build.spec
file.
The nice thing about .spec
files is that they are executed like Python scripts, meaning you can embed Python statements into the
file and have them execute before the build starts. Here’s my spec file for LIFX-Control-Panel:
# -*- mode: python -*-
import datetime
bd = datetime.datetime.now().isoformat()
auth = "Sawyer McLane"
vers = "1.3.4"
is_debug = False
# Write version info into _constants.py resource file
with open('_constants.py', 'w') as f:
f.write("VERSION = \"{}\"\n".format(vers))
f.write("BUILD_DATE = \"{}\"\n".format(bd))
f.write("AUTHOR = \"{}\"\n".format(auth))
f.write("DEBUGGING = {}".format(str(is_debug)))
# Write version info into default config file
with open('default.ini', 'r') as f:
initdata = f.readlines()
initdata[-1] = "builddate = {}\n".format(bd)
initdata[-2] = "author = {}\n".format(auth)
initdata[-3] = "version = {}\n".format(vers)
with open('default.ini', 'w') as f:
f.writelines(initdata)
# Rest of the boilerplate spec file build commands...
EDIT: I’ve uploaded my full spec file as a gist. Here you can see how the whole file goes together.
So what’s going on here? Well, I have 4 variables that I want to keep updated. The first 3 are self explanatory. is_debug
enables certain debug statements in the code, and turns on the Python stdout console in the background. The first with
context block simply overwrites theconstants.py
file with the new values. The second block is a little more advanced. It reads in the existing default.ini
file,
and changes the corresponding lines, then writes the value back. This keeps existing values unchanged.
Afterwards, _constants.py
looks like this:
VERSION = "1.3.4"
BUILD_DATE = "2018-06-06T15:40:54.587551"
AUTHOR = "Sawyer McLane"
DEBUGGING = True
and default.ini
looks like this:
[AverageColor]
defaultmonitor = get_primary_monitor()
[PresetColors]
[Keybinds]
# Used for diagnostic purposes. Please do not change.
[Info]
version = 1.3.4
author = Sawyer McLane
builddate = 2018-06-06T15:40:54.587551
And that’s all there really is to it. Anywhere you want to use your constants, just import _constants
and the variables are in your namespace. So fire away!