From 34236e347c14e998be08effeaefa655929e29331 Mon Sep 17 00:00:00 2001 From: giovannibajo Date: Fri, 12 Mar 2010 19:26:33 +0000 Subject: [PATCH] Complete runtime support for .egg-files. Now Build.py automatically adds a runtime hook whenever a .egg file is packaged. This hook adds the .egg to sys.path at startup, so that it is picked up by the import hooks. In one-dir mode, .eggs files are packaged into a "eggs" subdirectory. In one-file mode, .eggs files are within the executable, and extracted at startup in the temporary directory. I removed the old runtime hook by Hartmut which decompressed .eggs to disk because it is not required anymore since we directly use zipimport to access .eggs. git-svn-id: http://svn.pyinstaller.org/trunk@772 8dd32b29-ccff-0310-8a9a-9233e24343b1 --- Build.py | 8 +-- source/common/launch.c | 6 +-- support/_pyi_egg_extract.py | 100 ------------------------------------ support/_pyi_egg_install.py | 43 ++++++++++++++++ 4 files changed, 51 insertions(+), 106 deletions(-) delete mode 100644 support/_pyi_egg_extract.py create mode 100644 support/_pyi_egg_install.py diff --git a/Build.py b/Build.py index 09128e4..38d429d 100755 --- a/Build.py +++ b/Build.py @@ -279,9 +279,9 @@ class Analysis(Target): def assemble(self): print "running Analysis", os.path.basename(self.out) # Reset seen variable to correctly discover dependencies - # if there are multiple Analysis in a single specfile. + # if there are multiple Analysis in a single specfile. bindepend.seen = {} - + paths = self.pathex for i in range(len(paths)): # FIXME: isn't self.pathex already norm-abs-pathed? @@ -334,7 +334,7 @@ class Analysis(Target): if isinstance(mod, mf.ExtensionModule): binaries.append((mod.__name__, fnm, 'EXTENSION')) elif isinstance(mod, (mf.PkgInZipModule, mf.PyInZipModule)): - zipfiles.append((os.path.basename(str(mod.owner)), + zipfiles.append(("eggs/" + os.path.basename(str(mod.owner)), str(mod.owner), 'ZIPFILE')) else: # mf.PyModule instances expose a list of binary @@ -346,6 +346,8 @@ class Analysis(Target): binaries.extend(bindepend.Dependencies(binaries, platform=target_platform)) self.fixMissingPythonLib(binaries) + if zipfiles: + scripts[-1:-1] = [("_pyi_egg_install.py", os.path.join(HOMEPATH, "support/_pyi_egg_install.py"), 'PYSOURCE')] # Add realtime hooks just before the last script (which is # the entrypoint of the application). scripts[-1:-1] = rthooks diff --git a/source/common/launch.c b/source/common/launch.c index cd0e330..29f21f0 100644 --- a/source/common/launch.c +++ b/source/common/launch.c @@ -333,7 +333,7 @@ int openArchive() filelen = findDigitalSignature(); if (filelen < 1) return -1; - /* The digital signature has been aligned to 8-bytes boundary. + /* The digital signature has been aligned to 8-bytes boundary. We need to look for our cookie taking into account some padding. */ for (i = 0; i < 8; ++i) @@ -871,7 +871,7 @@ unsigned char *extract(TOC *ptoc) block_size = PI_PyInt_AsLong(PI_PyDict_GetItemString(aes_dict, "block_size")); iv = malloc(block_size); memset(iv, 0, block_size); - + aes_obj = PI_PyObject_CallFunction(func_new, "s#Os#", data, 32, PI_PyDict_GetItemString(aes_dict, "MODE_CFB"), @@ -996,7 +996,7 @@ int extractBinaries(char **workpath) workpath[0] = '\0'; VS("Extracting binaries\n"); while (ptoc < f_tocend) { - if (ptoc->typcd == 'b' || ptoc->typcd == 'x') + if (ptoc->typcd == 'b' || ptoc->typcd == 'x' || ptoc->typcd == 'Z') if (extract2fs(ptoc)) return -1; ptoc = incrementTocPtr(ptoc); diff --git a/support/_pyi_egg_extract.py b/support/_pyi_egg_extract.py deleted file mode 100644 index f3e5c78..0000000 --- a/support/_pyi_egg_extract.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python -# -# Make .eggs and zipfiles available at runtime -# -# Copyright (C) 2008 Hartmut Goebel -# Licence: GNU General Public License version 3 (GPL v3) -# -# This file is part of PyInstaller -# -# pyinstaller is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# pyinstaller is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -import os -import sys -from tempfile import mkstemp - -import carchive - -MEIDIR = os.environ['_MEIPASS2'] - -def extract_resource(zip_path): - if zip_path in index: - # 'directory' entry: recursivly extract all member - for name in index[zip_path]: - last = extract_resource(os.path.join(zip_path, name)) - # return the real extracted directory name - return os.path.dirname(last) - - # 'file' entry: extract it - try: - real_path = os.path.join(MEIDIR, *zip_path.split(os.sep)) - - if os.path.isfile(real_path): - stat = os.stat(real_path) - size = content[zip_path][1] - if stat.st_size==size: # and stat.st_mtime==timestamp: - # size and stamp match, don't bother extracting - return real_path - - outf, tmpnam = mkstemp(".$extract", dir=os.path.dirname(real_path)) - os.write(outf, archive.extract(zip_path)[1]) - os.close(outf) - try: - os.rename(tmpnam, real_path) - except os.error: - if os.path.isfile(real_path): - stat = os.stat(real_path) - # todo: check timestamp (compare with sys.executable) - if stat.st_size == size: - # size and stamp match, somebody did it just ahead of - # us, so we're done - return real_path - elif os.name=='nt': # Windows, del old file and retry - unlink(real_path) - os.rename(tmpnam, real_path) - return real_path - raise - - except os.error: - # todo: report a user-friendly error - raise - return real_path - -#--- - -archive = carchive.CArchive(sys.executable) -archive.loadtoc() - -# get contents of archive in a format more suitable for us -# list only zipfiles (typcd 'Z') and eggs (typcd 'E') -contents = dict([(path, (typcd, ulen)) - for (dpos, dlen, ulen, flag, typcd, path) in archive.toc - if typcd in 'EZ']) - -# build index for recursivly extracting directories -index = {} -for path in contents: - parts = path.split(os.sep) - while parts: - parent = os.sep.join(parts[:-1]) - if parent in index: - index[parent].append(parts[-1]) - break - else: - index[parent] = [parts.pop()] - -# Add the zipfiles to sys.path -for zip_path, (typcd, ulen) in contents.items(): - sys.path.append(extract_resource(zip_path)) diff --git a/support/_pyi_egg_install.py b/support/_pyi_egg_install.py new file mode 100644 index 0000000..9c59be2 --- /dev/null +++ b/support/_pyi_egg_install.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +# +# Make .eggs and zipfiles available at runtime +# +# Copyright (C) 2010 Giovanni Bajo +# +# This file is part of PyInstaller +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# In addition to the permissions in the GNU General Public License, the +# authors give you unlimited permission to link or embed the compiled +# version of this file into combinations with other programs, and to +# distribute those combinations without any restriction coming from the +# use of this file. (The General Public License restrictions do apply in +# other respects; for example, they cover modification of the file, and +# distribution when not linked into a combine executable.) +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +import os + +d = "eggs" +if "_MEIPASS2" in os.environ: + d = os.path.join(os.environ["_MEIPASS2"], d) +else: + d = os.path.join(os.path.dirname(sys.argv[0]), d) + +for fn in os.listdir(d): + sys.path.append(os.path.join(d, fn)) + print "adding", sys.path[-1] + +