Skip to content


Subversion checkout URL

You can clone with
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

178 lines (139 sloc) 6.651 kB
from __future__ import print_function
"A distutils extension to freeze Python 3.2+ scripts into Windows executables."
import sys, os, re
from distutils.core import Command, setup
from distutils.errors import DistutilsError
from distutils.extension import Extension
from os.path import abspath, dirname, exists, join
from distutils.util import get_platform
from build_exe import *
def main():
# if sys.version_info < (3, 2):
# raise DistutilsError('Kelvin requires Python 3.2 or later')
version = get_version()
files = [ abspath(join('src', f)) for f in os.listdir('src') if f.endswith('.cpp') ]
settings = { 'define_macros' : [('KELVIN_VERSION', version)],
'libraries' : ['Imagehlp', 'Shell32'] }
# We need to set a build_lib since most of the distutils commands change it depending on
# whether there are extensions or not. We don't have extensions, but we do have
# executables which should be treated the same.
build_lib = join('build', 'lib.{}-{}'.format(get_platform(), sys.version[0:3]))
exe_dir = join(build_lib, 'kelvin', 'data')
distclass = Distribution2,
name = 'kelvin',
version = version,
description = 'Freezes Python scripts into Windows apps',
long_description = __doc__,
url = '',
author = 'Michael Kleehammer',
author_email = '',
packages = ['kelvin'],
exe_modules = [
Executable('kelvinc', 'console', files, **settings),
Executable('kelvinw', 'windows', files, **settings)
options = { 'build' : { 'build_lib': build_lib },
'build_exe' : { 'build_lib': exe_dir },
'clean' : { 'build_lib': build_lib },
'install' : { 'build_lib': build_lib } },
classifiers = ['Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
cmdclass = { 'build_exe': BuildExeCommand }
# -- Normal Versions --
# For normal builds, Kelvin uses the form <major>.<minor>[b<beta>], such as:
# 1.2 version 1.2
# 1.3b6 1.3 beta 6, which is after 1.2 and before 1.3
# To automate versioning where possible, the version is determined from git tags of the form "v<major>.<minor>", such
# as "v1.2".
# -- Beta Versions --
# Any build from a commit that doesn't have a version tag is considered a beta build for the *next* version. To
# automate beta versioning, the beta number is simply the number of commits since the previous version.
# For example, if the previous tag is "v1.2" and there have been 6 commits since, then there have been 6 commits that
# will become version 1.3 when it is released (tagged). Therefore the version is 1.3b6.
# -- Uncommitted Code --
# If code is not committed, a version is needed but it must not be confused with officially committed versions.
# Therefore the word 'modified' is appended such as 1.3b6modified.
# -- Special Builds --
# Unfortunately Python versioning libraries (distutils, setuptools, packaging) don't support the concept of special
# builds. In particular, builds from git topic branches or feature branches need to be versioned, but they also need
# an identifier to let users know they are not official.
# For the moment, distutils' loose version format allows any text, so we'll append the branch name if it isn't master.
# For example, a special branch named "hotfix301" would generate a version like "1.3b6-hotfix301".
# -- Rebasing --
# One benefit of this technique is that every commit has a version. Past versions that were never built can be at any
# time without coordinating versions or new branches. It does require that rebasing not be performed on the master
# branch. Rebasing should be performed on topic branches before they are merged into master.
# IMPORTANT: Do not upload special builds, betas, or modified versions to public repositories! Both appear to sort
# *before* the official versions: LooseVersion('1.3') < LooseVersion('1.3b6').
def get_version():
Returns the version as a string.
1. If in an unzipped source directory (from sdist), returns the version from the PKG-INFO file.
2. If in a git repository, uses the latest tag from git describe.
3. This should probably be an error, but it will return 0.1b1.
return _get_version_pkginfo() or _get_version_git() or '0.1b1'
def _get_version_pkginfo():
filename = join(dirname(abspath(__file__)), 'PKG-INFO')
if exists(filename):
re_ver = re.compile(r'^Version: \s+ (\d.*)', re.VERBOSE)
for line in open(filename):
match =
if match:
return None
def _get_version_git():
n, result = getoutput('git describe --tags --match v*')
if n:
print('WARNING: git describe failed with: %s %s' % (n, result))
return None
match = re.match(r'v (\d+).(\d+) (?: -(\d+)-g[0-9a-z]+)?', result, re.VERBOSE)
if not match:
return None
major = int(
minor = int(
beta = int( or 0)
n, result = getoutput('git branch')
branch ='\* (\w+)', result).group(1)
modified = bool(getoutput('git --no-pager diff --quiet')[0]) # working directory
if not modified:
modified = bool(getoutput('git --no-pager diff --quiet --cached')[0]) # index
if beta:
# This is a beta of the next minor release, so increment the minor number to reflect this.
minor += 1
version = '{}.{}'.format(major, minor)
if beta:
version += 'b{}'.format(beta)
if modified:
version += 'modified'
if branch != 'master':
version += '-' + branch
return version
def getoutput(cmd):
pipe = os.popen(cmd, 'r')
text ='\n')
status = pipe.close() or 0
return status, text
if __name__ == '__main__':
Jump to Line
Something went wrong with that request. Please try again.