#!/usr/bin/python # ---------------------------------------------------------------------------- # build_console: Build axys-console into executable binary file with PyInstaller # # Author: Bin Zhang # # License: MIT # ---------------------------------------------------------------------------- ''' Build axys-console into executable binary file with PyInstaller ''' import os import json import subprocess import excopy import ConfigParser import sys import shutil from argparse import ArgumentParser def run_shell(cmd, cwd=None): p = subprocess.Popen(cmd, shell=True, cwd=cwd) p.wait() if p.returncode: raise subprocess.CalledProcessError(returncode=p.returncode, cmd=cmd) return p.returncode def os_is_win32(): return sys.platform == 'win32' def os_is_linux(): return 'linux' in sys.platform class Builder(object): CONFIG_FILE = "config.json" KEY_COPY_CONFIG = "copy_config" KEY_MODIFY_CONFIG = "modify_config" KEY_HIDDEN_IMPORT = "hidden_import" ENTRANCE_FILE = "bin/axys.py" CMD_FORMAT = 'pyinstaller -F %s %s --distpath "%s" --specpath "%s" --workpath "%s" --clean -y "%s"' def __init__(self, args): self.my_path = os.path.realpath(os.path.dirname(__file__)) # get the source path if args.src_path is None: src_path = os.path.abspath(os.path.join(self.my_path, os.path.pardir)) else: src_path = os.path.expanduser(args.src_path) if os.path.isabs(src_path): self.src_path = src_path else: self.src_path = os.path.abspath(src_path) if not os.path.isdir(self.src_path): raise Exception("%s is not a available path." % self.src_path) self.entrance_file = os.path.join(self.src_path, Builder.ENTRANCE_FILE) # get the dst path if args.dst_path is None: self.dst_path = os.path.abspath("output") else: dst_path = os.path.expanduser(args.dst_path) if os.path.isabs(dst_path): self.dst_path = dst_path else: self.dst_path = os.path.abspath(dst_path) if os_is_linux(): self.dst_path = os.path.join(self.dst_path, "linux") else: self.dst_path = os.path.join(self.dst_path, sys.platform) # parse config file cfg_file = os.path.join(self.my_path, Builder.CONFIG_FILE) f = open(cfg_file) self.cfg_info = json.load(f) f.close() def _get_dirs(self, path, dir_list=None): if dir_list is None: dir_list = [] if not os.path.isdir(path): return dir_list for name in os.listdir(path): full_path = os.path.join(path, name) if os.path.isdir(full_path): dir_list.append(full_path) self._get_dirs(full_path, dir_list) return dir_list def modify_files(self, modify_info): import re modify_file = modify_info["file_path"] if not os.path.isabs(modify_file): modify_file = os.path.abspath(os.path.join(self.dst_path, modify_file)) if not os.path.isfile(modify_file): return pattern = modify_info["pattern"] replace_str = modify_info["replace_string"] f = open(modify_file) lines = f.readlines() f.close() new_lines = [] for line in lines: new_line = re.sub(pattern, replace_str, line) new_lines.append(new_line) f = open(modify_file, "w") f.writelines(new_lines) f.close() def do_build(self): print("Source Path : %s" % self.src_path) print("Output Path : %s" % self.dst_path) print("Start building") if os.path.exists(self.dst_path): shutil.rmtree(self.dst_path) # copy files copy_config = self.cfg_info[Builder.KEY_COPY_CONFIG] copy_cfgs = copy_config["common"] if sys.platform in copy_config: copy_cfgs += copy_config[sys.platform] elif os_is_linux(): copy_cfgs += copy_config["linux"] for element in copy_cfgs: excopy.copy_files_with_config(element, self.src_path, self.dst_path) # modify files modify_config = self.cfg_info[Builder.KEY_MODIFY_CONFIG] for element in modify_config: self.modify_files(element) # get the path parameter plugins_path = os.path.join(self.src_path, "plugins") bin_path = os.path.join(self.src_path, "bin") dir_list = self._get_dirs(plugins_path) dir_list.append(plugins_path) dir_list.append(bin_path) dir_list.append(self.src_path) if os_is_win32(): sep = ";" else: sep = ":" path_param = "-p %s" % sep.join(dir_list) # get the runtime-hook parameter _cp = ConfigParser.ConfigParser(allow_no_value=True) _cp.optionxform = str _cp.read(os.path.join(self.src_path, "bin/axys.ini")) runtime_hook_param = "" hidden_import_param = "" # add hidden import params for config.json if self.cfg_info.has_key(Builder.KEY_HIDDEN_IMPORT): hidden_import_cfg = self.cfg_info[Builder.KEY_HIDDEN_IMPORT] else: hidden_import_cfg = {} if len(hidden_import_cfg) > 0: for key in hidden_import_cfg: hidden_import_param += "--hidden-import %s " % key runtime_hook_param += '--runtime-hook "%s" ' % os.path.join(self.src_path, hidden_import_cfg[key]) for s in _cp.sections(): if s == 'plugins': for classname in _cp.options(s): parts = classname.split(".") module_name = parts[0] hidden_import_param += "--hidden-import %s " % module_name module_path = os.path.join(plugins_path, module_name) if os.path.isdir(module_path): runtime_hook_param += '--runtime-hook "%s" ' % ("%s/__init__.py" % module_path) else: module_file = "%s.py" % module_path if os.path.isfile(module_file): runtime_hook_param += '--runtime-hook "%s" ' % module_file # additional hooks path add_hook_dir_param = '--additional-hooks-dir "%s" ' % plugins_path add_hook_dir_param += '--additional-hooks-dir "%s" ' % bin_path add_hook_dir_param += '--additional-hooks-dir "%s"' % self.src_path # build *.py if os_is_linux(): spec_path = os.path.join(self.my_path, "build", "linux") else: spec_path = os.path.join(self.my_path, "build", sys.platform) work_path = spec_path if os.path.exists(spec_path): shutil.rmtree(spec_path) build_cmd = Builder.CMD_FORMAT % (path_param, '%s %s %s' % (hidden_import_param, add_hook_dir_param, runtime_hook_param), self.dst_path, spec_path, work_path, self.entrance_file) run_shell(build_cmd) print("Building succeed.") if __name__ == "__main__": parser = ArgumentParser(description="Generate executable file for axys-console by PyInstaller.") parser.add_argument('-s', '--src-path', dest='src_path', help='Specify the path of axys-console.') parser.add_argument('-d', '--dst-path', dest='dst_path', help='Specify the path of output.') (args, unknown) = parser.parse_known_args() if len(unknown) > 0: print("unknown arguments: %s" % unknown) builder = Builder(args) builder.do_build()