def freeze(dist): """Freeze the given distribution data using py2exe.""" includes = dist.includes excludes = dist.excludes options = dist.freezer_options # Merge in any encludes/excludes given in freezer_options includes.append("esky") for inc in options.pop("includes", ()): includes.append(inc) for exc in options.pop("excludes", ()): excludes.append(exc) if "pypy" not in includes and "pypy" not in excludes: excludes.append("pypy") # py2exe expects some arguments on the main distribution object. # We handle data_files ourselves, so fake it out for py2exe. if getattr(dist.distribution, "console", None): msg = "don't call setup(console=[...]) with esky;" msg += " use setup(scripts=[...]) instead" raise RuntimeError(msg) if getattr(dist.distribution, "windows", None): msg = "don't call setup(windows=[...]) with esky;" msg += " use setup(scripts=[...]) instead" raise RuntimeError(msg) dist.distribution.console = [] dist.distribution.windows = [] my_data_files = dist.distribution.data_files dist.distribution.data_files = [] for exe in dist.get_executables(): # Pass any executable kwds through to py2exe. # We handle "icon" and "gui_only" ourselves. s = exe._kwds.copy() s["script"] = exe.script s["dest_base"] = exe.name[:-4] if exe.icon is not None and "icon_resources" not in s: s["icon_resources"] = [(1, exe.icon)] if exe.gui_only: dist.distribution.windows.append(s) else: dist.distribution.console.append(s) if "zipfile" in options: dist.distribution.zipfile = options.pop("zipfile") # Create the py2exe cmd and adjust its options cmd = custom_py2exe(dist.distribution) cmd.includes = includes cmd.excludes = excludes if "bundle_files" in options: if options["bundle_files"] < 3 and dist.compile_bootstrap_exes: err = "can't compile bootstrap exes when bundle_files < 3" raise RuntimeError(err) for (nm, val) in options.iteritems(): setattr(cmd, nm, val) cmd.dist_dir = dist.freeze_dir cmd.finalize_options() # Actually run the freeze process cmd.run() # Copy data files into the freeze dir dist.distribution.data_files = my_data_files for (src, dst) in dist.get_data_files(): dst = os.path.join(dist.freeze_dir, dst) dstdir = os.path.dirname(dst) if not os.path.isdir(dstdir): dist.mkpath(dstdir) dist.copy_file(src, dst) # Place a marker file so we know how it was frozen os.mkdir(os.path.join(dist.freeze_dir, ESKY_CONTROL_DIR)) marker_file = os.path.join(ESKY_CONTROL_DIR, "f-py2exe-%d%d.txt") % sys.version_info[:2] open(os.path.join(dist.freeze_dir, marker_file), "w").close() # Copy package data into the library.zip # For now, we don't try to put package data into a bundled zipfile. dist_zipfile = dist.distribution.zipfile if dist_zipfile is None: for (src, arcnm) in dist.get_package_data(): err = "zipfile=None can't be used with package_data (yet...)" raise RuntimeError(err) elif not cmd.skip_archive: lib = zipfile.ZipFile(os.path.join(dist.freeze_dir, dist_zipfile), "a") for (src, arcnm) in dist.get_package_data(): lib.write(src, arcnm) lib.close() else: for (src, arcnm) in dist.get_package_data(): lib = os.path.join(dist.freeze_dir, os.path.dirname(dist_zipfile)) dest = os.path.join(lib, os.path.dirname(src)) f = os.path.basename(src) if not os.path.isdir(dest): dist.mkpath(dest) dist.copy_file(src, os.path.join(dest, f)) # There's no need to copy library.zip into the bootstrap env, as the # chainloader will run before py2exe goes looking for it. pass # Create the bootstraping code, using custom code if specified. # It gets stored as a marshalled list of code objects directly in the exe. esky_name = dist.distribution.get_name() code_source = ["__esky_name__ = %r" % (esky_name, )] code_source.append(inspect.getsource(esky.bootstrap)) if dist.compile_bootstrap_exes: from esky.bdist_esky import pypy_libpython from esky.bdist_esky import pypy_winres code_source.append(inspect.getsource(pypy_libpython)) code_source.append(inspect.getsource(pypy_winres)) code_source.append(_CUSTOM_PYPY_CHAINLOADER) code_source.append(dist.get_bootstrap_code()) code_source = "\n".join(code_source) for exe in dist.get_executables(normalise=False): if not exe.include_in_bootstrap_env: continue fexe = os.path.join(dist.freeze_dir, exe.name) bsexe = dist.compile_to_bootstrap_exe(exe, code_source) winres.copy_safe_resources(fexe, bsexe) # We may also need the bundled MSVCRT libs for nm in os.listdir(dist.freeze_dir): if is_core_dependency(nm) and nm.startswith("Microsoft"): dist.copy_to_bootstrap_env(nm) else: code_source.append(_FAKE_ESKY_BOOTSTRAP_MODULE) code_source.append(_CUSTOM_WIN32_CHAINLOADER) code_source.append(dist.get_bootstrap_code()) code_source.append("bootstrap()") code_source = "\n".join(code_source) code = marshal.dumps([compile(code_source, "__main__.py", "exec")]) # Copy any core dependencies into the bootstrap env. for nm in os.listdir(dist.freeze_dir): if is_core_dependency(nm): dist.copy_to_bootstrap_env(nm) # Copy the loader program for each script into the bootstrap env. for exe in dist.get_executables(normalise=False): if not exe.include_in_bootstrap_env: continue exepath = dist.copy_to_bootstrap_env(exe.name) # Read the py2exe metadata from the frozen exe. We will # need to duplicate some of these fields when to rewrite it. coderes = winres.load_resource(exepath, u"PYTHONSCRIPT", 1, 0) headsz = struct.calcsize("iiii") (magic, optmz, unbfrd, codesz) = struct.unpack("iiii", coderes[:headsz]) assert magic == 0x78563412 # Insert the bootstrap code into the exe as a resource. # This appears to have the happy side-effect of stripping any # extra data from the end of the exe, which is exactly what we # want when zipfile=None is specified; otherwise each bootstrap # exe would also contain the whole bundled zipfile. coderes = struct.pack( "iiii", magic, # magic value used for integrity checking, optmz, # optimization level to enable unbfrd, # whether to use unbuffered output len(code), ) + b"\x00" + code + b"\x00\x00" winres.add_resource(exepath, coderes, u"PYTHONSCRIPT", 1, 0) # If the python dll hasn't been copied into the bootstrap env, # make sure it's stored in each bootstrap dll as a resource. pydll = u"python%d%d.dll" % sys.version_info[:2] if not os.path.exists(os.path.join(dist.bootstrap_dir, pydll)): buf = ctypes.create_string_buffer(3000) GetModuleFileNameA = ctypes.windll.kernel32.GetModuleFileNameA if not GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf), 3000): raise ctypes.WinError() with open(buf.value, "rb") as f: pydll_bytes = f.read() for exe in dist.get_executables(normalise=False): if not exe.include_in_bootstrap_env: continue exepath = os.path.join(dist.bootstrap_dir, exe.name) try: winres.load_resource(exepath, pydll.upper(), 1, 0) except EnvironmentError: winres.add_resource(exepath, pydll_bytes, pydll.upper(), 1, 0)
def freeze(dist): """Freeze the given distribution data using py2exe.""" includes = dist.includes excludes = dist.excludes options = dist.freezer_options # Merge in any encludes/excludes given in freezer_options includes.append("esky") for inc in options.pop("includes",()): includes.append(inc) for exc in options.pop("excludes",()): excludes.append(exc) if "pypy" not in includes and "pypy" not in excludes: excludes.append("pypy") # py2exe expects some arguments on the main distribution object. # We handle data_files ourselves, so fake it out for py2exe. if getattr(dist.distribution,"console",None): msg = "don't call setup(console=[...]) with esky;" msg += " use setup(scripts=[...]) instead" raise RuntimeError(msg) if getattr(dist.distribution,"windows",None): msg = "don't call setup(windows=[...]) with esky;" msg += " use setup(scripts=[...]) instead" raise RuntimeError(msg) dist.distribution.console = [] dist.distribution.windows = [] my_data_files = dist.distribution.data_files dist.distribution.data_files = [] for exe in dist.get_executables(): # Pass any executable kwds through to py2exe. # We handle "icon" and "gui_only" ourselves. s = exe._kwds.copy() s["script"] = exe.script s["dest_base"] = exe.name[:-4] if exe.icon is not None and "icon_resources" not in s: s["icon_resources"] = [(1,exe.icon)] if exe.gui_only: dist.distribution.windows.append(s) else: dist.distribution.console.append(s) if "zipfile" in options: dist.distribution.zipfile = options.pop("zipfile") # Create the py2exe cmd and adjust its options cmd = custom_py2exe(dist.distribution) cmd.includes = includes cmd.excludes = excludes if "bundle_files" in options: if options["bundle_files"] < 3 and dist.compile_bootstrap_exes: err = "can't compile bootstrap exes when bundle_files < 3" raise RuntimeError(err) for (nm,val) in options.iteritems(): setattr(cmd,nm,val) cmd.dist_dir = dist.freeze_dir cmd.finalize_options() # Actually run the freeze process cmd.run() # Copy data files into the freeze dir dist.distribution.data_files = my_data_files for (src,dst) in dist.get_data_files(): dst = os.path.join(dist.freeze_dir,dst) dstdir = os.path.dirname(dst) if not os.path.isdir(dstdir): dist.mkpath(dstdir) dist.copy_file(src,dst) # Place a marker file so we know how it was frozen os.mkdir(os.path.join(dist.freeze_dir,ESKY_CONTROL_DIR)) marker_file = os.path.join(ESKY_CONTROL_DIR,"f-py2exe-%d%d.txt")%sys.version_info[:2] open(os.path.join(dist.freeze_dir,marker_file),"w").close() # Copy package data into the library.zip # For now, we don't try to put package data into a bundled zipfile. dist_zipfile = dist.distribution.zipfile if dist_zipfile is None: for (src,arcnm) in dist.get_package_data(): err = "zipfile=None can't be used with package_data (yet...)" raise RuntimeError(err) elif not cmd.skip_archive: lib = zipfile.ZipFile(os.path.join(dist.freeze_dir,dist_zipfile),"a") for (src,arcnm) in dist.get_package_data(): lib.write(src,arcnm) lib.close() else: for (src,arcnm) in dist.get_package_data(): lib = os.path.join(dist.freeze_dir,os.path.dirname(dist_zipfile)) dest = os.path.join(lib, os.path.dirname(src)) f = os.path.basename(src) if not os.path.isdir(dest): dist.mkpath(dest) dist.copy_file(src,os.path.join(dest, f)) # There's no need to copy library.zip into the bootstrap env, as the # chainloader will run before py2exe goes looking for it. pass # Create the bootstraping code, using custom code if specified. # It gets stored as a marshalled list of code objects directly in the exe. esky_name = dist.distribution.get_name() code_source = ["__esky_name__ = %r" % (esky_name,)] code_source.append(inspect.getsource(esky.bootstrap)) if dist.compile_bootstrap_exes: from esky.bdist_esky import pypy_libpython from esky.bdist_esky import pypy_winres code_source.append(inspect.getsource(pypy_libpython)) code_source.append(inspect.getsource(pypy_winres)) code_source.append(_CUSTOM_PYPY_CHAINLOADER) code_source.append(dist.get_bootstrap_code()) code_source = "\n".join(code_source) for exe in dist.get_executables(normalise=False): if not exe.include_in_bootstrap_env: continue fexe = os.path.join(dist.freeze_dir,exe.name) bsexe = dist.compile_to_bootstrap_exe(exe,code_source) winres.copy_safe_resources(fexe,bsexe) # We may also need the bundled MSVCRT libs for nm in os.listdir(dist.freeze_dir): if is_core_dependency(nm) and nm.startswith("Microsoft"): dist.copy_to_bootstrap_env(nm) else: code_source.append(_FAKE_ESKY_BOOTSTRAP_MODULE) code_source.append(_CUSTOM_WIN32_CHAINLOADER) code_source.append(dist.get_bootstrap_code()) code_source.append("bootstrap()") code_source = "\n".join(code_source) code = marshal.dumps([compile(code_source,"__main__.py","exec")]) # Copy any core dependencies into the bootstrap env. for nm in os.listdir(dist.freeze_dir): if is_core_dependency(nm): dist.copy_to_bootstrap_env(nm) # Copy the loader program for each script into the bootstrap env. for exe in dist.get_executables(normalise=False): if not exe.include_in_bootstrap_env: continue exepath = dist.copy_to_bootstrap_env(exe.name) # Read the py2exe metadata from the frozen exe. We will # need to duplicate some of these fields when to rewrite it. coderes = winres.load_resource(exepath,u"PYTHONSCRIPT",1,0) headsz = struct.calcsize("iiii") (magic,optmz,unbfrd,codesz) = struct.unpack("iiii",coderes[:headsz]) assert magic == 0x78563412 # Insert the bootstrap code into the exe as a resource. # This appears to have the happy side-effect of stripping any # extra data from the end of the exe, which is exactly what we # want when zipfile=None is specified; otherwise each bootstrap # exe would also contain the whole bundled zipfile. coderes = struct.pack("iiii", magic, # magic value used for integrity checking, optmz, # optimization level to enable unbfrd, # whether to use unbuffered output len(code), ) + "\x00" + code + "\x00\x00" winres.add_resource(exepath,coderes,u"PYTHONSCRIPT",1,0) # If the python dll hasn't been copied into the bootstrap env, # make sure it's stored in each bootstrap dll as a resource. pydll = u"python%d%d.dll" % sys.version_info[:2] if not os.path.exists(os.path.join(dist.bootstrap_dir,pydll)): buf = ctypes.create_string_buffer(3000) GetModuleFileNameA = ctypes.windll.kernel32.GetModuleFileNameA if not GetModuleFileNameA(sys.dllhandle,ctypes.byref(buf),3000): raise ctypes.WinError() with open(buf.value,"rb") as f: pydll_bytes = f.read() for exe in dist.get_executables(normalise=False): if not exe.include_in_bootstrap_env: continue exepath = os.path.join(dist.bootstrap_dir,exe.name) try: winres.load_resource(exepath,pydll.upper(),1,0) except EnvironmentError: winres.add_resource(exepath,pydll_bytes,pydll.upper(),1,0)
def freeze(dist): """Freeze the given distribution data using py2exe.""" includes = dist.includes excludes = dist.excludes options = dist.freezer_options # Merge in any encludes/excludes given in freezer_options includes.append("esky") for inc in options.pop("includes",()): includes.append(inc) for exc in options.pop("excludes",()): excludes.append(exc) # py2exe expects some arguments on the main distribution object. # We handle data_files ourselves, so fake it out for py2exe. dist.distribution.console = [] dist.distribution.windows = [] my_data_files = dist.distribution.data_files dist.distribution.data_files = [] for exe in dist.get_executables(): # Pass any executable kwds through to py2exe. # We handle "icon" and "gui_only" ourselves. s = exe._kwds.copy() s["script"] = exe.script s["dest_base"] = exe.name[:-4] if exe.icon is not None and "icon_resources" not in s: s["icon_resources"] = [(1,exe.icon)] if exe.gui_only: dist.distribution.windows.append(s) else: dist.distribution.console.append(s) if "zipfile" in options: dist.distribution.zipfile = options.pop("zipfile") # Create the py2exe cmd and adjust its options cmd = custom_py2exe(dist.distribution) cmd.includes = includes cmd.excludes = excludes for (nm,val) in options.iteritems(): setattr(cmd,nm,val) cmd.dist_dir = dist.freeze_dir cmd.finalize_options() # Actually run the freeze process cmd.run() # Copy data files into the freeze dir dist.distribution.data_files = my_data_files for (src,dst) in dist.get_data_files(): dst = os.path.join(dist.freeze_dir,dst) dstdir = os.path.dirname(dst) if not os.path.isdir(dstdir): dist.mkpath(dstdir) dist.copy_file(src,dst) # Place a marker fileso we know how it was frozen os.mkdir(os.path.join(dist.freeze_dir,ESKY_CONTROL_DIR)) marker_file = os.path.join(ESKY_CONTROL_DIR,"f-py2exe-%d%d.txt")%sys.version_info[:2] open(os.path.join(dist.freeze_dir,marker_file),"w").close() # Copy package data into the library.zip # For now, we don't try to put package data into a bundled zipfile. if dist.distribution.zipfile is not None: lib = zipfile.ZipFile(os.path.join(dist.freeze_dir,"library.zip"),"a") for (src,arcnm) in dist.get_package_data(): lib.write(src,arcnm) lib.close() else: for (src,arcnm) in dist.get_package_data(): err = "zipfile=None can't be used with package_data (yet...)" raise RuntimeError(err) # There's no need to copy library.zip into the bootstrap env, as the # chainloader will run before py2exe goes looking for it. pass # Create the bootstraping code, using custom code if specified. # It gets stored as a marshalled list of code objects directly in the exe. code_source = [inspect.getsource(esky.bootstrap)] code_source.append(_FAKE_ESKY_BOOTSTRAP_MODULE) code_source.append(_CUSTOM_WIN32_CHAINLOADER) code_source.append("__esky_name__ = '%s'" % (dist.distribution.get_name(),)) code_source.append(dist.get_bootstrap_code()) code_source.append("bootstrap()") code_source = "\n".join(code_source) code = marshal.dumps([compile(code_source,"__main__.py","exec")]) coderes = struct.pack("iiii", 0x78563412, # a magic value used for integrity checking, 0, # no optimization False, # normal buffered output len(code), ) + "\x00" + code + "\x00\x00" # We try to bundle the python DLL into all bootstrap executables, even # if it's not bundled in the frozen distribution. This helps keep the # bootstrap env small and minimises the chances of something going wrong. pydll = u"python%d%d.dll" % sys.version_info[:2] frozen_pydll = os.path.join(dist.freeze_dir,pydll) if os.path.exists(frozen_pydll): for nm in os.listdir(dist.freeze_dir): if nm == pydll: continue if nm.lower().endswith(".pyd") or nm.lower().endswith(".dll"): # There's an unbundled C-extension, so we can't bundle # the DLL or our bootstrapper won't work. pydll_bytes = None break else: with open(frozen_pydll,"rb") as f: pydll_bytes = f.read() else: # They've bundlded the dll into the zipfile. Rather than parse # it back out, I'm just going to grab it from the filesystem. sz = 0 res = 0 while res == sz: sz += 512 buf = ctypes.create_string_buffer(sz) res = ctypes.windll.kernel32.GetModuleFileNameA(sys.dllhandle,ctypes.byref(buf),sz) if not res: raise ctypes.WinError() with open(buf.value,"rb") as f: pydll_bytes = f.read() # Copy any core dependencies into the bootstrap env. for nm in os.listdir(dist.freeze_dir): if is_core_dependency(nm): if nm == pydll and pydll_bytes is not None: continue dist.copy_to_bootstrap_env(nm) # Copy the loader program for each script into the bootstrap env. for exe in dist.get_executables(normalise=False): if not exe.include_in_bootstrap_env: continue exepath = dist.copy_to_bootstrap_env(exe.name) # Insert the bootstrap code into the exe as a resource. # This appears to have the happy side-effect of stripping any extra # data from the end of the exe, which is exactly what we want when # zipfile=None is specified; otherwise each bootstrap EXE would also # contain the whole bundled zipfile. winres.add_resource(exepath,coderes,u"PYTHONSCRIPT",1,0) # Inline the pythonXY.dll as a resource in the exe. if pydll_bytes is not None: winres.add_resource(exepath,pydll_bytes,pydll.upper(),1,0)