Last active
August 22, 2016 10:27
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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