Files
pyinstaller/Build.py
T
giovannibajo fc067fe1c3 Revamp .egg (zipimport) support.
It looks like the support was only preliminar and broken.
Specifically:
   * mf.py was unable to look for dependencies within .eggs
   * iu.py was unable to use .egg file completely (!)
Probably, it never fully worked. Anyway, it's completely fixed
now (the setuptools tests pass) and I'm going to commit more
tests soon (without the external dependency).


git-svn-id: http://svn.pyinstaller.org/trunk@639 8dd32b29-ccff-0310-8a9a-9233e24343b1
2009-03-01 17:57:22 +00:00

1050 lines
36 KiB
Python
Executable File

#!/usr/bin/env python
#
# Build packages using spec files
#
# Copyright (C) 2005, Giovanni Bajo
# Based on previous work under copyright (c) 1999, 2002 McMillan Enterprises, Inc.
#
# 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.
#
# 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 sys
import os
import shutil
import pprint
import time
import py_compile
import tempfile
import md5
import UserList
import mf
import archive
import iu
import carchive
import bindepend
STRINGTYPE = type('')
TUPLETYPE = type((None,))
UNCOMPRESSED, COMPRESSED = range(2)
# todo: use pkg_resources here
HOMEPATH = os.path.dirname(sys.argv[0])
SPECPATH = None
BUILDPATH = None
WARNFILE = None
rthooks = {}
iswin = sys.platform[:3] == 'win'
cygwin = sys.platform == 'cygwin'
def _save_data(filename, data):
outf = open(filename, 'w')
pprint.pprint(data, outf)
outf.close()
def _load_data(filename):
return eval(open(filename, 'r').read())
def setupUPXFlags():
f = os.environ.get("UPX", "")
is24 = hasattr(sys, "version_info") and sys.version_info[:2] >= (2,4)
if iswin and is24:
# Binaries built with Visual Studio 7.1 require --strip-loadconf
# or they won't compress. Configure.py makes sure that UPX is new
# enough to support --strip-loadconf.
f = "--strip-loadconf " + f
# Do not compress any icon, so that additional icons in the executable
# can still be externally bound
f = "--compress-icons=0 " + f
f = "--best " + f
os.environ["UPX"] = f
def mtime(fnm):
try:
return os.stat(fnm)[8]
except:
return 0
def absnormpath(apath):
return os.path.abspath(os.path.normpath(apath))
def compile_pycos(toc):
"""Given a TOC or equivalent list of tuples, generates all the required
pyc/pyo files, writing in a local directory if required, and returns the
list of tuples with the updated pathnames.
"""
global BUILDPATH
# For those modules that need to be rebuilt, use the build directory
# PyInstaller creates during the build process.
basepath = "/".join([BUILDPATH, "localpycos"])
new_toc = []
for (nm, fnm, typ) in toc:
# Trim the terminal "c" or "o"
source_fnm = fnm[:-1]
# If the source is newer than the compiled, or the compiled doesn't
# exist, we need to perform a build ourselves.
if mtime(source_fnm) > mtime(fnm):
try:
py_compile.compile(source_fnm)
except IOError:
# If we're compiling on a system directory, probably we don't
# have write permissions; thus we compile to a local directory
# and change the TOC entry accordingly.
ext = os.path.splitext(fnm)[1]
if "__init__" not in fnm:
# If it's a normal module, use last part of the qualified
# name as module name and the first as leading path
leading, mod_name = nm.split(".")[:-1], nm.split(".")[-1]
else:
# In case of a __init__ module, use all the qualified name
# as leading path and use "__init__" as the module name
leading, mod_name = nm.split("."), "__init__"
leading.insert(0, basepath)
leading = "/".join(leading)
if not os.path.exists(leading):
os.makedirs(leading)
fnm = "/".join([leading, mod_name + ext])
py_compile.compile(source_fnm, fnm)
new_toc.append((nm, fnm, typ))
return new_toc
#--- functons for checking guts ---
def _check_guts_eq(attr, old, new, last_build):
"""
rebuild is required if values differ
"""
if old != new:
print "building because %s changed" % attr
return True
return False
def _check_guts_toc_mtime(attr, old, toc, last_build, pyc=0):
"""
rebuild is required if mtimes of files listed in old toc are newer
than ast_build
if pyc=1, check for .py files, too
"""
for (nm, fnm, typ) in old:
if mtime(fnm) > last_build:
print "building because %s changed" % fnm
return True
elif pyc and mtime(fnm[:-1]) > last_build:
print "building because %s changed" % fnm[:-1]
return True
return False
def _check_guts_toc(attr, old, toc, last_build, pyc=0):
"""
rebuild is required if either toc content changed if mtimes of
files listed in old toc are newer than ast_build
if pyc=1, check for .py files, too
"""
return _check_guts_eq (attr, old, toc, last_build) \
or _check_guts_toc_mtime(attr, old, toc, last_build, pyc=pyc)
#--
class Target:
invcnum = 0
def __init__(self):
self.invcnum = Target.invcnum
Target.invcnum += 1
self.out = os.path.join(BUILDPATH, 'out%s%d.toc' % (self.__class__.__name__,
self.invcnum))
self.outnm = os.path.basename(self.out)
self.dependencies = TOC()
def __postinit__(self):
print "checking %s" % (self.__class__.__name__,)
if self.check_guts(mtime(self.out)):
self.assemble()
GUTS = []
def check_guts(self, last_build):
pass
def get_guts(self, last_build, missing ='missing or bad'):
"""
returns None if guts have changed
"""
try:
data = _load_data(self.out)
except:
print "building because", os.path.basename(self.out), missing
return None
if len(data) != len(self.GUTS):
print "building because %s is bad" % self.outnm
return None
for i in range(len(self.GUTS)):
attr, func = self.GUTS[i]
if func is None:
# no check for this value
continue
if func(attr, data[i], getattr(self, attr), last_build):
return None
return data
class Analysis(Target):
def __init__(self, scripts=None, pathex=None, hookspath=None, excludes=None):
Target.__init__(self)
self.inputs = scripts
for script in scripts:
if not os.path.exists(script):
raise ValueError, "script '%s' not found" % script
self.pathex = []
if pathex:
for path in pathex:
self.pathex.append(absnormpath(path))
self.hookspath = hookspath
self.excludes = excludes
self.scripts = TOC()
self.pure = TOC()
self.binaries = TOC()
self.zipfiles = TOC()
self.datas = TOC()
self.__postinit__()
GUTS = (('inputs', _check_guts_eq),
('pathex', _check_guts_eq),
('hookspath', _check_guts_eq),
('excludes', _check_guts_eq),
('scripts', _check_guts_toc_mtime),
('pure', lambda *args: apply(_check_guts_toc_mtime,
args, {'pyc': 1 } )),
('binaries', _check_guts_toc_mtime),
('zipfiles', _check_guts_toc_mtime),
('datas', _check_guts_toc_mtime),
)
def check_guts(self, last_build):
if last_build == 0:
print "building %s because %s non existent" % (self.__class__.__name__, self.outnm)
return True
for fnm in self.inputs:
if mtime(fnm) > last_build:
print "building because %s changed" % fnm
return True
data = Target.get_guts(self, last_build)
if not data:
return True
scripts, pure, binaries, zipfiles, datas = data[-5:]
self.scripts = TOC(scripts)
self.pure = TOC(pure)
self.binaries = TOC(binaries)
self.zipfiles = TOC(zipfiles)
self.datas = TOC(datas)
return False
def assemble(self):
print "running Analysis", os.path.basename(self.out)
paths = self.pathex
for i in range(len(paths)):
# FIXME: isn't self.pathex already norm-abs-pathed?
paths[i] = absnormpath(paths[i])
###################################################
# Scan inputs and prepare:
dirs = {} # input directories
pynms = [] # python filenames with no extension
for script in self.inputs:
if not os.path.exists(script):
print "Analysis: script %s not found!" % script
sys.exit(1)
d, base = os.path.split(script)
if not d:
d = os.getcwd()
d = absnormpath(d)
pynm, ext = os.path.splitext(base)
dirs[d] = 1
pynms.append(pynm)
###################################################
# Initialize analyzer and analyze scripts
analyzer = mf.ImportTracker(dirs.keys()+paths, self.hookspath,
self.excludes,
target_platform=target_platform)
#print analyzer.path
scripts = [] # will contain scripts to bundle
for i in range(len(self.inputs)):
script = self.inputs[i]
print "Analyzing:", script
analyzer.analyze_script(script)
scripts.append((pynms[i], script, 'PYSOURCE'))
###################################################
# Fills pure, binaries and rthookcs lists to TOC
pure = [] # pure python modules
binaries = [] # binaries to bundle
zipfiles = [] # zipfiles to bundle
datas = [] # datafiles to bundle
rthooks = [] # rthooks if needed
for modnm, mod in analyzer.modules.items():
# FIXME: why can we have a mod == None here?
if mod is not None:
hooks = findRTHook(modnm) #XXX
if hooks:
rthooks.extend(hooks)
datas.extend(mod.datas)
if isinstance(mod, mf.BuiltinModule):
pass
else:
fnm = mod.__file__
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)),
str(mod.owner), 'ZIPFILE'))
else:
# mf.PyModule instances expose a list of binary
# dependencies, most probably shared libraries accessed
# via ctypes. Add them to the overall required binaries.
binaries.extend(mod.binaries)
if modnm != '__main__':
pure.append((modnm, fnm, 'PYMODULE'))
binaries.extend(bindepend.Dependencies(binaries,
platform=target_platform))
self.fixMissingPythonLib(binaries)
# Add realtime hooks just before the last script (which is
# the entrypoint of the application).
scripts[-1:-1] = rthooks
self.scripts = TOC(scripts)
self.pure = TOC(pure)
self.binaries = TOC(binaries)
self.zipfiles = TOC(zipfiles)
self.datas = TOC(datas)
try: # read .toc
oldstuff = _load_data(self.out)
except:
oldstuff = None
self.pure = TOC(compile_pycos(self.pure))
newstuff = (self.inputs, self.pathex, self.hookspath, self.excludes,
self.scripts, self.pure, self.binaries, self.zipfiles, self.datas)
if oldstuff != newstuff:
_save_data(self.out, newstuff)
wf = open(WARNFILE, 'w')
for ln in analyzer.getwarnings():
wf.write(ln+'\n')
wf.close()
print "Warnings written to %s" % WARNFILE
return 1
print self.out, "no change!"
return 0
def fixMissingPythonLib(self, binaries):
"""Add the Python library if missing from the binaries.
Some linux distributions (e.g. debian-based) statically build the
Python executable to the libpython, so bindepend doesn't include
it in its output.
"""
if target_platform != 'linux2': return
name = 'libpython%d.%d.so' % sys.version_info[:2]
for (nm, fnm, typ) in binaries:
if typ == 'BINARY' and name in fnm:
# lib found
return
lib = bindepend.findLibrary(name)
if lib is None:
raise IOError("Python library not found!")
binaries.append((os.path.split(lib)[1], lib, 'BINARY'))
def findRTHook(modnm):
hooklist = rthooks.get(modnm)
if hooklist:
rslt = []
for script in hooklist:
nm = os.path.basename(script)
nm = os.path.splitext(nm)[0]
if os.path.isabs(script):
path = script
else:
path = os.path.join(HOMEPATH, script)
rslt.append((nm, path, 'PYSOURCE'))
return rslt
return None
class PYZ(Target):
typ = 'PYZ'
def __init__(self, toc, name=None, level=9, crypt=None):
Target.__init__(self)
self.toc = toc
self.name = name
if name is None:
self.name = self.out[:-3] + 'pyz'
if config['useZLIB']:
self.level = level
else:
self.level = 0
if config['useCrypt'] and crypt is not None:
self.crypt = archive.Keyfile(crypt).key
else:
self.crypt = None
self.dependencies = compile_pycos(config['PYZ_dependencies'])
self.__postinit__()
GUTS = (('name', _check_guts_eq),
('level', _check_guts_eq),
('toc', _check_guts_toc), # todo: pyc=1
)
def check_guts(self, last_build):
if not os.path.exists(self.name):
print "rebuilding %s because %s is missing" % (self.outnm, os.path.basename(self.name))
return True
data = Target.get_guts(self, last_build)
if not data:
return True
return False
def assemble(self):
print "building PYZ", os.path.basename(self.out)
pyz = archive.ZlibArchive(level=self.level, crypt=self.crypt)
toc = self.toc - config['PYZ_dependencies']
pyz.build(self.name, toc)
_save_data(self.out, (self.name, self.level, self.crypt, self.toc))
return 1
def cacheDigest(fnm):
data = open(fnm, "rb").read()
digest = md5.new(data).digest()
return digest
def checkCache(fnm, strip, upx):
# On darwin a cache is required anyway to keep the libaries
# with relative install names
if not strip and not upx and sys.platform != 'darwin':
return fnm
if strip:
strip = 1
else:
strip = 0
if upx:
upx = 1
else:
upx = 0
# Load cache index
cachedir = os.path.join(HOMEPATH, 'bincache%d%d' % (strip, upx))
if not os.path.exists(cachedir):
os.makedirs(cachedir)
cacheindexfn = os.path.join(cachedir, "index.dat")
if os.path.exists(cacheindexfn):
cache_index = _load_data(cacheindexfn)
else:
cache_index = {}
# Verify if the file we're looking for is present in the cache.
basenm = os.path.normcase(os.path.basename(fnm))
digest = cacheDigest(fnm)
cachedfile = os.path.join(cachedir, basenm)
cmd = None
if cache_index.has_key(basenm):
if digest != cache_index[basenm]:
os.remove(cachedfile)
else:
return cachedfile
if upx:
if strip:
fnm = checkCache(fnm, 1, 0)
cmd = "upx --best -q \"%s\"" % cachedfile
else:
if strip:
cmd = "strip \"%s\"" % cachedfile
shutil.copy2(fnm, cachedfile)
os.chmod(cachedfile, 0755)
if cmd: os.system(cmd)
# update cache index
cache_index[basenm] = digest
_save_data(cacheindexfn, cache_index)
return cachedfile
UNCOMPRESSED, COMPRESSED, ENCRYPTED = range(3)
class PKG(Target):
typ = 'PKG'
xformdict = {'PYMODULE' : 'm',
'PYSOURCE' : 's',
'EXTENSION' : 'b',
'PYZ' : 'z',
'PKG' : 'a',
'DATA': 'x',
'BINARY': 'b',
'ZIPFILE': 'Z',
'EXECUTABLE': 'b'}
def __init__(self, toc, name=None, cdict=None, exclude_binaries=0,
strip_binaries=0, upx_binaries=0, crypt=0):
Target.__init__(self)
self.toc = toc
self.cdict = cdict
self.name = name
self.exclude_binaries = exclude_binaries
self.strip_binaries = strip_binaries
self.upx_binaries = upx_binaries
self.crypt = crypt
if name is None:
self.name = self.out[:-3] + 'pkg'
if self.cdict is None:
if config['useZLIB']:
self.cdict = {'EXTENSION':COMPRESSED,
'DATA':COMPRESSED,
'BINARY':COMPRESSED,
'EXECUTABLE':COMPRESSED,
'PYSOURCE':COMPRESSED,
'PYMODULE':COMPRESSED }
if self.crypt:
self.cdict['PYSOURCE'] = ENCRYPTED
self.cdict['PYMODULE'] = ENCRYPTED
else:
self.cdict = { 'PYSOURCE':UNCOMPRESSED }
self.__postinit__()
GUTS = (('name', _check_guts_eq),
('cdict', _check_guts_eq),
('toc', _check_guts_toc_mtime),
('exclude_binaries', _check_guts_eq),
('strip_binaries', _check_guts_eq),
('upx_binaries', _check_guts_eq),
('crypt', _check_guts_eq),
)
def check_guts(self, last_build):
if not os.path.exists(self.name):
print "rebuilding %s because %s is missing" % (self.outnm, os.path.basename(self.name))
return 1
data = Target.get_guts(self, last_build)
if not data:
return True
# todo: toc equal
return False
def assemble(self):
print "building PKG", os.path.basename(self.name)
trash = []
mytoc = []
toc = TOC()
for item in self.toc:
inm, fnm, typ = item
if typ == 'EXTENSION':
binext = os.path.splitext(fnm)[1]
if not os.path.splitext(inm)[1] == binext:
inm = inm + binext
toc.append((inm, fnm, typ))
seen = {}
for inm, fnm, typ in toc:
if typ in ('BINARY', 'EXTENSION'):
if self.exclude_binaries:
self.dependencies.append((inm, fnm, typ))
else:
fnm = checkCache(fnm, self.strip_binaries,
self.upx_binaries and ( iswin or cygwin )
and config['hasUPX'])
# Avoid importing the same binary extension twice. This might
# happen if they come from different sources (eg. once from
# binary dependence, and once from direct import).
if typ == 'BINARY' and seen.has_key(fnm):
continue
seen[fnm] = 1
mytoc.append((inm, fnm, self.cdict.get(typ,0),
self.xformdict.get(typ,'b')))
elif typ == 'OPTION':
mytoc.append((inm, '', 0, 'o'))
else:
mytoc.append((inm, fnm, self.cdict.get(typ,0), self.xformdict.get(typ,'b')))
archive = carchive.CArchive()
archive.build(self.name, mytoc)
_save_data(self.out,
(self.name, self.cdict, self.toc, self.exclude_binaries,
self.strip_binaries, self.upx_binaries, self.crypt))
for item in trash:
os.remove(item)
return 1
class EXE(Target):
typ = 'EXECUTABLE'
exclude_binaries = 0
append_pkg = 1
def __init__(self, *args, **kws):
Target.__init__(self)
self.console = kws.get('console',1)
self.debug = kws.get('debug',0)
self.name = kws.get('name',None)
self.icon = kws.get('icon',None)
self.versrsrc = kws.get('version',None)
self.strip = kws.get('strip',None)
self.upx = kws.get('upx',None)
self.crypt = kws.get('crypt', 0)
self.exclude_binaries = kws.get('exclude_binaries',0)
self.append_pkg = kws.get('append_pkg', self.append_pkg)
if self.name is None:
self.name = self.out[:-3] + 'exe'
if not os.path.isabs(self.name):
self.name = os.path.join(SPECPATH, self.name)
if target_iswin or cygwin:
self.pkgname = self.name[:-3] + 'pkg'
else:
self.pkgname = self.name + '.pkg'
self.toc = TOC()
for arg in args:
if isinstance(arg, TOC):
self.toc.extend(arg)
elif isinstance(arg, Target):
self.toc.append((os.path.basename(arg.name), arg.name, arg.typ))
self.toc.extend(arg.dependencies)
else:
self.toc.extend(arg)
self.toc.extend(config['EXE_dependencies'])
self.pkg = PKG(self.toc, cdict=kws.get('cdict',None), exclude_binaries=self.exclude_binaries,
strip_binaries=self.strip, upx_binaries=self.upx, crypt=self.crypt)
self.dependencies = self.pkg.dependencies
self.__postinit__()
GUTS = (('name', _check_guts_eq),
('console', _check_guts_eq),
('debug', _check_guts_eq),
('icon', _check_guts_eq),
('versrsrc', _check_guts_eq),
('strip', _check_guts_eq),
('upx', _check_guts_eq),
('crypt', _check_guts_eq),
('mtm', None,), # checked bellow
)
def check_guts(self, last_build):
if not os.path.exists(self.name):
print "rebuilding %s because %s missing" % (self.outnm, os.path.basename(self.name))
return 1
if not self.append_pkg and not os.path.exists(self.pkgname):
print "rebuilding because %s missing" % (
os.path.basename(self.pkgname),)
return 1
data = Target.get_guts(self, last_build)
if not data:
return True
icon, versrsrc = data[3:5]
if (icon or versrsrc) and not config['hasRsrcUpdate']:
# todo: really ignore :-)
print "ignoring icon and version resources = platform not capable"
mtm = data[-1]
crypt = data[-2]
if crypt != self.crypt:
print "rebuilding %s because crypt option changed" % outnm
return 1
if mtm != mtime(self.name):
print "rebuilding", self.outnm, "because mtimes don't match"
return True
if mtm < mtime(self.pkg.out):
print "rebuilding", self.outnm, "because pkg is more recent"
return True
return False
def _bootloader_postfix(self, exe):
if target_iswin:
exe = exe + "_"
is24 = hasattr(sys, "version_info") and sys.version_info[:2] >= (2,4)
exe = exe + "67"[is24]
exe = exe + "rd"[self.debug]
exe = exe + "wc"[self.console]
else:
if not self.console:
exe = exe + 'w'
if self.debug:
exe = exe + '_d'
return exe
def assemble(self):
print "building EXE from", os.path.basename(self.out)
trash = []
if not os.path.exists(os.path.dirname(self.name)):
os.makedirs(os.path.dirname(self.name))
outf = open(self.name, 'wb')
exe = self._bootloader_postfix('support/loader/run')
exe = os.path.join(HOMEPATH, exe)
if target_iswin or cygwin:
exe = exe + '.exe'
if config['hasRsrcUpdate']:
if self.icon:
tmpnm = tempfile.mktemp()
shutil.copy2(exe, tmpnm)
os.chmod(tmpnm, 0755)
icon.CopyIcons(tmpnm, self.icon)
trash.append(tmpnm)
exe = tmpnm
if self.versrsrc:
tmpnm = tempfile.mktemp()
shutil.copy2(exe, tmpnm)
os.chmod(tmpnm, 0755)
versionInfo.SetVersion(tmpnm, self.versrsrc)
trash.append(tmpnm)
exe = tmpnm
exe = checkCache(exe, self.strip, self.upx and config['hasUPX'])
self.copy(exe, outf)
if self.append_pkg:
print "Appending archive to EXE", self.name
self.copy(self.pkg.name, outf)
else:
print "Copying archive to", self.pkgname
shutil.copy2(self.pkg.name, self.pkgname)
outf.close()
os.chmod(self.name, 0755)
_save_data(self.out,
(self.name, self.console, self.debug, self.icon,
self.versrsrc, self.strip, self.upx, self.crypt, mtime(self.name)))
for item in trash:
os.remove(item)
return 1
def copy(self, fnm, outf):
inf = open(fnm, 'rb')
while 1:
data = inf.read(64*1024)
if not data:
break
outf.write(data)
class DLL(EXE):
def assemble(self):
print "building DLL", os.path.basename(self.out)
outf = open(self.name, 'wb')
dll = self._bootloader_postfix('support/loader/inprocsrvr')
dll = os.path.join(HOMEPATH, dll) + '.dll'
self.copy(dll, outf)
self.copy(self.pkg.name, outf)
outf.close()
os.chmod(self.name, 0755)
_save_data(self.out,
(self.name, self.console, self.debug, self.icon,
self.versrsrc, self.strip, self.upx, mtime(self.name)))
return 1
class COLLECT(Target):
def __init__(self, *args, **kws):
Target.__init__(self)
self.name = kws.get('name',None)
if self.name is None:
self.name = 'dist_' + self.out[:-4]
self.strip_binaries = kws.get('strip',0)
self.upx_binaries = kws.get('upx',0)
if not os.path.isabs(self.name):
self.name = os.path.join(SPECPATH, self.name)
self.toc = TOC()
for arg in args:
if isinstance(arg, TOC):
self.toc.extend(arg)
elif isinstance(arg, Target):
self.toc.append((os.path.basename(arg.name), arg.name, arg.typ))
if isinstance(arg, EXE) and not arg.append_pkg:
self.toc.append((os.path.basename(arg.pkgname), arg.pkgname, 'PKG'))
self.toc.extend(arg.dependencies)
else:
self.toc.extend(arg)
self.__postinit__()
GUTS = (('name', _check_guts_eq),
('strip_binaries', _check_guts_eq),
('upx_binaries', _check_guts_eq),
('toc', _check_guts_eq), # additional check below
)
def check_guts(self, last_build):
data = Target.get_guts(self, last_build)
if not data:
return True
toc = data[-1]
for inm, fnm, typ in self.toc:
if typ == 'EXTENSION':
ext = os.path.splitext(fnm)[1]
test = os.path.join(self.name, inm+ext)
else:
test = os.path.join(self.name, os.path.basename(fnm))
if not os.path.exists(test):
print "building %s because %s is missing" % (self.outnm, test)
return 1
if mtime(fnm) > mtime(test):
print "building %s because %s is more recent" % (self.outnm, fnm)
return 1
return 0
def assemble(self):
print "building COLLECT", os.path.basename(self.out)
if not os.path.exists(self.name):
os.makedirs(self.name)
toc = TOC()
for inm, fnm, typ in self.toc:
if typ == 'EXTENSION':
binext = os.path.splitext(fnm)[1]
if not os.path.splitext(inm)[1] == binext:
inm = inm + binext
toc.append((inm, fnm, typ))
for inm, fnm, typ in toc:
tofnm = os.path.join(self.name, inm)
todir = os.path.dirname(tofnm)
if not os.path.exists(todir):
os.makedirs(todir)
if typ in ('EXTENSION', 'BINARY'):
fnm = checkCache(fnm, self.strip_binaries,
self.upx_binaries and ( iswin or cygwin )
and config['hasUPX'])
shutil.copy2(fnm, tofnm)
if typ in ('EXTENSION', 'BINARY'):
os.chmod(tofnm, 0755)
_save_data(self.out,
(self.name, self.strip_binaries, self.upx_binaries, self.toc))
return 1
class TOC(UserList.UserList):
def __init__(self, initlist=None):
UserList.UserList.__init__(self)
self.fltr = {}
if initlist:
for tpl in initlist:
self.append(tpl)
def append(self, tpl):
try:
fn = tpl[0]
if tpl[2] == "BINARY":
# Normalize the case for binary files only (to avoid duplicates
# for different cases under Windows). We can't do that for
# Python files because the import semantic (even at runtime)
# depends on the case.
fn = os.path.normcase(fn)
if not self.fltr.get(fn):
self.data.append(tpl)
self.fltr[fn] = 1
except TypeError:
print "TOC found a %s, not a tuple" % tpl
raise
def insert(self, pos, tpl):
fn = tpl[0]
if tpl[2] == "BINARY":
fn = os.path.normcase(fn)
if not self.fltr.get(fn):
self.data.insert(pos, tpl)
self.fltr[fn] = 1
def __add__(self, other):
rslt = TOC(self.data)
rslt.extend(other)
return rslt
def __radd__(self, other):
rslt = TOC(other)
rslt.extend(self.data)
return rslt
def extend(self, other):
for tpl in other:
self.append(tpl)
def __sub__(self, other):
fd = self.fltr.copy()
# remove from fd if it's in other
for tpl in other:
if fd.get(tpl[0],0):
del fd[tpl[0]]
rslt = TOC()
# return only those things still in fd (preserve order)
for tpl in self.data:
if fd.get(tpl[0],0):
rslt.append(tpl)
return rslt
def __rsub__(self, other):
rslt = TOC(other)
return rslt.__sub__(self)
def intersect(self, other):
rslt = TOC()
for tpl in other:
if self.fltr.get(tpl[0],0):
rslt.append(tpl)
return rslt
class Tree(Target, TOC):
def __init__(self, root=None, prefix=None, excludes=None):
Target.__init__(self)
TOC.__init__(self)
self.root = root
self.prefix = prefix
self.excludes = excludes
if excludes is None:
self.excludes = []
self.__postinit__()
GUTS = (('root', _check_guts_eq),
('prefix', _check_guts_eq),
('excludes', _check_guts_eq),
('toc', None),
)
def check_guts(self, last_build):
data = Target.get_guts(self, last_build)
if not data:
return True
stack = [ data[0] ] # root
toc = data[3] # toc
while stack:
d = stack.pop()
if mtime(d) > last_build:
print "building %s because directory %s changed" % (self.outnm, d)
return True
for nm in os.listdir(d):
path = os.path.join(d, nm)
if os.path.isdir(path):
stack.append(path)
self.data = toc
return False
def assemble(self):
print "building Tree", os.path.basename(self.out)
stack = [(self.root, self.prefix)]
excludes = {}
xexcludes = {}
for nm in self.excludes:
if nm[0] == '*':
xexcludes[nm[1:]] = 1
else:
excludes[nm] = 1
rslt = []
while stack:
dir, prefix = stack.pop()
for fnm in os.listdir(dir):
if excludes.get(fnm, 0) == 0:
ext = os.path.splitext(fnm)[1]
if xexcludes.get(ext,0) == 0:
fullfnm = os.path.join(dir, fnm)
rfnm = prefix and os.path.join(prefix, fnm) or fnm
if os.path.isdir(fullfnm):
stack.append((fullfnm, rfnm))
else:
rslt.append((rfnm, fullfnm, 'DATA'))
self.data = rslt
try:
oldstuff = _load_data(self.out)
except:
oldstuff = None
newstuff = (self.root, self.prefix, self.excludes, self.data)
if oldstuff != newstuff:
_save_data(self.out, newstuff)
return 1
print self.out, "no change!"
return 0
def TkTree():
tclroot = config['TCL_root']
tclnm = os.path.join('_MEI', os.path.basename(tclroot))
tkroot = config['TK_root']
tknm = os.path.join('_MEI', os.path.basename(tkroot))
tcltree = Tree(tclroot, tclnm, excludes=['demos','encoding','*.lib'])
tktree = Tree(tkroot, tknm, excludes=['demos','encoding','*.lib'])
return tcltree + tktree
def TkPKG():
return PKG(TkTree(), name='tk.pkg')
#---
def build(spec):
global SPECPATH, BUILDPATH, WARNFILE, rthooks
rthooks = _load_data(os.path.join(HOMEPATH, 'rthooks.dat'))
SPECPATH, specnm = os.path.split(spec)
specnm = os.path.splitext(specnm)[0]
if SPECPATH == '':
SPECPATH = os.getcwd()
WARNFILE = os.path.join(SPECPATH, 'warn%s.txt' % specnm)
BUILDPATH = os.path.join(SPECPATH, 'build',
"pyi." + config['target_platform'], specnm)
if '-o' in sys.argv:
bpath = sys.argv[sys.argv.index('-o')+1]
if os.path.isabs(bpath):
BUILDPATH = bpath
else:
BUILDPATH = os.path.join(SPECPATH, bpath)
if not os.path.exists(BUILDPATH):
os.makedirs(BUILDPATH)
execfile(spec)
def main(specfile, configfilename):
global target_platform, target_iswin, config
global icon, versionInfo
try:
config = _load_data(configfilename)
except IOError:
print "You must run Configure.py before building!"
sys.exit(1)
target_platform = config.get('target_platform', sys.platform)
target_iswin = target_platform[:3] == 'win'
if target_platform == sys.platform:
# _not_ cross compiling
if config['pythonVersion'] != sys.version:
print "The current version of Python is not the same with which PyInstaller was configured."
print "Please re-run Configure.py with this version."
sys.exit(1)
if config.setdefault('pythonDebug', None) != __debug__:
print "python optimization flags changed: rerun Configure.py with the same [-O] option"
print "Configure.py optimize=%s, Build.py optimize=%s" % (not config['pythonDebug'], not __debug__)
sys.exit(1)
if config['hasRsrcUpdate']:
import icon, versionInfo
if config['hasUPX']:
setupUPXFlags()
if not config['useELFEXE']:
EXE.append_pkg = 0
build(specfile)
if __name__ == '__main__':
from optparse import OptionParser
parser = OptionParser('%prog [options] specfile')
parser.add_option('-C', '--configfile',
default=os.path.join(HOMEPATH, 'config.dat'),
help='Name of generated configfile (default: %default)')
opts, args = parser.parse_args()
if len(args) != 1:
parser.error('Requires exactly one .spec-file')
main(args[0], configfilename=opts.configfile)