Skip to content

Instantly share code, notes, and snippets.

@hithwen
Last active August 22, 2016 10:27
Show Gist options
  • Select an option

  • Save hithwen/5643731 to your computer and use it in GitHub Desktop.

Select an option

Save hithwen/5643731 to your computer and use it in GitHub Desktop.
Create compiled executable from multimodule python project: The process consists in various steps: 1 - compile your python code and generate a mirror project of .so 2 - Create a a main.py file that import all required third party libraries and your main module and call it 3 - Package it with pyinstaller
import distutils.core
from distutils.extension import Extension
from Cython.Distutils import build_ext # @UnresolvedImport
import os
import shutil
import sys
project_pkg_path = "DESTINATION PATH"
project_python_path = "PATH TO YOUR PYTHON PROJ"
ignored_files = ['__init__.py']
ignored_dirs = ['test', 'server']
def scandir(dir_=project_pkg_path, files=[]):
'''Creates a list of all files to be compiled (and deletes previous c files)'''
for file_ in os.listdir(dir_):
path = os.path.join(dir_, file_)
if os.path.isfile(path) and path.endswith(".py") \
and os.path.split(path)[1] not in ignored_files:
new_file = os.path.relpath(path, project_python_path)
new_file = new_file.replace(os.path.sep, ".")[:-3]
files.append(new_file)
c_file = new_file + '.c'
if os.path.exists(c_file):
os.unlink(c_file)
elif os.path.isdir(path) and os.path.split(path)[1] not in ignored_dirs:
scandir(path, files)
return files
# generate an Extension object from its dotted name
def makeExtension(extName):
extPath = extName.replace(".", os.path.sep)+".py"
return Extension(
extName,
[os.path.join(projecte_python_path, extPath)],
include_dirs = [project_python_path, '.'], # adding the '.' to include_dirs is CRUCIAL!!
#extra_compile_args = ["-O3", "-Wall"],
#extra_link_args = ['-g'],
#libraries = ["dv",],
)
def get_build_dir():
build_dir = os.path.join(project_python_path, 'build')
for dir_ in os.listdir(build_dir):
if dir_.startswith('lib'):
return os.path.join(build_dir, dir_)
if __name__ == "__main__":
shutil.rmtree(os.path.join(project_python_path, 'build'))
# get the list of extensions
extNames = scandir()
# and build up the set of Extension objects
extensions = [makeExtension(name) for name in extNames]
# finally, we can pass all this to distutils
distutils.core.setup(name = 'myproject',
version = "0.0.1",
cmdclass = {'build_ext': build_ext},
ext_modules = extensions,
packages=['myproject']
)
#execute with python cython-setup.py build_ext
from cython.setup import get_build_dir
import os
import subprocess
from cython.setup import get_build_dir
resources = '''dict_tree = Tree('SRC_PATH/resources', prefix = 'resources')
a.datas += dict_tree
a.binaries += Tree('COMPILED_PATH/myproj', prefix='myproj')
'''
class PyInstaller():
def get_dep_transform(self, dep):
transform = {'python-graph-core': 'pygraph',
'argparse': 'argparse',
'factory-boy': 'factory',
'pyyaml': 'yaml',
'python-dateutil': 'dateutil',
}
try:
return transform[dep]
except KeyError:
return dep
def generate_main(self, requirements_file, main_file):
'''gathers external dependencies from requirements.txt'''
with open(requirements_file) as f:
requirements = f.readlines()
deps = ''
for req in requirements:
if req == '#### Test requirements\n':
break
if not req.startswith('#'):
dep_name = self.get_dep_transform(req.split('==')[0])
deps += 'import %s \n' % self.get_dep_transform(dep_name)
with file(main_file, 'w') as main:
main.write(deps)
main.write('import sys\nimport sqlite3\n' \
+ 'from dateutil import parser\n' \
+ 'import myproj.main \n' \
+ 'bmyproj.main.main(sys.argv[1:])\n')
def create_spec(self, sources_path, compiled_path):
main_compiled_file = os.path.join(compiled_path, "main.py")
requirements_file = '%s/myproject/requirements.txt' % sources_path
self.generate_main(requirements_file, main_compiled_file)
command = 'python utils/makespec.py %s --onefile' % main_compiled_file
p = subprocess.Popen(command, cwd=self.py_installer_path, shell=True)
p.wait()
if p.returncode != 0:
raise Exception('Packaging failed')
spec_file = os.path.join(self.py_installer_path, 'main/main.spec')
resources = self.resources.replace('SRC_PATH', sources_path)
resources = resources.replace('COMPILED_PATH', compiled_path)
fileutils.search_and_replace(spec_file, 'exe =', resources + '\nexe =')
return spec_file
def package_app(self):
current_dir = os.path.dirname(__file__)
compiled_path = get_build_dir()
sources_path = os.path.realpath(os.path.join(current_dir, '../../..'))
spec_file = self.create_spec(sources_path, compiled_path)
print 'creating executable'
command = 'python pyinstaller.py -p %s %s --onefile %s'
command = command % (compiled_path, self.extra_options(), spec_file)
print 'executing %s\n' % command
p = subprocess.Popen(command, cwd=self.py_installer_path, shell=True)
p.wait()
if p.returncode != 0:
raise Exception('Packaging failed')
return self.exeFile
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment