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
This commit is contained in:
giovannibajo
2010-03-12 19:26:33 +00:00
parent 7b9611b9c1
commit 34236e347c
4 changed files with 51 additions and 106 deletions
+5 -3
View File
@@ -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
+3 -3
View File
@@ -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);
-100
View File
@@ -1,100 +0,0 @@
#!/usr/bin/env python
#
# Make .eggs and zipfiles available at runtime
#
# Copyright (C) 2008 Hartmut Goebel <h.goebel@goebel-consult.de>
# Licence: GNU General Public License version 3 (GPL v3)
#
# This file is part of PyInstaller <http://www.pyinstaller.org>
#
# 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 <http://www.gnu.org/licenses/>.
#
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))
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python
#
# Make .eggs and zipfiles available at runtime
#
# Copyright (C) 2010 Giovanni Bajo <rasky@develer.com>
#
# This file is part of PyInstaller <http://www.pyinstaller.org>
#
# 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]