from __future__ import print_function import logging import os import sys import re from os.path import abspath, basename, join, isdir, isfile, islink from egginst.exe_data import cli, gui from egginst.utils import on_win, rm_rf hashbang_pat = re.compile(r'#!.+$', re.M) executable = sys.executable logger = logging.getLogger(__name__) def get_executable(pythonw=False, with_quotes=False): res = executable if on_win: # sys.executable may actually be pythonw.exe in order to avoid # popping up a cmd shell during install. p = re.compile(r'pythonw?\.exe', re.I) res = p.sub('pythonw.exe' if pythonw else 'python.exe', res) if with_quotes: res = '"%s"' % res return res def write_exe(dst, script_type='console_scripts'): """ This function is only used on Windows. It either writes cli.exe or gui.exe to the destination specified, depending on script_type. The binary content of these files are read from the module exe_data, which may be generated by the following small script (run from the setuptools directory which contains the file cli.exe and gui.exe: fo = open('exe_data.py', 'w') for name in ['cli', 'gui']: data = open('%s.exe' % name, 'rb').read() fo.write('%s = %r\n' % (name, data)) fo.close() """ if script_type == 'console_scripts': data = cli elif script_type == 'gui_scripts': data = gui else: raise Exception("Did not except script_type=%r" % script_type) rm_rf(dst) try: with open(dst, 'wb') as fp: fp.write(data) except IOError: # When bootstrapping, the file egginst.exe is in use and can therefore # not be rewritten, which is OK since its content is always the same. pass os.chmod(dst, 0o755) def create_proxy(src, bin_dir): """ create a proxy of src in bin_dir (Windows only) """ logger.info("Creating proxy executable to: %r", src) assert src.endswith('.exe') dst_name = basename(src) if dst_name.startswith('epd-'): dst_name = dst_name[4:] dst = join(bin_dir, dst_name) write_exe(dst) dst_script = dst[:-4] + '-script.py' rm_rf(dst_script) with open(dst_script, 'w') as fo: fo.write('''\ #!"%(python)s" # This proxy was created by egginst from an egg with special instructions # import sys import subprocess src = %(src)r sys.exit(subprocess.call([src] + sys.argv[1:])) ''' % dict(python=get_executable(), src=src)) return dst, dst_script def create_proxies(egg): # This function is called on Windows only if not isdir(egg.bin_dir): os.makedirs(egg.bin_dir) for line in egg.iter_files_to_install(): arcname, action = line.split() logger.info("arcname=%r action=%r", arcname, action) if action == 'PROXY': ei = 'EGG-INFO/' if arcname.startswith(ei): src = abspath(join(egg.meta_dir, arcname[len(ei):])) else: src = abspath(join(egg.prefix, arcname)) logger.info(" src: %r", src) egg.files.extend(create_proxy(src, egg.bin_dir)) else: data = egg.z.read(arcname) dst = abspath(join(egg.prefix, action, basename(arcname))) logger.info(" dst: %r", dst) rm_rf(dst) with open(dst, 'wb') as fo: fo.write(data) egg.files.append(dst) def write_script(path, entry_pt, egg_name): """ Write an entry point script to path. """ logger.info('Creating script: %s', path) assert entry_pt.count(':') == 1 module, func = entry_pt.strip().split(':') rm_rf(path) with open(path, 'w') as fo: fo.write('''\ #!%(python)s # This script was created by egginst when installing: # # %(egg_name)s # if __name__ == '__main__': import sys from %(module)s import %(func)s sys.exit(%(func)s()) ''' % dict(python=get_executable(pythonw=path.endswith('.pyw'), with_quotes=on_win), egg_name=egg_name, module=module, func=func)) os.chmod(path, 0o755) def create(egg, conf): if not isdir(egg.bin_dir): os.makedirs(egg.bin_dir) for script_type in ['gui_scripts', 'console_scripts']: if script_type not in conf.sections(): continue for name, entry_pt in conf.items(script_type): fname = name if on_win: exe_path = join(egg.bin_dir, '%s.exe' % name) write_exe(exe_path, script_type) egg.files.append(exe_path) fname += '-script.py' if script_type == 'gui_scripts': fname += 'w' path = join(egg.bin_dir, fname) write_script(path, entry_pt, egg.fn) egg.files.append(path) def fix_script(path): """ Fixes a single located at path. """ if islink(path) or not isfile(path): return with open(path, "rb") as fi: data = fi.read() try: data = data.decode("utf8") except UnicodeDecodeError: return if ' egginst ' in data: # This string is in the comment when write_script() creates # the script, so there is no need to fix anything. return m = hashbang_pat.match(data) if not (m and 'python' in m.group().lower()): return python = get_executable(with_quotes=on_win) new_data = hashbang_pat.sub('#!' + python.replace('\\', '\\\\'), data, count=1) if new_data == data: return logger.info("Updating: %r", path) with open(path, 'wb') as fo: fo.write(new_data.encode("utf8")) os.chmod(path, 0o755) def fix_scripts(egg): for path in egg.files: if path.startswith(egg.bin_dir): fix_script(path)