diff --git a/pyinstaller/.pylintrc b/pyinstaller/.pylintrc new file mode 100644 index 0000000..f4eee35 --- /dev/null +++ b/pyinstaller/.pylintrc @@ -0,0 +1,239 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS,altgraph,junitxml,macholib,pefile.py,__subprocess.py,six.py,unittest2 + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +disable=C0111 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=parseable + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 diff --git a/pyinstaller/.svn/entries b/pyinstaller/.svn/entries new file mode 100644 index 0000000..fd83217 --- /dev/null +++ b/pyinstaller/.svn/entries @@ -0,0 +1,133 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +pyinstaller-gui.py +file + + + +add + +pip-egg-info +dir + + + +add + +buildtests +dir + + + +add + +.pylintrc +file + + + +add + +setup.py +file + + + +add + +pyinstaller.py +file + + + +add + +e2etests +dir + + + +add + +utils +dir + + + +add + +source +dir + + + +add + +README.rst +file + + + +add + +doc +dir + + + +add + +PyInstaller +dir + + + +add + +MANIFEST.in +file + + + +add + +support +dir + + + +add + +examples +dir + + + +add + diff --git a/pyinstaller/MANIFEST.in b/pyinstaller/MANIFEST.in new file mode 100644 index 0000000..b815892 --- /dev/null +++ b/pyinstaller/MANIFEST.in @@ -0,0 +1 @@ +exclude .hgignore diff --git a/pyinstaller/PyInstaller/.svn/entries b/pyinstaller/PyInstaller/.svn/entries new file mode 100644 index 0000000..67d434b --- /dev/null +++ b/pyinstaller/PyInstaller/.svn/entries @@ -0,0 +1,119 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +bindepend.py +file + + + +add + +depend +dir + + + +add + +makespec.py +file + + + +add + +utils +dir + + + +add + +hooks +dir + + + +add + +__init__.py +file + + + +add + +build.py +file + + + +add + +fake +dir + + + +add + +lib +dir + + + +add + +loader +dir + + + +add + +configure.py +file + + + +add + +log.py +file + + + +add + +compat.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/__init__.py b/pyinstaller/PyInstaller/__init__.py new file mode 100644 index 0000000..4657a77 --- /dev/null +++ b/pyinstaller/PyInstaller/__init__.py @@ -0,0 +1,92 @@ +# +# Copyright (C) 2011 by Hartmut Goebel +# +# 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 + +__all__ = ('HOMEPATH', 'CONFIGDIR', 'PLATFORM', + 'VERSION', 'get_version', + 'is_py23', 'is_py24', 'is_py25', 'is_py26', 'is_py27', + 'is_win', 'is_cygwin', 'is_darwin', 'is_unix', 'is_linux', + 'is_solar', 'is_aix') + +import os +import sys + +# Fail hard if Python does not have minimum required version +if sys.version_info < (2, 3): + raise SystemExit('PyInstaller requires at least Python 2.3, sorry.') + +# Extend PYTHONPATH with 3rd party libraries bundled with PyInstaller. +# (otherwise e.g. macholib won't work on Mac OS X) +from PyInstaller import lib +sys.path.insert(0, lib.__path__[0]) + +from PyInstaller import compat +from PyInstaller.utils import git + +VERSION = (2, 0, 0) + + +is_py23 = compat.is_py23 +is_py24 = compat.is_py24 +is_py25 = compat.is_py25 +is_py26 = compat.is_py26 +is_py27 = compat.is_py27 + +is_win = compat.is_win +is_cygwin = compat.is_cygwin +is_darwin = compat.is_darwin + +is_linux = compat.is_linux +is_solar = compat.is_solar +is_aix = compat.is_aix + +is_unix = compat.is_unix + + +HOMEPATH = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + +if is_win: + CONFIGDIR = compat.getenv('APPDATA') + if not CONFIGDIR: + CONFIGDIR = os.path.expanduser('~\\Application Data') +elif is_darwin: + CONFIGDIR = os.path.expanduser('~/Library/Application Support') +else: + # According to XDG specification + # http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + CONFIGDIR = compat.getenv('XDG_DATA_HOME') + if not CONFIGDIR: + CONFIGDIR = os.path.expanduser('~/.local/share') +CONFIGDIR = os.path.join(CONFIGDIR, 'pyinstaller') + +PLATFORM = compat.system() + '-' + compat.architecture() + +# path extensions for module seach +# :fixme: this should not be a global variable +__pathex__ = [] + + +def get_version(): + version = '%s.%s' % (VERSION[0], VERSION[1]) + if VERSION[2]: + version = '%s.%s' % (version, VERSION[2]) + if len(VERSION) >= 4 and VERSION[3]: + version = '%s%s' % (version, VERSION[3]) + # include git revision in version string + if VERSION[3] == 'dev' and VERSION[4] > 0: + version = '%s-%s' % (version, VERSION[4]) + return version diff --git a/pyinstaller/PyInstaller/bindepend.py b/pyinstaller/PyInstaller/bindepend.py new file mode 100644 index 0000000..ab994a6 --- /dev/null +++ b/pyinstaller/PyInstaller/bindepend.py @@ -0,0 +1,631 @@ +#! /usr/bin/env python +# +# Find external dependencies of binary libraries. +# +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 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 os +import sys +import re +from glob import glob +# Required for extracting eggs. +import zipfile + +from PyInstaller import is_win, is_unix, is_aix, is_cygwin, is_darwin, is_py26 +from PyInstaller.depend import dylib +from PyInstaller.utils import winutils +import PyInstaller.compat as compat +from PyInstaller.compat import set + + +import PyInstaller.log as logging +logger = logging.getLogger('PyInstaller.build.bindepend') + +seen = {} + +if is_win: + if is_py26: + try: + import win32api + import pywintypes + except ImportError: + raise SystemExit("Error: PyInstaller for Python 2.6+ on Windows " + "needs pywin32.\r\nPlease install from " + "http://sourceforge.net/projects/pywin32/") + + from PyInstaller.utils.winmanifest import RT_MANIFEST + from PyInstaller.utils.winmanifest import GetManifestResources + from PyInstaller.utils.winmanifest import Manifest + + try: + from PyInstaller.utils.winmanifest import winresource + except ImportError, detail: + winresource = None + + +def getfullnameof(mod, xtrapath=None): + """ + Return the full path name of MOD. + + MOD is the basename of a dll or pyd. + XTRAPATH is a path or list of paths to search first. + Return the full path name of MOD. + Will search the full Windows search path, as well as sys.path + """ + # Search sys.path first! + epath = sys.path + winutils.get_system_path() + if xtrapath is not None: + if type(xtrapath) == type(''): + epath.insert(0, xtrapath) + else: + epath = xtrapath + epath + for p in epath: + npth = os.path.join(p, mod) + if os.path.exists(npth): + return npth + # second try: lower case filename + for p in epath: + npth = os.path.join(p, mod.lower()) + if os.path.exists(npth): + return npth + return '' + + +def _getImports_pe(pth): + """ + Find the binary dependencies of PTH. + + This implementation walks through the PE header + and uses library pefile for that and supports + 32/64bit Windows + """ + import PyInstaller.lib.pefile as pefile + dlls = set() + # By default library pefile parses all PE information. + # We are only interested in the list of dependent dlls. + # Performance is improved by reading only needed information. + # https://code.google.com/p/pefile/wiki/UsageExamples + pe = pefile.PE(pth, fast_load=True) + pe.parse_data_directories(directories=[ + pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']]) + # Some libraries have no other binary dependencies. Use empty list + # in that case. Otherwise pefile would return None. + # e.g. C:\windows\system32\kernel32.dll on Wine + for entry in getattr(pe, 'DIRECTORY_ENTRY_IMPORT', []): + dlls.add(entry.dll) + return dlls + + +def _extract_from_egg(toc): + """ + Ensure all binary modules in zipped eggs get extracted and + included with the frozen executable. + + The supplied toc is directly modified to make changes effective. + + return modified table of content + """ + for item in toc: + # Item is a tupple + # (mod_name, path, type) + modname, pth, typ = item + if not os.path.isfile(pth): + pth = check_extract_from_egg(pth)[0][0] + # Replace value in original data structure. + toc.remove(item) + toc.append((modname, pth, typ)) + return toc + + +def Dependencies(lTOC, xtrapath=None, manifest=None): + """ + Expand LTOC to include all the closure of binary dependencies. + + LTOC is a logical table of contents, ie, a seq of tuples (name, path). + Return LTOC expanded by all the binary dependencies of the entries + in LTOC, except those listed in the module global EXCLUDES + + manifest should be a winmanifest.Manifest instance on Windows, so + that all dependent assemblies can be added + """ + # Extract all necessary binary modules from Python eggs to be included + # directly with PyInstaller. + lTOC = _extract_from_egg(lTOC) + + for nm, pth, typ in lTOC: + if seen.get(nm.upper(), 0): + continue + logger.debug("Analyzing %s", pth) + seen[nm.upper()] = 1 + if is_win: + for ftocnm, fn in selectAssemblies(pth, manifest): + lTOC.append((ftocnm, fn, 'BINARY')) + for lib, npth in selectImports(pth, xtrapath): + if seen.get(lib.upper(), 0) or seen.get(npth.upper(), 0): + continue + seen[npth.upper()] = 1 + lTOC.append((lib, npth, 'BINARY')) + + return lTOC + + +def pkg_resouces_get_default_cache(): + """ + Determine the default cache location + + This returns the ``PYTHON_EGG_CACHE`` environment variable, if set. + Otherwise, on Windows, it returns a 'Python-Eggs' subdirectory of the + 'Application Data' directory. On all other systems, it's '~/.python-eggs'. + """ + # This function borrowed from setuptools/pkg_resources + egg_cache = compat.getenv('PYTHON_EGG_CACHE') + if egg_cache is not None: + return egg_cache + + if os.name != 'nt': + return os.path.expanduser('~/.python-eggs') + + app_data = 'Application Data' # XXX this may be locale-specific! + app_homes = [ + (('APPDATA',), None), # best option, should be locale-safe + (('USERPROFILE',), app_data), + (('HOMEDRIVE', 'HOMEPATH'), app_data), + (('HOMEPATH',), app_data), + (('HOME',), None), + (('WINDIR',), app_data), # 95/98/ME + ] + + for keys, subdir in app_homes: + dirname = '' + for key in keys: + if key in os.environ: + dirname = os.path.join(dirname, compat.getenv(key)) + else: + break + else: + if subdir: + dirname = os.path.join(dirname, subdir) + return os.path.join(dirname, 'Python-Eggs') + else: + raise RuntimeError( + "Please set the PYTHON_EGG_CACHE enviroment variable" + ) + + +def check_extract_from_egg(pth, todir=None): + r""" + Check if path points to a file inside a python egg file, extract the + file from the egg to a cache directory (following pkg_resources + convention) and return [(extracted path, egg file path, relative path + inside egg file)]. + Otherwise, just return [(original path, None, None)]. + If path points to an egg file directly, return a list with all files + from the egg formatted like above. + + Example: + >>> check_extract_from_egg(r'C:\Python26\Lib\site-packages\my.egg\mymodule\my.pyd') + [(r'C:\Users\UserName\AppData\Roaming\Python-Eggs\my.egg-tmp\mymodule\my.pyd', + r'C:\Python26\Lib\site-packages\my.egg', r'mymodule/my.pyd')] + """ + rv = [] + if os.path.altsep: + pth = pth.replace(os.path.altsep, os.path.sep) + components = pth.split(os.path.sep) + for i, name in enumerate(components): + if name.lower().endswith(".egg"): + eggpth = os.path.sep.join(components[:i + 1]) + if os.path.isfile(eggpth): + # eggs can also be directories! + try: + egg = zipfile.ZipFile(eggpth) + except zipfile.BadZipfile, e: + raise SystemExit("Error: %s %s" % (eggpth, e)) + if todir is None: + # Use the same directory as setuptools/pkg_resources. So, + # if the specific egg was accessed before (not necessarily + # by pyinstaller), the extracted contents already exist + # (pkg_resources puts them there) and can be used. + todir = os.path.join(pkg_resouces_get_default_cache(), + name + "-tmp") + if components[i + 1:]: + members = ["/".join(components[i + 1:])] + else: + members = egg.namelist() + for member in members: + pth = os.path.join(todir, member) + if not os.path.isfile(pth): + dirname = os.path.dirname(pth) + if not os.path.isdir(dirname): + os.makedirs(dirname) + f = open(pth, "wb") + f.write(egg.read(member)) + f.close() + rv.append((pth, eggpth, member)) + return rv + return [(pth, None, None)] + + +def getAssemblies(pth): + """ + Return the dependent assemblies of a binary. + """ + if pth.lower().endswith(".manifest"): + return [] + # check for manifest file + manifestnm = pth + ".manifest" + if os.path.isfile(manifestnm): + fd = open(manifestnm, "rb") + res = {RT_MANIFEST: {1: {0: fd.read()}}} + fd.close() + elif not winresource: + # resource access unavailable (needs pywin32) + return [] + else: + # check the binary for embedded manifest + try: + res = GetManifestResources(pth) + except winresource.pywintypes.error, exc: + if exc.args[0] == winresource.ERROR_BAD_EXE_FORMAT: + logger.info('Cannot get manifest resource from non-PE ' + 'file %s', pth) + return [] + raise + rv = [] + if RT_MANIFEST in res and len(res[RT_MANIFEST]): + for name in res[RT_MANIFEST]: + for language in res[RT_MANIFEST][name]: + # check the manifest for dependent assemblies + try: + manifest = Manifest() + manifest.filename = ":".join([pth, str(RT_MANIFEST), + str(name), str(language)]) + manifest.parse_string(res[RT_MANIFEST][name][language], + False) + except Exception, exc: + logger.error("Can not parse manifest resource %s, %s" + "from %s", name, language, pth) + logger.exception(exc) + else: + if manifest.dependentAssemblies: + logger.debug("Dependent assemblies of %s:", pth) + logger.debug(", ".join([assembly.getid() + for assembly in + manifest.dependentAssemblies])) + rv.extend(manifest.dependentAssemblies) + return rv + + +def selectAssemblies(pth, manifest=None): + """ + Return a binary's dependent assemblies files that should be included. + + Return a list of pairs (name, fullpath) + """ + rv = [] + if manifest: + _depNames = set([dep.name for dep in manifest.dependentAssemblies]) + for assembly in getAssemblies(pth): + if seen.get(assembly.getid().upper(), 0): + continue + if manifest and not assembly.name in _depNames: + # Add assembly as dependency to our final output exe's manifest + logger.info("Adding %s to dependent assemblies " + "of final executable", assembly.name) + manifest.dependentAssemblies.append(assembly) + _depNames.add(assembly.name) + if not dylib.include_library(assembly.name): + logger.debug("Skipping assembly %s", assembly.getid()) + continue + if assembly.optional: + logger.debug("Skipping optional assembly %s", assembly.getid()) + continue + files = assembly.find_files() + if files: + seen[assembly.getid().upper()] = 1 + for fn in files: + fname, fext = os.path.splitext(fn) + if fext.lower() == ".manifest": + nm = assembly.name + fext + else: + nm = os.path.basename(fn) + ftocnm = nm + if assembly.language not in (None, "", "*", "neutral"): + ftocnm = os.path.join(assembly.getlanguage(), + ftocnm) + nm, ftocnm, fn = [item.encode(sys.getfilesystemencoding()) + for item in + (nm, + ftocnm, + fn)] + if not seen.get(fn.upper(), 0): + logger.debug("Adding %s", ftocnm) + seen[nm.upper()] = 1 + seen[fn.upper()] = 1 + rv.append((ftocnm, fn)) + else: + #logger.info("skipping %s part of assembly %s dependency of %s", + # ftocnm, assembly.name, pth) + pass + else: + logger.error("Assembly %s not found", assembly.getid()) + return rv + + +def selectImports(pth, xtrapath=None): + """ + Return the dependencies of a binary that should be included. + + Return a list of pairs (name, fullpath) + """ + rv = [] + if xtrapath is None: + xtrapath = [os.path.dirname(pth)] + else: + assert isinstance(xtrapath, list) + xtrapath = [os.path.dirname(pth)] + xtrapath # make a copy + dlls = getImports(pth) + for lib in dlls: + if seen.get(lib.upper(), 0): + continue + if not is_win and not is_cygwin: + # all other platforms + npth = lib + lib = os.path.basename(lib) + else: + # plain win case + npth = getfullnameof(lib, xtrapath) + + # now npth is a candidate lib if found + # check again for excludes but with regex FIXME: split the list + if npth: + candidatelib = npth + else: + candidatelib = lib + + if not dylib.include_library(candidatelib): + if (candidatelib.find('libpython') < 0 and + candidatelib.find('Python.framework') < 0): + # skip libs not containing (libpython or Python.framework) + if not seen.get(npth.upper(), 0): + logger.debug("Skipping %s dependency of %s", + lib, os.path.basename(pth)) + continue + else: + pass + + if npth: + if not seen.get(npth.upper(), 0): + logger.debug("Adding %s dependency of %s", + lib, os.path.basename(pth)) + rv.append((lib, npth)) + else: + logger.error("lib not found: %s dependency of %s", lib, pth) + + return rv + + +def _getImports_ldd(pth): + """ + Find the binary dependencies of PTH. + + This implementation is for ldd platforms (mostly unix). + """ + rslt = set() + if is_aix: + # Match libs of the form 'archive.a(sharedobject.so)' + # Will not match the fake lib '/unix' + lddPattern = re.compile(r"\s+(.*?)(\(.*\))") + else: + lddPattern = re.compile(r"\s+(.*?)\s+=>\s+(.*?)\s+\(.*\)") + + for line in compat.exec_command('ldd', pth).strip().splitlines(): + m = lddPattern.search(line) + if m: + if is_aix: + lib = m.group(1) + name = os.path.basename(lib) + m.group(2) + else: + name, lib = m.group(1), m.group(2) + if name[:10] in ('linux-gate', 'linux-vdso'): + # linux-gate is a fake library which does not exist and + # should be ignored. See also: + # http://www.trilithium.com/johan/2005/08/linux-gate/ + continue + + if os.path.exists(lib): + # Add lib if it is not already found. + if lib not in rslt: + rslt.add(lib) + else: + logger.error('Can not find %s in path %s (needed by %s)', + name, lib, pth) + return rslt + + +def _getImports_macholib(pth): + """ + Find the binary dependencies of PTH. + + This implementation is for Mac OS X and uses library macholib. + """ + from PyInstaller.lib.macholib.MachO import MachO + from PyInstaller.lib.macholib.mach_o import LC_RPATH + from PyInstaller.lib.macholib.dyld import dyld_find + rslt = set() + seen = set() # Libraries read from binary headers. + + ## Walk through mach binary headers. + + m = MachO(pth) + for header in m.headers: + for idx, name, lib in header.walkRelocatables(): + # Sometimes some libraries are present multiple times. + if lib not in seen: + seen.add(lib) + + # Walk through mach binary headers and look for LC_RPATH. + # macholib can't handle @rpath. LC_RPATH has to be read + # from the MachO header. + # TODO Do we need to remove LC_RPATH from MachO load commands? + # Will it cause any harm to leave them untouched? + # Removing LC_RPATH should be implemented when getting + # files from the bincache if it is necessary. + run_paths = set() + for header in m.headers: + for command in header.commands: + # A command is a tupple like: + # (, + # , + # '../lib\x00\x00') + cmd_type = command[0].cmd + if cmd_type == LC_RPATH: + rpath = command[2] + # Remove trailing '\x00' characters. + # e.g. '../lib\x00\x00' + rpath = rpath.rstrip('\x00') + # Make rpath absolute. According to Apple doc LC_RPATH + # is always relative to the binary location. + rpath = os.path.normpath(os.path.join(os.path.dirname(pth), rpath)) + run_paths.update([rpath]) + + ## Try to find files in file system. + + # In cases with @loader_path or @executable_path + # try to look in the same directory as the checked binary is. + # This seems to work in most cases. + exec_path = os.path.abspath(os.path.dirname(pth)) + + for lib in seen: + + # Suppose that @rpath is not used for system libraries and + # using macholib can be avoided. + # macholib can't handle @rpath. + if lib.startswith('@rpath'): + lib = lib.replace('@rpath', '.') # Make path relative. + final_lib = None # Absolute path to existing lib on disk. + # Try multiple locations. + for run_path in run_paths: + # @rpath may contain relative value. Use exec_path as + # base path. + if not os.path.isabs(run_path): + run_path = os.path.join(exec_path, run_path) + # Stop looking for lib when found in first location. + if os.path.exists(os.path.join(run_path, lib)): + final_lib = os.path.abspath(os.path.join(run_path, lib)) + rslt.add(final_lib) + break + # Log error if no existing file found. + if not final_lib: + logger.error('Can not find path %s (needed by %s)', lib, pth) + + # Macholib has to be used to get absolute path to libraries. + else: + # macholib can't handle @loader_path. It has to be + # handled the same way as @executable_path. + # It is also replaced by 'exec_path'. + if lib.startswith('@loader_path'): + lib = lib.replace('@loader_path', '@executable_path') + try: + lib = dyld_find(lib, executable_path=exec_path) + rslt.add(lib) + except ValueError: + logger.error('Can not find path %s (needed by %s)', lib, pth) + + return rslt + + +def getImports(pth): + """ + Forwards to the correct getImports implementation for the platform. + """ + if is_win or is_cygwin: + if pth.lower().endswith(".manifest"): + return [] + try: + return _getImports_pe(pth) + except Exception, exception: + # Assemblies can pull in files which aren't necessarily PE, + # but are still needed by the assembly. Any additional binary + # dependencies should already have been handled by + # selectAssemblies in that case, so just warn, return an empty + # list and continue. + if logger.isEnabledFor(logging.WARN): + # logg excaption only if level >= warn + logger.warn('Can not get binary dependencies for file: %s', pth) + logger.exception(exception) + return [] + elif is_darwin: + return _getImports_macholib(pth) + else: + return _getImports_ldd(pth) + + +def findLibrary(name): + """ + Look for a library in the system. + + Emulate the algorithm used by dlopen. + `name`must include the prefix, e.g. ``libpython2.4.so`` + """ + assert is_unix, "Current implementation for Unix only (Linux, Solaris, AIX)" + + lib = None + + # Look in the LD_LIBRARY_PATH + lp = compat.getenv('LD_LIBRARY_PATH', '') + for path in lp.split(os.pathsep): + libs = glob(os.path.join(path, name + '*')) + if libs: + lib = libs[0] + break + + # Look in /etc/ld.so.cache + if lib is None: + expr = r'/[^\(\)\s]*%s\.[^\(\)\s]*' % re.escape(name) + m = re.search(expr, compat.exec_command('/sbin/ldconfig', '-p')) + if m: + lib = m.group(0) + + # Look in the known safe paths + if lib is None: + paths = ['/lib', '/usr/lib'] + if is_aix: + paths.append('/opt/freeware/lib') + for path in paths: + libs = glob(os.path.join(path, name + '*')) + if libs: + lib = libs[0] + break + + # give up :( + if lib is None: + return None + + # Resolve the file name into the soname + dir = os.path.dirname(lib) + return os.path.join(dir, getSoname(lib)) + + +def getSoname(filename): + """ + Return the soname of a library. + """ + cmd = ["objdump", "-p", "-j", ".dynamic", filename] + m = re.search(r'\s+SONAME\s+([^\s]+)', compat.exec_command(*cmd)) + if m: + return m.group(1) diff --git a/pyinstaller/PyInstaller/build.py b/pyinstaller/PyInstaller/build.py new file mode 100644 index 0000000..b368a44 --- /dev/null +++ b/pyinstaller/PyInstaller/build.py @@ -0,0 +1,1625 @@ +#!/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 py_compile +import imp +import tempfile +import UserList +import bindepend + +from PyInstaller.loader import archive, carchive + +import PyInstaller.depend.imptracker +import PyInstaller.depend.modules + +from PyInstaller import HOMEPATH, CONFIGDIR, PLATFORM +from PyInstaller import is_win, is_unix, is_aix, is_darwin, is_cygwin +from PyInstaller import is_py23, is_py24 +import PyInstaller.compat as compat + +from PyInstaller.compat import hashlib, set +from PyInstaller.depend import dylib +from PyInstaller.utils import misc + + +import PyInstaller.log as logging +if is_win: + from PyInstaller.utils import winmanifest + + +logger = logging.getLogger('PyInstaller.build') + +STRINGTYPE = type('') +TUPLETYPE = type((None,)) +UNCOMPRESSED, COMPRESSED = range(2) + +DEFAULT_BUILDPATH = os.path.join('SPECPATH', 'build', + 'pyi.TARGET_PLATFORM', 'SPECNAME') + +SPEC = None +SPECPATH = None +BUILDPATH = None +WARNFILE = None +NOCONFIRM = None + +# Some modules are included if they are detected at build-time or +# if a command-line argument is specified. (e.g. --ascii) +HIDDENIMPORTS = [] + +rthooks = {} + + +def _save_data(filename, data): + dirname = os.path.dirname(filename) + if not os.path.exists(dirname): + os.makedirs(dirname) + outf = open(filename, 'w') + pprint.pprint(data, outf) + outf.close() + + +def _load_data(filename): + return eval(open(filename, 'rU').read()) + + +def setupUPXFlags(): + f = compat.getenv("UPX", "") + if is_win and is_py24: + # 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 + compat.setenv("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 = os.path.join(BUILDPATH, "localpycos") + + new_toc = [] + for (nm, fnm, typ) in toc: + if typ != 'PYMODULE': + new_toc.append((nm, fnm, typ)) + continue + + # Trim the terminal "c" or "o" + source_fnm = fnm[:-1] + + # We need to perform a build ourselves if the source is newer + # than the compiled, or the compiled doesn't exist, or if it + # has been written by a different Python version. + needs_compile = (mtime(source_fnm) > mtime(fnm) + or + open(fnm, 'rb').read()[:4] != imp.get_magic()) + if needs_compile: + try: + py_compile.compile(source_fnm, fnm) + logger.debug("compiled %s", 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 = os.path.join(basepath, *leading) + + if not os.path.exists(leading): + os.makedirs(leading) + + fnm = os.path.join(leading, mod_name + ext) + needs_compile = (mtime(source_fnm) > mtime(fnm) + or + open(fnm, 'rb').read()[:4] != imp.get_magic()) + if needs_compile: + py_compile.compile(source_fnm, fnm) + logger.debug("compiled %s", source_fnm) + + new_toc.append((nm, fnm, typ)) + + return new_toc + + +def addSuffixToExtensions(toc): + """ + Returns a new TOC with proper library suffix for EXTENSION items. + """ + new_toc = TOC() + for inm, fnm, typ in toc: + if typ in ('EXTENSION', 'DEPENDENCY'): + binext = os.path.splitext(fnm)[1] + if not os.path.splitext(inm)[1] == binext: + inm = inm + binext + new_toc.append((inm, 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: + logger.info("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: + logger.info("building because %s changed", fnm) + return True + elif pyc and mtime(fnm[:-1]) > last_build: + logger.info("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)) + + +def _check_path_overlap(path): + """ + Check that path does not overlap with BUILDPATH or SPECPATH (i.e. + BUILDPATH and SPECPATH may not start with path, which could be + caused by a faulty hand-edited specfile) + + Raise SystemExit if there is overlap, return True otherwise + """ + specerr = 0 + if BUILDPATH.startswith(path): + logger.error('Specfile error: The output path "%s" contains ' + 'BUILDPATH (%s)', path, BUILDPATH) + specerr += 1 + if SPECPATH.startswith(path): + logger.error('Specfile error: The output path "%s" contains ' + 'SPECPATH (%s)', path, SPECPATH) + specerr += 1 + if specerr: + raise SystemExit('Error: Please edit/recreate the specfile (%s) ' + 'and set a different output name (e.g. "dist").' + % SPEC) + return True + + +def _rmtree(path): + """ + Remove directory and all its contents, but only after user confirmation, + or if the -y option is set + """ + if NOCONFIRM: + choice = 'y' + elif sys.stdout.isatty(): + choice = raw_input('WARNING: The output directory "%s" and ALL ITS ' + 'CONTENTS will be REMOVED! Continue? (y/n)' % path) + else: + raise SystemExit('Error: The output directory "%s" is not empty. ' + 'Please remove all its contents or use the ' + '-y option (remove output directory without ' + 'confirmation).' % path) + if choice.strip().lower() == 'y': + logger.info('Removing dir %s', path) + shutil.rmtree(path) + else: + raise SystemExit('User aborted') + + +def check_egg(pth): + """Check if path points to a file inside a python egg file (or to an egg + directly).""" + if is_py23: + if os.path.altsep: + pth = pth.replace(os.path.altsep, os.path.sep) + components = pth.split(os.path.sep) + sep = os.path.sep + else: + components = pth.replace("\\", "/").split("/") + sep = "/" + if is_win: + sep = "\\" + for i, name in zip(range(0, len(components)), components): + if name.lower().endswith(".egg"): + eggpth = sep.join(components[:i + 1]) + if os.path.isfile(eggpth): + # eggs can also be directories! + return True + return False + +#-- + + +class Target: + invcnum = 0 + + def __init__(self): + # Get a (per class) unique number to avoid conflicts between + # toc objects + self.invcnum = self.__class__.invcnum + self.__class__.invcnum += 1 + self.out = os.path.join(BUILDPATH, 'out%02d-%s.toc' % + (self.invcnum, self.__class__.__name__)) + self.outnm = os.path.basename(self.out) + self.dependencies = TOC() + + def __postinit__(self): + logger.info("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: + logger.info("building because %s %s", os.path.basename(self.out), missing) + return None + + if len(data) != len(self.GUTS): + logger.info("building because %s is bad", self.outnm) + return None + for i, (attr, func) in enumerate(self.GUTS): + 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): + _old_scripts = set(( + absnormpath(os.path.join(HOMEPATH, "support", "_mountzlib.py")), + absnormpath(os.path.join(CONFIGDIR, "support", "useUnicode.py")), + absnormpath(os.path.join(CONFIGDIR, "support", "useTK.py")), + absnormpath(os.path.join(HOMEPATH, "support", "useUnicode.py")), + absnormpath(os.path.join(HOMEPATH, "support", "useTK.py")), + absnormpath(os.path.join(HOMEPATH, "support", "unpackTK.py")), + absnormpath(os.path.join(HOMEPATH, "support", "removeTK.py")), + )) + + def __init__(self, scripts=None, pathex=None, hiddenimports=None, + hookspath=None, excludes=None): + Target.__init__(self) + # Include initialization Python code in PyInstaller analysis. + _init_code_path = os.path.join(HOMEPATH, 'PyInstaller', 'loader') + self.inputs = [ + os.path.join(HOMEPATH, "support", "_pyi_bootstrap.py"), + os.path.join(_init_code_path, 'archive.py'), + os.path.join(_init_code_path, 'carchive.py'), + os.path.join(_init_code_path, 'iu.py'), + ] + for script in scripts: + if absnormpath(script) in self._old_scripts: + logger.warn('Ignoring obsolete auto-added script %s', script) + continue + if not os.path.exists(script): + raise ValueError("script '%s' not found" % script) + self.inputs.append(script) + self.pathex = [] + if pathex: + self.pathex = [absnormpath(path) for path in pathex] + + self.hiddenimports = hiddenimports or [] + # Include modules detected at build time. Like 'codecs' and encodings. + self.hiddenimports.extend(HIDDENIMPORTS) + + self.hookspath = hookspath + self.excludes = excludes + self.scripts = TOC() + self.pure = TOC() + self.binaries = TOC() + self.zipfiles = TOC() + self.datas = TOC() + self.dependencies = 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), + ('hiddenimports', _check_guts_eq), + ) + + def check_guts(self, last_build): + if last_build == 0: + logger.info("building %s because %s non existent", self.__class__.__name__, self.outnm) + return True + for fnm in self.inputs: + if mtime(fnm) > last_build: + logger.info("building because %s changed", fnm) + return True + + data = Target.get_guts(self, last_build) + if not data: + return True + scripts, pure, binaries, zipfiles, datas, hiddenimports = data[-6:] + self.scripts = TOC(scripts) + self.pure = TOC(pure) + self.binaries = TOC(binaries) + self.zipfiles = TOC(zipfiles) + self.datas = TOC(datas) + self.hiddenimports = hiddenimports + return False + + def assemble(self): + logger.info("running Analysis %s", os.path.basename(self.out)) + # Reset seen variable to correctly discover dependencies + # if there are multiple Analysis in a single specfile. + bindepend.seen = {} + + python = sys.executable + if not is_win: + while os.path.islink(python): + python = os.path.join(os.path.dirname(python), os.readlink(python)) + depmanifest = None + else: + depmanifest = winmanifest.Manifest(type_="win32", name=specnm, + processorArchitecture=winmanifest.processor_architecture(), + version=(1, 0, 0, 0)) + depmanifest.filename = os.path.join(BUILDPATH, + specnm + ".exe.manifest") + + binaries = [] # binaries to bundle + + # Always add Python's dependencies first + # This ensures that its assembly depencies under Windows get pulled in + # first, so that .pyd files analyzed later which may not have their own + # manifest and may depend on DLLs which are part of an assembly + # referenced by Python's manifest, don't cause 'lib not found' messages + binaries.extend(bindepend.Dependencies([('', python, '')], + manifest=depmanifest)[1:]) + + ################################################### + # Scan inputs and prepare: + dirs = {} # input directories + pynms = [] # python filenames with no extension + for script in self.inputs: + if not os.path.exists(script): + raise SystemExit("Error: Analysis: script %s not found!" % script) + 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 importTracker and analyze scripts + importTracker = PyInstaller.depend.imptracker.ImportTracker( + dirs.keys() + self.pathex, self.hookspath, self.excludes) + PyInstaller.__pathex__ = self.pathex[:] + scripts = [] # will contain scripts to bundle + for i, script in enumerate(self.inputs): + logger.info("Analyzing %s", script) + importTracker.analyze_script(script) + scripts.append((pynms[i], script, 'PYSOURCE')) + PyInstaller.__pathex__ = [] + + # analyze the script's hidden imports + for modnm in self.hiddenimports: + if modnm in importTracker.modules: + logger.info("Hidden import %r has been found otherwise", modnm) + continue + logger.info("Analyzing hidden import %r", modnm) + importTracker.analyze_one(modnm) + if not modnm in importTracker.modules: + logger.error("Hidden import %r not found", modnm) + + ################################################### + # Fills pure, binaries and rthookcs lists to TOC + pure = [] # pure python modules + zipfiles = [] # zipfiles to bundle + datas = [] # datafiles to bundle + rthooks = [] # rthooks if needed + + # Find rthooks. + logger.info("Looking for run-time hooks") + for modnm, mod in importTracker.modules.items(): + rthooks.extend(_findRTHook(modnm)) + + # Analyze rthooks. Runtime hooks has to be also analyzed. + # Otherwise some dependencies could be missing. + # Data structure in format: + # ('rt_hook_mod_name', '/rt/hook/file/name.py', 'PYSOURCE') + for hook_mod, hook_file, mod_type in rthooks: + logger.info("Analyzing rthook %s", hook_file) + importTracker.analyze_script(hook_file) + + for modnm, mod in importTracker.modules.items(): + # FIXME: why can we have a mod == None here? + if mod is None: + continue + + datas.extend(mod.datas) + + if isinstance(mod, PyInstaller.depend.modules.BuiltinModule): + pass + elif isinstance(mod, PyInstaller.depend.modules.ExtensionModule): + binaries.append((mod.__name__, mod.__file__, 'EXTENSION')) + # allows hooks to specify additional dependency + # on other shared libraries loaded at runtime (by dlopen) + binaries.extend(mod.binaries) + elif isinstance(mod, (PyInstaller.depend.modules.PkgInZipModule, PyInstaller.depend.modules.PyInZipModule)): + zipfiles.append(("eggs/" + 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, mod.__file__, 'PYMODULE')) + + # Add remaining binary dependencies + binaries.extend(bindepend.Dependencies(binaries, + manifest=depmanifest)) + if is_win: + depmanifest.writeprettyxml() + self.fixMissingPythonLib(binaries) + if zipfiles: + scripts.insert(-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 + 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 = tuple([getattr(self, g[0]) for g in self.GUTS]) + if oldstuff != newstuff: + _save_data(self.out, newstuff) + wf = open(WARNFILE, 'w') + for ln in importTracker.getwarnings(): + wf.write(ln + '\n') + wf.close() + logger.info("Warnings written to %s", WARNFILE) + return 1 + logger.info("%s no change!", self.out) + 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. + + Darwin custom builds could possibly also have non-framework style libraries, + so this method also checks for that variant as well. + """ + + if is_aix: + # Shared libs on AIX are archives with shared object members, thus the ".a" suffix. + names = ('libpython%d.%d.a' % sys.version_info[:2],) + elif is_unix: + # Other *nix platforms. + names = ('libpython%d.%d.so' % sys.version_info[:2],) + elif is_darwin: + names = ('Python', 'libpython%d.%d.dylib' % sys.version_info[:2]) + else: + return + + for (nm, fnm, typ) in binaries: + for name in names: + if typ == 'BINARY' and name in fnm: + # lib found + return + + # Resume search using the first item in names. + name = names[0] + + logger.info('Looking for Python library %s', name) + + if is_unix: + lib = bindepend.findLibrary(name) + if lib is None: + raise IOError("Python library not found!") + + elif is_darwin: + # On MacPython, Analysis.assemble is able to find the libpython with + # no additional help, asking for sys.executable dependencies. + # However, this fails on system python, because the shared library + # is not listed as a dependency of the binary (most probably it's + # opened at runtime using some dlopen trickery). + # This happens on Mac OS X when Python is compiled as Framework. + + # Python compiled as Framework contains same values in sys.prefix + # and exec_prefix. That's why we can use just sys.prefix. + # In virtualenv PyInstaller is not able to find Python library. + # We need special care for this case. + if compat.is_virtualenv: + py_prefix = sys.real_prefix + else: + py_prefix = sys.prefix + + logger.info('Looking for Python library in %s', py_prefix) + + lib = os.path.join(py_prefix, name) + if not os.path.exists(lib): + raise IOError("Python library not found!") + + binaries.append((os.path.basename(lib), lib, 'BINARY')) + + +def _findRTHook(modnm): + rslt = [] + for script in rthooks.get(modnm) or []: + 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 + + +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' + # Level of zlib compression. + self.level = level + 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), + ('crypt', _check_guts_eq), + ('toc', _check_guts_toc), # todo: pyc=1 + ) + + def check_guts(self, last_build): + if not os.path.exists(self.name): + logger.info("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): + logger.info("building PYZ %s", 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 = hashlib.md5(data).digest() + return digest + + +def checkCache(fnm, strip=0, upx=0, dist_nm=None): + """ + Cache prevents preprocessing binary files again and again. + + 'dist_nm' Filename relative to dist directory. We need it on Mac + to determine level of paths for @loader_path like + '@loader_path/../../' for qt4 plugins. + """ + # On darwin a cache is required anyway to keep the libaries + # with relative install names. Caching on darwin does not work + # since we need to modify binary headers to use relative paths + # to dll depencies and starting with '@loader_path'. + + if ((not strip and not upx and not is_darwin and not is_win) + or fnm.lower().endswith(".manifest")): + return fnm + if strip: + strip = 1 + else: + strip = 0 + if upx: + upx = 1 + else: + upx = 0 + + # Load cache index + # Make cachedir per Python major/minor version. + # This allows parallel building of executables with different + # Python versions as one user. + pyver = ('py%d%s') % (sys.version_info[0], sys.version_info[1]) + cachedir = os.path.join(CONFIGDIR, 'bincache%d%d_%s' % (strip, upx, pyver)) + 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 basenm in cache_index: + if digest != cache_index[basenm]: + os.remove(cachedfile) + else: + # On Mac OS X we need relative paths to dll dependencies + # starting with @executable_path + if is_darwin: + dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm) + return cachedfile + if upx: + if strip: + fnm = checkCache(fnm, 1, 0) + bestopt = "--best" + # FIXME: Linux builds of UPX do not seem to contain LZMA (they assert out) + # A better configure-time check is due. + if config["hasUPX"] >= (3,) and os.name == "nt": + bestopt = "--lzma" + + upx_executable = "upx" + if config.get('upx_dir'): + upx_executable = os.path.join(config['upx_dir'], upx_executable) + cmd = [upx_executable, bestopt, "-q", cachedfile] + else: + if strip: + # -S = strip only debug symbols. + # The default strip behaviour breaks some shared libraries + # under Mac OSX + cmd = ["strip", "-S", cachedfile] + shutil.copy2(fnm, cachedfile) + os.chmod(cachedfile, 0755) + + if pyasm and fnm.lower().endswith(".pyd"): + # If python.exe has dependent assemblies, check for embedded manifest + # of cached pyd file because we may need to 'fix it' for pyinstaller + try: + res = winmanifest.GetManifestResources(os.path.abspath(cachedfile)) + except winresource.pywintypes.error, e: + if e.args[0] == winresource.ERROR_BAD_EXE_FORMAT: + # Not a win32 PE file + pass + else: + logger.error(os.path.abspath(cachedfile)) + raise + else: + if winmanifest.RT_MANIFEST in res and len(res[winmanifest.RT_MANIFEST]): + for name in res[winmanifest.RT_MANIFEST]: + for language in res[winmanifest.RT_MANIFEST][name]: + try: + manifest = winmanifest.Manifest() + manifest.filename = ":".join([cachedfile, + str(winmanifest.RT_MANIFEST), + str(name), + str(language)]) + manifest.parse_string(res[winmanifest.RT_MANIFEST][name][language], + False) + except Exception, exc: + logger.error("Cannot parse manifest resource %s, " + "%s from", name, language) + logger.error(cachedfile) + logger.exception(exc) + else: + # Fix the embedded manifest (if any): + # Extension modules built with Python 2.6.5 have + # an empty element, we need to add + # dependentAssemblies from python.exe for + # pyinstaller + olen = len(manifest.dependentAssemblies) + _depNames = set([dep.name for dep in + manifest.dependentAssemblies]) + for pydep in pyasm: + if not pydep.name in _depNames: + logger.info("Adding %r to dependent " + "assemblies of %r", + pydep.name, cachedfile) + manifest.dependentAssemblies.append(pydep) + _depNames.update(pydep.name) + if len(manifest.dependentAssemblies) > olen: + try: + manifest.update_resources(os.path.abspath(cachedfile), + [name], + [language]) + except Exception, e: + logger.error(os.path.abspath(cachedfile)) + raise + + if cmd: + try: + compat.exec_command(*cmd) + except OSError, e: + raise SystemExit("Execution failed: %s" % e) + + # update cache index + cache_index[basenm] = digest + _save_data(cacheindexfn, cache_index) + + # On Mac OS X we need relative paths to dll dependencies + # starting with @executable_path + if is_darwin: + dylib.mac_set_relative_dylib_deps(cachedfile, dist_nm) + 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', + 'DEPENDENCY': 'd'} + + 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: + 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 + 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): + logger.info("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): + logger.info("building PKG %s", os.path.basename(self.name)) + trash = [] + mytoc = [] + seen = {} + toc = addSuffixToExtensions(self.toc) + for inm, fnm, typ in toc: + if not os.path.isfile(fnm) and check_egg(fnm): + # file is contained within python egg, it is added with the egg + continue + if typ in ('BINARY', 'EXTENSION', 'DEPENDENCY'): + if self.exclude_binaries and typ != 'DEPENDENCY': + self.dependencies.append((inm, fnm, typ)) + else: + fnm = checkCache(fnm, self.strip_binaries, + self.upx_binaries and (is_win or is_cygwin) + and config['hasUPX'], dist_nm=inm) + # 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 fnm in seen: + 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.manifest = kws.get('manifest', None) + self.resources = kws.get('resources', []) + 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 is_win or is_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) + if is_win: + filename = os.path.join(BUILDPATH, specnm + ".exe.manifest") + self.manifest = winmanifest.create_manifest(filename, self.manifest, + self.console) + self.toc.append((os.path.basename(self.name) + ".manifest", filename, + 'BINARY')) + 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), + ('resources', _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): + logger.info("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): + logger.info("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, resources = data[3:6] + if (icon or versrsrc or resources) and not config['hasRsrcUpdate']: + # todo: really ignore :-) + logger.info("ignoring icon, version, manifest and resources = platform not capable") + + mtm = data[-1] + crypt = data[-2] + if crypt != self.crypt: + logger.info("rebuilding %s because crypt option changed", self.outnm) + return 1 + if mtm != mtime(self.name): + logger.info("rebuilding %s because mtimes don't match", self.outnm) + return True + if mtm < mtime(self.pkg.out): + logger.info("rebuilding %s because pkg is more recent", self.outnm) + return True + + return False + + def _bootloader_file(self, exe): + if not self.console: + exe = exe + 'w' + if self.debug: + exe = exe + '_d' + return os.path.join("support", "loader", PLATFORM, exe) + + def assemble(self): + logger.info("building EXE from %s", 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_file('run') + exe = os.path.join(HOMEPATH, exe) + if is_win or is_cygwin: + exe = exe + '.exe' + if config['hasRsrcUpdate'] and (self.icon or self.versrsrc or + self.resources): + tmpnm = tempfile.mktemp() + shutil.copy2(exe, tmpnm) + os.chmod(tmpnm, 0755) + if self.icon: + icon.CopyIcons(tmpnm, self.icon) + if self.versrsrc: + versioninfo.SetVersion(tmpnm, self.versrsrc) + for res in self.resources: + res = res.split(",") + for i in range(1, len(res)): + try: + res[i] = int(res[i]) + except ValueError: + pass + resfile = res[0] + restype = resname = reslang = None + if len(res) > 1: + restype = res[1] + if len(res) > 2: + resname = res[2] + if len(res) > 3: + reslang = res[3] + try: + winresource.UpdateResourcesFromResFile(tmpnm, resfile, + [restype or "*"], + [resname or "*"], + [reslang or "*"]) + except winresource.pywintypes.error, exc: + if exc.args[0] != winresource.ERROR_BAD_EXE_FORMAT: + logger.exception(exc) + continue + if not restype or not resname: + logger.error("resource type and/or name not specified") + continue + if "*" in (restype, resname): + logger.error("no wildcards allowed for resource type " + "and name when source file does not " + "contain resources") + continue + try: + winresource.UpdateResourcesFromDataFile(tmpnm, + resfile, + restype, + [resname], + [reslang or 0]) + except winresource.pywintypes.error, exc: + logger.exception(exc) + trash.append(tmpnm) + exe = tmpnm + exe = checkCache(exe, self.strip, self.upx and config['hasUPX']) + self.copy(exe, outf) + if self.append_pkg: + logger.info("Appending archive to EXE %s", self.name) + self.copy(self.pkg.name, outf) + else: + logger.info("Copying archive to %s", self.pkgname) + shutil.copy2(self.pkg.name, self.pkgname) + outf.close() + os.chmod(self.name, 0755) + guts = (self.name, self.console, self.debug, self.icon, + self.versrsrc, self.resources, self.strip, self.upx, + self.crypt, mtime(self.name)) + assert len(guts) == len(self.GUTS) + _save_data(self.out, guts) + 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): + logger.info("building DLL %s", os.path.basename(self.out)) + outf = open(self.name, 'wb') + dll = self._bootloader_file('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.manifest, self.resources, 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): + for tocnm, fnm, typ in arg.toc: + if tocnm == os.path.basename(arg.name) + ".manifest": + self.toc.append((tocnm, fnm, typ)) + if 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): + # COLLECT always needs to be executed, since it will clean the output + # directory anyway to make sure there is no existing cruft accumulating + return 1 + + def assemble(self): + if _check_path_overlap(self.name) and os.path.isdir(self.name): + _rmtree(self.name) + logger.info("building COLLECT %s", os.path.basename(self.out)) + os.makedirs(self.name) + toc = addSuffixToExtensions(self.toc) + for inm, fnm, typ in toc: + if not os.path.isfile(fnm) and check_egg(fnm): + # file is contained within python egg, it is added with the egg + continue + 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 (is_win or is_cygwin) + and config['hasUPX'], dist_nm=inm) + if typ != 'DEPENDENCY': + 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 BUNDLE(Target): + def __init__(self, *args, **kws): + + # BUNDLE only has a sense under Mac OS X, it's a noop on other platforms + if not is_darwin: + return + + # icns icon for app bundle. + self.icon = kws.get('icon', os.path.join(os.path.dirname(__file__), + '..', 'source', 'images', 'icon-windowed.icns')) + + Target.__init__(self) + self.name = kws.get('name', None) + if self.name is not None: + self.appname = os.path.splitext(os.path.basename(self.name))[0] + self.version = kws.get("version", "0.0.0") + self.toc = TOC() + for arg in args: + if isinstance(arg, EXE): + self.toc.append((os.path.basename(arg.name), arg.name, arg.typ)) + self.toc.extend(arg.dependencies) + elif isinstance(arg, TOC): + self.toc.extend(arg) + elif isinstance(arg, COLLECT): + self.toc.extend(arg.toc) + else: + logger.info("unsupported entry %s", arg.__class__.__name__) + # Now, find values for app filepath (name), app name (appname), and name + # of the actual executable (exename) from the first EXECUTABLE item in + # toc, which might have come from a COLLECT too (not from an EXE). + for inm, name, typ in self.toc: + if typ == "EXECUTABLE": + self.exename = name + if self.name is None: + self.appname = "Mac%s" % (os.path.splitext(inm)[0],) + self.name = os.path.join(SPECPATH, self.appname + ".app") + else: + self.name = os.path.join(SPECPATH, self.name) + break + self.__postinit__() + + GUTS = (('toc', _check_guts_eq), # additional check below + ) + + def check_guts(self, last_build): + # BUNDLE always needs to be executed, since it will clean the output + # directory anyway to make sure there is no existing cruft accumulating + return 1 + + def assemble(self): + if _check_path_overlap(self.name) and os.path.isdir(self.name): + _rmtree(self.name) + logger.info("building BUNDLE %s", os.path.basename(self.out)) + + # Create a minimal Mac bundle structure + os.makedirs(os.path.join(self.name, "Contents", "MacOS")) + os.makedirs(os.path.join(self.name, "Contents", "Resources")) + os.makedirs(os.path.join(self.name, "Contents", "Frameworks")) + + # Copy icns icon to Resources directory. + if os.path.exists(self.icon): + shutil.copy(self.icon, os.path.join(self.name, 'Contents', 'Resources')) + else: + logger.warn("icon not found %s" % self.icon) + + # Key/values for a minimal Info.plist file + info_plist_dict = {"CFBundleDisplayName": self.appname, + "CFBundleName": self.appname, + # Fix for #156 - 'MacOS' must be in the name - not sure why + "CFBundleExecutable": 'MacOS/%s' % os.path.basename(self.exename), + "CFBundleIconFile": os.path.basename(self.icon), + "CFBundleInfoDictionaryVersion": "6.0", + "CFBundlePackageType": "APPL", + "CFBundleShortVersionString": self.version, + + # Setting this to 1 will cause Mac OS X *not* to show + # a dock icon for the PyInstaller process which + # decompresses the real executable's contents. As a + # side effect, the main application doesn't get one + # as well, but at startup time the loader will take + # care of transforming the process type. + "LSBackgroundOnly": "1", + + } + info_plist = """ + + +""" + for k, v in info_plist_dict.items(): + info_plist += "%s\n%s\n" % (k, v) + info_plist += """ +""" + f = open(os.path.join(self.name, "Contents", "Info.plist"), "w") + f.write(info_plist) + f.close() + + toc = addSuffixToExtensions(self.toc) + for inm, fnm, typ in toc: + # Copy files from cache. This ensures that are used files with relative + # paths to dynamic library dependencies (@executable_path) + if typ in ('EXTENSION', 'BINARY'): + fnm = checkCache(fnm, dist_nm=inm) + tofnm = os.path.join(self.name, "Contents", "MacOS", inm) + todir = os.path.dirname(tofnm) + if not os.path.exists(todir): + os.makedirs(todir) + shutil.copy2(fnm, tofnm) + + ## For some hooks copy resource to ./Contents/Resources dir. + # PyQt4 hook: On Mac Qt requires resources 'qt_menu.nib'. + # It is copied from dist directory. + qt_menu_dir = os.path.join(self.name, 'Contents', 'MacOS', 'qt_menu.nib') + qt_menu_dest = os.path.join(self.name, 'Contents', 'Resources', 'qt_menu.nib') + if os.path.exists(qt_menu_dir): + shutil.copytree(qt_menu_dir, qt_menu_dest) + + 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: + logger.info("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: + logger.info("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): + logger.info("building Tree %s", 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 + logger.info("%s no change!", self.out) + return 0 + + +class MERGE(object): + """ + Merge repeated dependencies from other executables into the first + execuable. Data and binary files are then present only once and some + disk space is thus reduced. + """ + def __init__(self, *args): + """ + Repeated dependencies are then present only once in the first + executable in the 'args' list. Other executables depend on the + first one. Other executables have to extract necessary files + from the first executable. + + args dependencies in a list of (Analysis, id, filename) tuples. + Replace id with the correct filename. + """ + # The first Analysis object with all dependencies. + # Any item from the first executable cannot be removed. + self._main = None + + self._dependencies = {} + + self._id_to_path = {} + for _, i, p in args: + self._id_to_path[i] = p + + # Get the longest common path + self._common_prefix = os.path.dirname(os.path.commonprefix([os.path.abspath(a.scripts[-1][1]) for a, _, _ in args])) + if self._common_prefix[-1] != os.sep: + self._common_prefix += os.sep + logger.info("Common prefix: %s", self._common_prefix) + + self._merge_dependencies(args) + + def _merge_dependencies(self, args): + """ + Filter shared dependencies to be only in first executable. + """ + for analysis, _, _ in args: + path = os.path.abspath(analysis.scripts[-1][1]).replace(self._common_prefix, "", 1) + path = os.path.splitext(path)[0] + if path in self._id_to_path: + path = self._id_to_path[path] + self._set_dependencies(analysis, path) + + def _set_dependencies(self, analysis, path): + """ + Syncronize the Analysis result with the needed dependencies. + """ + for toc in (analysis.binaries, analysis.datas): + for i, tpl in enumerate(toc): + if not tpl[1] in self._dependencies.keys(): + logger.debug("Adding dependency %s located in %s" % (tpl[1], path)) + self._dependencies[tpl[1]] = path + else: + dep_path = self._get_relative_path(path, self._dependencies[tpl[1]]) + logger.debug("Referencing %s to be a dependecy for %s, located in %s" % (tpl[1], path, dep_path)) + analysis.dependencies.append((":".join((dep_path, tpl[0])), tpl[1], "DEPENDENCY")) + toc[i] = (None, None, None) + # Clean the list + toc[:] = [tpl for tpl in toc if tpl != (None, None, None)] + + # TODO move this function to PyInstaller.compat module (probably improve + # function compat.relpath() + def _get_relative_path(self, startpath, topath): + start = startpath.split(os.sep)[:-1] + start = ['..'] * len(start) + if start: + start.append(topath) + return os.sep.join(start) + else: + return topath + + +def TkTree(): + raise SystemExit('TkTree has been removed in PyInstaller 2.0. ' + 'Please update your spec-file. See ' + 'http://www.pyinstaller.org/wiki/MigrateTo2.0 for details') + + +def TkPKG(): + raise SystemExit('TkPKG has been removed in PyInstaller 2.0. ' + 'Please update your spec-file. See ' + 'http://www.pyinstaller.org/wiki/MigrateTo2.0 for details') + + +def build(spec, buildpath): + global SPECPATH, BUILDPATH, WARNFILE, rthooks, SPEC, specnm + rthooks = _load_data(os.path.join(HOMEPATH, 'support', 'rthooks.dat')) + SPEC = spec + SPECPATH, specnm = os.path.split(spec) + specnm = os.path.splitext(specnm)[0] + if SPECPATH == '': + SPECPATH = os.getcwd() + BUILDPATH = os.path.join(SPECPATH, 'build', + "pyi." + sys.platform, specnm) + # Check and adjustment for build path + if buildpath != DEFAULT_BUILDPATH: + bpath = buildpath + if os.path.isabs(bpath): + BUILDPATH = bpath + else: + BUILDPATH = os.path.join(SPECPATH, bpath) + WARNFILE = os.path.join(BUILDPATH, 'warn%s.txt' % specnm) + if not os.path.exists(BUILDPATH): + os.makedirs(BUILDPATH) + # Executing the specfile (it's a valid python file) + execfile(spec) + + +def __add_options(parser): + parser.add_option('--buildpath', default=DEFAULT_BUILDPATH, + help='Buildpath (default: %default)') + parser.add_option('-y', '--noconfirm', + action="store_true", default=False, + help='Remove output directory (default: %s) without ' + 'confirmation' % os.path.join('SPECPATH', 'dist', 'SPECNAME')) + parser.add_option('--upx-dir', default=None, + help='Directory containing UPX (default: search in path)') + parser.add_option("-a", "--ascii", action="store_true", + help="do NOT include unicode encodings " + "(default: included if available)") + + +def main(specfile, buildpath, noconfirm, ascii=False, **kw): + global config + global icon, versioninfo, winresource, winmanifest, pyasm + global HIDDENIMPORTS, NOCONFIRM + NOCONFIRM = noconfirm + + # Test unicode support. + if not ascii: + HIDDENIMPORTS.extend(misc.get_unicode_modules()) + + # FIXME: this should be a global import, but can't due to recursive imports + import PyInstaller.configure as configure + config = configure.get_config(kw.get('upx_dir')) + + if config['hasRsrcUpdate']: + from PyInstaller.utils import icon, versioninfo, winresource + pyasm = bindepend.getAssemblies(sys.executable) + else: + pyasm = None + + if config['hasUPX']: + setupUPXFlags() + + if not config['useELFEXE']: + EXE.append_pkg = 0 + + build(specfile, buildpath) diff --git a/pyinstaller/PyInstaller/compat.py b/pyinstaller/PyInstaller/compat.py new file mode 100644 index 0000000..d607415 --- /dev/null +++ b/pyinstaller/PyInstaller/compat.py @@ -0,0 +1,335 @@ +# +# Various classes and functions to provide some backwards-compatibility +# with previous versions of Python from 2.3 onward. +# +# Copyright (C) 2011, Martin Zibricky +# +# 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 dircache # Module removed in Python 3 +import os +import sys + +try: + import subprocess +except ImportError: + # :todo: remove when dropping Python 2.3 compatibility + # fall back to out version of `subprocess` + import PyInstaller.lib.__subprocess as subprocess + + +is_py23 = sys.version_info >= (2, 3) +is_py24 = sys.version_info >= (2, 4) +is_py25 = sys.version_info >= (2, 5) +is_py26 = sys.version_info >= (2, 6) +is_py27 = sys.version_info >= (2, 7) + +is_win = sys.platform.startswith('win') +is_cygwin = sys.platform == 'cygwin' +is_darwin = sys.platform == 'darwin' # Mac OS X + +# Unix platforms +is_linux = sys.platform.startswith('linux') +is_solar = sys.platform.startswith('sun') # Solaris +is_aix = sys.platform.startswith('aix') + +# Some code parts are similar to several unix platforms +# (e.g. Linux, Solaris, AIX) +# Mac OS X is not considered as unix since there are many +# platform specific details for Mac in PyInstaller. +is_unix = is_linux or is_solar or is_aix + + +# In debug mode a .log file is written. +if __debug__: + import UserDict + + class LogDict(UserDict.UserDict): + count = 0 + + def __init__(self, *args): + UserDict.UserDict.__init__(self, *args) + LogDict.count += 1 + logfile = "logdict%s-%d.log" % (".".join(map(str, sys.version_info)), + LogDict.count) + if os.path.isdir("build"): + logfile = os.path.join("build", logfile) + self.logfile = open(logfile, "w") + + def __setitem__(self, key, value): + self.logfile.write("%s: %s -> %s\n" % (key, self.data.get(key), value)) + UserDict.UserDict.__setitem__(self, key, value) + + def __delitem__(self, key): + self.logfile.write(" DEL %s\n" % key) + UserDict.UserDict.__delitem__(self, key) +else: + LogDict = dict + + +# Correct extension ending: 'c' or 'o' +if __debug__: + PYCO = 'c' +else: + PYCO = 'o' + + +# os.devnull is available since Python 2.4+. +if hasattr(os, 'devnull'): + devnull = os.devnull +else: + if is_win: + devnull = 'nul' + else: + devnull = '/dev/null' + + +# If ctypes is present, specific dependency discovery can be enabled. +try: + import ctypes +except ImportError: + ctypes = None + + +if 'PYTHONCASEOK' not in os.environ: + def caseOk(filename): + files = dircache.listdir(os.path.dirname(filename)) + return os.path.basename(filename) in files +else: + def caseOk(filename): + return True + + +# Obsolete command line options (do not exist anymore). +_OLD_OPTIONS = [ + '--upx', '-X', + '-K', '--tk', + '-C', '--configfile', + '--skip-configure', + ] + + +# Options for python interpreter when invoked in a subprocess. +_PYOPTS = __debug__ and '-O' or '' + + +try: + # Python 2.5+ + import hashlib +except ImportError: + class hashlib(object): + from md5 import new as md5 + from sha import new as sha + + +# In Python 2.4+ there is a builtin type set(). In Python 2.3 +# it is class Set in module sets. +try: + from __builtin__ import set +except ImportError: + from sets import Set as set + + +# Function os.path.relpath() available in Python 2.6+. +if hasattr(os.path, 'relpath'): + from os.path import relpath +# Own implementation of relpath function. +else: + def relpath(path, start=os.curdir): + """ + Return a relative version of a path. + """ + if not path: + raise ValueError("no path specified") + # Normalize paths. + path = os.path.normpath(path) + start = os.path.abspath(start) + os.sep # os.sep has to be here. + # Get substring. + relative = path[len(start):len(path)] + return relative + + +# Some code parts needs to behave different when running in virtualenv. +is_virtualenv = hasattr(sys, 'real_prefix') + + +def architecture(): + """ + Returns the bit depth of the python interpreter's architecture as + a string ('32bit' or '64bit'). Similar to platform.architecture(), + but with fixes for universal binaries on MacOS. + """ + import platform + if is_darwin: + # Darwin's platform.architecture() is buggy and always + # returns "64bit" event for the 32bit version of Python's + # universal binary. So we roll out our own (that works + # on Darwin). + if sys.maxint > 2L ** 32: + return '64bit' + else: + return '32bit' + else: + return platform.architecture()[0] + + +def system(): + import platform + # On some Windows installation (Python 2.4) platform.system() is + # broken and incorrectly returns 'Microsoft' instead of 'Windows'. + # http://mail.python.org/pipermail/patches/2007-June/022947.html + syst = platform.system() + if syst == 'Microsoft': + return 'Windows' + return syst + + +# Set and get environment variables does not handle unicode strings correctly +# on Windows. + +# Acting on os.environ instead of using getenv()/setenv()/unsetenv(), +# as suggested in : +# "Calling putenv() directly does not change os.environ, so it's +# better to modify os.environ." (Same for unsetenv.) + +def getenv(name, default=None): + """ + Returns unicode string containing value of environment variable 'name'. + """ + return os.environ.get(name, default) + + +def setenv(name, value): + """ + Accepts unicode string and set it as environment variable 'name' containing + value 'value'. + """ + os.environ[name] = value + + +def unsetenv(name): + """ + Delete the environment variable 'name'. + """ + # Some platforms (e.g. AIX) do not support `os.unsetenv()` and + # thus `del os.environ[name]` has no effect onto the real + # environment. For this case we set the value to the empty string. + os.environ[name] = "" + del os.environ[name] + + +# Exec commands in subprocesses. + + +def exec_command(*cmdargs): + """ + Wrap creating subprocesses + + Return stdout of the invoked command. + Todo: Use module `subprocess` if available, else `os.system()` + """ + return subprocess.Popen(cmdargs, stdout=subprocess.PIPE).communicate()[0] + + +def exec_command_rc(*cmdargs, **kwargs): + """ + Wrap creating subprocesses. + + Return exit code of the invoked command. + Todo: Use module `subprocess` if available, else `os.system()` + """ + return subprocess.call(cmdargs, **kwargs) + + +def exec_command_all(*cmdargs, **kwargs): + """ + Wrap creating subprocesses + + Return tuple (exit_code, stdout, stderr) of the invoked command. + """ + proc = subprocess.Popen(cmdargs, bufsize=-1, # Default OS buffer size. + stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) + # Waits for subprocess to complete. + out, err = proc.communicate() + + return proc.returncode, out, err + + +def __wrap_python(args, kwargs): + cmdargs = [sys.executable] + + # Mac OS X supports universal binaries (binary for multiple architectures. + # We need to ensure that subprocess binaries are running for the same + # architecture as python executable. + # It is necessary to run binaries with 'arch' command. + if is_darwin: + mapping = {'32bit': '-i386', '64bit': '-x86_64'} + py_prefix = ['arch', mapping[architecture()]] + cmdargs = py_prefix + cmdargs + + if _PYOPTS: + cmdargs.append(_PYOPTS) + + cmdargs.extend(args) + return cmdargs, kwargs + + +def exec_python(*args, **kwargs): + """ + Wrap running python script in a subprocess. + + Return stdout of the invoked command. + """ + cmdargs, kwargs = __wrap_python(args, kwargs) + return exec_command(*cmdargs, **kwargs) + + +def exec_python_rc(*args, **kwargs): + """ + Wrap running python script in a subprocess. + + Return exit code of the invoked command. + """ + cmdargs, kwargs = __wrap_python(args, kwargs) + return exec_command_rc(*cmdargs, **kwargs) + + +def exec_python_all(*args, **kwargs): + """ + Wrap running python script in a subprocess. + + Return tuple (exit_code, stdout, stderr) of the invoked command. + """ + cmdargs, kwargs = __wrap_python(args, kwargs) + return exec_command_all(*cmdargs, **kwargs) + + +# Obsolete command line options. + + +def __obsolete_option(option, opt, value, parser): + parser.error('%s option does not exist anymore (obsolete).' % opt) + + +def __add_obsolete_options(parser): + """ + Add the obsolete options to a option-parser instance and + print error message when they are present. + """ + g = parser.add_option_group('Obsolete options (not used anymore)') + g.add_option(*_OLD_OPTIONS, + **{'action': 'callback', + 'callback': __obsolete_option, + 'help': 'These options do not exist anymore.'}) diff --git a/pyinstaller/PyInstaller/configure.py b/pyinstaller/PyInstaller/configure.py new file mode 100644 index 0000000..1ef22cb --- /dev/null +++ b/pyinstaller/PyInstaller/configure.py @@ -0,0 +1,174 @@ +#! /usr/bin/env python +# +# Configure PyInstaller for the current Python installation. +# +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 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 os +import sys +import shutil +import re +import time +import inspect + +from PyInstaller import HOMEPATH, PLATFORM +from PyInstaller import is_win, is_unix, is_darwin, is_py24, get_version + +import PyInstaller.build as build +import PyInstaller.compat as compat + +import PyInstaller.log as logging +import PyInstaller.depend.modules +import PyInstaller.depend.imptracker + +logger = logging.getLogger('PyInstaller.configure') + + +def test_Crypt(config): + # TODO: disabled for now + config["useCrypt"] = 0 + return + + #Crypt support. We need to build the AES module and we'll use distutils + # for that. FIXME: the day we'll use distutils for everything this will be + # a solved problem. + logger.info("trying to build crypt support...") + from distutils.core import run_setup + cwd = os.getcwd() + args = sys.argv[:] + try: + os.chdir(os.path.join(HOMEPATH, "source", "crypto")) + dist = run_setup("setup.py", ["install"]) + if dist.have_run.get("install", 0): + config["useCrypt"] = 1 + logger.info("... crypto support available") + else: + config["useCrypt"] = 0 + logger.info("... error building crypto support") + finally: + os.chdir(cwd) + sys.argv = args + + +def test_RsrcUpdate(config): + config['hasRsrcUpdate'] = 0 + if not is_win: + return + # only available on windows + logger.info("Testing for ability to set icons, version resources...") + try: + import win32api + from PyInstaller.utils import icon, versioninfo + except ImportError, detail: + logger.info('... resource update unavailable - %s', detail) + return + + test_exe = os.path.join(HOMEPATH, 'support', 'loader', PLATFORM, 'runw.exe') + if not os.path.exists(test_exe): + config['hasRsrcUpdate'] = 0 + logger.error('... resource update unavailable - %s not found', test_exe) + return + + # The test_exe may be read-only + # make a writable copy and test using that + rw_test_exe = os.path.join(compat.getenv('TEMP'), 'me_test_exe.tmp') + shutil.copyfile(test_exe, rw_test_exe) + try: + hexe = win32api.BeginUpdateResource(rw_test_exe, 0) + except: + logger.info('... resource update unavailable - win32api.BeginUpdateResource failed') + else: + win32api.EndUpdateResource(hexe, 1) + config['hasRsrcUpdate'] = 1 + logger.info('... resource update available') + os.remove(rw_test_exe) + + +def test_UPX(config, upx_dir): + logger.debug('Testing for UPX ...') + cmd = "upx" + if upx_dir: + cmd = os.path.normpath(os.path.join(upx_dir, cmd)) + + hasUPX = 0 + try: + vers = compat.exec_command(cmd, '-V').strip().splitlines() + if vers: + v = vers[0].split()[1] + hasUPX = tuple(map(int, v.split("."))) + if is_win and is_py24 and hasUPX < (1, 92): + logger.error('UPX is too old! Python 2.4 under Windows requires UPX 1.92+') + hasUPX = 0 + except Exception, e: + if isinstance(e, OSError) and e.errno == 2: + # No such file or directory + pass + else: + logger.info('An exception occured when testing for UPX:') + logger.info(' %r', e) + if hasUPX: + is_available = 'available' + else: + is_available = 'not available' + logger.info('UPX is %s.', is_available) + config['hasUPX'] = hasUPX + config['upx_dir'] = upx_dir + + +def find_PYZ_dependencies(config): + logger.debug("Computing PYZ dependencies") + # We need to import `archive` from `PyInstaller` directory, but + # not from package `PyInstaller` + import PyInstaller.loader + a = PyInstaller.depend.imptracker.ImportTracker([ + os.path.dirname(inspect.getsourcefile(PyInstaller.loader)), + os.path.join(HOMEPATH, 'support')]) + + a.analyze_r('archive') + mod = a.modules['archive'] + toc = build.TOC([(mod.__name__, mod.__file__, 'PYMODULE')]) + for i, (nm, fnm, typ) in enumerate(toc): + mod = a.modules[nm] + tmp = [] + for importednm, isdelayed, isconditional, level in mod.imports: + if not isconditional: + realnms = a.analyze_one(importednm, nm) + for realnm in realnms: + imported = a.modules[realnm] + if not isinstance(imported, PyInstaller.depend.modules.BuiltinModule): + tmp.append((imported.__name__, imported.__file__, imported.typ)) + toc.extend(tmp) + toc.reverse() + config['PYZ_dependencies'] = toc.data + + +def get_config(upx_dir, **kw): + if is_darwin and compat.architecture() == '64bit': + logger.warn('You are running 64-bit Python: created binaries will only' + ' work on Mac OS X 10.6+.\nIf you need 10.4-10.5 compatibility,' + ' run Python as a 32-bit binary with this command:\n\n' + ' VERSIONER_PYTHON_PREFER_32_BIT=yes arch -i386 %s\n' % sys.executable) + # wait several seconds for user to see this message + time.sleep(4) + + # if not set by Make.py we can assume Windows + config = {'useELFEXE': 1} + test_Crypt(config) + test_RsrcUpdate(config) + test_UPX(config, upx_dir) + find_PYZ_dependencies(config) + return config diff --git a/pyinstaller/PyInstaller/depend/.svn/entries b/pyinstaller/PyInstaller/depend/.svn/entries new file mode 100644 index 0000000..8c1b576 --- /dev/null +++ b/pyinstaller/PyInstaller/depend/.svn/entries @@ -0,0 +1,77 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/depend +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +modules.py +file + + + +add + +owner.py +file + + + +add + +__init__.py +file + + + +add + +utils.py +file + + + +add + +impdirector.py +file + + + +add + +imptracker.py +file + + + +add + +dylib.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/depend/__init__.py b/pyinstaller/PyInstaller/depend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/depend/dylib.py b/pyinstaller/PyInstaller/depend/dylib.py new file mode 100644 index 0000000..0b53ca6 --- /dev/null +++ b/pyinstaller/PyInstaller/depend/dylib.py @@ -0,0 +1,242 @@ +# +# Copyright (C) 2005-2011, Giovanni Bajo +# Based on previous work under copyright (c) 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 + +# Note also that you should check the results to make sure that the +# dlls are redistributable. I've listed most of the common MS dlls +# under "excludes" below; add to this list as necessary (or use the +# "excludes" option in the INSTALL section of the config file). + +""" +Manipulating with dynamic libraries. +""" + + +__all__ = ['exclude_list', 'include_list', 'include_library'] + +import os +import re + +from PyInstaller import is_win, is_unix, is_aix, is_darwin +from PyInstaller.compat import set + + +import PyInstaller.log as logging +logger = logging.getLogger('PyInstaller.build.dylib') + + +_BOOTLOADER_FNAMES = set(['run', 'run_d', 'runw', 'runw_d']) + + +# Regex excludes +# Ignoring some system libraries speeds up packaging process +_excludes = {} +# Regex includes - overrides excludes. +# Include list is used only to override specific libraries +# from exclude list. +_includes = {} + + +_win_excludes = { + # MS assembly excludes + r'^Microsoft\.Windows\.Common-Controls$': 1, +} + + +_unix_excludes = { + r'/libc\.so\..*': 1, + r'/libdl\.so\..*': 1, + r'/libm\.so\..*': 1, + r'/libpthread\.so\..*': 1, + r'/librt\.so\..*': 1, + r'/libthread_db\.so\..*': 1, + r'/libdb-.*\.so': 1, + # glibc regex excludes. + r'/ld-linux\.so\..*': 1, + r'/libBrokenLocale\.so\..*': 1, + r'/libanl\.so\..*': 1, + r'/libcidn\.so\..*': 1, + r'/libcrypt\.so\..*': 1, + r'/libnsl\.so\..*': 1, + r'/libnss_compat.*\.so\..*': 1, + r'/libnss_dns.*\.so\..*': 1, + r'/libnss_files.*\.so\..*': 1, + r'/libnss_hesiod.*\.so\..*': 1, + r'/libnss_nis.*\.so\..*': 1, + r'/libnss_nisplus.*\.so\..*': 1, + r'/libresolv\.so\..*': 1, + r'/libutil\.so\..*': 1, + # libGL can reference some hw specific libraries (like nvidia libs). + r'/libGL\..*': 1, +} + +_aix_excludes = { + r'/libbz2\.a': 1, + r'/libc\.a': 1, + r'/libC\.a': 1, + r'/libcrypt\.a': 1, + r'/libdl\.a': 1, + r'/libintl\.a': 1, + r'/libpthreads\.a': 1, + r'/librt\\.a': 1, + r'/librtl\.a': 1, + r'/libz\.a': 1, +} + + +if is_win: + _excludes = _win_excludes + from PyInstaller.utils import winutils + sep = '[%s]' % re.escape(os.sep + os.altsep) + # Exclude everything from the Windows directory by default. + windir = re.escape(winutils.get_windows_dir()) + _excludes['^%s%s' % (windir, sep)] = 1 + # Allow pythonNN.dll, pythoncomNN.dll, pywintypesNN.dll + _includes[r'%spy(?:thon(?:com(?:loader)?)?|wintypes)\d+\.dll$' % sep] = 1 + +elif is_aix: + # The exclude list for AIX differs from other *nix platforms. + _excludes = _aix_excludes +elif is_unix: + # Common excludes for *nix platforms -- except AIX. + _excludes = _unix_excludes + + +class ExcludeList(object): + def __init__(self): + self.regex = re.compile('|'.join(_excludes.keys()), re.I) + + def search(self, libname): + # Running re.search() on '' regex never returns None. + if _excludes: + return self.regex.search(libname) + else: + return False + + +class IncludeList(object): + def __init__(self): + self.regex = re.compile('|'.join(_includes.keys()), re.I) + + def search(self, libname): + # Running re.search() on '' regex never returns None. + if _includes: + return self.regex.search(libname) + else: + return False + + +exclude_list = ExcludeList() +include_list = IncludeList() + + +if is_darwin: + # On Mac use macholib to decide if a binary is a system one. + from PyInstaller.lib.macholib import util + + class MacExcludeList(object): + def search(self, libname): + return util.in_system_path(libname) + + exclude_list = MacExcludeList() + + +def include_library(libname): + """ + Check if a dynamic library should be included with application or not. + """ + # For configuration phase we need to have exclude / include lists None + # so these checking is skipped and library gets included. + if exclude_list: + if exclude_list.search(libname) and not include_list.search(libname): + # Library is excluded and is not overriden by include list. + # It should be then excluded. + return False + else: + # Include library + return True + else: + # By default include library. + return True + + +def mac_set_relative_dylib_deps(libname, distname): + """ + On Mac OS X set relative paths to dynamic library dependencies + of `libname`. + + Relative paths allow to avoid using environment variable DYLD_LIBRARY_PATH. + There are known some issues with DYLD_LIBRARY_PATH. Relative paths is + more flexible mechanism. + + Current location of dependend libraries is derived from the location + of the library path (paths start with '@loader_path'). + + 'distname' path of the library relative to dist directory of frozen + executable. We need this to determine the level of directory + level for @loader_path of binaries not found in dist directory. + + E.g. qt4 plugins are not in the same directory as Qt*.dylib + files. Without using '@loader_path/../..' for qt plugins + Mac OS X would not be able to resolve shared library + dependencies and qt plugins will not be loaded. + """ + + from PyInstaller.lib.macholib import util + from PyInstaller.lib.macholib.MachO import MachO + + # Ignore bootloader otherwise PyInstaller fails with exception like + # 'ValueError: total_size > low_offset (288 > 0)' + if os.path.basename(libname) in _BOOTLOADER_FNAMES: + return + + # Determine how many directories up is the directory with shared + # dynamic libraries. '../' + # E.g. ./qt4_plugins/images/ -> ./../../ + parent_dir = '' + # Check if distname is not only base filename. + if os.path.dirname(distname): + parent_level = len(os.path.dirname(distname).split(os.sep)) + parent_dir = parent_level * (os.pardir + os.sep) + + def match_func(pth): + """ + For system libraries is still used absolute path. It is unchanged. + """ + # Match non system dynamic libraries. + if not util.in_system_path(pth): + # Use relative path to dependend dynamic libraries bases on + # location of the executable. + return os.path.join('@loader_path', parent_dir, + os.path.basename(pth)) + + # Rewrite mach headers with @loader_path. + dll = MachO(libname) + dll.rewriteLoadCommands(match_func) + + # Write changes into file. + # Write code is based on macholib example. + try: + f = open(dll.filename, 'rb+') + for header in dll.headers: + f.seek(0) + dll.write(f) + f.seek(0, 2) + f.flush() + f.close() + except Exception: + pass diff --git a/pyinstaller/PyInstaller/depend/impdirector.py b/pyinstaller/PyInstaller/depend/impdirector.py new file mode 100644 index 0000000..925f8bd --- /dev/null +++ b/pyinstaller/PyInstaller/depend/impdirector.py @@ -0,0 +1,170 @@ +# +# Copyright (C) 2005, Giovanni Bajo +# +# Based on previous work under copyright (c) 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 + + +# ImportDirectors live on the metapath. +# There's one for builtins and one for sys.path. +# Windows gets one for modules gotten from the Registry +# There should be one for Frozen modules +# Mac would have them for PY_RESOURCE modules etc. +# A generalization of Owner - their concept of "turf" is broader + +import os +import sys +import imp +import marshal + +from PyInstaller import depend +from PyInstaller.compat import set + +import PyInstaller.depend.owner +import PyInstaller.log as logging + +logger = logging.getLogger('PyInstaller.build.mf') + + +def getDescr(fnm): + ext = os.path.splitext(fnm)[1] + for (suffix, mode, typ) in imp.get_suffixes(): + if suffix == ext: + return (suffix, mode, typ) + + +class ImportDirector(PyInstaller.depend.owner.Owner): + pass + + +class BuiltinImportDirector(ImportDirector): + def __init__(self): + self.path = 'Builtins' + + def getmod(self, nm, isbuiltin=imp.is_builtin): + if isbuiltin(nm): + return depend.modules.BuiltinModule(nm) + return None + + +class RegistryImportDirector(ImportDirector): + # for Windows only + def __init__(self): + self.path = "WindowsRegistry" + self.map = {} + try: + import win32api + import win32con + except ImportError: + return + + subkey = r"Software\Python\PythonCore\%s\Modules" % sys.winver + for root in (win32con.HKEY_CURRENT_USER, win32con.HKEY_LOCAL_MACHINE): + try: + hkey = win32api.RegOpenKeyEx(root, subkey, 0, win32con.KEY_READ) + except Exception, e: + logger.debug('RegistryImportDirector: %s' % e) + continue + + numsubkeys, numvalues, lastmodified = win32api.RegQueryInfoKey(hkey) + for i in range(numsubkeys): + subkeyname = win32api.RegEnumKey(hkey, i) + hskey = win32api.RegOpenKeyEx(hkey, subkeyname, 0, win32con.KEY_READ) + val = win32api.RegQueryValueEx(hskey, '') + desc = getDescr(val[0]) + #print " RegistryImportDirector got %s %s" % (val[0], desc) #XXX + self.map[subkeyname] = (val[0], desc) + hskey.Close() + hkey.Close() + break + + def getmod(self, nm, loadco=marshal.loads): + stuff = self.map.get(nm) + if stuff: + fnm, (suffix, mode, typ) = stuff + if typ == imp.C_EXTENSION: + return depend.modules.ExtensionModule(nm, fnm) + elif typ == imp.PY_SOURCE: + try: + stuff = open(fnm, 'rU').read() + '\n' + co = compile(stuff, fnm, 'exec') + except SyntaxError, e: + logger.exception(e) + raise SystemExit(10) + else: + stuff = open(fnm, 'rb').read() + co = loadco(stuff[8:]) + return depend.modules.PyModule(nm, fnm, co) + return None + + +class PathImportDirector(ImportDirector): + def __init__(self, pathlist=None, importers=None): + if pathlist is None: + self.path = sys.path + else: + self.path = pathlist + + self.ownertypes = filter(None, [ + PyInstaller.depend.owner.DirOwner, + PyInstaller.depend.owner.ZipOwner, + PyInstaller.depend.owner.PYZOwner, + PyInstaller.depend.owner.Owner, + ]) + + if importers: + self.shadowpath = importers + else: + self.shadowpath = {} + self.building = set() + + def __str__(self): + return str(self.path) + + def getmod(self, nm): + mod = None + for thing in self.path: + if isinstance(thing, basestring): + owner = self.shadowpath.get(thing, -1) + if owner == -1: + owner = self.shadowpath[thing] = self.__makeOwner(thing) + if owner: + mod = owner.getmod(nm) + else: + mod = thing.getmod(nm) + if mod: + break + return mod + + def __makeOwner(self, path): + if path in self.building: + return None + self.building.add(path) + owner = None + for klass in self.ownertypes: + try: + # this may cause an import, which may cause recursion + # hence the protection + owner = klass(path) + except PyInstaller.depend.owner.OwnerError: + pass + except Exception, e: + #print "FIXME: Wrong exception", e + pass + else: + break + self.building.remove(path) + return owner diff --git a/pyinstaller/PyInstaller/depend/imptracker.py b/pyinstaller/PyInstaller/depend/imptracker.py new file mode 100644 index 0000000..29d155d --- /dev/null +++ b/pyinstaller/PyInstaller/depend/imptracker.py @@ -0,0 +1,325 @@ +# +# Copyright (C) 2005, Giovanni Bajo +# +# Based on previous work under copyright (c) 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 glob + +from PyInstaller import depend, hooks +from PyInstaller.compat import is_win, LogDict, set + +import PyInstaller.log as logging +import PyInstaller.depend.owner +import PyInstaller.depend.impdirector + +logger = logging.getLogger('PyInstaller.build.mf') + + +#=================Import Tracker============================# +# This one doesn't really import, just analyzes +# If it *were* importing, it would be the one-and-only ImportManager +# ie, the builtin import + +UNTRIED = -1 + +imptyps = ['top-level', 'conditional', 'delayed', 'delayed, conditional'] + + +class ImportTracker: + # really the equivalent of builtin import + def __init__(self, xpath=None, hookspath=None, excludes=None): + self.path = [] + self.warnings = {} + if xpath: + self.path = xpath + self.path.extend(sys.path) + self.modules = LogDict() + + # RegistryImportDirector is necessary only on Windows. + if is_win: + self.metapath = [ + PyInstaller.depend.impdirector.BuiltinImportDirector(), + PyInstaller.depend.impdirector.RegistryImportDirector(), + PyInstaller.depend.impdirector.PathImportDirector(self.path) + ] + else: + self.metapath = [ + PyInstaller.depend.impdirector.BuiltinImportDirector(), + PyInstaller.depend.impdirector.PathImportDirector(self.path) + ] + + if hookspath: + hooks.__path__.extend(hookspath) + if excludes is None: + self.excludes = set() + else: + self.excludes = set(excludes) + + def analyze_r(self, nm, importernm=None): + importer = importernm + if importer is None: + importer = '__main__' + seen = {} + nms = self.analyze_one(nm, importernm) + nms = map(None, nms, [importer] * len(nms)) + i = 0 + while i < len(nms): + nm, importer = nms[i] + if seen.get(nm, 0): + del nms[i] + mod = self.modules[nm] + if mod: + mod.xref(importer) + else: + i = i + 1 + seen[nm] = 1 + j = i + mod = self.modules[nm] + if mod: + mod.xref(importer) + for name, isdelayed, isconditional, level in mod.imports: + imptyp = isdelayed * 2 + isconditional + newnms = self.analyze_one(name, nm, imptyp, level) + newnms = map(None, newnms, [nm] * len(newnms)) + nms[j:j] = newnms + j = j + len(newnms) + return map(lambda a: a[0], nms) + + def analyze_one(self, nm, importernm=None, imptyp=0, level=-1): + """ + break the name being imported up so we get: + a.b.c -> [a, b, c] ; ..z -> ['', '', z] + """ + #print '## analyze_one', nm, importernm, imptyp, level + if not nm: + nm = importernm + importernm = None + level = 0 + nmparts = nm.split('.') + + if level < 0: + # behaviour up to Python 2.4 (and default in Python 2.5) + # first see if we could be importing a relative name + contexts = [None] + if importernm: + if self.ispackage(importernm): + contexts.insert(0, importernm) + else: + pkgnm = ".".join(importernm.split(".")[:-1]) + if pkgnm: + contexts.insert(0, pkgnm) + elif level == 0: + # absolute import, do not try relative + importernm = None + contexts = [None] + elif level > 0: + # relative import, do not try absolute + if self.ispackage(importernm): + level -= 1 + if level > 0: + importernm = ".".join(importernm.split('.')[:-level]) + contexts = [importernm, None] + importernm = None + + _all = None + + assert contexts + + # so contexts is [pkgnm, None] or just [None] + if nmparts[-1] == '*': + del nmparts[-1] + _all = [] + nms = [] + for context in contexts: + ctx = context + for i, nm in enumerate(nmparts): + if ctx: + fqname = ctx + '.' + nm + else: + fqname = nm + mod = self.modules.get(fqname, UNTRIED) + if mod is UNTRIED: + logger.debug('Analyzing %s', fqname) + mod = self.doimport(nm, ctx, fqname) + if mod: + nms.append(mod.__name__) + ctx = fqname + else: + break + else: + # no break, point i beyond end + i = i + 1 + if i: + break + # now nms is the list of modules that went into sys.modules + # just as result of the structure of the name being imported + # however, each mod has been scanned and that list is in mod.imports + if i < len(nmparts): + if ctx: + if hasattr(self.modules[ctx], nmparts[i]): + return nms + if not self.ispackage(ctx): + return nms + self.warnings["W: no module named %s (%s import by %s)" % (fqname, imptyps[imptyp], importernm or "__main__")] = 1 + if fqname in self.modules: + del self.modules[fqname] + return nms + if _all is None: + return nms + bottommod = self.modules[ctx] + if bottommod.ispackage(): + for nm in bottommod._all: + if not hasattr(bottommod, nm): + mod = self.doimport(nm, ctx, ctx + '.' + nm) + if mod: + nms.append(mod.__name__) + else: + bottommod.warnings.append("W: name %s not found" % nm) + return nms + + def analyze_script(self, fnm): + try: + stuff = open(fnm, 'rU').read() + '\n' + co = compile(stuff, fnm, 'exec') + except SyntaxError, e: + logger.exception(e) + raise SystemExit(10) + mod = depend.modules.PyScript(fnm, co) + self.modules['__main__'] = mod + return self.analyze_r('__main__') + + def ispackage(self, nm): + return self.modules[nm].ispackage() + + def doimport(self, nm, ctx, fqname): + """ + + nm name + e.g.: + ctx context + e.g.: + fqname fully qualified name + e.g.: + + Return dict containing collected information about module ( + """ + + #print "doimport", nm, ctx, fqname + # NOTE that nm is NEVER a dotted name at this point + assert ("." not in nm), nm + if fqname in self.excludes: + return None + if ctx: + parent = self.modules[ctx] + if parent.ispackage(): + mod = parent.doimport(nm) + if mod: + # insert the new module in the parent package + # FIXME why? + setattr(parent, nm, mod) + else: + # if parent is not a package, there is nothing more to do + return None + else: + # now we're dealing with an absolute import + # try to import nm using available directors + for director in self.metapath: + mod = director.getmod(nm) + if mod: + break + # here we have `mod` from: + # mod = parent.doimport(nm) + # or + # mod = director.getmod(nm) + if mod: + mod.__name__ = fqname + # now look for hooks + # this (and scan_code) are instead of doing "exec co in mod.__dict__" + try: + hookmodnm = 'hook-' + fqname + hooks = __import__('PyInstaller.hooks', globals(), locals(), [hookmodnm]) + hook = getattr(hooks, hookmodnm) + except AttributeError: + pass + else: + mod = self._handle_hook(mod, hook) + if fqname != mod.__name__: + logger.warn("%s is changing its name to %s", + fqname, mod.__name__) + self.modules[mod.__name__] = mod + # The following line has to be at the end of if statement because + # 'mod' might be replaced by a new object within a hook. + self.modules[fqname] = mod + else: + assert (mod == None), mod + self.modules[fqname] = None + # should be equivalent using only one + # self.modules[fqname] = mod + # here + return mod + + def _handle_hook(self, mod, hook): + if hasattr(hook, 'hook'): + mod = hook.hook(mod) + if hasattr(hook, 'hiddenimports'): + for impnm in hook.hiddenimports: + mod.imports.append((impnm, 0, 0, -1)) + if hasattr(hook, 'attrs'): + for attr, val in hook.attrs: + setattr(mod, attr, val) + if hasattr(hook, 'datas'): + # hook.datas is a list of globs of files or + # directories to bundle as datafiles. For each + # glob, a destination directory is specified. + def _visit((base, dest_dir, datas), dirname, names): + for fn in names: + fn = os.path.join(dirname, fn) + if os.path.isfile(fn): + datas.append((dest_dir + fn[len(base) + 1:], fn, 'DATA')) + + datas = mod.datas # shortcut + for g, dest_dir in hook.datas: + if dest_dir: + dest_dir += os.sep + for fn in glob.glob(g): + if os.path.isfile(fn): + datas.append((dest_dir + os.path.basename(fn), fn, 'DATA')) + else: + os.path.walk(fn, _visit, + (os.path.dirname(fn), dest_dir, datas)) + return mod + + def getwarnings(self): + warnings = self.warnings.keys() + for nm, mod in self.modules.items(): + if mod: + for w in mod.warnings: + warnings.append(w + ' - %s (%s)' % (mod.__name__, mod.__file__)) + return warnings + + def getxref(self): + mods = self.modules.items() # (nm, mod) + mods.sort() + rslt = [] + for nm, mod in mods: + if mod: + importers = mod._xref.keys() + importers.sort() + rslt.append((nm, importers)) + return rslt diff --git a/pyinstaller/PyInstaller/depend/modules.py b/pyinstaller/PyInstaller/depend/modules.py new file mode 100644 index 0000000..1e79e26 --- /dev/null +++ b/pyinstaller/PyInstaller/depend/modules.py @@ -0,0 +1,156 @@ +# +# Copyright (C) 2005, Giovanni Bajo +# +# Based on previous work under copyright (c) 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 + + +# All we're doing here is tracking, not importing +# If we were importing, these would be hooked to the real module objects +import os + +from PyInstaller.compat import ctypes, PYCO +from PyInstaller.depend.utils import _resolveCtypesImports, scan_code +import PyInstaller.depend.impdirector + + +class Module: + _ispkg = 0 + typ = 'UNKNOWN' + + def __init__(self, nm): + self.__name__ = nm + self.__file__ = None + self._all = [] + self.imports = [] + self.warnings = [] + self.binaries = [] + self.datas = [] + self._xref = {} + + def ispackage(self): + return self._ispkg + + def doimport(self, nm): + pass + + def xref(self, nm): + self._xref[nm] = 1 + + def __str__(self): + return ("<%s %r %s imports=%s binaries=%s datas=%s>" % + (self.__class__.__name__, self.__name__, self.__file__, + self.imports, self.binaries, self.datas)) + + +class BuiltinModule(Module): + typ = 'BUILTIN' + + def __init__(self, nm): + Module.__init__(self, nm) + + +class ExtensionModule(Module): + typ = 'EXTENSION' + + def __init__(self, nm, pth): + Module.__init__(self, nm) + self.__file__ = pth + + +class PyModule(Module): + typ = 'PYMODULE' + + def __init__(self, nm, pth, co): + Module.__init__(self, nm) + self.co = co + self.__file__ = pth + if os.path.splitext(self.__file__)[1] == '.py': + self.__file__ = self.__file__ + PYCO + self.scancode() + + def scancode(self): + self.imports, self.warnings, self.binaries, allnms = scan_code(self.co) + if allnms: + self._all = allnms + if ctypes and self.binaries: + self.binaries = _resolveCtypesImports(self.binaries) + + +class PyScript(PyModule): + typ = 'PYSOURCE' + + def __init__(self, pth, co): + Module.__init__(self, '__main__') + self.co = co + self.__file__ = pth + self.scancode() + + +class PkgModule(PyModule): + typ = 'PYMODULE' + + def __init__(self, nm, pth, co): + PyModule.__init__(self, nm, pth, co) + self._ispkg = 1 + pth = os.path.dirname(pth) + self.__path__ = [pth] + self._update_director(force=True) + + def _update_director(self, force=False): + if force or self.subimporter.path != self.__path__: + self.subimporter = PyInstaller.depend.impdirector.PathImportDirector(self.__path__) + + def doimport(self, nm): + self._update_director() + mod = self.subimporter.getmod(nm) + if mod: + mod.__name__ = self.__name__ + '.' + mod.__name__ + return mod + + +class PkgInPYZModule(PyModule): + def __init__(self, nm, co, pyzowner): + PyModule.__init__(self, nm, co.co_filename, co) + self._ispkg = 1 + self.__path__ = [str(pyzowner)] + self.owner = pyzowner + + def doimport(self, nm): + mod = self.owner.getmod(self.__name__ + '.' + nm) + return mod + + +class PyInZipModule(PyModule): + typ = 'ZIPFILE' + + def __init__(self, zipowner, nm, pth, co): + PyModule.__init__(self, nm, co.co_filename, co) + self.owner = zipowner + + +class PkgInZipModule(PyModule): + typ = 'ZIPFILE' + + def __init__(self, zipowner, nm, pth, co): + PyModule.__init__(self, nm, co.co_filename, co) + self._ispkg = 1 + self.__path__ = [str(zipowner)] + self.owner = zipowner + + def doimport(self, nm): + mod = self.owner.getmod(self.__name__ + '.' + nm) + return mod diff --git a/pyinstaller/PyInstaller/depend/owner.py b/pyinstaller/PyInstaller/depend/owner.py new file mode 100644 index 0000000..1af3fa1 --- /dev/null +++ b/pyinstaller/PyInstaller/depend/owner.py @@ -0,0 +1,254 @@ +# +# Copyright (C) 2005, Giovanni Bajo +# +# Based on previous work under copyright (c) 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 + + +# An Owner does imports from a particular piece of turf +# That is, there's an Owner for each thing on sys.path +# There are owners for directories and .pyz files. +# There could be owners for zip files, or even URLs. +# Note that they replace the string in sys.path, +# but str(sys.path[n]) should yield the original string. + + +import imp +import marshal +import os + +from PyInstaller import depend +from PyInstaller.compat import PYCO, caseOk +from PyInstaller.loader import archive + + +import PyInstaller.log as logging +logger = logging.getLogger('PyInstaller.build.mf') + + +class OwnerError(Exception): + pass + + +class Owner: + """ + Base class for loading Python bytecode from different places. + """ + def __init__(self, path): + self.path = path + + def __str__(self): + return self.path + + def getmod(self, nm): + return None + + +class BaseDirOwner(Owner): + """ + Base class for loading bytecode of Python modules from file system. + """ + def _getsuffixes(self): + return imp.get_suffixes() + + def getmod(self, nm, getsuffixes=None, loadco=marshal.loads): + if getsuffixes is None: + getsuffixes = self._getsuffixes + possibles = [(nm, 0, None)] + if self._isdir(nm) and self._caseok(nm): + possibles.insert(0, (os.path.join(nm, '__init__'), 1, nm)) + py = pyc = None + for pth, ispkg, pkgpth in possibles: + for ext, mode, typ in getsuffixes(): + attempt = pth + ext + modtime = self._modtime(attempt) + if modtime is not None: + # Check case + if not self._caseok(attempt): + continue + if typ == imp.C_EXTENSION: + #logger.debug("%s.getmod -> ExtensionModule(%s, %s)", self.__class__.__name__, nm, attempt) + return depend.modules.ExtensionModule(nm, os.path.join(self.path, attempt)) + elif typ == imp.PY_SOURCE: + py = (attempt, modtime) + else: + pyc = (attempt, modtime) + if py or pyc: + break + if py is None and pyc is None: + #logger.debug("%s.getmod -> (py == pyc == None)", self.__class__.__name__) + return None + + co = None + ## if nm == 'archive': + ## import pdb ; pdb.set_trace() + if pyc: + stuff = self._read(pyc[0]) + # If this file was not generated for this version of + # Python, we need to regenerate it. + if stuff[:4] != imp.get_magic(): + logger.warn("wrong version .py%s found (%s), will use .py", + PYCO, pyc[0]) + else: + try: + co = loadco(stuff[8:]) + pth = pyc[0] + except (ValueError, EOFError): + pyc = None + logger.warn("bad .py%s found (%s), will use .py", + PYCO, pyc[0]) + + if co is None or py and pyc[1] < py[1]: + # If we have no pyc or py is newer + try: + stuff = self._read(py[0]) + '\n' + co = compile(stuff.replace("\r\n", "\n"), py[0], 'exec') + pth = py[0] + PYCO + logger.debug("compiled %s", pth) + except SyntaxError, e: + logger.exception(e) + raise SystemExit(10) + + if co is None: + #logger.debug("%s.getmod -> None", self.__class__.__name__) + return None + + pth = os.path.join(self.path, pth) + if not os.path.isabs(pth): + pth = os.path.abspath(pth) + if ispkg: + mod = self._pkgclass()(nm, pth, co) + else: + mod = self._modclass()(nm, pth, co) + #logger.debug("%s.getmod -> %s", self.__class__.__name__, mod) + return mod + + +class DirOwner(BaseDirOwner): + + def __init__(self, path): + if path == '': + path = os.getcwd() + if not os.path.isdir(path): + raise OwnerError("%s is not a directory" % repr(path)) + Owner.__init__(self, path) + + def _isdir(self, fn): + return os.path.isdir(os.path.join(self.path, fn)) + + def _modtime(self, fn): + try: + return os.stat(os.path.join(self.path, fn))[8] + except OSError: + return None + + def _read(self, fn): + return open(os.path.join(self.path, fn), 'rb').read() + + def _pkgclass(self): + return depend.modules.PkgModule + + def _modclass(self): + return depend.modules.PyModule + + def _caseok(self, fn): + return caseOk(os.path.join(self.path, fn)) + + +class ZipOwner(BaseDirOwner): + """ + Load bytecode of Python modules from .egg files. + + zipimporter cannot be used here because it has a stupid bug: + + >>> z.find_module("setuptools.setuptools.setuptools.setuptools.setuptools") is not None + True + + So mf will go into infinite recursion. Instead, we'll reuse + the BaseDirOwner logic, simply changing the template methods. + """ + def __init__(self, path): + import zipfile + try: + self.zf = zipfile.ZipFile(path, "r") + except IOError: + raise OwnerError("%s is not a zipfile" % path) + Owner.__init__(self, path) + + def getmod(self, fn): + fn = fn.replace(".", "/") + return BaseDirOwner.getmod(self, fn) + + def _modtime(self, fn): + # zipfiles always use forward slashes + fn = fn.replace("\\", "/") + try: + dt = self.zf.getinfo(fn).date_time + return dt + except KeyError: + return None + + def _isdir(self, fn): + # No way to find out if "fn" is a directory + # so just always look into it in case it is. + return True + + def _caseok(self, fn): + # zipfile is always case-sensitive, so surely + # there is no case mismatch. + return True + + def _read(self, fn): + # zipfiles always use forward slashes + fn = fn.replace("\\", "/") + return self.zf.read(fn) + + def _pkgclass(self): + return lambda *args: depend.modules.PkgInZipModule(self, *args) + + def _modclass(self): + return lambda *args: depend.modules.PyInZipModule(self, *args) + + +class PYZOwner(Owner): + """ + Class for loading bytecode of Python modules from PYZ files. + + PYZ file is internal PyInstaller format embedded into final executable. + + It is possible to have a custom .spec file which packs a subset of Python + files into a PYZ file, and then drop it on the disk somewhere. When the PYZ + file is added to sys.path, PYZOwner will parse it and make the modules + within it available at import time. + + NOTE: PYZ format cannot be replaced by zipimport module. + + The problem is that we have no control over zipimport; for instance, + it doesn't work if the zip file is embedded into a PKG appended + to an executable, like we create in one-file. + """ + def __init__(self, path): + self.pyz = archive.ZlibArchive(path) + Owner.__init__(self, path) + + def getmod(self, nm): + rslt = self.pyz.extract(nm) + if not rslt: + return None + ispkg, co = rslt + if ispkg: + return depend.modules.PkgInPYZModule(nm, co, self) + return depend.modules.PyModule(nm, self.path, co) diff --git a/pyinstaller/PyInstaller/depend/utils.py b/pyinstaller/PyInstaller/depend/utils.py new file mode 100644 index 0000000..26e67cc --- /dev/null +++ b/pyinstaller/PyInstaller/depend/utils.py @@ -0,0 +1,357 @@ +# +# Copyright (C) 2005, Giovanni Bajo +# +# Based on previous work under copyright (c) 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 + + +# Scan the code object for imports, __all__ and wierd stuff + + +import dis +import os + +from PyInstaller import compat +from PyInstaller.compat import set, ctypes + +from PyInstaller import is_unix, is_darwin, is_py25, is_py27 + +import PyInstaller.depend.utils +import PyInstaller.log as logging + + +logger = logging.getLogger(__name__) + + +IMPORT_NAME = dis.opname.index('IMPORT_NAME') +IMPORT_FROM = dis.opname.index('IMPORT_FROM') +try: + IMPORT_STAR = dis.opname.index('IMPORT_STAR') +except: + IMPORT_STAR = None +STORE_NAME = dis.opname.index('STORE_NAME') +STORE_FAST = dis.opname.index('STORE_FAST') +STORE_GLOBAL = dis.opname.index('STORE_GLOBAL') +try: + STORE_MAP = dis.opname.index('STORE_MAP') +except: + STORE_MAP = None +LOAD_GLOBAL = dis.opname.index('LOAD_GLOBAL') +LOAD_ATTR = dis.opname.index('LOAD_ATTR') +LOAD_NAME = dis.opname.index('LOAD_NAME') +EXEC_STMT = dis.opname.index('EXEC_STMT') +try: + SET_LINENO = dis.opname.index('SET_LINENO') +except ValueError: + SET_LINENO = None +BUILD_LIST = dis.opname.index('BUILD_LIST') +LOAD_CONST = dis.opname.index('LOAD_CONST') +if is_py25: + LOAD_CONST_level = LOAD_CONST +else: + LOAD_CONST_level = None +if is_py27: + COND_OPS = set([dis.opname.index('POP_JUMP_IF_TRUE'), + dis.opname.index('POP_JUMP_IF_FALSE'), + dis.opname.index('JUMP_IF_TRUE_OR_POP'), + dis.opname.index('JUMP_IF_FALSE_OR_POP'), + ]) +else: + COND_OPS = set([dis.opname.index('JUMP_IF_FALSE'), + dis.opname.index('JUMP_IF_TRUE'), + ]) +JUMP_FORWARD = dis.opname.index('JUMP_FORWARD') +try: + STORE_DEREF = dis.opname.index('STORE_DEREF') +except ValueError: + STORE_DEREF = None +STORE_OPS = set([STORE_NAME, STORE_FAST, STORE_GLOBAL, STORE_DEREF, STORE_MAP]) +#IMPORT_STAR -> IMPORT_NAME mod ; IMPORT_STAR +#JUMP_IF_FALSE / JUMP_IF_TRUE / JUMP_FORWARD +HASJREL = set(dis.hasjrel) + + +def pass1(code): + instrs = [] + i = 0 + n = len(code) + curline = 0 + incondition = 0 + out = 0 + while i < n: + if i >= out: + incondition = 0 + c = code[i] + i = i + 1 + op = ord(c) + if op >= dis.HAVE_ARGUMENT: + oparg = ord(code[i]) + ord(code[i + 1]) * 256 + i = i + 2 + else: + oparg = None + if not incondition and op in COND_OPS: + incondition = 1 + out = oparg + if op in HASJREL: + out += i + elif incondition and op == JUMP_FORWARD: + out = max(out, i + oparg) + if op == SET_LINENO: + curline = oparg + else: + instrs.append((op, oparg, incondition, curline)) + return instrs + + +def scan_code(co, m=None, w=None, b=None, nested=0): + instrs = pass1(co.co_code) + if m is None: + m = [] + if w is None: + w = [] + if b is None: + b = [] + all = [] + lastname = None + level = -1 # import-level, same behaviour as up to Python 2.4 + for i, (op, oparg, conditional, curline) in enumerate(instrs): + if op == IMPORT_NAME: + if level <= 0: + name = lastname = co.co_names[oparg] + else: + name = lastname = co.co_names[oparg] + #print 'import_name', name, `lastname`, level + m.append((name, nested, conditional, level)) + elif op == IMPORT_FROM: + name = co.co_names[oparg] + #print 'import_from', name, `lastname`, level, + if level > 0 and (not lastname or lastname[-1:] == '.'): + name = lastname + name + else: + name = lastname + '.' + name + #print name + m.append((name, nested, conditional, level)) + assert lastname is not None + elif op == IMPORT_STAR: + assert lastname is not None + m.append((lastname + '.*', nested, conditional, level)) + elif op == STORE_NAME: + if co.co_names[oparg] == "__all__": + j = i - 1 + pop, poparg, pcondtl, pline = instrs[j] + if pop != BUILD_LIST: + w.append("W: __all__ is built strangely at line %s" % pline) + else: + all = [] + while j > 0: + j = j - 1 + pop, poparg, pcondtl, pline = instrs[j] + if pop == LOAD_CONST: + all.append(co.co_consts[poparg]) + else: + break + elif op in STORE_OPS: + pass + elif op == LOAD_CONST_level: + # starting with Python 2.5, _each_ import is preceeded with a + # LOAD_CONST to indicate the relative level. + if isinstance(co.co_consts[oparg], (int, long)): + level = co.co_consts[oparg] + elif op == LOAD_GLOBAL: + name = co.co_names[oparg] + cndtl = ['', 'conditional'][conditional] + lvl = ['top-level', 'delayed'][nested] + if name == "__import__": + w.append("W: %s %s __import__ hack detected at line %s" % (lvl, cndtl, curline)) + elif name == "eval": + w.append("W: %s %s eval hack detected at line %s" % (lvl, cndtl, curline)) + elif op == EXEC_STMT: + cndtl = ['', 'conditional'][conditional] + lvl = ['top-level', 'delayed'][nested] + w.append("W: %s %s exec statement detected at line %s" % (lvl, cndtl, curline)) + else: + lastname = None + + if ctypes: + # ctypes scanning requires a scope wider than one bytecode instruction, + # so the code resides in a separate function for clarity. + ctypesb, ctypesw = scan_code_for_ctypes(co, instrs, i) + b.extend(ctypesb) + w.extend(ctypesw) + + for c in co.co_consts: + if isinstance(c, type(co)): + # FIXME: "all" was not updated here nor returned. Was it the desired + # behaviour? + _, _, _, all_nested = scan_code(c, m, w, b, 1) + all.extend(all_nested) + return m, w, b, all + + +def scan_code_for_ctypes(co, instrs, i): + """ + Detects ctypes dependencies, using reasonable heuristics that should + cover most common ctypes usages; returns a tuple of two lists, one + containing names of binaries detected as dependencies, the other containing + warnings. + """ + + def _libFromConst(i): + """Extracts library name from an expected LOAD_CONST instruction and + appends it to local binaries list. + """ + op, oparg, conditional, curline = instrs[i] + if op == LOAD_CONST: + soname = co.co_consts[oparg] + b.append(soname) + + b = [] + + op, oparg, conditional, curline = instrs[i] + + if op in (LOAD_GLOBAL, LOAD_NAME): + name = co.co_names[oparg] + + if name in ("CDLL", "WinDLL"): + # Guesses ctypes imports of this type: CDLL("library.so") + + # LOAD_GLOBAL 0 (CDLL) <--- we "are" here right now + # LOAD_CONST 1 ('library.so') + + _libFromConst(i + 1) + + elif name == "ctypes": + # Guesses ctypes imports of this type: ctypes.DLL("library.so") + + # LOAD_GLOBAL 0 (ctypes) <--- we "are" here right now + # LOAD_ATTR 1 (CDLL) + # LOAD_CONST 1 ('library.so') + + op2, oparg2, conditional2, curline2 = instrs[i + 1] + if op2 == LOAD_ATTR: + if co.co_names[oparg2] in ("CDLL", "WinDLL"): + # Fetch next, and finally get the library name + _libFromConst(i + 2) + + elif name in ("cdll", "windll"): + # Guesses ctypes imports of these types: + + # * cdll.library (only valid on Windows) + + # LOAD_GLOBAL 0 (cdll) <--- we "are" here right now + # LOAD_ATTR 1 (library) + + # * cdll.LoadLibrary("library.so") + + # LOAD_GLOBAL 0 (cdll) <--- we "are" here right now + # LOAD_ATTR 1 (LoadLibrary) + # LOAD_CONST 1 ('library.so') + + op2, oparg2, conditional2, curline2 = instrs[i + 1] + if op2 == LOAD_ATTR: + if co.co_names[oparg2] != "LoadLibrary": + # First type + soname = co.co_names[oparg2] + ".dll" + b.append(soname) + else: + # Second type, needs to fetch one more instruction + _libFromConst(i + 2) + + # If any of the libraries has been requested with anything different from + # the bare filename, drop that entry and warn the user - pyinstaller would + # need to patch the compiled pyc file to make it work correctly! + + w = [] + for bin in list(b): + if bin != os.path.basename(bin): + b.remove(bin) + w.append("W: ignoring %s - ctypes imports only supported using bare filenames" % (bin,)) + + return b, w + + +def _resolveCtypesImports(cbinaries): + """Completes ctypes BINARY entries for modules with their full path. + """ + from ctypes.util import find_library + + if is_unix: + envvar = "LD_LIBRARY_PATH" + elif is_darwin: + envvar = "DYLD_LIBRARY_PATH" + else: + envvar = "PATH" + + def _setPaths(): + path = os.pathsep.join(PyInstaller.__pathex__) + old = compat.getenv(envvar) + if old is not None: + path = os.pathsep.join((path, old)) + compat.setenv(envvar, path) + return old + + def _restorePaths(old): + if old is None: + compat.unsetenv(envvar) + else: + compat.setenv(envvar, old) + + ret = [] + + # Try to locate the shared library on disk. This is done by + # executing ctypes.utile.find_library prepending ImportTracker's + # local paths to library search paths, then replaces original values. + old = _setPaths() + for cbin in cbinaries: + # Ignore annoying warnings like: + # 'W: library kernel32.dll required via ctypes not found' + # 'W: library coredll.dll required via ctypes not found' + if cbin in ['coredll.dll', 'kernel32.dll']: + continue + ext = os.path.splitext(cbin)[1] + # On Windows, only .dll files can be loaded. + if os.name == "nt" and ext.lower() in [".so", ".dylib"]: + continue + cpath = find_library(os.path.splitext(cbin)[0]) + if is_unix: + # CAVEAT: find_library() is not the correct function. Ctype's + # documentation says that it is meant to resolve only the filename + # (as a *compiler* does) not the full path. Anyway, it works well + # enough on Windows and Mac. On Linux, we need to implement + # more code to find out the full path. + if cpath is None: + cpath = cbin + # "man ld.so" says that we should first search LD_LIBRARY_PATH + # and then the ldcache + for d in compat.getenv(envvar, '').split(os.pathsep): + if os.path.isfile(os.path.join(d, cpath)): + cpath = os.path.join(d, cpath) + break + else: + text = compat.exec_command("/sbin/ldconfig", "-p") + for L in text.strip().splitlines(): + if cpath in L: + cpath = L.split("=>", 1)[1].strip() + assert os.path.isfile(cpath) + break + else: + cpath = None + if cpath is None: + logger.warn("library %s required via ctypes not found", cbin) + else: + ret.append((cbin, cpath, "BINARY")) + _restorePaths(old) + return ret diff --git a/pyinstaller/PyInstaller/fake/.svn/entries b/pyinstaller/PyInstaller/fake/.svn/entries new file mode 100644 index 0000000..ab5a6f5 --- /dev/null +++ b/pyinstaller/PyInstaller/fake/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/fake +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +fake-site.py +file + + + +add + +__init__.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/fake/__init__.py b/pyinstaller/PyInstaller/fake/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/fake/fake-site.py b/pyinstaller/PyInstaller/fake/fake-site.py new file mode 100644 index 0000000..9410af5 --- /dev/null +++ b/pyinstaller/PyInstaller/fake/fake-site.py @@ -0,0 +1,43 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# This is a fake 'site' module available in default Python Library. +# +# The real 'site' does some magic to find paths to other possible +# Python modules. We do not want this behaviour for frozen applications. +# +# Fake 'site' makes PyInstaller to work with distutils and to work inside +# virtualenv environment. + + +# TODO test the following code stub from real 'site' module. + + +# Prefixes for site-packages; add additional prefixes like /usr/local here +PREFIXES = [] + +# Enable per user site-packages directory +# set it to False to disable the feature or True to force the feature +ENABLE_USER_SITE = False + + +# for distutils.commands.install +# These values are initialized by the getuserbase() and getusersitepackages() +# functions, through the main() function when Python starts. +USER_SITE = None +USER_BASE = None diff --git a/pyinstaller/PyInstaller/hooks/.svn/entries b/pyinstaller/PyInstaller/hooks/.svn/entries new file mode 100644 index 0000000..3805a30 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/.svn/entries @@ -0,0 +1,868 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/hooks +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +hook-pywintypes.py +file + + + +add + +hook-parser.py +file + + + +add + +hook-DateTime.mxDateTime.py +file + + + +add + +hook-DateTime.py +file + + + +add + +hook-paste.exceptions.reporter.py +file + + + +add + +hook-cx_Oracle.py +file + + + +add + +hook-pywinauto.py +file + + + +add + +hook-PyQt4.uic.py +file + + + +add + +hook-xml.py +file + + + +add + +__init__.py +file + + + +add + +hook-django.contrib.py +file + + + +add + +hook-_mysql.py +file + + + +add + +hook-PyQt4.Qt.py +file + + + +add + +hook-PIL.Image.py +file + + + +add + +hook-pygments.lexers.py +file + + + +add + +hook-PyQt4.QtGui.py +file + + + +add + +hook-email.message.py +file + + + +add + +hook-pyttsx.py +file + + + +add + +hook-wx.lib.pubsub.setuparg1.py +file + + + +add + +hook-PyQt4.QtCore.py +file + + + +add + +hook-PyQt4.QtNetwork.py +file + + + +add + +hook-PyQt4.phonon.py +file + + + +add + +hook-setuptools.py +file + + + +add + +hook-xml.dom.html.py +file + + + +add + +hook-pygame.py +file + + + +add + +hook-enchant.checker.py +file + + + +add + +hook-xml.dom.html.HTMLDocument.py +file + + + +add + +hook-PIL.py +file + + + +add + +hook-OpenGL.py +file + + + +add + +hook-django.db.py +file + + + +add + +hook-kinterbasdb.py +file + + + +add + +hook-_sre.py +file + + + +add + +hook-pyexpat.py +file + + + +add + +hook-PIL.SpiderImagePlugin.py +file + + + +add + +hook-time.py +file + + + +add + +hook-pywinauto.tests.py +file + + + +add + +hook-win32com.py +file + + + +add + +hook-django.db.backends.mysql.py +file + + + +add + +hook-django.db.backends.oracle.py +file + + + +add + +hook-psycopg2.py +file + + + +add + +hook-django.contrib.sessions.py +file + + + +add + +hook-zmq.py +file + + + +add + +hook-_elementtree.py +file + + + +add + +hook-cPickle.py +file + + + +add + +hook-regex.py +file + + + +add + +hook-iu.py +file + + + +add + +hook-PyQt4.QtHelp.py +file + + + +add + +hook-xml.dom.ext.reader.py +file + + + +add + +hook-xml.etree.cElementTree.py +file + + + +add + +shared_PIL_Image.py +file + + + +add + +hook-lxml.etree.py +file + + + +add + +hook-_tkinter.py +file + + + +add + +hook-matplotlib.backends.py +file + + + +add + +hook-pyodbc.py +file + + + +add + +hook-PyQt4.Qwt5.py +file + + + +add + +hook-pythoncom.py +file + + + +add + +django-import-finder.py +file + + + +add + +hook-Image.py +file + + + +add + +hookutils.py +file + + + +add + +hook-carchive.py +file + + + +add + +hook-PyQt4.Qt3Support.py +file + + + +add + +hook-PyQt4.QtSql.py +file + + + +add + +hook-django.core.cache.py +file + + + +add + +hook-PyQt4.uic.port_v3.py +file + + + +add + +hook-matplotlib.numerix.py +file + + + +add + +hook-django.db.backends.py +file + + + +add + +hook-codecs.py +file + + + +add + +hook-win32ui.py +file + + + +add + +hook-django.py +file + + + +add + +hook-sqlalchemy.py +file + + + +add + +hook-django.core.management.py +file + + + +add + +hook-PyQt4.QtTest.py +file + + + +add + +shared_PIL_SpiderImagePlugin.py +file + + + +add + +hook-wx.lib.activex.py +file + + + +add + +hook-storm.database.py +file + + + +add + +hook-SpiderImagePlugin.py +file + + + +add + +hook-encodings.py +file + + + +add + +hook-anydbm.py +file + + + +add + +hook-PyQt4.QtWebKit.py +file + + + +add + +hook-dns.rdata.py +file + + + +add + +hook-xml.dom.py +file + + + +add + +enchant-datafiles-finder.py +file + + + +add + +hook-PyQt4.py +file + + + +add + +hook-distutils.py +file + + + +add + +hook-PyQt4.QtSvg.py +file + + + +add + +hook-qt.py +file + + + +add + +hook-enchant.checker.wxSpellCheckerDialog.py +file + + + +add + +hook-PyQt4.QtXml.py +file + + + +add + +hook-pygments.styles.py +file + + + +add + +hook-xml.sax.saxexts.py +file + + + +add + +hook-lxml.objectify.py +file + + + +add + +hook-usb.py +file + + + +add + +hook-mako.codegen.py +file + + + +add + +hook-PyQt4.uic.port_v2.py +file + + + +add + +hook-xml.dom.domreg.py +file + + + +add + +hook-xml.sax.py +file + + + +add + +hook-OpenGL_accelerate.py +file + + + +add + +hook-PyQt4.QtAssistant.py +file + + + +add + +hook-cStringIO.py +file + + + +add + +hook-site.py +file + + + +add + +hook-babel.py +file + + + +add + +hook-gadfly.py +file + + + +add + +hook-vtkpython.py +file + + + +add + +hook-matplotlib.py +file + + + +add + +hook-PyQt4.QtOpenGL.py +file + + + +add + +hook-django.core.mail.py +file + + + +add + +hook-tables.py +file + + + +add + +hook-cElementTree.py +file + + + +add + +hook-django.db.backends.mysql.base.py +file + + + +add + +hook-django.db.backends.oracle.base.py +file + + + +add + +hook-xml.dom.ext.py +file + + + +add + +hook-win32com.client.py +file + + + +add + +hook-enchant.py +file + + + +add + +hook-os.py +file + + + +add + +hook-h5py.py +file + + + +add + +hook-gtk.py +file + + + +add + +hook-email.py +file + + + +add + +hook-django.core.py +file + + + +add + +hook-PyQt4.QtScript.py +file + + + +add + +hook-wx.lib.pubsub.core.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/hooks/__init__.py b/pyinstaller/PyInstaller/hooks/__init__.py new file mode 100644 index 0000000..aed1ccb --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# empty diff --git a/pyinstaller/PyInstaller/hooks/django-import-finder.py b/pyinstaller/PyInstaller/hooks/django-import-finder.py new file mode 100644 index 0000000..f7179ad --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/django-import-finder.py @@ -0,0 +1,58 @@ +# Copyright (C) 2009, Lorenzo Berni +# Based on previous work under copyright (c) 2001, 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 os + +import PyInstaller.compat as compat +from hookutils import logger + +if not compat.getenv("DJANGO_SETTINGS_MODULE"): + compat.setenv("DJANGO_SETTINGS_MODULE", "settings") + +from django.conf import settings + +hiddenimports = (list(settings.AUTHENTICATION_BACKENDS) + + [settings.DEFAULT_FILE_STORAGE] + + list(settings.FILE_UPLOAD_HANDLERS) + + list(settings.INSTALLED_APPS) + + list(settings.MIDDLEWARE_CLASSES) + + list(settings.TEMPLATE_CONTEXT_PROCESSORS) + + list(settings.TEMPLATE_LOADERS) + + [settings.ROOT_URLCONF]) + +def find_url_callbacks(urls_module): + urlpatterns = urls_module.urlpatterns + hid_list = [urls_module.__name__] + for pattern in urlpatterns: + if isinstance(pattern, RegexURLPattern): + hid_list.append(pattern.callback.__module__) + elif isinstance(pattern, RegexURLResolver): + hid_list += find_url_callbacks(pattern.urlconf_module) + return hid_list + +from django.core.urlresolvers import RegexURLPattern, RegexURLResolver + +base_module_name = ".".join(compat.getenv("DJANGO_SETTINGS_MODULE", "settings").split(".")[:-1]) +if base_module_name: + base_module = __import__(base_module_name, {}, {}, ["urls"]) + urls = base_module.urls +else: + import urls +hiddenimports += find_url_callbacks(urls) + +logger.debug('%r', sorted(set(hiddenimports))) + diff --git a/pyinstaller/PyInstaller/hooks/enchant-datafiles-finder.py b/pyinstaller/PyInstaller/hooks/enchant-datafiles-finder.py new file mode 100644 index 0000000..046e338 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/enchant-datafiles-finder.py @@ -0,0 +1,29 @@ + +import os +import enchant + +def _win32_data_files(): + # This is basically a copy of enchant.utils.win32_data_files as of + # release 1.6.0. We use this as a fallback for older versions of + # enchant which do not have this function. + # enchant is licenced under LGPL. + dataDirs = ("share/enchant/myspell","share/enchant/ispell","lib/enchant") + mainDir = os.path.abspath(os.path.dirname(enchant.__file__)) + dataFiles = [] + for dataDir in dataDirs: + files = [] + fullDir = os.path.join(mainDir,os.path.normpath(dataDir)) + for fn in os.listdir(fullDir): + fullFn = os.path.join(fullDir,fn) + if os.path.isfile(fullFn): + files.append(fullFn) + dataFiles.append((dataDir,files)) + return dataFiles + +try: + from enchant.utils import win32_data_files +except: + # fall back to the function above + win32_data_files = _win32_data_files + +print win32_data_files() diff --git a/pyinstaller/PyInstaller/hooks/hook-DateTime.mxDateTime.py b/pyinstaller/PyInstaller/hooks/hook-DateTime.mxDateTime.py new file mode 100644 index 0000000..2820f6d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-DateTime.mxDateTime.py @@ -0,0 +1,17 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +attrs = [('__version__', '1.3.0')] diff --git a/pyinstaller/PyInstaller/hooks/hook-DateTime.py b/pyinstaller/PyInstaller/hooks/hook-DateTime.py new file mode 100644 index 0000000..48294f6 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-DateTime.py @@ -0,0 +1,17 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +hiddenimports = ['ISO', 'ARPA', 'ODMG', 'Locale', 'Feasts', 'Parser', 'NIST'] diff --git a/pyinstaller/PyInstaller/hooks/hook-Image.py b/pyinstaller/PyInstaller/hooks/hook-Image.py new file mode 100644 index 0000000..8c24118 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-Image.py @@ -0,0 +1,21 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# Forward to shared code for PIL. PIL can be imported either as a top-level package +# (from PIL import Image), or not (import Image), because it installs a +# PIL.pth. +from PyInstaller.hooks.shared_PIL_Image import * diff --git a/pyinstaller/PyInstaller/hooks/hook-OpenGL.py b/pyinstaller/PyInstaller/hooks/hook-OpenGL.py new file mode 100644 index 0000000..ddc3f44 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-OpenGL.py @@ -0,0 +1,41 @@ +# +# Copyright (C) 2009, Lorenzo Mancini +# +# 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 + + +# Hook for PyOpenGL 3.x versions from 3.0.0b6 up. Previous versions have a +# plugin system based on pkg_resources which is problematic to handle correctly +# under pyinstaller; 2.x versions used to run fine without hooks, so this one +# shouldn't hurt. + + +from PyInstaller.compat import is_win, is_darwin +from PyInstaller.hooks.hookutils import opengl_arrays_modules + + +# PlatformPlugin performs a conditional import based on os.name and +# sys.platform. PyInstaller misses this so let's add it ourselves... +if is_win: + hiddenimports = ['OpenGL.platform.win32'] +elif is_darwin: + hiddenimports = ['OpenGL.platform.darwin'] +# Use glx for other platforms (Linux, ...) +else: + hiddenimports = ['OpenGL.platform.glx'] + + +# Arrays modules are needed too. +hiddenimports += opengl_arrays_modules() diff --git a/pyinstaller/PyInstaller/hooks/hook-OpenGL_accelerate.py b/pyinstaller/PyInstaller/hooks/hook-OpenGL_accelerate.py new file mode 100644 index 0000000..bd6fc07 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-OpenGL_accelerate.py @@ -0,0 +1,26 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# OpenGL_accelerate contais modules written in cython. This module +# should speed up some functions from OpenGL module. The following +# hiddenimports are not resolved by PyInstaller because OpenGL_accelerate +# is compiled to native Python modules. +hiddenimports = [ + 'OpenGL_accelerate.wrapper', + 'OpenGL_accelerate.formathandler', +] diff --git a/pyinstaller/PyInstaller/hooks/hook-PIL.Image.py b/pyinstaller/PyInstaller/hooks/hook-PIL.Image.py new file mode 100644 index 0000000..4e5429f --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PIL.Image.py @@ -0,0 +1,29 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# FIXME: for a strange bug in Python's import machinery, we need to adjust +# the module name before proceeding to the PIL import. The name would +# otherwise be "hooks.hook-PIL.Image", which will then produce this +# monstruosity: +# +# +__name__ = "hook-image" + +# Forward to shared code for PIL. PIL can be imported either as a top-level package +# (from PIL import Image), or not (import Image), because it installs a +# PIL.pth. +from PyInstaller.hooks.shared_PIL_Image import * diff --git a/pyinstaller/PyInstaller/hooks/hook-PIL.SpiderImagePlugin.py b/pyinstaller/PyInstaller/hooks/hook-PIL.SpiderImagePlugin.py new file mode 100644 index 0000000..0b5c963 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PIL.SpiderImagePlugin.py @@ -0,0 +1,21 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# Forward to shared code for PIL. PIL can be imported either as a top-level package +# (from PIL import Image), or not (import Image), because it installs a +# PIL.pth. +from PyInstaller.hooks.shared_PIL_SpiderImagePlugin import * diff --git a/pyinstaller/PyInstaller/hooks/hook-PIL.py b/pyinstaller/PyInstaller/hooks/hook-PIL.py new file mode 100644 index 0000000..ad2447c --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PIL.py @@ -0,0 +1 @@ +# empty (just to need Python import machinery happy) diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qt.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qt.py new file mode 100644 index 0000000..1ae8eb0 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qt.py @@ -0,0 +1,15 @@ +hiddenimports = ['sip', + 'PyQt4._qt', + 'PyQt4.QtAssistant', + 'PyQt4.QtCore', + 'PyQt4.QtGui', + 'PyQt4.QtNetwork', + 'PyQt4.Qt3Support', + 'PyQt4.QtSql', + 'PyQt4.QtSvg', + 'PyQt4.QtTest', + 'PyQt4.QtSql', + 'PyQt4.QtXml', + 'PyQt4.QtWebKit', + 'PyQt4.QtOpenGL', + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qt3Support.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qt3Support.py new file mode 100644 index 0000000..b66e07a --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qt3Support.py @@ -0,0 +1 @@ +hiddenimports = ['sip', 'PyQt4._qt'] diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtAssistant.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtAssistant.py new file mode 100644 index 0000000..b0e19aa --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtAssistant.py @@ -0,0 +1,2 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4._qt'] + diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtCore.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtCore.py new file mode 100644 index 0000000..0cc484c --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtCore.py @@ -0,0 +1,8 @@ +hiddenimports = ['sip', "PyQt4._qt"] + +from PyInstaller.hooks.hookutils import qt4_plugins_binaries + + +def hook(mod): + mod.binaries.extend(qt4_plugins_binaries('codecs')) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtGui.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtGui.py new file mode 100644 index 0000000..8a93619 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtGui.py @@ -0,0 +1,12 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4._qt'] + +from PyInstaller.hooks.hookutils import qt4_plugins_binaries + + +def hook(mod): + mod.binaries.extend(qt4_plugins_binaries('accessible')) + mod.binaries.extend(qt4_plugins_binaries('iconengines')) + mod.binaries.extend(qt4_plugins_binaries('imageformats')) + mod.binaries.extend(qt4_plugins_binaries('inputmethods')) + mod.binaries.extend(qt4_plugins_binaries('graphicssystems')) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtHelp.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtHelp.py new file mode 100644 index 0000000..d811a6c --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtHelp.py @@ -0,0 +1,26 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +hiddenimports = [ + 'sip', + 'PyQt4.QtCore', + 'PyQt4.QtGui', + 'PyQt4.QtSql', + 'PyQt4.QtNetwork', + 'PyQt4._qt' +] diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtNetwork.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtNetwork.py new file mode 100644 index 0000000..8b4a18e --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtNetwork.py @@ -0,0 +1,9 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4._qt'] + +from PyInstaller.hooks.hookutils import qt4_plugins_binaries + + +def hook(mod): + # Network Bearer Management in Qt4 4.7+ + mod.binaries.extend(qt4_plugins_binaries('bearer')) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtOpenGL.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtOpenGL.py new file mode 100644 index 0000000..d0bbcf9 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtOpenGL.py @@ -0,0 +1,2 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4._qt'] + diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtScript.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtScript.py new file mode 100644 index 0000000..2608e26 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtScript.py @@ -0,0 +1,8 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4._qt'] + +from PyInstaller.hooks.hookutils import qt4_plugins_binaries + + +def hook(mod): + mod.binaries.extend(qt4_plugins_binaries('script')) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtSql.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtSql.py new file mode 100644 index 0000000..4120f43 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtSql.py @@ -0,0 +1,8 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4._qt'] + +from PyInstaller.hooks.hookutils import qt4_plugins_binaries + + +def hook(mod): + mod.binaries.extend(qt4_plugins_binaries('sqldrivers')) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtSvg.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtSvg.py new file mode 100644 index 0000000..d0bbcf9 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtSvg.py @@ -0,0 +1,2 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4._qt'] + diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtTest.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtTest.py new file mode 100644 index 0000000..6238354 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtTest.py @@ -0,0 +1 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4.QtGui', 'PyQt4._qt'] diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtWebKit.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtWebKit.py new file mode 100644 index 0000000..1ed68b1 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtWebKit.py @@ -0,0 +1 @@ +hiddenimports = ["sip", "PyQt4.QtCore", "PyQt4.QtGui", "PyQt4.QtNetwork", "PyQt4._qt"] diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtXml.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtXml.py new file mode 100644 index 0000000..b0e19aa --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.QtXml.py @@ -0,0 +1,2 @@ +hiddenimports = ['sip', 'PyQt4.QtCore', 'PyQt4._qt'] + diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qwt5.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qwt5.py new file mode 100644 index 0000000..726dec7 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.Qwt5.py @@ -0,0 +1,10 @@ +from PyInstaller.hooks.hookutils import eval_statement + +hiddenimports = ["PyQt4.QtCore", "PyQt4.QtGui", "PyQt4.QtSvg"] + +if eval_statement("from PyQt4 import Qwt5; print hasattr(Qwt5, 'toNumpy')"): + hiddenimports.append("numpy") +if eval_statement("from PyQt4 import Qwt5; print hasattr(Qwt5, 'toNumeric')"): + hiddenimports.append("Numeric") +if eval_statement("from PyQt4 import Qwt5; print hasattr(Qwt5, 'toNumarray')"): + hiddenimports.append("numarray") diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.phonon.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.phonon.py new file mode 100644 index 0000000..810b435 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.phonon.py @@ -0,0 +1,8 @@ +hiddenimports = ['sip', 'PyQt4.QtGui', 'PyQt4._qt'] + +from PyInstaller.hooks.hookutils import qt4_plugins_binaries + + +def hook(mod): + mod.binaries.extend(qt4_plugins_binaries('phonon_backend')) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.py new file mode 100644 index 0000000..0181715 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.py @@ -0,0 +1,13 @@ +import sys +from PyInstaller.hooks.hookutils import qt4_menu_nib_dir + +# In the new consolidated mode any PyQt depends on _qt +hiddenimports = ['sip', 'PyQt4._qt'] + +# For Qt to work on Mac OS X it is necessary include +# directory qt_menu.nib. This directory contains some +# resource files necessary to run PyQt app. +if sys.platform.startswith('darwin'): + datas = [ + (qt4_menu_nib_dir(), ''), + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.port_v2.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.port_v2.py new file mode 100644 index 0000000..03e8e98 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.port_v2.py @@ -0,0 +1,27 @@ +# Copyright (C) 2009, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +def hook(mod): + # forbid imports in the port_v2 directory under Python 3 + # The code wouldn't import and would crash the build process. + if sys.hexversion >= 0x03000000: + mod.__path__ = [] + return mod + + diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.port_v3.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.port_v3.py new file mode 100644 index 0000000..ea3e7cf --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.port_v3.py @@ -0,0 +1,27 @@ +# Copyright (C) 2009, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +def hook(mod): + # forbid imports in the port_v3 directory under Python 2 + # The code wouldn't import and would crash the build process. + if sys.hexversion < 0x03000000: + mod.__path__ = [] + return mod + + diff --git a/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.py b/pyinstaller/PyInstaller/hooks/hook-PyQt4.uic.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-SpiderImagePlugin.py b/pyinstaller/PyInstaller/hooks/hook-SpiderImagePlugin.py new file mode 100644 index 0000000..0b5c963 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-SpiderImagePlugin.py @@ -0,0 +1,21 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# Forward to shared code for PIL. PIL can be imported either as a top-level package +# (from PIL import Image), or not (import Image), because it installs a +# PIL.pth. +from PyInstaller.hooks.shared_PIL_SpiderImagePlugin import * diff --git a/pyinstaller/PyInstaller/hooks/hook-_elementtree.py b/pyinstaller/PyInstaller/hooks/hook-_elementtree.py new file mode 100644 index 0000000..32ce1aa --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-_elementtree.py @@ -0,0 +1,18 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +hiddenimports = ['pyexpat', 'xml.etree.ElementTree', 'copy'] diff --git a/pyinstaller/PyInstaller/hooks/hook-_mysql.py b/pyinstaller/PyInstaller/hooks/hook-_mysql.py new file mode 100644 index 0000000..0296888 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-_mysql.py @@ -0,0 +1,6 @@ +# Hook for _mysql, required if higher-level pure python module is not imported +# Author: htgoebel +# Date: 2011-11-18 +# Ticket: #323 + +hiddenimports = ['_mysql_exceptions'] diff --git a/pyinstaller/PyInstaller/hooks/hook-_sre.py b/pyinstaller/PyInstaller/hooks/hook-_sre.py new file mode 100644 index 0000000..5643bb4 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-_sre.py @@ -0,0 +1,17 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +hiddenimports = ['copy_reg'] diff --git a/pyinstaller/PyInstaller/hooks/hook-_tkinter.py b/pyinstaller/PyInstaller/hooks/hook-_tkinter.py new file mode 100644 index 0000000..f1599d4 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-_tkinter.py @@ -0,0 +1,165 @@ +# +# Copyright (C) 2012, Martin Zibricky +# Copyright (C) 2011, Hartmut Goebel +# Copyright (C) 2005, Giovanni Bajo +# +# 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 os +import sys + +import PyInstaller.bindepend + +from PyInstaller.compat import is_py24, is_win, is_darwin, is_unix, is_virtualenv +from PyInstaller.build import Tree +from PyInstaller.hooks.hookutils import exec_statement, logger + + +def _handle_broken_tk(): + """ + Workaround for broken Tcl/Tk detection in virtualenv on Windows. + + There is a bug in older versions of virtualenv in setting paths + to Tcl/Tk properly. PyInstaller running in virtualenv is then + not able to find Tcl/Tk. + + This issue has been experienced in virtualenv with Python 2.4 on Win7. + + https://github.com/pypa/virtualenv/issues/93 + """ + if is_win and is_virtualenv and is_py24: + basedir = os.path.join(sys.real_prefix, 'tcl') + files = os.listdir(basedir) + v = os.environ + # Detect Tcl/Tk paths. + for f in files: + abs_path = os.path.join(basedir, f) + if f.startswith('tcl') and os.path.isdir(abs_path): + v['TCL_LIBRARY'] = abs_path + if f.startswith('tk') and os.path.isdir(abs_path): + v['TK_LIBRARY'] = abs_path + if f.startswith('tix') and os.path.isdir(abs_path): + v['TIX_LIBRARY'] = abs_path + + +def _find_tk_darwin_frameworks(binaries): + """ + Tcl and Tk are installed as Mac OS X Frameworks. + """ + tcl_root = tk_root = None + for nm, fnm in binaries: + if nm == 'Tcl': + tcl_root = os.path.join(os.path.dirname(fnm), 'Resources/Scripts') + if nm == 'Tk': + tk_root = os.path.join(os.path.dirname(fnm), 'Resources/Scripts') + return tcl_root, tk_root + + +def _find_tk_tclshell(): + """ + Get paths to Tcl/Tk from the Tcl shell command 'info library'. + + This command will return path to TCL_LIBRARY. + On most systems are Tcl and Tk libraries installed + in the same prefix. + """ + tcl_root = tk_root = None + + # Python code to get path to TCL_LIBRARY. + code = 'from Tkinter import Tcl; t = Tcl(); print t.eval("info library")' + + tcl_root = exec_statement(code) + tk_version = exec_statement('from _tkinter import TK_VERSION as v; print v') + # TK_LIBRARY is in the same prefix as Tcl. + tk_root = os.path.join(os.path.dirname(tcl_root), 'tk%s' % tk_version) + return tcl_root, tk_root + + +def _find_tk(mod): + """ + Find paths with Tcl and Tk data files to be bundled by PyInstaller. + + Return: + tcl_root path to Tcl data files. + tk_root path to Tk data files. + """ + bins = PyInstaller.bindepend.selectImports(mod.__file__) + + if is_darwin: + # _tkinter depends on system Tcl/Tk frameworks. + if not bins: + # 'mod.binaries' can't be used because on Mac OS X _tkinter.so + # might depend on system Tcl/Tk frameworks and these are not + # included in 'mod.binaries'. + bins = PyInstaller.bindepend.getImports(mod.__file__) + # Reformat data structure from + # set(['lib1', 'lib2', 'lib3']) + # to + # [('Tcl', '/path/to/Tcl'), ('Tk', '/path/to/Tk')] + mapping = {} + for l in bins: + mapping[os.path.basename(l)] = l + bins = [ + ('Tcl', mapping['Tcl']), + ('Tk', mapping['Tk']), + ] + + # _tkinter depends on Tcl/Tk compiled as frameworks. + path_to_tcl = bins[0][1] + if 'Library/Frameworks' in path_to_tcl: + tcl_tk = _find_tk_darwin_frameworks(bins) + # Tcl/Tk compiled as on Linux other Unices. + # For example this is the case of Tcl/Tk from macports. + else: + tcl_tk = _find_tk_tclshell() + + else: + tcl_tk = _find_tk_tclshell() + + return tcl_tk + + +def _collect_tkfiles(mod): + # Workaround for broken Tcl/Tk detection in virtualenv on Windows. + _handle_broken_tk() + + tcl_root, tk_root = _find_tk(mod) + + tcldir = "tcl" + tkdir = "tk" + + tcltree = Tree(tcl_root, os.path.join('_MEI', tcldir), + excludes=['demos', 'encoding', '*.lib', 'tclConfig.sh']) + tktree = Tree(tk_root, os.path.join('_MEI', tkdir), + excludes=['demos', 'encoding', '*.lib', 'tkConfig.sh']) + return (tcltree + tktree) + + +def hook(mod): + # If not supported platform, skip TCL/TK detection. + if not (is_win or is_darwin or is_unix): + logger.info("... skipping TCL/TK detection on this platform (%s)", + sys.platform) + return mod + + # Get the Tcl/Tk data files for bundling with executable. + #try: + tk_files = _collect_tkfiles(mod) + mod.datas.extend(tk_files) + #except: + #logger.error("could not find TCL/TK") + + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-anydbm.py b/pyinstaller/PyInstaller/hooks/hook-anydbm.py new file mode 100644 index 0000000..cb15dfe --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-anydbm.py @@ -0,0 +1,19 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +#anydbm +hiddenimports = ['dbhash', 'gdbm', 'dbm', 'dumbdbm'] diff --git a/pyinstaller/PyInstaller/hooks/hook-babel.py b/pyinstaller/PyInstaller/hooks/hook-babel.py new file mode 100644 index 0000000..40a5f1b --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-babel.py @@ -0,0 +1,10 @@ +from PyInstaller.hooks.hookutils import exec_statement + +hiddenimports = ["babel.dates"] + +babel_localedata_dir = exec_statement( + "import babel.localedata; print babel.localedata._dirname") + +datas = [ + (babel_localedata_dir, ""), +] diff --git a/pyinstaller/PyInstaller/hooks/hook-cElementTree.py b/pyinstaller/PyInstaller/hooks/hook-cElementTree.py new file mode 100644 index 0000000..0f2cc4d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-cElementTree.py @@ -0,0 +1,19 @@ +# Copyright (C) 2007, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# cElementTree has a hidden import +hiddenimports = ['elementtree.ElementTree'] diff --git a/pyinstaller/PyInstaller/hooks/hook-cPickle.py b/pyinstaller/PyInstaller/hooks/hook-cPickle.py new file mode 100644 index 0000000..77b0099 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-cPickle.py @@ -0,0 +1,17 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +hiddenimports = ['copy_reg', 'types', 'string'] diff --git a/pyinstaller/PyInstaller/hooks/hook-cStringIO.py b/pyinstaller/PyInstaller/hooks/hook-cStringIO.py new file mode 100644 index 0000000..5643bb4 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-cStringIO.py @@ -0,0 +1,17 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +hiddenimports = ['copy_reg'] diff --git a/pyinstaller/PyInstaller/hooks/hook-carchive.py b/pyinstaller/PyInstaller/hooks/hook-carchive.py new file mode 100644 index 0000000..2d04680 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-carchive.py @@ -0,0 +1,25 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +def hook(mod): + for i, m in enumerate(mod.imports): + if m[0] == 'strop': + del mod.imports[i] + break + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-codecs.py b/pyinstaller/PyInstaller/hooks/hook-codecs.py new file mode 100644 index 0000000..ad9dd0d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-codecs.py @@ -0,0 +1,17 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +hiddenimports = ['encodings'] diff --git a/pyinstaller/PyInstaller/hooks/hook-cx_Oracle.py b/pyinstaller/PyInstaller/hooks/hook-cx_Oracle.py new file mode 100644 index 0000000..65ac410 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-cx_Oracle.py @@ -0,0 +1,18 @@ +# Copyright (C) 2010, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +hiddenimports = ['decimal'] + diff --git a/pyinstaller/PyInstaller/hooks/hook-distutils.py b/pyinstaller/PyInstaller/hooks/hook-distutils.py new file mode 100644 index 0000000..5b017d9 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-distutils.py @@ -0,0 +1,72 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 distutils +import distutils.sysconfig +import marshal +import os +import sys + +from PyInstaller import compat +from PyInstaller.compat import is_win + + +# distutils module requires Makefile and pyconfig.h files from Python +# installation. 'distutils.sysconfig' parses these files to get some +# information from them. +_CONFIG_H = distutils.sysconfig.get_config_h_filename() +_MAKEFILE = distutils.sysconfig.get_makefile_filename() + + +# In virtualenv sys.prefix is overridden. +if hasattr(sys, 'real_prefix'): + sys_prefix = sys.real_prefix +else: + sys_prefix = sys.prefix + + +# Relative path to config_h in the dist directory. +_frozen_config_h = compat.relpath(os.path.dirname(_CONFIG_H), sys_prefix) +# Data files in PyInstaller hook format. +datas = [ + (_CONFIG_H, _frozen_config_h), +] + + +# On Windows Makefile does not exist. +if not is_win: + _frozen_makefile = compat.relpath(os.path.dirname(_MAKEFILE), sys_prefix) + datas.append((_MAKEFILE, _frozen_makefile)) + + +def hook(mod): + """ + Contributed by jkp@kirkconsulting.co.uk + This hook checks for the distutils hacks present when using the + virtualenv package. + """ + # Non-empty means PyInstaller is running inside virtualenv. + # Virtualenv overrides real distutils modules. + if hasattr(distutils, 'distutils_path'): + mod_path = os.path.join(distutils.distutils_path, '__init__.pyc') + try: + parsed_code = marshal.loads(open(mod_path, 'rb').read()[8:]) + except IOError: + parsed_code = compile(open(mod_path[:-1], 'rU').read(), mod_path, 'exec') + mod.__init__('distutils', mod_path, parsed_code) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-django.contrib.py b/pyinstaller/PyInstaller/hooks/hook-django.contrib.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-django.contrib.sessions.py b/pyinstaller/PyInstaller/hooks/hook-django.contrib.sessions.py new file mode 100644 index 0000000..dfcdd8d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.contrib.sessions.py @@ -0,0 +1,33 @@ +# Copyright (C) 2009, Lorenzo Berni +# Based on previous work under copyright (c) 2001, 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 os +import glob + +def hook(mod): + global hiddenimports + + modpath = mod.__path__[0] + hiddenimports = [] + + for fn in glob.glob(os.path.join(modpath, 'backends', '*.py')): + fn = os.path.basename(fn) + fn = os.path.splitext(fn)[0] + hiddenimports.append('django.contrib.sessions.backends.' + fn) + + return mod + diff --git a/pyinstaller/PyInstaller/hooks/hook-django.core.cache.py b/pyinstaller/PyInstaller/hooks/hook-django.core.cache.py new file mode 100644 index 0000000..17996f4 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.core.cache.py @@ -0,0 +1,33 @@ +# Copyright (C) 2009, Lorenzo Berni +# Based on previous work under copyright (c) 2001, 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 os +import glob + +def hook(mod): + global hiddenimports + + modpath = mod.__path__[0] + hiddenimports = [] + + for fn in glob.glob(os.path.join(modpath, 'backends', '*.py')): + fn = os.path.basename(fn) + fn = os.path.splitext(fn)[0] + hiddenimports.append('django.core.cache.backends.' + fn) + + return mod + diff --git a/pyinstaller/PyInstaller/hooks/hook-django.core.mail.py b/pyinstaller/PyInstaller/hooks/hook-django.core.mail.py new file mode 100644 index 0000000..bf05028 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.core.mail.py @@ -0,0 +1,31 @@ +# Copyright (C) 2009, Lorenzo Berni +# Based on previous work under copyright (c) 2001, 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 + +# django.core.mail uses part of the email package. +# Problem is: when using runserver with autoreload mode, the thread that +# checks fore changed files unwillingly trigger further imports within +# the email package because of the LazyImporter in email (used in 2.5 for +# backward compatibility). +# We then need to name those modules as hidden imports, otherwise at +# runtime the autoreload thread will complain with a traceback. +hiddenimports = [ + 'email.mime.message', + 'email.mime.image', + 'email.mime.text', + 'email.mime.multipart', + 'email.mime.audio' +] diff --git a/pyinstaller/PyInstaller/hooks/hook-django.core.management.py b/pyinstaller/PyInstaller/hooks/hook-django.core.management.py new file mode 100644 index 0000000..203938d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.core.management.py @@ -0,0 +1,34 @@ +# Copyright (C) 2009, Lorenzo Berni +# Based on previous work under copyright (c) 2001, 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 os +import glob + +def hook(mod): + global hiddenimports + + modpath = mod.__path__[0] + + hiddenimports = [] + + for fn in glob.glob(os.path.join(modpath, 'commands', '*.py')): + fn = os.path.basename(fn) + fn = os.path.splitext(fn)[0] + hiddenimports.append('django.core.management.commands.' + fn) + + return mod + diff --git a/pyinstaller/PyInstaller/hooks/hook-django.core.py b/pyinstaller/PyInstaller/hooks/hook-django.core.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-django.db.backends.mysql.base.py b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.mysql.base.py new file mode 100644 index 0000000..b4d9533 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.mysql.base.py @@ -0,0 +1,2 @@ +# Compiler module (see class DatabaseOperations) +hiddenimports = ["django.db.backends.mysql.compiler"] diff --git a/pyinstaller/PyInstaller/hooks/hook-django.db.backends.mysql.py b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.mysql.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-django.db.backends.oracle.base.py b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.oracle.base.py new file mode 100644 index 0000000..4cb0b76 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.oracle.base.py @@ -0,0 +1 @@ +hiddenimports = ["django.db.backends.oracle.compiler"] diff --git a/pyinstaller/PyInstaller/hooks/hook-django.db.backends.oracle.py b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.oracle.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-django.db.backends.py b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.py new file mode 100644 index 0000000..e00faae --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.db.backends.py @@ -0,0 +1,36 @@ +# Copyright (C) 2009, Lorenzo Berni +# Based on previous work under copyright (c) 2001, 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 os +import glob + +def hook(mod): + global hiddenimports + + modpath = mod.__path__[0] + hiddenimports = [] + + for fn in glob.glob(os.path.join(modpath, '*')): + if os.path.isdir(fn): + fn = os.path.basename(fn) + hiddenimports.append('django.db.backends.' + fn + '.base') + + # Compiler (see class BaseDatabaseOperations) + hiddenimports.append("django.db.models.sql.compiler") + + return mod + diff --git a/pyinstaller/PyInstaller/hooks/hook-django.db.py b/pyinstaller/PyInstaller/hooks/hook-django.db.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-django.py b/pyinstaller/PyInstaller/hooks/hook-django.py new file mode 100644 index 0000000..efe66c7 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-django.py @@ -0,0 +1,49 @@ +# Copyright (C) 2009, Lorenzo Berni +# Based on previous work under copyright (c) 2001, 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 glob +import os +import PyInstaller +import PyInstaller.compat as compat + +from hookutils import django_dottedstring_imports, find_django_root + +python_path = compat.getenv("PYTHONPATH") + +if python_path: + python_path = os.pathsep.join([python_path] + PyInstaller.__pathex__) +else: + python_path = os.pathsep.join(PyInstaller.__pathex__) + +django_root_dirs = [find_django_root(path) + for path in python_path.split(os.pathsep)] + +if not django_root_dirs: + raise RuntimeError("No django root directory found. Please check your " + "pathex definition in the project spec file.") + +if django_root_dirs[0] in PyInstaller.__pathex__: + raise RuntimeError("The django root directory is defined in the pathex. " + "You have to define the parent directory instead of " + "the django root directory.") + +compat.setenv("PYTHONPATH", python_path) + +hiddenimports = [django_dottedstring_imports(root_dir) + for root_dir in django_root_dirs] + + diff --git a/pyinstaller/PyInstaller/hooks/hook-dns.rdata.py b/pyinstaller/PyInstaller/hooks/hook-dns.rdata.py new file mode 100644 index 0000000..3c3c3d2 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-dns.rdata.py @@ -0,0 +1,21 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +hiddenimports = [ + "dns.rdtypes.*", + "dns.rdtypes.ANY.*" +] diff --git a/pyinstaller/PyInstaller/hooks/hook-email.message.py b/pyinstaller/PyInstaller/hooks/hook-email.message.py new file mode 100644 index 0000000..a15a86c --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-email.message.py @@ -0,0 +1,6 @@ +# email.message imports the old-style naming of two modules: +# email.Iterators and email.Generator. Since those modules +# don't exist anymore and there are import trick to map them +# to the real modules (lowercase), we need to specify them +# as hidden imports to make PyInstaller package them. +hiddenimports = [ "email.iterators", "email.generator" ] diff --git a/pyinstaller/PyInstaller/hooks/hook-email.py b/pyinstaller/PyInstaller/hooks/hook-email.py new file mode 100644 index 0000000..8fd1fda --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-email.py @@ -0,0 +1,29 @@ +# +# Copyright (C) 2011, Martin Zibricky +# +# 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 + + +from PyInstaller import is_py25 + + +# These modules must be included with 'email' module for +# lazy loading to provide name mapping from new-style (lower case) names +# email. -> email. +# email.MIME -> email.mime. +if is_py25: + import email + hiddenimports = ['email.' + x.lower() for x in email._LOWERNAMES] + hiddenimports += ['email.mime.' + x.lower() for x in email._MIMENAMES] diff --git a/pyinstaller/PyInstaller/hooks/hook-enchant.checker.py b/pyinstaller/PyInstaller/hooks/hook-enchant.checker.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-enchant.checker.wxSpellCheckerDialog.py b/pyinstaller/PyInstaller/hooks/hook-enchant.checker.wxSpellCheckerDialog.py new file mode 100644 index 0000000..51ee76d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-enchant.checker.wxSpellCheckerDialog.py @@ -0,0 +1,11 @@ + +# enchant.checker.wxSpellCheckerDialog causes pyinstaller to include +# whole gtk and wx libraries if they are installed. This module is +# thus ignored to prevent this. + +# TODO find better workaround +def hook(mod): + # Workaround DOES NOT work with well with python 2.6 + # let's just disable it + #return None + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-enchant.py b/pyinstaller/PyInstaller/hooks/hook-enchant.py new file mode 100644 index 0000000..9fc803d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-enchant.py @@ -0,0 +1,10 @@ +import sys + +from hookutils import eval_script + +if sys.platform == 'win32': + files = eval_script('enchant-datafiles-finder.py') + datas = [] # data files in PyInstaller hook format + for d in files: + for f in d[1]: + datas.append((f, d[0])) diff --git a/pyinstaller/PyInstaller/hooks/hook-encodings.py b/pyinstaller/PyInstaller/hooks/hook-encodings.py new file mode 100644 index 0000000..c9bc8bf --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-encodings.py @@ -0,0 +1,29 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +#encodings', +attrs = [('search_function',0)] + +import os, sys, glob +from distutils import sysconfig +hiddenimports = [] +libpath = sysconfig.get_python_lib(plat_specific=0, standard_lib=1) +for f in glob.glob(os.path.join(libpath, "encodings", "*.py")): + f = os.path.basename(f) + f = os.path.splitext(f)[0] + if f != "__init__": + hiddenimports.append(f) diff --git a/pyinstaller/PyInstaller/hooks/hook-gadfly.py b/pyinstaller/PyInstaller/hooks/hook-gadfly.py new file mode 100644 index 0000000..fe5173e --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-gadfly.py @@ -0,0 +1 @@ +hiddenimports = ["sql_mar"] diff --git a/pyinstaller/PyInstaller/hooks/hook-gtk.py b/pyinstaller/PyInstaller/hooks/hook-gtk.py new file mode 100644 index 0000000..af490f7 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-gtk.py @@ -0,0 +1,23 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# Submited by Seth Remington (ticket#15) +# Refined by Marco Bonifazi (via e-mail) +hiddenimports = ['gtkglext', 'gdkgl', 'gdkglext', 'gdk', 'gtk.gdk', 'gtk.gtkgl', + 'gtk.gtkgl._gtkgl', 'gtkgl', 'pangocairo', 'pango', 'atk', + 'gobject', 'gtk.glade', 'cairo', 'gio', + 'gtk.keysyms'] diff --git a/pyinstaller/PyInstaller/hooks/hook-h5py.py b/pyinstaller/PyInstaller/hooks/hook-h5py.py new file mode 100644 index 0000000..582a354 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-h5py.py @@ -0,0 +1,6 @@ +# Hook for http://pypi.python.org/pypi/h5py/ +# Author: dhyams +# Date: 2011-10-14 +# Ticket: #437 + +hiddenimports = ['_proxy','utils','defs'] diff --git a/pyinstaller/PyInstaller/hooks/hook-iu.py b/pyinstaller/PyInstaller/hooks/hook-iu.py new file mode 100644 index 0000000..5ec0466 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-iu.py @@ -0,0 +1,37 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +def hook(mod): + names = sys.builtin_module_names + if 'posix' in names: + removes = ['nt', 'dos', 'os2', 'mac', 'win32api'] + elif 'nt' in names: + removes = ['dos', 'os2', 'mac'] + elif 'os2' in names: + removes = ['nt', 'dos', 'mac', 'win32api'] + elif 'dos' in names: + removes = ['nt', 'os2', 'mac', 'win32api'] + elif 'mac' in names: + removes = ['nt', 'dos', 'os2', 'win32api'] + mod.imports = [m + for m in mod.imports + # if first part of module-name not in removes + if m[0].split('.', 1)[0] not in removes + ] + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-kinterbasdb.py b/pyinstaller/PyInstaller/hooks/hook-kinterbasdb.py new file mode 100644 index 0000000..35eb1de --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-kinterbasdb.py @@ -0,0 +1,25 @@ +# Copyirght (C) 2005, Eugene Prigorodov +# +# 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 + +# Courtesy of Eugene Prigorodov + +#kinterbasdb +hiddenimports = ['k_exceptions', 'services', 'typeconv_naked', + 'typeconv_backcompat', 'typeconv_23plus', + 'typeconv_datetime_stdlib', 'typeconv_datetime_mx', + 'typeconv_datetime_naked', 'typeconv_fixed_fixedpoint', + 'typeconv_fixed_stdlib', 'typeconv_text_unicode', + 'typeconv_util_isinstance', '_kinterbasdb', '_kiservices'] diff --git a/pyinstaller/PyInstaller/hooks/hook-lxml.etree.py b/pyinstaller/PyInstaller/hooks/hook-lxml.etree.py new file mode 100644 index 0000000..2198e01 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-lxml.etree.py @@ -0,0 +1,2 @@ +# Contributed by pyplant@googlemail.com +hiddenimports = ['_elementpath', 'gzip'] diff --git a/pyinstaller/PyInstaller/hooks/hook-lxml.objectify.py b/pyinstaller/PyInstaller/hooks/hook-lxml.objectify.py new file mode 100644 index 0000000..9d83432 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-lxml.objectify.py @@ -0,0 +1 @@ +hiddenimports = ['lxml.etree'] diff --git a/pyinstaller/PyInstaller/hooks/hook-mako.codegen.py b/pyinstaller/PyInstaller/hooks/hook-mako.codegen.py new file mode 100644 index 0000000..03400e3 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-mako.codegen.py @@ -0,0 +1,3 @@ +# codegen generates Python code that is then executed through exec(). +# This Python code imports the following modules. +hiddenimports = ['mako.cache', 'make.runtime', 'mako.filters'] diff --git a/pyinstaller/PyInstaller/hooks/hook-matplotlib.backends.py b/pyinstaller/PyInstaller/hooks/hook-matplotlib.backends.py new file mode 100644 index 0000000..9b07a08 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-matplotlib.backends.py @@ -0,0 +1,23 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +from PyInstaller.hooks.hookutils import matplotlib_backends + + +# Include only available matplotlib backends. +hiddenimports = matplotlib_backends() diff --git a/pyinstaller/PyInstaller/hooks/hook-matplotlib.numerix.py b/pyinstaller/PyInstaller/hooks/hook-matplotlib.numerix.py new file mode 100644 index 0000000..a33415c --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-matplotlib.numerix.py @@ -0,0 +1,10 @@ +# Contributed by Peter Burgers +# The matplotlib.numerix package sneaks these imports in under the radar: +hiddenimports = [ + 'fft', + 'linear_algebra', + 'random_array', + 'ma', + 'mlab', +] + diff --git a/pyinstaller/PyInstaller/hooks/hook-matplotlib.py b/pyinstaller/PyInstaller/hooks/hook-matplotlib.py new file mode 100644 index 0000000..17a2f96 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-matplotlib.py @@ -0,0 +1,8 @@ +from PyInstaller.hooks.hookutils import exec_statement + +mpl_data_dir = exec_statement( + "import matplotlib; print matplotlib._get_data_path()") + +datas = [ + (mpl_data_dir, ""), +] diff --git a/pyinstaller/PyInstaller/hooks/hook-os.py b/pyinstaller/PyInstaller/hooks/hook-os.py new file mode 100644 index 0000000..602010d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-os.py @@ -0,0 +1,33 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +def hook(mod): + if 'posix' in sys.builtin_module_names: + removes = ['nt', 'ntpath', 'dos', 'dospath', 'os2', 'mac', 'macpath', + 'ce', 'riscos', 'riscospath', 'win32api', 'riscosenviron'] + elif 'nt' in sys.builtin_module_names: + removes = ['dos', 'dospath', 'os2', 'mac', 'macpath', 'ce', 'riscos', + 'riscospath', 'riscosenviron',] + + mod.imports = [m + for m in mod.imports + # if first part of module-name not in removes + if m[0].split('.', 1)[0] not in removes + ] + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-parser.py b/pyinstaller/PyInstaller/hooks/hook-parser.py new file mode 100644 index 0000000..5643bb4 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-parser.py @@ -0,0 +1,17 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +hiddenimports = ['copy_reg'] diff --git a/pyinstaller/PyInstaller/hooks/hook-paste.exceptions.reporter.py b/pyinstaller/PyInstaller/hooks/hook-paste.exceptions.reporter.py new file mode 100644 index 0000000..8baf705 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-paste.exceptions.reporter.py @@ -0,0 +1,3 @@ +# some modules use the old-style import: explicitly include +# the new module when the old one is referenced +hiddenimports = ["email.mime.text", "email.mime.multipart"] diff --git a/pyinstaller/PyInstaller/hooks/hook-psycopg2.py b/pyinstaller/PyInstaller/hooks/hook-psycopg2.py new file mode 100644 index 0000000..2ac1aac --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-psycopg2.py @@ -0,0 +1 @@ +hiddenimports = ['mx.DateTime'] diff --git a/pyinstaller/PyInstaller/hooks/hook-pyexpat.py b/pyinstaller/PyInstaller/hooks/hook-pyexpat.py new file mode 100644 index 0000000..676c98f --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pyexpat.py @@ -0,0 +1,18 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +hiddenimports = ['xmlparse', 'xmltok'] diff --git a/pyinstaller/PyInstaller/hooks/hook-pygame.py b/pyinstaller/PyInstaller/hooks/hook-pygame.py new file mode 100644 index 0000000..9dc314f --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pygame.py @@ -0,0 +1,7 @@ +# Hook for pygame._view, required for develop releases between +# 2011-02-08 and 2011-08-31, including prebuilt-pygame1.9.2a0 +# Author: htgoebel +# Date: 2011-11-18 +# Ticket: #406 + +hiddenimports = ['pygame._view'] diff --git a/pyinstaller/PyInstaller/hooks/hook-pygments.lexers.py b/pyinstaller/PyInstaller/hooks/hook-pygments.lexers.py new file mode 100644 index 0000000..d1f1fa6 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pygments.lexers.py @@ -0,0 +1,2 @@ + +hiddenimports = ['agile', 'dotnet'] diff --git a/pyinstaller/PyInstaller/hooks/hook-pygments.styles.py b/pyinstaller/PyInstaller/hooks/hook-pygments.styles.py new file mode 100644 index 0000000..316960a --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pygments.styles.py @@ -0,0 +1,2 @@ + +hiddenimports = ['default'] diff --git a/pyinstaller/PyInstaller/hooks/hook-pyodbc.py b/pyinstaller/PyInstaller/hooks/hook-pyodbc.py new file mode 100644 index 0000000..b73b534 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pyodbc.py @@ -0,0 +1,29 @@ +# +# Copyright (C) 2012, Martin Zibricky +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + + +# Contributed by Don Dwiggins +from PyInstaller.hooks.hookutils import get_pyextension_imports + + +# It's hard to detect imports of binary Python module without importing it. +# Let's try importing that module in a subprocess. +# TODO function get_pyextension_imports() is experimental and we need +# to evaluate its usage here and its suitability for other hooks. +hiddenimports = get_pyextension_imports('pyodbc') diff --git a/pyinstaller/PyInstaller/hooks/hook-pythoncom.py b/pyinstaller/PyInstaller/hooks/hook-pythoncom.py new file mode 100644 index 0000000..3c5c73b --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pythoncom.py @@ -0,0 +1,34 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 PyInstaller.depend.modules + +hiddenimports = ['win32com.server.policy'] + + +def hook(mod): + import sys + newname = 'pythoncom%d%d' % sys.version_info[:2] + if mod.typ == 'EXTENSION': + mod.__name__ = newname + else: + import win32api + h = win32api.LoadLibrary(newname + '.dll') + pth = win32api.GetModuleFileName(h) + #win32api.FreeLibrary(h) + mod = PyInstaller.depend.modules.ExtensionModule(newname, pth) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-pyttsx.py b/pyinstaller/PyInstaller/hooks/hook-pyttsx.py new file mode 100644 index 0000000..c87f492 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pyttsx.py @@ -0,0 +1,27 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# pyttsx imports drivers module based on specific platform. +# Found at http://mrmekon.tumblr.com/post/5272210442/pyinstaller-and-pyttsx +hiddenimports = [ + 'drivers', + 'drivers.dummy', + 'drivers.espeak', + 'drivers.nsss', + 'drivers.sapi5', +] diff --git a/pyinstaller/PyInstaller/hooks/hook-pywinauto.py b/pyinstaller/PyInstaller/hooks/hook-pywinauto.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/hooks/hook-pywinauto.tests.py b/pyinstaller/PyInstaller/hooks/hook-pywinauto.tests.py new file mode 100644 index 0000000..241b54d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pywinauto.tests.py @@ -0,0 +1,14 @@ +hiddenimports = [ + "allcontrols", + "asianhotkey", + "comboboxdroppedheight", + "comparetoreffont", + "leadtrailspaces", + "miscvalues", + "missalignment", + "missingextrastring", + "overlapping", + "repeatedhotkey", + "translation", + "truncation", +] diff --git a/pyinstaller/PyInstaller/hooks/hook-pywintypes.py b/pyinstaller/PyInstaller/hooks/hook-pywintypes.py new file mode 100644 index 0000000..bb3507e --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-pywintypes.py @@ -0,0 +1,32 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 PyInstaller.depend.modules + + +def hook(mod): + import sys + newname = 'pywintypes%d%d' % sys.version_info[:2] + if mod.typ == 'EXTENSION': + mod.__name__ = newname + else: + import win32api + h = win32api.LoadLibrary(newname + '.dll') + pth = win32api.GetModuleFileName(h) + #win32api.FreeLibrary(h) + mod = PyInstaller.depend.modules.ExtensionModule(newname, pth) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-qt.py b/pyinstaller/PyInstaller/hooks/hook-qt.py new file mode 100644 index 0000000..5f126b3 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-qt.py @@ -0,0 +1,19 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# PyQt (qt.pyd) has a secret dependence on sip.pyd +hiddenimports = ['sip'] diff --git a/pyinstaller/PyInstaller/hooks/hook-regex.py b/pyinstaller/PyInstaller/hooks/hook-regex.py new file mode 100644 index 0000000..6373058 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-regex.py @@ -0,0 +1,18 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +hiddenimports = ['warnings'] diff --git a/pyinstaller/PyInstaller/hooks/hook-setuptools.py b/pyinstaller/PyInstaller/hooks/hook-setuptools.py new file mode 100644 index 0000000..3263a0f --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-setuptools.py @@ -0,0 +1,28 @@ +# +# Copyright (C) 2011, Martin Zibricky +# +# 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 + +from PyInstaller import is_unix, is_darwin + +hiddenimports = [ + # Test case import/test_zipimport2 fails during importing + # pkg_resources or setuptools when module not present. + 'distutils.command.build_ext', +] + +# Necessary for setuptools on Mac/Unix +if is_unix or is_darwin: + hiddenimports.append('syslog') diff --git a/pyinstaller/PyInstaller/hooks/hook-site.py b/pyinstaller/PyInstaller/hooks/hook-site.py new file mode 100644 index 0000000..13558ed --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-site.py @@ -0,0 +1,39 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# Replace the code of real 'site' module by fake code doing nothing. +# +# The real 'site' does some magic to find paths to other possible +# Python modules. We do not want this behaviour for frozen applications. +# +# Fake 'site' makes PyInstaller to work with distutils and to work inside +# virtualenv environment. + + +import os + +import PyInstaller + + +def hook(mod): + # Replace mod by fake 'site' module. + pyi_dir = os.path.abspath(os.path.dirname(PyInstaller.__file__)) + fake_file = os.path.join(pyi_dir, 'fake', 'fake-site.py') + new_code_object = PyInstaller.utils.misc.get_code_object(fake_file) + mod = PyInstaller.depend.modules.PyModule('site', fake_file, new_code_object) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-sqlalchemy.py b/pyinstaller/PyInstaller/hooks/hook-sqlalchemy.py new file mode 100644 index 0000000..0365651 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-sqlalchemy.py @@ -0,0 +1,43 @@ +# Copyright (C) 2009, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# Contributed by Greg Copeland + +from PyInstaller.hooks.hookutils import exec_statement + +# include most common database bindings +# some database bindings are detected and include some +# are not. We should explicitly include database backends. +hiddenimports = ['pysqlite2', 'MySQLdb', 'psycopg2'] + +# sqlalchemy.databases package from pre 0.6 sqlachemy versions +databases = exec_statement("import sqlalchemy.databases;print sqlalchemy.databases.__all__") +databases = eval(databases.strip()) + +for n in databases: + hiddenimports.append("sqlalchemy.databases." + n) + +# sqlalchemy.dialects package from 0.6 and newer sqlachemy versions +version = exec_statement('import sqlalchemy; print sqlalchemy.__version__') +is_alch06 = version >= '0.6' + +if is_alch06: + dialects = exec_statement("import sqlalchemy.dialects;print sqlalchemy.dialects.__all__") + dialects = eval(dialects.strip()) + + for n in databases: + hiddenimports.append("sqlalchemy.dialects." + n) diff --git a/pyinstaller/PyInstaller/hooks/hook-storm.database.py b/pyinstaller/PyInstaller/hooks/hook-storm.database.py new file mode 100644 index 0000000..05b36b6 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-storm.database.py @@ -0,0 +1,10 @@ +# Hook for storm ORM +# Author: mail@georg-schoelly.ch +# Date: 2011-10-14 +# Ticket: #437 + +hiddenimports = [ + 'storm.databases.sqlite', + 'storm.databases.postgres', + 'storm.databases.mysql' + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-tables.py b/pyinstaller/PyInstaller/hooks/hook-tables.py new file mode 100644 index 0000000..6588759 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-tables.py @@ -0,0 +1 @@ +hiddenimports = ["tables._comp_lzo", "tables._comp_bzip2"] diff --git a/pyinstaller/PyInstaller/hooks/hook-time.py b/pyinstaller/PyInstaller/hooks/hook-time.py new file mode 100644 index 0000000..660fcb0 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-time.py @@ -0,0 +1,20 @@ +# Copyright (C) 2006, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# Since Python 2.3, builtin module "time" imports Python module _strptime +# to implement "time.strptime". +hiddenimports = ['_strptime'] diff --git a/pyinstaller/PyInstaller/hooks/hook-usb.py b/pyinstaller/PyInstaller/hooks/hook-usb.py new file mode 100644 index 0000000..f26f73e --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-usb.py @@ -0,0 +1,61 @@ +# +# Copyright (C) 2012, Chien-An "Zero" Cho +# +# 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 ctypes.util +import os + +from PyInstaller.depend.utils import _resolveCtypesImports +from PyInstaller.compat import is_cygwin + + +# Include glob for library lookup in run-time hook. +hiddenimports = ['glob'] + + +# This method will try to resolve your libusb libraries in the +# following orders: +# +# libusb-1.0, libusb-0.1, openusb +# +# NOTE: Mind updating run-time hook when adding further libs. +libusb_candidates = ( + # libusb10 + 'usb-1.0', 'usb', 'libusb-1.0', + # libusb01 + 'usb-0.1', 'libusb0', + # openusb + 'openusb', +) + + +def hook(mod): + for candidate in libusb_candidates: + libname = ctypes.util.find_library(candidate) + if libname is not None: + break + + if libname is not None: + # Use basename here because Python returns full library path + # on Mac OSX when using ctypes.util.find_library. + bins = [os.path.basename(libname)] + mod.binaries.extend(_resolveCtypesImports(bins)) + elif is_cygwin: + bins = ['cygusb-1.0-0.dll', 'cygusb0.dll'] + mod.binaries.extend(_resolveCtypesImports(bins)[0:1]) + + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-vtkpython.py b/pyinstaller/PyInstaller/hooks/hook-vtkpython.py new file mode 100644 index 0000000..3a8eb87 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-vtkpython.py @@ -0,0 +1,23 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# courtesy of David C. Morrill (4/2/2002) +import os +if os.name == 'posix': + hiddenimports = ['libvtkCommonPython','libvtkFilteringPython','libvtkIOPython','libvtkImagingPython','libvtkGraphicsPython','libvtkRenderingPython','libvtkHybridPython','libvtkParallelPython','libvtkPatentedPython'] +else: + hiddenimports = ['vtkCommonPython','vtkFilteringPython','vtkIOPython','vtkImagingPython','vtkGraphicsPython','vtkRenderingPython','vtkHybridPython','vtkParallelPython','vtkPatentedPython'] diff --git a/pyinstaller/PyInstaller/hooks/hook-win32com.client.py b/pyinstaller/PyInstaller/hooks/hook-win32com.client.py new file mode 100644 index 0000000..1f5654d --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-win32com.client.py @@ -0,0 +1,18 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +attrs = [('NeedUnicodeConversions', 0), ('Dispatch',0)] diff --git a/pyinstaller/PyInstaller/hooks/hook-win32com.py b/pyinstaller/PyInstaller/hooks/hook-win32com.py new file mode 100644 index 0000000..58f2d18 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-win32com.py @@ -0,0 +1,39 @@ +# +# Copyright (C) 2012, Martin Zibricky +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 os + + +hiddenimports = [ + # win32com client and server util + # modules could be hidden imports + # of some modules using win32com. + # Included for completeness. + 'win32com.client.util', + 'win32com.server.util', +] + + +def hook(mod): + # win32com module changes sys.path and wrapps win32comext modules. + pth = str(mod.__path__[0]) + if os.path.isdir(pth): + mod.__path__.append( + os.path.normpath(os.path.join(pth, '..', 'win32comext'))) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-win32ui.py b/pyinstaller/PyInstaller/hooks/hook-win32ui.py new file mode 100644 index 0000000..b3dfb9a --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-win32ui.py @@ -0,0 +1,18 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +hiddenimports = ['cStringIO', 'traceback'] diff --git a/pyinstaller/PyInstaller/hooks/hook-wx.lib.activex.py b/pyinstaller/PyInstaller/hooks/hook-wx.lib.activex.py new file mode 100644 index 0000000..27088f0 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-wx.lib.activex.py @@ -0,0 +1,2 @@ +from PyInstaller.hooks.hookutils import exec_statement +exec_statement("import wx.lib.activex") #this needed because comtypes wx.lib.activex generates some stuff diff --git a/pyinstaller/PyInstaller/hooks/hook-wx.lib.pubsub.core.py b/pyinstaller/PyInstaller/hooks/hook-wx.lib.pubsub.core.py new file mode 100644 index 0000000..6820f43 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-wx.lib.pubsub.core.py @@ -0,0 +1,36 @@ +# +# Copyright (C) 2012, Daniel Hyams +# +# 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 os +import PyInstaller.hooks.hookutils + +from PyInstaller.hooks.hookutils import logger + + +def hook(mod): + pth = str(mod.__path__[0]) + if os.path.isdir(pth): + # If the user imported setuparg1, this is detected + # by the hook-wx.lib.pubsub.setuparg1.py hook. That + # hook sets PyInstaller.hooks.hookutils.wxpubsub + # to "arg1", and we set the appropriate path here. + protocol = getattr(PyInstaller.hooks.hookutils, 'wxpubsub', 'kwargs') + logger.info('wx.lib.pubsub: Adding %s protocol path' % protocol) + mod.__path__.append(os.path.normpath(os.path.join(pth, protocol))) + + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-wx.lib.pubsub.setuparg1.py b/pyinstaller/PyInstaller/hooks/hook-wx.lib.pubsub.setuparg1.py new file mode 100644 index 0000000..54929fe --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-wx.lib.pubsub.setuparg1.py @@ -0,0 +1,25 @@ +# +# Copyright (C) 2012, Daniel Hyams +# +# 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 PyInstaller.hooks.hookutils + + +# If the user imports setuparg1, we just set an attribute +# in PyInstaller.hooks.hookutils that allows us to later +# find out about this. +PyInstaller.hooks.hookutils.wxpubsub = 'arg1' diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.dom.domreg.py b/pyinstaller/PyInstaller/hooks/hook-xml.dom.domreg.py new file mode 100644 index 0000000..ae2e2bb --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.dom.domreg.py @@ -0,0 +1,19 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +#xml.dom.domreg line 54 +hiddenimports = ['xml.dom.minidom','xml.dom.DOMImplementation'] diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.dom.ext.py b/pyinstaller/PyInstaller/hooks/hook-xml.dom.ext.py new file mode 100644 index 0000000..3418768 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.dom.ext.py @@ -0,0 +1,40 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +attrs = [('Node',0), + ('NodeFilter',0), + ('XML_NAMESPACE',0), + ('XMLNS_NAMESPACE',0), + ('DOMException',0), + ('HTML_4_TRANSITIONAL_INLINE',0), + ('IsDOMString',0), + ('FtDomException',0), + ('NodeTypeDict',0), + ('NodeTypeToClassName',0), + ('Print',0), + ('PrettyPrint',0), + ('XHtmlPrettyPrint',0), + ('XHtmlPrint',0), + ('ReleaseNode',0), + ('StripHtml',0), + ('StripXml',0), + ('GetElementById',0), + ('XmlSpaceState',0), + ('GetAllNs',0), + ('SplitQName',0), + ('SeekNss',0), + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.dom.ext.reader.py b/pyinstaller/PyInstaller/hooks/hook-xml.dom.ext.reader.py new file mode 100644 index 0000000..5b78628 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.dom.ext.reader.py @@ -0,0 +1,23 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +attrs = [('ReleaseNode',0), + ('StrStream',0), + ('BaseUriResolver',0), + ('BASIC_RESOLVER',0), + ('Reader',0), + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.dom.html.HTMLDocument.py b/pyinstaller/PyInstaller/hooks/hook-xml.dom.html.HTMLDocument.py new file mode 100644 index 0000000..dde8639 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.dom.html.HTMLDocument.py @@ -0,0 +1,72 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +#xml.dom.html.HTMLDocument +hiddenimports = ['xml.dom.html.HTMLAnchorElement', + 'xml.dom.html.HTMLAppletElement', + 'xml.dom.html.HTMLAreaElement', + 'xml.dom.html.HTMLBaseElement', + 'xml.dom.html.HTMLBaseFontElement', + 'xml.dom.html.HTMLBodyElement', + 'xml.dom.html.HTMLBRElement', + 'xml.dom.html.HTMLButtonElement', + 'xml.dom.html.HTMLDirectoryElement', + 'xml.dom.html.HTMLDivElement', + 'xml.dom.html.HTMLDListElement', + 'xml.dom.html.HTMLElement', + 'xml.dom.html.HTMLFieldSetElement', + 'xml.dom.html.HTMLFontElement', + 'xml.dom.html.HTMLFormElement', + 'xml.dom.html.HTMLFrameElement', + 'xml.dom.html.HTMLFrameSetElement', + 'xml.dom.html.HTMLHeadElement', + 'xml.dom.html.HTMLHeadingElement', + 'xml.dom.html.HTMLHRElement', + 'xml.dom.html.HTMLHtmlElement', + 'xml.dom.html.HTMLIFrameElement', + 'xml.dom.html.HTMLImageElement', + 'xml.dom.html.HTMLInputElement', + 'xml.dom.html.HTMLIsIndexElement', + 'xml.dom.html.HTMLLabelElement', + 'xml.dom.html.HTMLLegendElement', + 'xml.dom.html.HTMLLIElement', + 'xml.dom.html.HTMLLinkElement', + 'xml.dom.html.HTMLMapElement', + 'xml.dom.html.HTMLMenuElement', + 'xml.dom.html.HTMLMetaElement', + 'xml.dom.html.HTMLModElement', + 'xml.dom.html.HTMLObjectElement', + 'xml.dom.html.HTMLOListElement', + 'xml.dom.html.HTMLOptGroupElement', + 'xml.dom.html.HTMLOptionElement', + 'xml.dom.html.HTMLParagraphElement', + 'xml.dom.html.HTMLParamElement', + 'xml.dom.html.HTMLPreElement', + 'xml.dom.html.HTMLQuoteElement', + 'xml.dom.html.HTMLScriptElement', + 'xml.dom.html.HTMLSelectElement', + 'xml.dom.html.HTMLStyleElement', + 'xml.dom.html.HTMLTableCaptionElement', + 'xml.dom.html.HTMLTableCellElement', + 'xml.dom.html.HTMLTableColElement', + 'xml.dom.html.HTMLTableElement', + 'xml.dom.html.HTMLTableRowElement', + 'xml.dom.html.HTMLTableSectionElement', + 'xml.dom.html.HTMLTextAreaElement', + 'xml.dom.html.HTMLTitleElement', + 'xml.dom.html.HTMLUListElement', + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.dom.html.py b/pyinstaller/PyInstaller/hooks/hook-xml.dom.html.py new file mode 100644 index 0000000..b769d83 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.dom.html.py @@ -0,0 +1,33 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +attrs = [('HTML_4_STRICT_INLINE',0), + ('HTML_4_TRANSITIONAL_INLINE',0), + ('HTML_FORBIDDEN_END',0), + ('HTML_OPT_END',0), + ('HTML_BOOLEAN_ATTRS',0), + ('HTML_CHARACTER_ENTITIES',0), + ('HTML_NAME_ALLOWED',0), + ('HTML_DTD',0), + ('HTMLDOMImplementation',0), + ('htmlImplementation',0), + ('utf8_to_code',0), + ('ConvertChar',0), + ('UseHtmlCharEntities',0), + ('TranslateHtmlCdata',0), + ('SECURE_HTML_ELEMS',0), + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.dom.py b/pyinstaller/PyInstaller/hooks/hook-xml.dom.py new file mode 100644 index 0000000..645aa27 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.dom.py @@ -0,0 +1,80 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +attrs = [('Node', 0), + ('INDEX_SIZE_ERR', 1), + ('DOMSTRING_SIZE_ERR', 2), + ('HIERARCHY_REQUEST_ERR', 3), + ('WRONG_DOCUMENT_ERR', 4), + ('INVALID_CHARACTER_ERR ', 5), + ('NO_DATA_ALLOWED_ERR', 6), + ('NO_MODIFICATION_ALLOWED_ERR', 7), + ('NOT_FOUND_ERR', 8), + ('NOT_SUPPORTED_ERR', 9), + ('INUSE_ATTRIBUTE_ERR', 10), + ('INVALID_STATE_ERR', 11), + ('SYNTAX_ERR', 12), + ('INVALID_MODIFICATION_ERR', 13), + ('NAMESPACE_ERR', 14), + ('INVALID_ACCESS_ERR', 15), + ('DOMException', 0), + ('IndexSizeErr', 0), + ('DomstringSizeErr', 0), + ('HierarchyRequestErr', 0), + ('WrongDocumentErr', 0), + ('InvalidCharacterErr', 0), + ('NoDataAllowedErr', 0), + ('NoModificationAllowedErr', 0), + ('NotFoundErr', 0), + ('NotSupportedErr', 0), + ('InuseAttributeErr', 0), + ('InvalidStateErr', 0), + ('SyntaxErr', 0), + ('InvalidModificationErr', 0), + ('NamespaceErr', 0), + ('InvalidAccessErr', 0), + ('getDOMImplementation', 0), + ('registerDOMImplementation', 0), +] + +def hook(mod): + if mod.__file__.find('_xmlplus') > -1: + mod.UNSPECIFIED_EVENT_TYPE_ERR = 0 + mod.FT_EXCEPTION_BASE = 1000 + mod.XML_PARSE_ERR = 1001 + mod.BAD_BOUNDARYPOINTS_ERR = 1 + mod.INVALID_NODE_TYPE_ERR = 2 + mod.EventException = None + mod.RangeException = None + mod.FtException = None + if hasattr(mod, 'DomstringSizeErr'): + del mod.DomstringSizeErr + mod.DOMStringSizeErr = None + mod.UnspecifiedEventTypeErr = None + mod.XmlParseErr = None + mod.BadBoundaryPointsErr = None + mod.InvalidNodeTypeErr = None + mod.DOMImplementation = None + mod.implementation = None + mod.XML_NAMESPACE = None + mod.XMLNS_NAMESPACE = None + mod.XHTML_NAMESPACE = None + mod.DOMExceptionStrings = None + mod.EventExceptionStrings = None + mod.FtExceptionStrings = None + mod.RangeExceptionStrings = None + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.etree.cElementTree.py b/pyinstaller/PyInstaller/hooks/hook-xml.etree.cElementTree.py new file mode 100644 index 0000000..face9e9 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.etree.cElementTree.py @@ -0,0 +1,19 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# cElementTree has a hidden import (Python >=2.5 stdlib version) +hiddenimports = ['xml.etree.ElementTree'] diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.py b/pyinstaller/PyInstaller/hooks/hook-xml.py new file mode 100644 index 0000000..69cdb69 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.py @@ -0,0 +1,39 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +hiddenimports = ['xml.sax.xmlreader','xml.sax.expatreader'] + +def hook(mod): + # This hook checks for the infamous _xmlcore hack + # http://www.amk.ca/diary/2003/03/pythons__xmlplus_hack.html + + from hookutils import exec_statement + import marshal + + txt = exec_statement("import xml;print xml.__file__") + + if txt.find('_xmlplus') > -1: + if txt.endswith(".py"): + txt = txt + 'c' + try: + co = marshal.loads(open(txt, 'rb').read()[8:]) + except IOError: + co = compile(open(txt[:-1], 'rU').read(), txt, 'exec') + old_pth = mod.__path__[:] + mod.__init__('xml', txt, co) + mod.__path__.extend(old_pth) + return mod diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.sax.py b/pyinstaller/PyInstaller/hooks/hook-xml.sax.py new file mode 100644 index 0000000..b06006f --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.sax.py @@ -0,0 +1,29 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +attrs = [('InputSource', 0), + ('ContentHandler', 0), + ('ErrorHandler', 0), + ('SAXException', 0), + ('SAXNotRecognizedException', 0), + ('SAXParseException', 0), + ('SAXNotSupportedException', 0), + ('SAXReaderNotAvailable', 0), + ('parse', 0), + ('parseString', 0), + ('make_parser', 0), + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-xml.sax.saxexts.py b/pyinstaller/PyInstaller/hooks/hook-xml.sax.saxexts.py new file mode 100644 index 0000000..82a82aa --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-xml.sax.saxexts.py @@ -0,0 +1,30 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +#xml.sax.saxexts +hiddenimports = ["xml.sax.drivers2.drv_pyexpat", + "xml.sax.drivers.drv_xmltok", + 'xml.sax.drivers2.drv_xmlproc', + "xml.sax.drivers.drv_xmltoolkit", + "xml.sax.drivers.drv_xmllib", + "xml.sax.drivers.drv_xmldc", + 'xml.sax.drivers.drv_pyexpat', + 'xml.sax.drivers.drv_xmlproc_val', + 'xml.sax.drivers.drv_htmllib', + 'xml.sax.drivers.drv_sgmlop', + "xml.sax.drivers.drv_sgmllib", + ] diff --git a/pyinstaller/PyInstaller/hooks/hook-zmq.py b/pyinstaller/PyInstaller/hooks/hook-zmq.py new file mode 100644 index 0000000..79d1901 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hook-zmq.py @@ -0,0 +1,27 @@ +# +# Copyright (C) 2011, Vinay Sajip +# +# 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 + + +# Hook for PyZMQ. Cython based Python bindings for messaging library ZeroMQ. +# http://www.zeromq.org/ + + +hiddenimports = [ + 'zmq.core.pysocket', + 'zmq.utils.jsonapi', + 'zmq.utils.strtypes', +] diff --git a/pyinstaller/PyInstaller/hooks/hookutils.py b/pyinstaller/PyInstaller/hooks/hookutils.py new file mode 100644 index 0000000..aa07abb --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/hookutils.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python + +import glob +import os +import sys +import PyInstaller +import PyInstaller.compat as compat +from PyInstaller.compat import is_darwin, set +from PyInstaller.utils import misc + +import PyInstaller.log as logging +logger = logging.getLogger(__name__) + + +def __exec_python_cmd(cmd): + """ + Executes an externally spawned Python interpreter and returns + anything that was emitted in the standard output as a single + string. + """ + # Prepend PYTHONPATH with pathex + pp = os.pathsep.join(PyInstaller.__pathex__) + old_pp = compat.getenv('PYTHONPATH') + if old_pp: + pp = os.pathsep.join([pp, old_pp]) + compat.setenv("PYTHONPATH", pp) + try: + try: + txt = compat.exec_python(*cmd) + except OSError, e: + raise SystemExit("Execution failed: %s" % e) + finally: + if old_pp is not None: + compat.setenv("PYTHONPATH", old_pp) + else: + compat.unsetenv("PYTHONPATH") + return txt.strip() + + +def exec_statement(statement): + """Executes a Python statement in an externally spawned interpreter, and + returns anything that was emitted in the standard output as a single string. + """ + cmd = ['-c', statement] + return __exec_python_cmd(cmd) + + +def exec_script(scriptfilename, *args): + """ + Executes a Python script in an externally spawned interpreter, and + returns anything that was emitted in the standard output as a + single string. + + To prevent missuse, the script passed to hookutils.exec-script + must be located in the `hooks` directory. + """ + + if scriptfilename != os.path.basename(scriptfilename): + raise SystemError("To prevent missuse, the script passed to " + "hookutils.exec-script must be located in " + "the `hooks` directory.") + + cmd = [os.path.join(os.path.dirname(__file__), scriptfilename)] + cmd.extend(args) + return __exec_python_cmd(cmd) + + +def eval_statement(statement): + txt = exec_statement(statement).strip() + if not txt: + # return an empty string which is "not true" but iterable + return '' + return eval(txt) + + +def eval_script(scriptfilename, *args): + txt = exec_script(scriptfilename, *args).strip() + if not txt: + # return an empty string which is "not true" but iterable + return '' + return eval(txt) + + +def get_pyextension_imports(modname): + """ + Return list of modules required by binary (C/C++) Python extension. + + Python extension files ends with .so (Unix) or .pyd (Windows). + It's almost impossible to analyze binary extension and its dependencies. + + Module cannot be imported directly. + + Let's at least try import it in a subprocess and get the diffrence + in module list from sys.modules. + + This function could be used for 'hiddenimports' in PyInstaller hooks files. + """ + + statement = """ +import sys +# Importing distutils filters common modules, especiall in virtualenv. +import distutils +original_modlist = sys.modules.keys() +# When importing this module - sys.modules gets updated. +import %(modname)s +all_modlist = sys.modules.keys() +diff = set(all_modlist) - set(original_modlist) +# Module list contain original modname. We do not need it there. +diff.discard('%(modname)s') +# Print module list to stdout. +print list(diff) +""" % {'modname': modname} + module_imports = eval_statement(statement) + + + if not module_imports: + logger.error('Cannot find imports for module %s' % modname) + return [] # Means no imports found or looking for imports failed. + #module_imports = filter(lambda x: not x.startswith('distutils'), module_imports) + return module_imports + + +def qt4_plugins_dir(): + qt4_plugin_dirs = eval_statement( + "from PyQt4.QtCore import QCoreApplication;" + "app=QCoreApplication([]);" + "print map(unicode,app.libraryPaths())") + if not qt4_plugin_dirs: + logger.error("Cannot find PyQt4 plugin directories") + return "" + for d in qt4_plugin_dirs: + if os.path.isdir(d): + return str(d) # must be 8-bit chars for one-file builds + logger.error("Cannot find existing PyQt4 plugin directory") + return "" + + +def qt4_phonon_plugins_dir(): + qt4_plugin_dirs = eval_statement( + "from PyQt4.QtGui import QApplication;" + "app=QApplication([]); app.setApplicationName('pyinstaller');" + "from PyQt4.phonon import Phonon;" + "v=Phonon.VideoPlayer(Phonon.VideoCategory);" + "print map(unicode,app.libraryPaths())") + if not qt4_plugin_dirs: + logger.error("Cannot find PyQt4 phonon plugin directories") + return "" + for d in qt4_plugin_dirs: + if os.path.isdir(d): + return str(d) # must be 8-bit chars for one-file builds + logger.error("Cannot find existing PyQt4 phonon plugin directory") + return "" + + +def qt4_plugins_binaries(plugin_type): + """Return list of dynamic libraries formated for mod.binaries.""" + binaries = [] + pdir = qt4_plugins_dir() + files = misc.dlls_in_dir(os.path.join(pdir, plugin_type)) + for f in files: + binaries.append(( + os.path.join('qt4_plugins', plugin_type, os.path.basename(f)), + f, 'BINARY')) + return binaries + + +def qt4_menu_nib_dir(): + """Return path to Qt resource dir qt_menu.nib.""" + menu_dir = '' + # Detect MacPorts prefix (usually /opt/local). + # Suppose that PyInstaller is using python from macports. + macports_prefix = sys.executable.split('/Library')[0] + # list of directories where to look for qt_menu.nib + dirs = [ + # Qt4 from MacPorts not compiled as framework. + os.path.join(macports_prefix, 'lib', 'Resources'), + # Qt4 from MacPorts compiled as framework. + os.path.join(macports_prefix, 'libexec', 'qt4-mac', 'lib', + 'QtGui.framework', 'Versions', '4', 'Resources'), + # Qt4 installed into default location. + '/Library/Frameworks/QtGui.framework/Resources', + '/Library/Frameworks/QtGui.framework/Versions/4/Resources', + '/Library/Frameworks/QtGui.Framework/Versions/Current/Resources', + ] + + # Qt4 from Homebrew compiled as framework + import glob + globpath = '/usr/local/Cellar/qt/4.*/lib/QtGui.framework/Versions/4/Resources' + qt_homebrew_dirs = glob.glob(globpath) + dirs += qt_homebrew_dirs + + # Check directory existence + for d in dirs: + d = os.path.join(d, 'qt_menu.nib') + if os.path.exists(d): + menu_dir = d + break + + if not menu_dir: + logger.error('Cannont find qt_menu.nib directory') + return menu_dir + + +def django_dottedstring_imports(django_root_dir): + package_name = os.path.basename(django_root_dir) + compat.setenv("DJANGO_SETTINGS_MODULE", "%s.settings" % package_name) + return eval_script("django-import-finder.py") + + +def find_django_root(dir): + entities = set(os.listdir(dir)) + if "manage.py" in entities and "settings.py" in entities and "urls.py" in entities: + return [dir] + else: + django_root_directories = [] + for entity in entities: + path_to_analyze = os.path.join(dir, entity) + if os.path.isdir(path_to_analyze): + try: + dir_entities = os.listdir(path_to_analyze) + except (IOError, OSError): + # silently skip unreadable directories + continue + if "manage.py" in dir_entities and "settings.py" in dir_entities and "urls.py" in dir_entities: + django_root_directories.append(path_to_analyze) + return django_root_directories + + +def matplotlib_backends(): + """ + Return matplotlib backends availabe in current Python installation. + + All matplotlib backends are hardcoded. We have to try import them + and return the list of successfully imported backends. + """ + all_bk = eval_statement('import matplotlib; print matplotlib.rcsetup.all_backends') + avail_bk = [] + import_statement = """ +try: + __import__('matplotlib.backends.backend_%s') +except ImportError, e: + print str(e) +""" + + # CocoaAgg backend causes subprocess to exit and thus detection + # is not reliable. This backend is meaningful only on Mac OS X. + if not is_darwin and 'CocoaAgg' in all_bk: + all_bk.remove('CocoaAgg') + + # Try to import every backend in a subprocess. + for bk in all_bk: + stdout = exec_statement(import_statement % bk.lower()) + # Backend import is successfull if there is no text in stdout. + if not stdout: + avail_bk.append(bk) + + # Convert backend name to module name. + # e.g. GTKAgg -> backend_gtkagg + return ['backend_' + x.lower() for x in avail_bk] + + +def opengl_arrays_modules(): + """ + Return list of array modules for OpenGL module. + + e.g. 'OpenGL.arrays.vbo' + """ + statement = 'import OpenGL; print OpenGL.__path__[0]' + opengl_mod_path = PyInstaller.hooks.hookutils.exec_statement(statement) + arrays_mod_path = os.path.join(opengl_mod_path, 'arrays') + files = glob.glob(arrays_mod_path + '/*.py') + modules = [] + + for f in files: + mod = os.path.splitext(os.path.basename(f))[0] + # Skip __init__ module. + if mod == '__init__': + continue + modules.append('OpenGL.arrays.' + mod) + + return modules diff --git a/pyinstaller/PyInstaller/hooks/shared_PIL_Image.py b/pyinstaller/PyInstaller/hooks/shared_PIL_Image.py new file mode 100644 index 0000000..633b942 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/shared_PIL_Image.py @@ -0,0 +1,37 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +hiddenimports = [] + +def hook(mod): + global hiddenimports + # `PIL.Image` may be imported as `PIL.Image` or as `Image` + # (without the prefix). We need to use the same module name to + # avoid the same module under two different names. + __import__(mod.__name__) + image_mod = sys.modules[mod.__name__] + # PIL uses lazy initialization. + # first import the default stuff ... + image_mod.preinit() + # ... then every available plugin + image_mod.init() + for name in sys.modules: + if name.endswith("ImagePlugin"): + hiddenimports.append(name) + return mod diff --git a/pyinstaller/PyInstaller/hooks/shared_PIL_SpiderImagePlugin.py b/pyinstaller/PyInstaller/hooks/shared_PIL_SpiderImagePlugin.py new file mode 100644 index 0000000..eb8ac04 --- /dev/null +++ b/pyinstaller/PyInstaller/hooks/shared_PIL_SpiderImagePlugin.py @@ -0,0 +1,29 @@ +# Copyright (C) 2006, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# PIL's SpiderImagePlugin features a tkPhotoImage() method which imports +# ImageTk (and thus brings the whole Tcl/Tk library in). +# We cheat a little and remove the ImageTk import: I assume that if people +# are really using ImageTk in their application, they will also import it +# directly. + +def hook(mod): + for i, m in enumerate(mod.imports): + if m[0] == "ImageTk": + del mod.imports[i] + break + return mod diff --git a/pyinstaller/PyInstaller/lib/.svn/entries b/pyinstaller/PyInstaller/lib/.svn/entries new file mode 100644 index 0000000..60a02d6 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/.svn/entries @@ -0,0 +1,91 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/lib +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +unittest2 +dir + + + +add + +altgraph +dir + + + +add + +README.rst +file + + + +add + +__init__.py +file + + + +add + +junitxml +dir + + + +add + +six.py +file + + + +add + +pefile.py +file + + + +add + +__subprocess.py +file + + + +add + +macholib +dir + + + +add + diff --git a/pyinstaller/PyInstaller/lib/README.rst b/pyinstaller/PyInstaller/lib/README.rst new file mode 100644 index 0000000..cbf81dc --- /dev/null +++ b/pyinstaller/PyInstaller/lib/README.rst @@ -0,0 +1,28 @@ +Custom modifications of 3rd party libraries +=========================================== + +macholib +-------- + +- add fixed version string to ./macholib/__init__.py:: + + # For PyInstaller/lib/ define the version here, since there is no + # package-resource. + __version__ = '1.4.2' + +- remove the following line from ./macholib/utils.py, ./macholib/MachO.py, + ./macholib/MachOGraph.py. Otherwise macholib complains about + missing altgraph module:: + + from pkg_resources import require + require("altgraph") + +- remove the following line from ./macholib/utils.py:: + + from modulegraph.util import * + + +junitxml +-------- + +- hacked to support ignored tests in junit xml test report. diff --git a/pyinstaller/PyInstaller/lib/__init__.py b/pyinstaller/PyInstaller/lib/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/__init__.py @@ -0,0 +1 @@ +# diff --git a/pyinstaller/PyInstaller/lib/__subprocess.py b/pyinstaller/PyInstaller/lib/__subprocess.py new file mode 100644 index 0000000..05a7ce8 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/__subprocess.py @@ -0,0 +1,1337 @@ +#### +#### This is a plain checkout from +#### http://hg.python.org/cpython/raw-file/v2.6.5/Lib/subprocess.py +#### with a single change at the place marked +#### "change this to use pywin32 instead of the _subprocess driver" +#### So for Windows, pywin32 is used. +#### +#### When up/downgrading this module, please keep this header updated. +#### + +# subprocess - Subprocesses with accessible I/O streams +# +# For more information about this module, see PEP 324. +# +# This module should remain compatible with Python 2.2, see PEP 291. +# +# Copyright (c) 2003-2005 by Peter Astrand +# +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/2.4/license for licensing details. + +r"""subprocess - Subprocesses with accessible I/O streams + +This module allows you to spawn processes, connect to their +input/output/error pipes, and obtain their return codes. This module +intends to replace several other, older modules and functions, like: + +os.system +os.spawn* +os.popen* +popen2.* +commands.* + +Information about how the subprocess module can be used to replace these +modules and functions can be found below. + + + +Using the subprocess module +=========================== +This module defines one class called Popen: + +class Popen(args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0): + + +Arguments are: + +args should be a string, or a sequence of program arguments. The +program to execute is normally the first item in the args sequence or +string, but can be explicitly set by using the executable argument. + +On UNIX, with shell=False (default): In this case, the Popen class +uses os.execvp() to execute the child program. args should normally +be a sequence. A string will be treated as a sequence with the string +as the only item (the program to execute). + +On UNIX, with shell=True: If args is a string, it specifies the +command string to execute through the shell. If args is a sequence, +the first item specifies the command string, and any additional items +will be treated as additional shell arguments. + +On Windows: the Popen class uses CreateProcess() to execute the child +program, which operates on strings. If args is a sequence, it will be +converted to a string using the list2cmdline method. Please note that +not all MS Windows applications interpret the command line the same +way: The list2cmdline is designed for applications using the same +rules as the MS C runtime. + +bufsize, if given, has the same meaning as the corresponding argument +to the built-in open() function: 0 means unbuffered, 1 means line +buffered, any other positive value means use a buffer of +(approximately) that size. A negative bufsize means to use the system +default, which usually means fully buffered. The default value for +bufsize is 0 (unbuffered). + +stdin, stdout and stderr specify the executed programs' standard +input, standard output and standard error file handles, respectively. +Valid values are PIPE, an existing file descriptor (a positive +integer), an existing file object, and None. PIPE indicates that a +new pipe to the child should be created. With None, no redirection +will occur; the child's file handles will be inherited from the +parent. Additionally, stderr can be STDOUT, which indicates that the +stderr data from the applications should be captured into the same +file handle as for stdout. + +If preexec_fn is set to a callable object, this object will be called +in the child process just before the child is executed. + +If close_fds is true, all file descriptors except 0, 1 and 2 will be +closed before the child process is executed. + +if shell is true, the specified command will be executed through the +shell. + +If cwd is not None, the current directory will be changed to cwd +before the child is executed. + +If env is not None, it defines the environment variables for the new +process. + +If universal_newlines is true, the file objects stdout and stderr are +opened as a text files, but lines may be terminated by any of '\n', +the Unix end-of-line convention, '\r', the Macintosh convention or +'\r\n', the Windows convention. All of these external representations +are seen as '\n' by the Python program. Note: This feature is only +available if Python is built with universal newline support (the +default). Also, the newlines attribute of the file objects stdout, +stdin and stderr are not updated by the communicate() method. + +The startupinfo and creationflags, if given, will be passed to the +underlying CreateProcess() function. They can specify things such as +appearance of the main window and priority for the new process. +(Windows only) + + +This module also defines two shortcut functions: + +call(*popenargs, **kwargs): + Run command with arguments. Wait for command to complete, then + return the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + retcode = call(["ls", "-l"]) + +check_call(*popenargs, **kwargs): + Run command with arguments. Wait for command to complete. If the + exit code was zero then return, otherwise raise + CalledProcessError. The CalledProcessError object will have the + return code in the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + check_call(["ls", "-l"]) + +Exceptions +---------- +Exceptions raised in the child process, before the new program has +started to execute, will be re-raised in the parent. Additionally, +the exception object will have one extra attribute called +'child_traceback', which is a string containing traceback information +from the childs point of view. + +The most common exception raised is OSError. This occurs, for +example, when trying to execute a non-existent file. Applications +should prepare for OSErrors. + +A ValueError will be raised if Popen is called with invalid arguments. + +check_call() will raise CalledProcessError, if the called process +returns a non-zero return code. + + +Security +-------- +Unlike some other popen functions, this implementation will never call +/bin/sh implicitly. This means that all characters, including shell +metacharacters, can safely be passed to child processes. + + +Popen objects +============= +Instances of the Popen class have the following methods: + +poll() + Check if child process has terminated. Returns returncode + attribute. + +wait() + Wait for child process to terminate. Returns returncode attribute. + +communicate(input=None) + Interact with process: Send data to stdin. Read data from stdout + and stderr, until end-of-file is reached. Wait for process to + terminate. The optional input argument should be a string to be + sent to the child process, or None, if no data should be sent to + the child. + + communicate() returns a tuple (stdout, stderr). + + Note: The data read is buffered in memory, so do not use this + method if the data size is large or unlimited. + +The following attributes are also available: + +stdin + If the stdin argument is PIPE, this attribute is a file object + that provides input to the child process. Otherwise, it is None. + +stdout + If the stdout argument is PIPE, this attribute is a file object + that provides output from the child process. Otherwise, it is + None. + +stderr + If the stderr argument is PIPE, this attribute is file object that + provides error output from the child process. Otherwise, it is + None. + +pid + The process ID of the child process. + +returncode + The child return code. A None value indicates that the process + hasn't terminated yet. A negative value -N indicates that the + child was terminated by signal N (UNIX only). + + +Replacing older functions with the subprocess module +==================================================== +In this section, "a ==> b" means that b can be used as a replacement +for a. + +Note: All functions in this section fail (more or less) silently if +the executed program cannot be found; this module raises an OSError +exception. + +In the following examples, we assume that the subprocess module is +imported with "from subprocess import *". + + +Replacing /bin/sh shell backquote +--------------------------------- +output=`mycmd myarg` +==> +output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] + + +Replacing shell pipe line +------------------------- +output=`dmesg | grep hda` +==> +p1 = Popen(["dmesg"], stdout=PIPE) +p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) +output = p2.communicate()[0] + + +Replacing os.system() +--------------------- +sts = os.system("mycmd" + " myarg") +==> +p = Popen("mycmd" + " myarg", shell=True) +pid, sts = os.waitpid(p.pid, 0) + +Note: + +* Calling the program through the shell is usually not required. + +* It's easier to look at the returncode attribute than the + exitstatus. + +A more real-world example would look like this: + +try: + retcode = call("mycmd" + " myarg", shell=True) + if retcode < 0: + print >>sys.stderr, "Child was terminated by signal", -retcode + else: + print >>sys.stderr, "Child returned", retcode +except OSError, e: + print >>sys.stderr, "Execution failed:", e + + +Replacing os.spawn* +------------------- +P_NOWAIT example: + +pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") +==> +pid = Popen(["/bin/mycmd", "myarg"]).pid + + +P_WAIT example: + +retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") +==> +retcode = call(["/bin/mycmd", "myarg"]) + + +Vector example: + +os.spawnvp(os.P_NOWAIT, path, args) +==> +Popen([path] + args[1:]) + + +Environment example: + +os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) +==> +Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) + + +Replacing os.popen* +------------------- +pipe = os.popen("cmd", mode='r', bufsize) +==> +pipe = Popen("cmd", shell=True, bufsize=bufsize, stdout=PIPE).stdout + +pipe = os.popen("cmd", mode='w', bufsize) +==> +pipe = Popen("cmd", shell=True, bufsize=bufsize, stdin=PIPE).stdin + + +(child_stdin, child_stdout) = os.popen2("cmd", mode, bufsize) +==> +p = Popen("cmd", shell=True, bufsize=bufsize, + stdin=PIPE, stdout=PIPE, close_fds=True) +(child_stdin, child_stdout) = (p.stdin, p.stdout) + + +(child_stdin, + child_stdout, + child_stderr) = os.popen3("cmd", mode, bufsize) +==> +p = Popen("cmd", shell=True, bufsize=bufsize, + stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) +(child_stdin, + child_stdout, + child_stderr) = (p.stdin, p.stdout, p.stderr) + + +(child_stdin, child_stdout_and_stderr) = os.popen4("cmd", mode, + bufsize) +==> +p = Popen("cmd", shell=True, bufsize=bufsize, + stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) +(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) + +On Unix, os.popen2, os.popen3 and os.popen4 also accept a sequence as +the command to execute, in which case arguments will be passed +directly to the program without shell intervention. This usage can be +replaced as follows: + +(child_stdin, child_stdout) = os.popen2(["/bin/ls", "-l"], mode, + bufsize) +==> +p = Popen(["/bin/ls", "-l"], bufsize=bufsize, stdin=PIPE, stdout=PIPE) +(child_stdin, child_stdout) = (p.stdin, p.stdout) + +Return code handling translates as follows: + +pipe = os.popen("cmd", 'w') +... +rc = pipe.close() +if rc != None and rc % 256: + print "There were some errors" +==> +process = Popen("cmd", 'w', shell=True, stdin=PIPE) +... +process.stdin.close() +if process.wait() != 0: + print "There were some errors" + + +Replacing popen2.* +------------------ +(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) +==> +p = Popen(["somestring"], shell=True, bufsize=bufsize + stdin=PIPE, stdout=PIPE, close_fds=True) +(child_stdout, child_stdin) = (p.stdout, p.stdin) + +On Unix, popen2 also accepts a sequence as the command to execute, in +which case arguments will be passed directly to the program without +shell intervention. This usage can be replaced as follows: + +(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, + mode) +==> +p = Popen(["mycmd", "myarg"], bufsize=bufsize, + stdin=PIPE, stdout=PIPE, close_fds=True) +(child_stdout, child_stdin) = (p.stdout, p.stdin) + +The popen2.Popen3 and popen2.Popen4 basically works as subprocess.Popen, +except that: + +* subprocess.Popen raises an exception if the execution fails +* the capturestderr argument is replaced with the stderr argument. +* stdin=PIPE and stdout=PIPE must be specified. +* popen2 closes all filedescriptors by default, but you have to specify + close_fds=True with subprocess.Popen. +""" + +import sys +mswindows = (sys.platform == "win32") + +import os +import types +import traceback +import gc +import signal + +# Exception classes used by this module. +class CalledProcessError(Exception): + """This exception is raised when a process run by check_call() returns + a non-zero exit status. The exit status will be stored in the + returncode attribute.""" + def __init__(self, returncode, cmd): + self.returncode = returncode + self.cmd = cmd + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + + +if mswindows: + import threading + import msvcrt + if 1: # <-- change this to use pywin32 instead of the _subprocess driver + import pywintypes + from win32api import GetStdHandle, STD_INPUT_HANDLE, \ + STD_OUTPUT_HANDLE, STD_ERROR_HANDLE + from win32api import GetCurrentProcess, DuplicateHandle, \ + GetModuleFileName, GetVersion + from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE + from win32pipe import CreatePipe + from win32process import CreateProcess, STARTUPINFO, \ + GetExitCodeProcess, STARTF_USESTDHANDLES, \ + STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE + from win32process import TerminateProcess + from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 + else: + from _subprocess import * + class STARTUPINFO: + dwFlags = 0 + hStdInput = None + hStdOutput = None + hStdError = None + wShowWindow = 0 + class pywintypes: + error = IOError +else: + import select + import errno + import fcntl + import pickle + +__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"] + +try: + MAXFD = os.sysconf("SC_OPEN_MAX") +except: + MAXFD = 256 + +# True/False does not exist on 2.2.0 +#try: +# False +#except NameError: +# False = 0 +# True = 1 + +_active = [] + +def _cleanup(): + for inst in _active[:]: + if inst._internal_poll(_deadstate=sys.maxint) >= 0: + try: + _active.remove(inst) + except ValueError: + # This can happen if two threads create a new Popen instance. + # It's harmless that it was already removed, so ignore. + pass + +PIPE = -1 +STDOUT = -2 + + +def _eintr_retry_call(func, *args): + while True: + try: + return func(*args) + except OSError, e: + if e.errno == errno.EINTR: + continue + raise + + +def call(*popenargs, **kwargs): + """Run command with arguments. Wait for command to complete, then + return the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + retcode = call(["ls", "-l"]) + """ + return Popen(*popenargs, **kwargs).wait() + + +def check_call(*popenargs, **kwargs): + """Run command with arguments. Wait for command to complete. If + the exit code was zero then return, otherwise raise + CalledProcessError. The CalledProcessError object will have the + return code in the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + check_call(["ls", "-l"]) + """ + retcode = call(*popenargs, **kwargs) + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + if retcode: + raise CalledProcessError(retcode, cmd) + return retcode + + +def list2cmdline(seq): + """ + Translate a sequence of arguments into a command line + string, using the same rules as the MS C runtime: + + 1) Arguments are delimited by white space, which is either a + space or a tab. + + 2) A string surrounded by double quotation marks is + interpreted as a single argument, regardless of white space + or pipe characters contained within. A quoted string can be + embedded in an argument. + + 3) A double quotation mark preceded by a backslash is + interpreted as a literal double quotation mark. + + 4) Backslashes are interpreted literally, unless they + immediately precede a double quotation mark. + + 5) If backslashes immediately precede a double quotation mark, + every pair of backslashes is interpreted as a literal + backslash. If the number of backslashes is odd, the last + backslash escapes the next double quotation mark as + described in rule 3. + """ + + # See + # http://msdn.microsoft.com/en-us/library/17w5ykft.aspx + # or search http://msdn.microsoft.com for + # "Parsing C++ Command-Line Arguments" + result = [] + needquote = False + for arg in seq: + bs_buf = [] + + # Add a space to separate this argument from the others + if result: + result.append(' ') + + needquote = (" " in arg) or ("\t" in arg) or ("|" in arg) or not arg + if needquote: + result.append('"') + + for c in arg: + if c == '\\': + # Don't know if we need to double yet. + bs_buf.append(c) + elif c == '"': + # Double backslashes. + result.append('\\' * len(bs_buf)*2) + bs_buf = [] + result.append('\\"') + else: + # Normal char + if bs_buf: + result.extend(bs_buf) + bs_buf = [] + result.append(c) + + # Add remaining backslashes, if any. + if bs_buf: + result.extend(bs_buf) + + if needquote: + result.extend(bs_buf) + result.append('"') + + return ''.join(result) + + +class Popen(object): + def __init__(self, args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0): + """Create new Popen instance.""" + _cleanup() + + self._child_created = False + if not isinstance(bufsize, (int, long)): + raise TypeError("bufsize must be an integer") + + if mswindows: + if preexec_fn is not None: + raise ValueError("preexec_fn is not supported on Windows " + "platforms") + if close_fds and (stdin is not None or stdout is not None or + stderr is not None): + raise ValueError("close_fds is not supported on Windows " + "platforms if you redirect stdin/stdout/stderr") + else: + # POSIX + if startupinfo is not None: + raise ValueError("startupinfo is only supported on Windows " + "platforms") + if creationflags != 0: + raise ValueError("creationflags is only supported on Windows " + "platforms") + + self.stdin = None + self.stdout = None + self.stderr = None + self.pid = None + self.returncode = None + self.universal_newlines = universal_newlines + + # Input and output objects. The general principle is like + # this: + # + # Parent Child + # ------ ----- + # p2cwrite ---stdin---> p2cread + # c2pread <--stdout--- c2pwrite + # errread <--stderr--- errwrite + # + # On POSIX, the child objects are file descriptors. On + # Windows, these are Windows file handles. The parent objects + # are file descriptors on both platforms. The parent objects + # are None when not using PIPEs. The child objects are None + # when not redirecting. + + (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) = self._get_handles(stdin, stdout, stderr) + + self._execute_child(args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + if mswindows: + if p2cwrite is not None: + p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0) + if c2pread is not None: + c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0) + if errread is not None: + errread = msvcrt.open_osfhandle(errread.Detach(), 0) + + if p2cwrite is not None: + self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) + if c2pread is not None: + if universal_newlines: + self.stdout = os.fdopen(c2pread, 'rU', bufsize) + else: + self.stdout = os.fdopen(c2pread, 'rb', bufsize) + if errread is not None: + if universal_newlines: + self.stderr = os.fdopen(errread, 'rU', bufsize) + else: + self.stderr = os.fdopen(errread, 'rb', bufsize) + + + def _translate_newlines(self, data): + data = data.replace("\r\n", "\n") + data = data.replace("\r", "\n") + return data + + + def __del__(self, sys=sys): + if not self._child_created: + # We didn't get to successfully create a child process. + return + # In case the child hasn't been waited on, check if it's done. + self._internal_poll(_deadstate=sys.maxint) + if self.returncode is None and _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + + def communicate(self, input=None): + """Interact with process: Send data to stdin. Read data from + stdout and stderr, until end-of-file is reached. Wait for + process to terminate. The optional input argument should be a + string to be sent to the child process, or None, if no data + should be sent to the child. + + communicate() returns a tuple (stdout, stderr).""" + + # Optimization: If we are only using one pipe, or no pipe at + # all, using select() or threads is unnecessary. + if [self.stdin, self.stdout, self.stderr].count(None) >= 2: + stdout = None + stderr = None + if self.stdin: + if input: + self.stdin.write(input) + self.stdin.close() + elif self.stdout: + stdout = self.stdout.read() + self.stdout.close() + elif self.stderr: + stderr = self.stderr.read() + self.stderr.close() + self.wait() + return (stdout, stderr) + + return self._communicate(input) + + + def poll(self): + return self._internal_poll() + + + if mswindows: + # + # Windows methods + # + def _get_handles(self, stdin, stdout, stderr): + """Construct and return tupel with IO objects: + p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite + """ + if stdin is None and stdout is None and stderr is None: + return (None, None, None, None, None, None) + + p2cread, p2cwrite = None, None + c2pread, c2pwrite = None, None + errread, errwrite = None, None + + if stdin is None: + p2cread = GetStdHandle(STD_INPUT_HANDLE) + if p2cread is None: + p2cread, _ = CreatePipe(None, 0) + elif stdin == PIPE: + p2cread, p2cwrite = CreatePipe(None, 0) + elif isinstance(stdin, int): + p2cread = msvcrt.get_osfhandle(stdin) + else: + # Assuming file-like object + p2cread = msvcrt.get_osfhandle(stdin.fileno()) + p2cread = self._make_inheritable(p2cread) + + if stdout is None: + c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) + if c2pwrite is None: + _, c2pwrite = CreatePipe(None, 0) + elif stdout == PIPE: + c2pread, c2pwrite = CreatePipe(None, 0) + elif isinstance(stdout, int): + c2pwrite = msvcrt.get_osfhandle(stdout) + else: + # Assuming file-like object + c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) + c2pwrite = self._make_inheritable(c2pwrite) + + if stderr is None: + errwrite = GetStdHandle(STD_ERROR_HANDLE) + if errwrite is None: + _, errwrite = CreatePipe(None, 0) + elif stderr == PIPE: + errread, errwrite = CreatePipe(None, 0) + elif stderr == STDOUT: + errwrite = c2pwrite + elif isinstance(stderr, int): + errwrite = msvcrt.get_osfhandle(stderr) + else: + # Assuming file-like object + errwrite = msvcrt.get_osfhandle(stderr.fileno()) + errwrite = self._make_inheritable(errwrite) + + return (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + + def _make_inheritable(self, handle): + """Return a duplicate of handle, which is inheritable""" + return DuplicateHandle(GetCurrentProcess(), handle, + GetCurrentProcess(), 0, 1, + DUPLICATE_SAME_ACCESS) + + + def _find_w9xpopen(self): + """Find and return absolut path to w9xpopen.exe""" + w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), + "w9xpopen.exe") + if not os.path.exists(w9xpopen): + # Eeek - file-not-found - possibly an embedding + # situation - see if we can locate it in sys.exec_prefix + w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), + "w9xpopen.exe") + if not os.path.exists(w9xpopen): + raise RuntimeError("Cannot locate w9xpopen.exe, which is " + "needed for Popen to work with your " + "shell or platform.") + return w9xpopen + + + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + """Execute program (MS Windows version)""" + + if not isinstance(args, types.StringTypes): + args = list2cmdline(args) + + # Process startup details + if startupinfo is None: + startupinfo = STARTUPINFO() + if None not in (p2cread, c2pwrite, errwrite): + startupinfo.dwFlags |= STARTF_USESTDHANDLES + startupinfo.hStdInput = p2cread + startupinfo.hStdOutput = c2pwrite + startupinfo.hStdError = errwrite + + if shell: + startupinfo.dwFlags |= STARTF_USESHOWWINDOW + startupinfo.wShowWindow = SW_HIDE + comspec = os.environ.get("COMSPEC", "cmd.exe") + args = comspec + " /c " + args + if (GetVersion() >= 0x80000000L or + os.path.basename(comspec).lower() == "command.com"): + # Win9x, or using command.com on NT. We need to + # use the w9xpopen intermediate program. For more + # information, see KB Q150956 + # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) + w9xpopen = self._find_w9xpopen() + args = '"%s" %s' % (w9xpopen, args) + # Not passing CREATE_NEW_CONSOLE has been known to + # cause random failures on win9x. Specifically a + # dialog: "Your program accessed mem currently in + # use at xxx" and a hopeful warning about the + # stability of your system. Cost is Ctrl+C wont + # kill children. + creationflags |= CREATE_NEW_CONSOLE + + # Start the process + try: + hp, ht, pid, tid = CreateProcess(executable, args, + # no special security + None, None, + int(not close_fds), + creationflags, + env, + cwd, + startupinfo) + except pywintypes.error, e: + # Translate pywintypes.error to WindowsError, which is + # a subclass of OSError. FIXME: We should really + # translate errno using _sys_errlist (or simliar), but + # how can this be done from Python? + raise WindowsError(*e.args) + + # Retain the process handle, but close the thread handle + self._child_created = True + self._handle = hp + self.pid = pid + ht.Close() + + # Child is launched. Close the parent's copy of those pipe + # handles that only the child should have open. You need + # to make sure that no handles to the write end of the + # output pipe are maintained in this process or else the + # pipe will not close when the child process exits and the + # ReadFile will hang. + if p2cread is not None: + p2cread.Close() + if c2pwrite is not None: + c2pwrite.Close() + if errwrite is not None: + errwrite.Close() + + + def _internal_poll(self, _deadstate=None): + """Check if child process has terminated. Returns returncode + attribute.""" + if self.returncode is None: + if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: + self.returncode = GetExitCodeProcess(self._handle) + return self.returncode + + + def wait(self): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: + obj = WaitForSingleObject(self._handle, INFINITE) + self.returncode = GetExitCodeProcess(self._handle) + return self.returncode + + + def _readerthread(self, fh, buffer): + buffer.append(fh.read()) + + + def _communicate(self, input): + stdout = None # Return + stderr = None # Return + + if self.stdout: + stdout = [] + stdout_thread = threading.Thread(target=self._readerthread, + args=(self.stdout, stdout)) + stdout_thread.setDaemon(True) + stdout_thread.start() + if self.stderr: + stderr = [] + stderr_thread = threading.Thread(target=self._readerthread, + args=(self.stderr, stderr)) + stderr_thread.setDaemon(True) + stderr_thread.start() + + if self.stdin: + if input is not None: + self.stdin.write(input) + self.stdin.close() + + if self.stdout: + stdout_thread.join() + if self.stderr: + stderr_thread.join() + + # All data exchanged. Translate lists into strings. + if stdout is not None: + stdout = stdout[0] + if stderr is not None: + stderr = stderr[0] + + # Translate newlines, if requested. We cannot let the file + # object do the translation: It is based on stdio, which is + # impossible to combine with select (unless forcing no + # buffering). + if self.universal_newlines and hasattr(file, 'newlines'): + if stdout: + stdout = self._translate_newlines(stdout) + if stderr: + stderr = self._translate_newlines(stderr) + + self.wait() + return (stdout, stderr) + + def send_signal(self, sig): + """Send a signal to the process + """ + if sig == signal.SIGTERM: + self.terminate() + else: + raise ValueError("Only SIGTERM is supported on Windows") + + def terminate(self): + """Terminates the process + """ + TerminateProcess(self._handle, 1) + + kill = terminate + + else: + # + # POSIX methods + # + def _get_handles(self, stdin, stdout, stderr): + """Construct and return tupel with IO objects: + p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite + """ + p2cread, p2cwrite = None, None + c2pread, c2pwrite = None, None + errread, errwrite = None, None + + if stdin is None: + pass + elif stdin == PIPE: + p2cread, p2cwrite = os.pipe() + elif isinstance(stdin, int): + p2cread = stdin + else: + # Assuming file-like object + p2cread = stdin.fileno() + + if stdout is None: + pass + elif stdout == PIPE: + c2pread, c2pwrite = os.pipe() + elif isinstance(stdout, int): + c2pwrite = stdout + else: + # Assuming file-like object + c2pwrite = stdout.fileno() + + if stderr is None: + pass + elif stderr == PIPE: + errread, errwrite = os.pipe() + elif stderr == STDOUT: + errwrite = c2pwrite + elif isinstance(stderr, int): + errwrite = stderr + else: + # Assuming file-like object + errwrite = stderr.fileno() + + return (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + + def _set_cloexec_flag(self, fd): + try: + cloexec_flag = fcntl.FD_CLOEXEC + except AttributeError: + cloexec_flag = 1 + + old = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) + + + def _close_fds(self, but): + os.closerange(3, but) + os.closerange(but + 1, MAXFD) + + + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + """Execute program (POSIX version)""" + + if isinstance(args, types.StringTypes): + args = [args] + else: + args = list(args) + + if shell: + args = ["/bin/sh", "-c"] + args + + if executable is None: + executable = args[0] + + # For transferring possible exec failure from child to parent + # The first char specifies the exception type: 0 means + # OSError, 1 means some other error. + errpipe_read, errpipe_write = os.pipe() + try: + try: + self._set_cloexec_flag(errpipe_write) + + gc_was_enabled = gc.isenabled() + # Disable gc to avoid bug where gc -> file_dealloc -> + # write to stderr -> hang. http://bugs.python.org/issue1336 + gc.disable() + try: + self.pid = os.fork() + except: + if gc_was_enabled: + gc.enable() + raise + self._child_created = True + if self.pid == 0: + # Child + try: + # Close parent's pipe ends + if p2cwrite is not None: + os.close(p2cwrite) + if c2pread is not None: + os.close(c2pread) + if errread is not None: + os.close(errread) + os.close(errpipe_read) + + # Dup fds for child + if p2cread is not None: + os.dup2(p2cread, 0) + if c2pwrite is not None: + os.dup2(c2pwrite, 1) + if errwrite is not None: + os.dup2(errwrite, 2) + + # Close pipe fds. Make sure we don't close the same + # fd more than once, or standard fds. + if p2cread is not None and p2cread not in (0,): + os.close(p2cread) + if c2pwrite is not None and c2pwrite not in (p2cread, 1): + os.close(c2pwrite) + if errwrite is not None and errwrite not in (p2cread, c2pwrite, 2): + os.close(errwrite) + + # Close all other fds, if asked for + if close_fds: + self._close_fds(but=errpipe_write) + + if cwd is not None: + os.chdir(cwd) + + if preexec_fn: + preexec_fn() + + if env is None: + os.execvp(executable, args) + else: + os.execvpe(executable, args, env) + + except: + exc_type, exc_value, tb = sys.exc_info() + # Save the traceback and attach it to the exception object + exc_lines = traceback.format_exception(exc_type, + exc_value, + tb) + exc_value.child_traceback = ''.join(exc_lines) + os.write(errpipe_write, pickle.dumps(exc_value)) + + # This exitcode won't be reported to applications, so it + # really doesn't matter what we return. + os._exit(255) + + # Parent + if gc_was_enabled: + gc.enable() + finally: + # be sure the FD is closed no matter what + os.close(errpipe_write) + + if p2cread is not None and p2cwrite is not None: + os.close(p2cread) + if c2pwrite is not None and c2pread is not None: + os.close(c2pwrite) + if errwrite is not None and errread is not None: + os.close(errwrite) + + # Wait for exec to fail or succeed; possibly raising exception + # Exception limited to 1M + data = _eintr_retry_call(os.read, errpipe_read, 1048576) + finally: + # be sure the FD is closed no matter what + os.close(errpipe_read) + + if data != "": + _eintr_retry_call(os.waitpid, self.pid, 0) + child_exception = pickle.loads(data) + for fd in (p2cwrite, c2pread, errread): + if fd is not None: + os.close(fd) + raise child_exception + + + def _handle_exitstatus(self, sts): + if os.WIFSIGNALED(sts): + self.returncode = -os.WTERMSIG(sts) + elif os.WIFEXITED(sts): + self.returncode = os.WEXITSTATUS(sts) + else: + # Should never happen + raise RuntimeError("Unknown child exit status!") + + + def _internal_poll(self, _deadstate=None): + """Check if child process has terminated. Returns returncode + attribute.""" + if self.returncode is None: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + if pid == self.pid: + self._handle_exitstatus(sts) + except os.error: + if _deadstate is not None: + self.returncode = _deadstate + return self.returncode + + + def wait(self): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: + pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) + self._handle_exitstatus(sts) + return self.returncode + + + def _communicate(self, input): + read_set = [] + write_set = [] + stdout = None # Return + stderr = None # Return + + if self.stdin: + # Flush stdio buffer. This might block, if the user has + # been writing to .stdin in an uncontrolled fashion. + self.stdin.flush() + if input: + write_set.append(self.stdin) + else: + self.stdin.close() + if self.stdout: + read_set.append(self.stdout) + stdout = [] + if self.stderr: + read_set.append(self.stderr) + stderr = [] + + input_offset = 0 + while read_set or write_set: + try: + rlist, wlist, xlist = select.select(read_set, write_set, []) + except select.error, e: + if e.args[0] == errno.EINTR: + continue + raise + + if self.stdin in wlist: + # When select has indicated that the file is writable, + # we can write up to PIPE_BUF bytes without risk + # blocking. POSIX defines PIPE_BUF >= 512 + chunk = input[input_offset : input_offset + 512] + bytes_written = os.write(self.stdin.fileno(), chunk) + input_offset += bytes_written + if input_offset >= len(input): + self.stdin.close() + write_set.remove(self.stdin) + + if self.stdout in rlist: + data = os.read(self.stdout.fileno(), 1024) + if data == "": + self.stdout.close() + read_set.remove(self.stdout) + stdout.append(data) + + if self.stderr in rlist: + data = os.read(self.stderr.fileno(), 1024) + if data == "": + self.stderr.close() + read_set.remove(self.stderr) + stderr.append(data) + + # All data exchanged. Translate lists into strings. + if stdout is not None: + stdout = ''.join(stdout) + if stderr is not None: + stderr = ''.join(stderr) + + # Translate newlines, if requested. We cannot let the file + # object do the translation: It is based on stdio, which is + # impossible to combine with select (unless forcing no + # buffering). + if self.universal_newlines and hasattr(file, 'newlines'): + if stdout: + stdout = self._translate_newlines(stdout) + if stderr: + stderr = self._translate_newlines(stderr) + + self.wait() + return (stdout, stderr) + + def send_signal(self, sig): + """Send a signal to the process + """ + os.kill(self.pid, sig) + + def terminate(self): + """Terminate the process with SIGTERM + """ + self.send_signal(signal.SIGTERM) + + def kill(self): + """Kill the process with SIGKILL + """ + self.send_signal(signal.SIGKILL) + + +def _demo_posix(): + # + # Example 1: Simple redirection: Get process list + # + plist = Popen(["ps"], stdout=PIPE).communicate()[0] + print "Process list:" + print plist + + # + # Example 2: Change uid before executing child + # + if os.getuid() == 0: + p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) + p.wait() + + # + # Example 3: Connecting several subprocesses + # + print "Looking for 'hda'..." + p1 = Popen(["dmesg"], stdout=PIPE) + p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) + print repr(p2.communicate()[0]) + + # + # Example 4: Catch execution error + # + print + print "Trying a weird file..." + try: + print Popen(["/this/path/does/not/exist"]).communicate() + except OSError, e: + if e.errno == errno.ENOENT: + print "The file didn't exist. I thought so..." + print "Child traceback:" + print e.child_traceback + else: + print "Error", e.errno + else: + print >>sys.stderr, "Gosh. No error." + + +def _demo_windows(): + # + # Example 1: Connecting several subprocesses + # + print "Looking for 'PROMPT' in set output..." + p1 = Popen("set", stdout=PIPE, shell=True) + p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) + print repr(p2.communicate()[0]) + + # + # Example 2: Simple execution of program + # + print "Executing calc..." + p = Popen("calc") + p.wait() + + +if __name__ == "__main__": + if mswindows: + _demo_windows() + else: + _demo_posix() diff --git a/pyinstaller/PyInstaller/lib/altgraph/.svn/entries b/pyinstaller/PyInstaller/lib/altgraph/.svn/entries new file mode 100644 index 0000000..f0d5ae0 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/.svn/entries @@ -0,0 +1,84 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/lib/altgraph +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +ObjectGraph.py +file + + + +add + +GraphStat.py +file + + + +add + +__init__.py +file + + + +add + +GraphUtil.py +file + + + +add + +Graph.py +file + + + +add + +compat.py +file + + + +add + +GraphAlgo.py +file + + + +add + +Dot.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/lib/altgraph/Dot.py b/pyinstaller/PyInstaller/lib/altgraph/Dot.py new file mode 100644 index 0000000..7f38f8e --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/Dot.py @@ -0,0 +1,300 @@ +''' +altgraph.Dot - Interface to the dot language +============================================ + +The :py:mod:`~altgraph.Dot` module provides a simple interface to the +file format used in the `graphviz `_ +program. The module is intended to offload the most tedious part of the process +(the **dot** file generation) while transparently exposing most of its features. + +To display the graphs or to generate image files the `graphviz `_ +package needs to be installed on the system, moreover the :command:`dot` and :command:`dotty` programs must +be accesible in the program path so that they can be ran from processes spawned +within the module. + +Example usage +------------- + +Here is a typical usage:: + + from altgraph import Graph, Dot + + # create a graph + edges = [ (1,2), (1,3), (3,4), (3,5), (4,5), (5,4) ] + graph = Graph.Graph(edges) + + # create a dot representation of the graph + dot = Dot.Dot(graph) + + # display the graph + dot.display() + + # save the dot representation into the mydot.dot file + dot.save_dot(file_name='mydot.dot') + + # save dot file as gif image into the graph.gif file + dot.save_img(file_name='graph', file_type='gif') + +Directed graph and non-directed graph +------------------------------------- + +Dot class can use for both directed graph and non-directed graph +by passing ``graphtype`` parameter. + +Example:: + + # create directed graph(default) + dot = Dot.Dot(graph, graphtype="digraph") + + # create non-directed graph + dot = Dot.Dot(graph, graphtype="graph") + +Customizing the output +---------------------- + +The graph drawing process may be customized by passing +valid :command:`dot` parameters for the nodes and edges. For a list of all +parameters see the `graphviz `_ +documentation. + +Example:: + + # customizing the way the overall graph is drawn + dot.style(size='10,10', rankdir='RL', page='5, 5' , ranksep=0.75) + + # customizing node drawing + dot.node_style(1, label='BASE_NODE',shape='box', color='blue' ) + dot.node_style(2, style='filled', fillcolor='red') + + # customizing edge drawing + dot.edge_style(1, 2, style='dotted') + dot.edge_style(3, 5, arrowhead='dot', label='binds', labelangle='90') + dot.edge_style(4, 5, arrowsize=2, style='bold') + + +.. note:: + + dotty (invoked via :py:func:`~altgraph.Dot.display`) may not be able to + display all graphics styles. To verify the output save it to an image file + and look at it that way. + +Valid attributes +---------------- + + - dot styles, passed via the :py:meth:`Dot.style` method:: + + rankdir = 'LR' (draws the graph horizontally, left to right) + ranksep = number (rank separation in inches) + + - node attributes, passed via the :py:meth:`Dot.node_style` method:: + + style = 'filled' | 'invisible' | 'diagonals' | 'rounded' + shape = 'box' | 'ellipse' | 'circle' | 'point' | 'triangle' + + - edge attributes, passed via the :py:meth:`Dot.edge_style` method:: + + style = 'dashed' | 'dotted' | 'solid' | 'invis' | 'bold' + arrowhead = 'box' | 'crow' | 'diamond' | 'dot' | 'inv' | 'none' | 'tee' | 'vee' + weight = number (the larger the number the closer the nodes will be) + + - valid `graphviz colors `_ + + - for more details on how to control the graph drawing process see the + `graphviz reference `_. +''' +import os +from itertools import imap, ifilter +import warnings + +from altgraph import GraphError + + +class Dot(object): + ''' + A class providing a **graphviz** (dot language) representation + allowing a fine grained control over how the graph is being + displayed. + + If the :command:`dot` and :command:`dotty` programs are not in the current system path + their location needs to be specified in the contructor. + ''' + + def __init__(self, graph=None, nodes=None, edgefn=None, nodevisitor=None, edgevisitor=None, name="G", dot='dot', dotty='dotty', neato='neato', graphtype="digraph"): + ''' + Initialization. + ''' + self.name, self.attr = name, {} + + assert graphtype in ['graph', 'digraph'] + self.type = graphtype + + self.temp_dot = "tmp_dot.dot" + self.temp_neo = "tmp_neo.dot" + + self.dot, self.dotty, self.neato = dot, dotty, neato + + # self.nodes: node styles + # self.edges: edge styles + self.nodes, self.edges = {}, {} + + if graph is not None and nodes is None: + nodes = graph + if graph is not None and edgefn is None: + def edgefn(node, graph=graph): + return graph.out_nbrs(node) + if nodes is None: + nodes = () + + seen = set() + for node in nodes: + if nodevisitor is None: + style = {} + else: + style = nodevisitor(node) + if style is not None: + self.nodes[node] = {} + self.node_style(node, **style) + seen.add(node) + if edgefn is not None: + for head in seen: + for tail in ifilter(seen.__contains__, edgefn(head)): + if edgevisitor is None: + edgestyle = {} + else: + edgestyle = edgevisitor(head, tail) + if edgestyle is not None: + if head not in self.edges: + self.edges[head] = {} + self.edges[head][tail] = {} + self.edge_style(head, tail, **edgestyle) + + def style(self, **attr): + ''' + Changes the overall style + ''' + self.attr = attr + + def display(self, mode='dot'): + ''' + Displays the current graph via dotty + ''' + + if mode == 'neato': + self.save_dot(self.temp_neo) + neato_cmd = "%s -o %s %s" % (self.neato, self.temp_dot, self.temp_neo) + os.system(neato_cmd) + else: + self.save_dot(self.temp_dot) + + plot_cmd = "%s %s" % (self.dotty, self.temp_dot) + os.system(plot_cmd) + + def node_style(self, node, **kwargs): + ''' + Modifies a node style to the dot representation. + ''' + if node not in self.edges: + self.edges[node] = {} + self.nodes[node] = kwargs + + def all_node_style(self, **kwargs): + ''' + Modifies all node styles + ''' + for node in self.nodes: + self.node_style(node, **kwargs) + + def edge_style(self, head, tail, **kwargs): + ''' + Modifies an edge style to the dot representation. + ''' + if tail not in self.nodes: + raise GraphError("invalid node %s" % (tail,)) + + try: + if tail not in self.edges[head]: + self.edges[head][tail]= {} + self.edges[head][tail] = kwargs + except KeyError: + raise GraphError("invalid edge %s -> %s " % (head, tail) ) + + def iterdot(self): + # write graph title + if self.type == 'digraph': + yield 'digraph %s {\n' % (self.name,) + elif self.type == 'graph': + yield 'graph %s {\n' % (self.name,) + + else: + raise GraphError("unsupported graphtype %s" % (self.type,)) + + # write overall graph attributes + for attr_name, attr_value in self.attr.iteritems(): + yield '%s="%s";' % (attr_name, attr_value) + yield '\n' + + # some reusable patterns + cpatt = '%s="%s",' # to separate attributes + epatt = '];\n' # to end attributes + + # write node attributes + for node_name, node_attr in self.nodes.iteritems(): + yield '\t"%s" [' % (node_name,) + for attr_name, attr_value in node_attr.iteritems(): + yield cpatt % (attr_name, attr_value) + yield epatt + + # write edge attributes + for head in self.edges: + for tail in self.edges[head]: + if self.type == 'digraph': + yield '\t"%s" -> "%s" [' % (head, tail) + else: + yield '\t"%s" -- "%s" [' % (head, tail) + for attr_name, attr_value in self.edges[head][tail].iteritems(): + yield cpatt % (attr_name, attr_value) + yield epatt + + # finish file + yield '}\n' + + def __iter__(self): + return self.iterdot() + + def save_dot(self, file_name=None): + ''' + Saves the current graph representation into a file + ''' + + if not file_name: + warnings.warn(DeprecationWarning, "always pass a file_name") + file_name = self.temp_dot + + fp = open(file_name, "w") + try: + for chunk in self.iterdot(): + fp.write(chunk) + finally: + fp.close() + + def save_img(self, file_name=None, file_type="gif", mode='dot'): + ''' + Saves the dot file as an image file + ''' + + if not file_name: + warnings.warn(DeprecationWarning, "always pass a file_name") + file_name = "out" + + if mode == 'neato': + self.save_dot(self.temp_neo) + neato_cmd = "%s -o %s %s" % (self.neato, self.temp_dot, self.temp_neo) + os.system(neato_cmd) + plot_cmd = self.dot + else: + self.save_dot(self.temp_dot) + plot_cmd = self.dot + + file_name = "%s.%s" % (file_name, file_type) + create_cmd = "%s -T%s %s -o %s" % (plot_cmd, file_type, self.temp_dot, file_name) + os.system(create_cmd) diff --git a/pyinstaller/PyInstaller/lib/altgraph/Graph.py b/pyinstaller/PyInstaller/lib/altgraph/Graph.py new file mode 100644 index 0000000..3a88ee0 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/Graph.py @@ -0,0 +1,673 @@ +""" +altgraph.Graph - Base Graph class +================================= + +.. + #--Version 2.1 + #--Bob Ippolito October, 2004 + + #--Version 2.0 + #--Istvan Albert June, 2004 + + #--Version 1.0 + #--Nathan Denny, May 27, 1999 +""" + +from altgraph import GraphError +from collections import deque + +class Graph(object): + """ + The Graph class represents a directed graph with *N* nodes and *E* edges. + + Naming conventions: + + - the prefixes such as *out*, *inc* and *all* will refer to methods + that operate on the outgoing, incoming or all edges of that node. + + For example: :py:meth:`inc_degree` will refer to the degree of the node + computed over the incoming edges (the number of neighbours linking to + the node). + + - the prefixes such as *forw* and *back* will refer to the + orientation of the edges used in the method with respect to the node. + + For example: :py:meth:`forw_bfs` will start at the node then use the outgoing + edges to traverse the graph (goes forward). + """ + + def __init__(self, edges=None): + """ + Initialization + """ + + self.next_edge = 0 + self.nodes, self.edges = {}, {} + self.hidden_edges, self.hidden_nodes = {}, {} + + if edges is not None: + for item in edges: + if len(item) == 2: + head, tail = item + self.add_edge(head, tail) + elif len(item) == 3: + head, tail, data = item + self.add_edge(head, tail, data) + else: + raise GraphError("Cannot create edge from %s"%(item,)) + + + def __repr__(self): + return '' % ( + self.number_of_nodes(), self.number_of_edges()) + + def add_node(self, node, node_data=None): + """ + Adds a new node to the graph. Arbitrary data can be attached to the + node via the node_data parameter. Adding the same node twice will be + silently ignored. + + The node must be a hashable value. + """ + # + # the nodes will contain tuples that will store incoming edges, + # outgoing edges and data + # + # index 0 -> incoming edges + # index 1 -> outgoing edges + + if node in self.hidden_nodes: + # Node is present, but hidden + return + + if node not in self.nodes: + self.nodes[node] = ([], [], node_data) + + def add_edge(self, head_id, tail_id, edge_data=1, create_nodes=True): + """ + Adds a directed edge going from head_id to tail_id. + Arbitrary data can be attached to the edge via edge_data. + It may create the nodes if adding edges between nonexisting ones. + + :param head_id: head node + :param tail_id: tail node + :param edge_data: (optional) data attached to the edge + :param create_nodes: (optional) creates the head_id or tail_id node in case they did not exist + """ + # shorcut + edge = self.next_edge + + # add nodes if on automatic node creation + if create_nodes: + self.add_node(head_id) + self.add_node(tail_id) + + # update the corresponding incoming and outgoing lists in the nodes + # index 0 -> incoming edges + # index 1 -> outgoing edges + + try: + self.nodes[tail_id][0].append(edge) + self.nodes[head_id][1].append(edge) + except KeyError: + raise GraphError('Invalid nodes %s -> %s' % (head_id, tail_id)) + + # store edge information + self.edges[edge] = (head_id, tail_id, edge_data) + + + self.next_edge += 1 + + def hide_edge(self, edge): + """ + Hides an edge from the graph. The edge may be unhidden at some later + time. + """ + try: + head_id, tail_id, edge_data = self.hidden_edges[edge] = self.edges[edge] + self.nodes[tail_id][0].remove(edge) + self.nodes[head_id][1].remove(edge) + del self.edges[edge] + except KeyError: + raise GraphError('Invalid edge %s' % edge) + + def hide_node(self, node): + """ + Hides a node from the graph. The incoming and outgoing edges of the + node will also be hidden. The node may be unhidden at some later time. + """ + try: + all_edges = self.all_edges(node) + self.hidden_nodes[node] = (self.nodes[node], all_edges) + for edge in all_edges: + self.hide_edge(edge) + del self.nodes[node] + except KeyError: + raise GraphError('Invalid node %s' % node) + + def restore_node(self, node): + """ + Restores a previously hidden node back into the graph and restores + all of its incoming and outgoing edges. + """ + try: + self.nodes[node], all_edges = self.hidden_nodes[node] + for edge in all_edges: + self.restore_edge(edge) + del self.hidden_nodes[node] + except KeyError: + raise GraphError('Invalid node %s' % node) + + def restore_edge(self, edge): + """ + Restores a previously hidden edge back into the graph. + """ + try: + head_id, tail_id, data = self.hidden_edges[edge] + self.nodes[tail_id][0].append(edge) + self.nodes[head_id][1].append(edge) + self.edges[edge] = head_id, tail_id, data + del self.hidden_edges[edge] + except KeyError: + raise GraphError('Invalid edge %s' % edge) + + def restore_all_edges(self): + """ + Restores all hidden edges. + """ + for edge in self.hidden_edges.keys(): + try: + self.restore_edge(edge) + except GraphError: + pass + + def restore_all_nodes(self): + """ + Restores all hidden nodes. + """ + for node in self.hidden_nodes.keys(): + self.restore_node(node) + + def __contains__(self, node): + """ + Test whether a node is in the graph + """ + return node in self.nodes + + def edge_by_id(self, edge): + """ + Returns the edge that connects the head_id and tail_id nodes + """ + try: + head, tail, data = self.edges[edge] + except KeyError: + head, tail = None, None + raise GraphError('Invalid edge %s' % edge) + + return (head, tail) + + def edge_by_node(self, head, tail): + """ + Returns the edge that connects the head_id and tail_id nodes + """ + for edge in self.out_edges(head): + if self.tail(edge) == tail: + return edge + return None + + def number_of_nodes(self): + """ + Returns the number of nodes + """ + return len(self.nodes) + + def number_of_edges(self): + """ + Returns the number of edges + """ + return len(self.edges) + + def __iter__(self): + """ + Iterates over all nodes in the graph + """ + return iter(self.nodes) + + def node_list(self): + """ + Return a list of the node ids for all visible nodes in the graph. + """ + return self.nodes.keys() + + def edge_list(self): + """ + Returns an iterator for all visible nodes in the graph. + """ + return self.edges.keys() + + def number_of_hidden_edges(self): + """ + Returns the number of hidden edges + """ + return len(self.hidden_edges) + + def number_of_hidden_nodes(self): + """ + Returns the number of hidden nodes + """ + return len(self.hidden_nodes) + + def hidden_node_list(self): + """ + Returns the list with the hidden nodes + """ + return self.hidden_nodes.keys() + + def hidden_edge_list(self): + """ + Returns a list with the hidden edges + """ + return self.hidden_edges.keys() + + def describe_node(self, node): + """ + return node, node data, outgoing edges, incoming edges for node + """ + incoming, outgoing, data = self.nodes[node] + return node, data, outgoing, incoming + + def describe_edge(self, edge): + """ + return edge, edge data, head, tail for edge + """ + head, tail, data = self.edges[edge] + return edge, data, head, tail + + def node_data(self, node): + """ + Returns the data associated with a node + """ + return self.nodes[node][2] + + def edge_data(self, edge): + """ + Returns the data associated with an edge + """ + return self.edges[edge][2] + + def head(self, edge): + """ + Returns the node of the head of the edge. + """ + return self.edges[edge][0] + + def tail(self, edge): + """ + Returns node of the tail of the edge. + """ + return self.edges[edge][1] + + def out_nbrs(self, node): + """ + List of nodes connected by outgoing edges + """ + l = map(self.tail, self.out_edges(node)) + #l.sort() + return l + + def inc_nbrs(self, node): + """ + List of nodes connected by incoming edges + """ + l = map(self.head, self.inc_edges(node)) + #l.sort() + return l + + def all_nbrs(self, node): + """ + List of nodes connected by incoming and outgoing edges + """ + l = dict.fromkeys( self.inc_nbrs(node) + self.out_nbrs(node) ) + return list(l) + + def out_edges(self, node): + """ + Returns a list of the outgoing edges + """ + try: + return list(self.nodes[node][1]) + except KeyError: + raise GraphError('Invalid node %s' % node) + + return None + + def inc_edges(self, node): + """ + Returns a list of the incoming edges + """ + try: + return list(self.nodes[node][0]) + except KeyError: + raise GraphError('Invalid node %s' % node) + + return None + + def all_edges(self, node): + """ + Returns a list of incoming and outging edges. + """ + return set(self.inc_edges(node) + self.out_edges(node)) + + def out_degree(self, node): + """ + Returns the number of outgoing edges + """ + return len(self.out_edges(node)) + + def inc_degree(self, node): + """ + Returns the number of incoming edges + """ + return len(self.inc_edges(node)) + + def all_degree(self, node): + """ + The total degree of a node + """ + return self.inc_degree(node) + self.out_degree(node) + + def _topo_sort(self, forward=True): + """ + Topological sort. + + Returns a list of nodes where the successors (based on outgoing and + incoming edges selected by the forward parameter) of any given node + appear in the sequence after that node. + """ + topo_list = [] + queue = deque() + indeg = {} + + # select the operation that will be performed + if forward: + get_edges = self.out_edges + get_degree = self.inc_degree + get_next = self.tail + else: + get_edges = self.inc_edges + get_degree = self.out_degree + get_next = self.head + + for node in self.node_list(): + degree = get_degree(node) + if degree: + indeg[node] = degree + else: + queue.append(node) + + while queue: + curr_node = queue.popleft() + topo_list.append(curr_node) + for edge in get_edges(curr_node): + tail_id = get_next(edge) + if tail_id in indeg: + indeg[tail_id] -= 1 + if indeg[tail_id] == 0: + queue.append(tail_id) + + if len(topo_list) == len(self.node_list()): + valid = True + else: + # the graph has cycles, invalid topological sort + valid = False + + return (valid, topo_list) + + def forw_topo_sort(self): + """ + Topological sort. + + Returns a list of nodes where the successors (based on outgoing edges) + of any given node appear in the sequence after that node. + """ + return self._topo_sort(forward=True) + + def back_topo_sort(self): + """ + Reverse topological sort. + + Returns a list of nodes where the successors (based on incoming edges) + of any given node appear in the sequence after that node. + """ + return self._topo_sort(forward=False) + + def _bfs_subgraph(self, start_id, forward=True): + """ + Private method creates a subgraph in a bfs order. + + The forward parameter specifies whether it is a forward or backward + traversal. + """ + if forward: + get_bfs = self.forw_bfs + get_nbrs = self.out_nbrs + else: + get_bfs = self.back_bfs + get_nbrs = self.inc_nbrs + + g = Graph() + bfs_list = get_bfs(start_id) + for node in bfs_list: + g.add_node(node) + + for node in bfs_list: + for nbr_id in get_nbrs(node): + g.add_edge(node, nbr_id) + + return g + + def forw_bfs_subgraph(self, start_id): + """ + Creates and returns a subgraph consisting of the breadth first + reachable nodes based on their outgoing edges. + """ + return self._bfs_subgraph(start_id, forward=True) + + def back_bfs_subgraph(self, start_id): + """ + Creates and returns a subgraph consisting of the breadth first + reachable nodes based on the incoming edges. + """ + return self._bfs_subgraph(start_id, forward=False) + + def iterdfs(self, start, end=None, forward=True): + """ + Collecting nodes in some depth first traversal. + + The forward parameter specifies whether it is a forward or backward + traversal. + """ + visited, stack = set([start]), deque([start]) + + if forward: + get_edges = self.out_edges + get_next = self.tail + else: + get_edges = self.inc_edges + get_next = self.head + + while stack: + curr_node = stack.pop() + yield curr_node + if curr_node == end: + break + for edge in sorted(get_edges(curr_node)): + tail = get_next(edge) + if tail not in visited: + visited.add(tail) + stack.append(tail) + + def iterdata(self, start, end=None, forward=True, condition=None): + """ + Perform a depth-first walk of the graph (as ``iterdfs``) + and yield the item data of every node where condition matches. The + condition callback is only called when node_data is not None. + """ + + visited, stack = set([start]), deque([start]) + + if forward: + get_edges = self.out_edges + get_next = self.tail + else: + get_edges = self.inc_edges + get_next = self.head + + get_data = self.node_data + + while stack: + curr_node = stack.pop() + curr_data = get_data(curr_node) + if curr_data is not None: + if condition is not None and not condition(curr_data): + continue + yield curr_data + if curr_node == end: + break + for edge in get_edges(curr_node): + tail = get_next(edge) + if tail not in visited: + visited.add(tail) + stack.append(tail) + + def _iterbfs(self, start, end=None, forward=True): + """ + The forward parameter specifies whether it is a forward or backward + traversal. Returns a list of tuples where the first value is the hop + value the second value is the node id. + """ + queue, visited = deque([(start, 0)]), set([start]) + + # the direction of the bfs depends on the edges that are sampled + if forward: + get_edges = self.out_edges + get_next = self.tail + else: + get_edges = self.inc_edges + get_next = self.head + + while queue: + curr_node, curr_step = queue.popleft() + yield (curr_node, curr_step) + if curr_node == end: + break + for edge in get_edges(curr_node): + tail = get_next(edge) + if tail not in visited: + visited.add(tail) + queue.append((tail, curr_step + 1)) + + + def forw_bfs(self, start, end=None): + """ + Returns a list of nodes in some forward BFS order. + + Starting from the start node the breadth first search proceeds along + outgoing edges. + """ + return [node for node, step in self._iterbfs(start, end, forward=True)] + + def back_bfs(self, start, end=None): + """ + Returns a list of nodes in some backward BFS order. + + Starting from the start node the breadth first search proceeds along + incoming edges. + """ + return [node for node, step in self._iterbfs(start, end, forward=False)] + + def forw_dfs(self, start, end=None): + """ + Returns a list of nodes in some forward DFS order. + + Starting with the start node the depth first search proceeds along + outgoing edges. + """ + return list(self.iterdfs(start, end, forward=True)) + + def back_dfs(self, start, end=None): + """ + Returns a list of nodes in some backward DFS order. + + Starting from the start node the depth first search proceeds along + incoming edges. + """ + return list(self.iterdfs(start, end, forward=False)) + + def connected(self): + """ + Returns :py:data:`True` if the graph's every node can be reached from every + other node. + """ + node_list = self.node_list() + for node in node_list: + bfs_list = self.forw_bfs(node) + if len(bfs_list) != len(node_list): + return False + return True + + def clust_coef(self, node): + """ + Computes and returns the local clustering coefficient of node. The + local cluster coefficient is proportion of the actual number of edges between + neighbours of node and the maximum number of edges between those neighbours. + + See + for a formal definition. + """ + num = 0 + nbr_set = set(self.out_nbrs(node)) + + if node in nbr_set: + nbr_set.remove(node) # loop defense + + for nbr in nbr_set: + sec_set = set(self.out_nbrs(nbr)) + if nbr in sec_set: + sec_set.remove(nbr) # loop defense + num += len(nbr_set & sec_set) + + nbr_num = len(nbr_set) + if nbr_num: + clust_coef = float(num) / (nbr_num * (nbr_num - 1)) + else: + clust_coef = 0.0 + return clust_coef + + def get_hops(self, start, end=None, forward=True): + """ + Computes the hop distance to all nodes centered around a specified node. + + First order neighbours are at hop 1, their neigbours are at hop 2 etc. + Uses :py:meth:`forw_bfs` or :py:meth:`back_bfs` depending on the value of the forward + parameter. If the distance between all neighbouring nodes is 1 the hop + number corresponds to the shortest distance between the nodes. + + :param start: the starting node + :param end: ending node (optional). When not specified will search the whole graph. + :param forward: directionality parameter (optional). If C{True} (default) it uses L{forw_bfs} otherwise L{back_bfs}. + :return: returns a list of tuples where each tuple contains the node and the hop. + + Typical usage:: + + >>> print graph.get_hops(1, 8) + >>> [(1, 0), (2, 1), (3, 1), (4, 2), (5, 3), (7, 4), (8, 5)] + # node 1 is at 0 hops + # node 2 is at 1 hop + # ... + # node 8 is at 5 hops + """ + if forward: + return list(self._iterbfs(start=start, end=end, forward=True)) + else: + return list(self._iterbfs(start=start, end=end, forward=False)) diff --git a/pyinstaller/PyInstaller/lib/altgraph/GraphAlgo.py b/pyinstaller/PyInstaller/lib/altgraph/GraphAlgo.py new file mode 100644 index 0000000..3456de8 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/GraphAlgo.py @@ -0,0 +1,147 @@ +''' +altgraph.GraphAlgo - Graph algorithms +===================================== +''' +from altgraph import GraphError + +def dijkstra(graph, start, end=None): + """ + Dijkstra's algorithm for shortest paths + + `David Eppstein, UC Irvine, 4 April 2002 `_ + + `Python Cookbook Recipe `_ + + Find shortest paths from the start node to all nodes nearer than or equal to the end node. + + Dijkstra's algorithm is only guaranteed to work correctly when all edge lengths are positive. + This code does not verify this property for all edges (only the edges examined until the end + vertex is reached), but will correctly compute shortest paths even for some graphs with negative + edges, and will raise an exception if it discovers that a negative edge has caused it to make a mistake. + + *Adapted to altgraph by Istvan Albert, Pennsylvania State University - June, 9 2004* + + """ + D = {} # dictionary of final distances + P = {} # dictionary of predecessors + Q = _priorityDictionary() # estimated distances of non-final vertices + Q[start] = 0 + + for v in Q: + D[v] = Q[v] + if v == end: break + + for w in graph.out_nbrs(v): + edge_id = graph.edge_by_node(v,w) + vwLength = D[v] + graph.edge_data(edge_id) + if w in D: + if vwLength < D[w]: + raise GraphError("Dijkstra: found better path to already-final vertex") + elif w not in Q or vwLength < Q[w]: + Q[w] = vwLength + P[w] = v + + return (D,P) + +def shortest_path(graph, start, end): + """ + Find a single shortest path from the given start node to the given end node. + The input has the same conventions as dijkstra(). The output is a list of the nodes + in order along the shortest path. + + **Note that the distances must be stored in the edge data as numeric data** + """ + + D,P = dijkstra(graph, start, end) + Path = [] + while 1: + Path.append(end) + if end == start: break + end = P[end] + Path.reverse() + return Path + +# +# Utility classes and functions +# +class _priorityDictionary(dict): + ''' + Priority dictionary using binary heaps (internal use only) + + David Eppstein, UC Irvine, 8 Mar 2002 + + Implements a data structure that acts almost like a dictionary, with two modifications: + 1. D.smallest() returns the value x minimizing D[x]. For this to work correctly, + all values D[x] stored in the dictionary must be comparable. + 2. iterating "for x in D" finds and removes the items from D in sorted order. + Each item is not removed until the next item is requested, so D[x] will still + return a useful value until the next iteration of the for-loop. + Each operation takes logarithmic amortized time. + ''' + def __init__(self): + ''' + Initialize priorityDictionary by creating binary heap of pairs (value,key). + Note that changing or removing a dict entry will not remove the old pair from the heap + until it is found by smallest() or until the heap is rebuilt. + ''' + self.__heap = [] + dict.__init__(self) + + def smallest(self): + ''' + Find smallest item after removing deleted items from front of heap. + ''' + if len(self) == 0: + raise IndexError, "smallest of empty priorityDictionary" + heap = self.__heap + while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]: + lastItem = heap.pop() + insertionPoint = 0 + while 1: + smallChild = 2*insertionPoint+1 + if smallChild+1 < len(heap) and heap[smallChild] > heap[smallChild+1] : + smallChild += 1 + if smallChild >= len(heap) or lastItem <= heap[smallChild]: + heap[insertionPoint] = lastItem + break + heap[insertionPoint] = heap[smallChild] + insertionPoint = smallChild + return heap[0][1] + + def __iter__(self): + ''' + Create destructive sorted iterator of priorityDictionary. + ''' + def iterfn(): + while len(self) > 0: + x = self.smallest() + yield x + del self[x] + return iterfn() + + def __setitem__(self,key,val): + ''' + Change value stored in dictionary and add corresponding pair to heap. + Rebuilds the heap if the number of deleted items gets large, to avoid memory leakage. + ''' + dict.__setitem__(self,key,val) + heap = self.__heap + if len(heap) > 2 * len(self): + self.__heap = [(v,k) for k,v in self.iteritems()] + self.__heap.sort() # builtin sort probably faster than O(n)-time heapify + else: + newPair = (val,key) + insertionPoint = len(heap) + heap.append(None) + while insertionPoint > 0 and newPair < heap[(insertionPoint-1)//2]: + heap[insertionPoint] = heap[(insertionPoint-1)//2] + insertionPoint = (insertionPoint-1)//2 + heap[insertionPoint] = newPair + + def setdefault(self,key,val): + ''' + Reimplement setdefault to pass through our customized __setitem__. + ''' + if key not in self: + self[key] = val + return self[key] diff --git a/pyinstaller/PyInstaller/lib/altgraph/GraphStat.py b/pyinstaller/PyInstaller/lib/altgraph/GraphStat.py new file mode 100644 index 0000000..2d63dd8 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/GraphStat.py @@ -0,0 +1,73 @@ +''' +altgraph.GraphStat - Functions providing various graph statistics +================================================================= +''' +import sys + +def degree_dist(graph, limits=(0,0), bin_num=10, mode='out'): + ''' + Computes the degree distribution for a graph. + + Returns a list of tuples where the first element of the tuple is the center of the bin + representing a range of degrees and the second element of the tuple are the number of nodes + with the degree falling in the range. + + Example:: + + .... + ''' + + deg = [] + if mode == 'inc': + get_deg = graph.inc_degree + else: + get_deg = graph.out_degree + + for node in graph: + deg.append( get_deg(node) ) + + if not deg: + return [] + + results = _binning(values=deg, limits=limits, bin_num=bin_num) + + return results + +def _binning(values, limits=(0,0), bin_num=10): + ''' + Bins data that falls between certain limits, if the limits are (0, 0) the + minimum and maximum values are used. + + Returns a list of tuples where the first element of the tuple is the center of the bin + and the second element of the tuple are the counts. + ''' + if limits == (0, 0): + eps = 1.0/sys.maxint + min_val, max_val = min(values) - eps, max(values) + eps + else: + min_val, max_val = limits + + # get bin size + bin_size = (max_val - min_val)/float(bin_num) + bins = [0] * (bin_num) + + # will ignore these outliers for now + out_points = 0 + for value in values: + try: + if (value - min_val) < 0: + out_points += 1 + else: + index = int((value - min_val)/float(bin_size)) + bins[index] += 1 + except IndexError: + out_points += 1 + + # make it ready for an x,y plot + result = [] + center = (bin_size/2) + min_val + for i, y in enumerate(bins): + x = center + bin_size * i + result.append( (x,y) ) + + return result diff --git a/pyinstaller/PyInstaller/lib/altgraph/GraphUtil.py b/pyinstaller/PyInstaller/lib/altgraph/GraphUtil.py new file mode 100644 index 0000000..760e005 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/GraphUtil.py @@ -0,0 +1,137 @@ +''' +altgraph.GraphUtil - Utility classes and functions +================================================== +''' + +import random +from collections import deque +from altgraph import Graph +from altgraph import GraphError + +def generate_random_graph(node_num, edge_num, self_loops=False, multi_edges=False): + ''' + Generates and returns a :py:class:`~altgraph.Graph.Graph` instance with *node_num* nodes + randomly connected by *edge_num* edges. + ''' + g = Graph.Graph() + + if not multi_edges: + if self_loops: + max_edges = node_num * node_num + else: + max_edges = node_num * (node_num-1) + + if edge_num > max_edges: + raise GraphError("inconsistent arguments to 'generate_random_graph'") + + nodes = range(node_num) + + for node in nodes: + g.add_node(node) + + while 1: + head = random.choice(nodes) + tail = random.choice(nodes) + + # loop defense + if head == tail and not self_loops: + continue + + # multiple edge defense + if g.edge_by_node(head,tail) is not None and not multi_edges: + continue + + # add the edge + g.add_edge(head, tail) + if g.number_of_edges() >= edge_num: + break + + return g + +def generate_scale_free_graph(steps, growth_num, self_loops=False, multi_edges=False): + ''' + Generates and returns a :py:class:`~altgraph.Graph.Graph` instance that will have *steps* \* *growth_num* nodes + and a scale free (powerlaw) connectivity. Starting with a fully connected graph with *growth_num* nodes + at every step *growth_num* nodes are added to the graph and are connected to existing nodes with + a probability proportional to the degree of these existing nodes. + ''' + # FIXME: The code doesn't seem to do what the documentation claims. + graph = Graph.Graph() + + # initialize the graph + store = [] + for i in range(growth_num): + #store += [ i ] * (growth_num - 1) + for j in range(i + 1, growth_num): + store.append(i) + store.append(j) + graph.add_edge(i,j) + + # generate + for node in range(growth_num, steps * growth_num): + graph.add_node(node) + while ( graph.out_degree(node) < growth_num ): + nbr = random.choice(store) + + # loop defense + if node == nbr and not self_loops: + continue + + # multi edge defense + if graph.edge_by_node(node, nbr) and not multi_edges: + continue + + graph.add_edge(node, nbr) + + + for nbr in graph.out_nbrs(node): + store.append(node) + store.append(nbr) + + return graph + +def filter_stack(graph, head, filters): + """ + Perform a walk in a depth-first order starting + at *head*. + + Returns (visited, removes, orphans). + + * visited: the set of visited nodes + * removes: the list of nodes where the node + data does not all *filters* + * orphans: tuples of (last_good, node), + where node is not in removes, is directly + reachable from a node in *removes* and + *last_good* is the closest upstream node that is not + in *removes*. + """ + + visited, removes, orphans = set([head]), set(), set() + stack = deque([(head, head)]) + get_data = graph.node_data + get_edges = graph.out_edges + get_tail = graph.tail + + while stack: + last_good, node = stack.pop() + data = get_data(node) + if data is not None: + for filtfunc in filters: + if not filtfunc(data): + removes.add(node) + break + else: + last_good = node + for edge in get_edges(node): + tail = get_tail(edge) + if last_good is not node: + orphans.add((last_good, tail)) + if tail not in visited: + visited.add(tail) + stack.append((last_good, tail)) + + orphans = [(last_good, tail) for (last_good, tail) in orphans if tail not in removes] + #orphans.sort() + + return visited, removes, orphans diff --git a/pyinstaller/PyInstaller/lib/altgraph/ObjectGraph.py b/pyinstaller/PyInstaller/lib/altgraph/ObjectGraph.py new file mode 100644 index 0000000..626775f --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/ObjectGraph.py @@ -0,0 +1,192 @@ +""" +altgraph.ObjectGraph - Graph of objects with an identifier +========================================================== + +A graph of objects that have a "graphident" attribute. +graphident is the key for the object in the graph +""" + +from itertools import imap + +from altgraph import GraphError +from altgraph.Graph import Graph +from altgraph.GraphUtil import filter_stack + +class ObjectGraph(object): + """ + A graph of objects that have a "graphident" attribute. + graphident is the key for the object in the graph + """ + def __init__(self, graph=None, debug=0): + if graph is None: + graph = Graph() + self.graphident = self + self.graph = graph + self.debug = debug + self.indent = 0 + graph.add_node(self, None) + + def __repr__(self): + return '<%s>' % (type(self).__name__,) + + def flatten(self, condition=None, start=None): + """ + Iterate over the subgraph that is entirely reachable by condition + starting from the given start node or the ObjectGraph root + """ + if start is None: + start = self + start = self.getRawIdent(start) + return self.graph.iterdata(start=start, condition=condition) + + def nodes(self): + for ident in self.graph: + node = self.graph.node_data(ident) + if node is not None: + yield self.graph.node_data(ident) + + + def get_edges(self, node): + start = self.getRawIdent(node) + _, _, outraw, incraw = self.graph.describe_node(start) + def iter_edges(lst, n): + seen = set() + for tpl in imap(self.graph.describe_edge, lst): + ident = tpl[n] + if ident not in seen: + yield self.findNode(ident) + seen.add(ident) + return iter_edges(outraw, 3), iter_edges(incraw, 2) + + def filterStack(self, filters): + """ + Filter the ObjectGraph in-place by removing all edges to nodes that + do not match every filter in the given filter list + + Returns a tuple containing the number of: (nodes_visited, nodes_removed, nodes_orphaned) + """ + visited, removes, orphans = filter_stack(self.graph, self, filters) + + for last_good, tail in orphans: + self.graph.add_edge(last_good, tail, edge_data='orphan') + + for node in removes: + self.graph.hide_node(node) + + return len(visited)-1, len(removes), len(orphans) + + def removeNode(self, node): + """ + Remove the given node from the graph if it exists + """ + ident = self.getIdent(node) + if ident is not None: + self.graph.hide_node(ident) + + def removeReference(self, fromnode, tonode): + """ + Remove all edges from fromnode to tonode + """ + if fromnode is None: + fromnode = self + fromident = self.getIdent(fromnode) + toident = self.getIdent(tonode) + if fromident is not None and toident is not None: + while True: + edge = self.graph.edge_by_node(fromident, toident) + if edge is None: + break + self.graph.hide_edge(edge) + + def getIdent(self, node): + """ + Get the graph identifier for a node + """ + ident = self.getRawIdent(node) + if ident is not None: + return ident + node = self.findNode(node) + if node is None: + return None + return node.graphident + + def getRawIdent(self, node): + """ + Get the identifier for a node object + """ + if node is self: + return node + ident = getattr(node, 'graphident', None) + return ident + + def __contains__(self, node): + return self.findNode(node) is not None + + def findNode(self, node): + """ + Find the node on the graph + """ + ident = self.getRawIdent(node) + if ident is None: + ident = node + try: + return self.graph.node_data(ident) + except KeyError: + return None + + def addNode(self, node): + """ + Add a node to the graph referenced by the root + """ + self.msg(4, "addNode", node) + + try: + self.graph.restore_node(node.graphident) + except GraphError: + self.graph.add_node(node.graphident, node) + + def createReference(self, fromnode, tonode, edge_data=None): + """ + Create a reference from fromnode to tonode + """ + if fromnode is None: + fromnode = self + fromident, toident = self.getIdent(fromnode), self.getIdent(tonode) + if fromident is None or toident is None: + return + self.msg(4, "createReference", fromnode, tonode, edge_data) + self.graph.add_edge(fromident, toident, edge_data=edge_data) + + def createNode(self, cls, name, *args, **kw): + """ + Add a node of type cls to the graph if it does not already exist + by the given name + """ + m = self.findNode(name) + if m is None: + m = cls(name, *args, **kw) + self.addNode(m) + return m + + def msg(self, level, s, *args): + """ + Print a debug message with the given level + """ + if s and level <= self.debug: + print "%s%s %s" % (" " * self.indent, s, ' '.join(map(repr, args))) + + def msgin(self, level, s, *args): + """ + Print a debug message and indent + """ + if level <= self.debug: + self.msg(level, s, *args) + self.indent = self.indent + 1 + + def msgout(self, level, s, *args): + """ + Dedent and print a debug message + """ + if level <= self.debug: + self.indent = self.indent - 1 + self.msg(level, s, *args) diff --git a/pyinstaller/PyInstaller/lib/altgraph/__init__.py b/pyinstaller/PyInstaller/lib/altgraph/__init__.py new file mode 100644 index 0000000..93d330d --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/__init__.py @@ -0,0 +1,135 @@ +''' +altgraph - a python graph library +================================= + +altgraph is a fork of `graphlib `_ tailored +to use newer Python 2.3+ features, including additional support used by the +py2app suite (modulegraph and macholib, specifically). + +altgraph is a python based graph (network) representation and manipulation package. +It has started out as an extension to the `graph_lib module `_ +written by Nathan Denny it has been significantly optimized and expanded. + +The :class:`altgraph.Graph.Graph` class is loosely modeled after the `LEDA `_ +(Library of Efficient Datatypes) representation. The library +includes methods for constructing graphs, BFS and DFS traversals, +topological sort, finding connected components, shortest paths as well as a number +graph statistics functions. The library can also visualize graphs +via `graphviz `_. + +The package contains the following modules: + + - the :py:mod:`altgraph.Graph` module contains the :class:`~altgraph.Graph.Graph` class that stores the graph data + + - the :py:mod:`altgraph.GraphAlgo` module implements graph algorithms operating on graphs (:py:class:`~altgraph.Graph.Graph`} instances) + + - the :py:mod:`altgraph.GraphStat` module contains functions for computing statistical measures on graphs + + - the :py:mod:`altgraph.GraphUtil` module contains functions for generating, reading and saving graphs + + - the :py:mod:`altgraph.Dot` module contains functions for displaying graphs via `graphviz `_ + + - the :py:mod:`altgraph.ObjectGraph` module implements a graph of objects with a unique identifier + +Installation +------------ + +Download and unpack the archive then type:: + + python setup.py install + +This will install the library in the default location. For instructions on +how to customize the install procedure read the output of:: + + python setup.py --help install + +To verify that the code works run the test suite:: + + python setup.py test + +Example usage +------------- + +Lets assume that we want to analyze the graph below (links to the full picture) GRAPH_IMG. +Our script then might look the following way:: + + from altgraph import Graph, GraphAlgo, Dot + + # these are the edges + edges = [ (1,2), (2,4), (1,3), (2,4), (3,4), (4,5), (6,5), + (6,14), (14,15), (6, 15), (5,7), (7, 8), (7,13), (12,8), + (8,13), (11,12), (11,9), (13,11), (9,13), (13,10) ] + + # creates the graph + graph = Graph.Graph() + for head, tail in edges: + graph.add_edge(head, tail) + + # do a forward bfs from 1 at most to 20 + print graph.forw_bfs(1) + +This will print the nodes in some breadth first order:: + + [1, 2, 3, 4, 5, 7, 8, 13, 11, 10, 12, 9] + +If we wanted to get the hop-distance from node 1 to node 8 +we coud write:: + + print graph.get_hops(1, 8) + +This will print the following:: + + [(1, 0), (2, 1), (3, 1), (4, 2), (5, 3), (7, 4), (8, 5)] + +Node 1 is at 0 hops since it is the starting node, nodes 2,3 are 1 hop away ... +node 8 is 5 hops away. To find the shortest distance between two nodes you +can use:: + + print GraphAlgo.shortest_path(graph, 1, 12) + +It will print the nodes on one (if there are more) the shortest paths:: + + [1, 2, 4, 5, 7, 13, 11, 12] + +To display the graph we can use the GraphViz backend:: + + dot = Dot.Dot(graph) + + # display the graph on the monitor + dot.display() + + # save it in an image file + dot.save_img(file_name='graph', file_type='gif') + + + +.. + @author: U{Istvan Albert} + + @license: MIT License + + Copyright (c) 2004 Istvan Albert unless otherwise noted. + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do + so. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + @requires: Python 2.3 or higher + + @newfield contributor: Contributors: + @contributor: U{Reka Albert } + +''' + +__version__ = '0.7.0' + +class GraphError(ValueError): + pass diff --git a/pyinstaller/PyInstaller/lib/altgraph/compat.py b/pyinstaller/PyInstaller/lib/altgraph/compat.py new file mode 100644 index 0000000..377efe3 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/altgraph/compat.py @@ -0,0 +1,189 @@ +""" +Python 2.4-like compatibility library for Python 2.3 +""" +from itertools import izip, imap +try: + from itertools import tee, groupby + +except ImportError: + pass + +# +# builtins from 2.4 +# + +try: + set, frozenset +except NameError: + from sets import Set as set, ImmutableSet as frozenset + +try: + sorted +except NameError: + def sorted(iterable, cmp=None, key=None, reverse=False): + if key is not None: + a, b = tee(iterable) + iterable = izip(imap(key, iterable), iterable) + if cmp is not None: + iterable = list(iterable) + iterable.sort(cmp) + else: + iterable = isorted(iterable) + if key is not None: + iterable = [v for (k,v) in iterable] + if type(iterable) is not list: + iterable = list(iterable) + if reverse: + iterable.reverse() + return iterable + +try: + reversed +except NameError: + def reversed(iterable): + lst = list(iterable) + pop = lst.pop + while lst: + yield pop() + + +# +# itertools functions from 2.4 +# +try: + tee +except NameError: + def tee(iterable, n=2): + def gen(next, data={}, cnt=[0]): + for i in count(): + if i == cnt[0]: + item = data[i] = next() + cnt[0] += 1 + else: + item = data.pop(i) + yield item + return tuple(imap(gen, repeat(iter(iterable), n))) + +try: + groupby +except NameError: + class groupby(object): + def __init__(self, iterable, key=None): + if key is None: + key = lambda x: x + self.keyfunc = key + self.it = iter(iterable) + self.tgtkey = self.currkey = self.currvalue = xrange(0) + def __iter__(self): + return self + def next(self): + while self.currkey == self.tgtkey: + self.currvalue = self.it.next() # Exit on StopIteration + self.currkey = self.keyfunc(self.currvalue) + self.tgtkey = self.currkey + return (self.currkey, self._grouper(self.tgtkey)) + def _grouper(self, tgtkey): + while self.currkey == tgtkey: + yield self.currvalue + self.currvalue = self.it.next() # Exit on StopIteration + self.currkey = self.keyfunc(self.currvalue) + + +# +# operators from 2.4 +# +try: + from operator import attrgetter, itemgetter +except ImportError: + def attrgetter(attr): + def attrgetter(obj): + return getattr(obj, attr) + return attrgetter + + def itemgetter(item): + def itemgetter(obj): + return obj[item] + return itemgetter + + +# +# deque from 2.4's collections +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/259179/ +# +try: + from collections import deque +except ImportError: + class deque(object): + + def __init__(self, iterable=()): + self.data = dict(enumerate(iterable)) + self.left = 0 + self.right = len(self.data) + + def append(self, x): + self.data[self.right] = x + self.right += 1 + + def appendleft(self, x): + self.left -= 1 + self.data[self.left] = x + + def pop(self): + if self.left == self.right: + raise IndexError('cannot pop from empty deque') + self.right -= 1 + return self.data[self.right] + + def popleft(self): + if self.left == self.right: + raise IndexError('cannot pop from empty deque') + x = self.data[self.left] + self.left += 1 + return x + + def __len__(self): + return self.right - self.left + + def __iter__(self): + return imap(self.data.__getitem__, xrange(self.left, self.right)) + + def __repr__(self): + return 'deque(%r)' % (list(self),) + + def __getstate__(self): + return (tuple(self),) + + def __setstate__(self, s): + self.__init__(s[0]) + + def __hash__(self): + raise TypeError + + def __copy__(self): + return self.__class__(self) + + def __deepcopy__(self, memo={}): + from copy import deepcopy + result = self.__class__() + memo[id(self)] = result + result.__init__(deepcopy(tuple(self), memo)) + return result + +# +# new functions +# +import heapq as _heapq +def isorted(iterable): + lst = list(iterable) + _heapq.heapify(lst) + pop = _heapq.heappop + while lst: + yield pop(lst) + +def ireversed(iterable): + if isinstance(iterable, (list, tuple)): + for i in xrange(len(iterable)-1, -1, -1): + yield iterable[i] + else: + for obj in reversed(iterable): + yield obj diff --git a/pyinstaller/PyInstaller/lib/junitxml/.svn/entries b/pyinstaller/PyInstaller/lib/junitxml/.svn/entries new file mode 100644 index 0000000..3f4368e --- /dev/null +++ b/pyinstaller/PyInstaller/lib/junitxml/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/lib/junitxml +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +tests +dir + + + +add + +__init__.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/lib/junitxml/__init__.py b/pyinstaller/PyInstaller/lib/junitxml/__init__.py new file mode 100644 index 0000000..7700d59 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/junitxml/__init__.py @@ -0,0 +1,221 @@ +# +# junitxml: extensions to Python unittest to get output junitxml +# Copyright (C) 2009 Robert Collins +# +# Copying permitted under the LGPL-3 licence, included with this library. + +"""unittest compatible JUnit XML output.""" + + +import datetime +import re +import time +import unittest2 as unittest + +# same format as sys.version_info: "A tuple containing the five components of +# the version number: major, minor, micro, releaselevel, and serial. All +# values except releaselevel are integers; the release level is 'alpha', +# 'beta', 'candidate', or 'final'. The version_info value corresponding to the +# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a +# releaselevel of 'dev' for unreleased under-development code. +# +# If the releaselevel is 'alpha' then the major/minor/micro components are not +# established at this point, and setup.py will use a version of next-$(revno). +# If the releaselevel is 'final', then the tarball will be major.minor.micro. +# Otherwise it is major.minor.micro~$(revno). +__version__ = (0, 6, 0, 'final', 0) + + +def test_suite(): + import junitxml.tests + return junitxml.tests.test_suite() + + +class LocalTimezone(datetime.tzinfo): + + def __init__(self): + self._offset = None + + # It seems that the minimal possible implementation is to just return all + # None for every function, but then it breaks... + def utcoffset(self, dt): + if self._offset is None: + t = 1260423030 # arbitrary, but doesn't handle dst very well + dt = datetime.datetime + self._offset = (dt.fromtimestamp(t) - dt.utcfromtimestamp(t)) + return self._offset + + def dst(self, dt): + return datetime.timedelta(0) + + def tzname(self, dt): + return None + + +def _error_name(eclass): + module = eclass.__module__ + if module not in ("__main__", "builtins", "exceptions"): + return ".".join([module, eclass.__name__]) + return eclass.__name__ + + +_non_cdata = "[\0-\b\x0B-\x1F\uD800-\uDFFF\uFFFE\uFFFF]+" +if "\\u" in _non_cdata: + _non_cdata = _non_cdata.decode("unicode-escape") + def _strip_invalid_chars(s, _sub=re.compile(_non_cdata, re.UNICODE).sub): + if not isinstance(s, unicode): + try: + s = s.decode("utf-8") + except UnicodeDecodeError: + s = s.decode("ascii", "replace") + return _sub("", s).encode("utf-8") +else: + def _strip_invalid_chars(s, _sub=re.compile(_non_cdata, re.UNICODE).sub): + return _sub("", s) +def _escape_content(s): + return (_strip_invalid_chars(s) + .replace("&", "&") + .replace("<", "<") + .replace("]]>", "]]>")) +def _escape_attr(s): + return (_strip_invalid_chars(s) + .replace("&", "&") + .replace("<", "<") + .replace("]]>", "]]>") + .replace('"', """) + .replace("\t", " ") + .replace("\n", " ")) + + +class JUnitXmlResult(unittest.TestResult): + """A TestResult which outputs JUnit compatible XML.""" + + def __init__(self, stream): + """Create a JUnitXmlResult. + + :param stream: A stream to write results to. Note that due to the + nature of JUnit XML output, nnothing will be written to the stream + until stopTestRun() is called. + """ + self.__super = super(JUnitXmlResult, self) + self.__super.__init__() + # GZ 2010-09-03: We have a problem if passed a text stream in Python 3 + # as really we want to write raw UTF-8 to ensure that + # the encoding is not mangled later + self._stream = stream + self._results = [] + self._set_time = None + self._test_start = None + self._run_start = None + self._tz_info = None + + def startTestRun(self): + """Start a test run.""" + self._run_start = self._now() + + def _get_tzinfo(self): + if self._tz_info is None: + self._tz_info = LocalTimezone() + return self._tz_info + + def _now(self): + if self._set_time is not None: + return self._set_time + else: + return datetime.datetime.now(self._get_tzinfo()) + + def time(self, a_datetime): + self._set_time = a_datetime + if (self._run_start is not None and + self._run_start > a_datetime): + self._run_start = a_datetime + + def startTest(self, test): + self.__super.startTest(test) + self._test_start = self._now() + + def _duration(self, from_datetime): + try: + delta = self._now() - from_datetime + except TypeError: + n = self._now() + delta = datetime.timedelta(-1) + seconds = delta.days * 3600*24 + delta.seconds + return seconds + 0.000001 * delta.microseconds + + def _test_case_string(self, test): + duration = self._duration(self._test_start) + test_id = test.id() + # Split on the last dot not inside a parameter + class_end = test_id.rfind(".", 0, test_id.find("(")) + if class_end == -1: + classname, name = "", test_id + else: + classname, name = test_id[:class_end], test_id[class_end+1:] + self._results.append('\n' % (len(self.errors), + len(self.failures) + len(getattr(self, "unexpectedSuccesses", ())), + len(self.skipped), self.testsRun, duration)) + self._stream.write(''.join(self._results)) + self._stream.write('\n') + + def addError(self, test, error): + self.__super.addError(test, error) + self._test_case_string(test) + self._results.append('>\n') + self._results.append('%s\n\n' % ( + _escape_attr(_error_name(error[0])), + _escape_content(self._exc_info_to_string(error, test)))) + + def addFailure(self, test, error): + self.__super.addFailure(test, error) + self._test_case_string(test) + self._results.append('>\n') + self._results.append('%s\n\n' % + (_escape_attr(_error_name(error[0])), + _escape_content(self._exc_info_to_string(error, test)))) + + def addSuccess(self, test): + self.__super.addSuccess(test) + self._test_case_string(test) + self._results.append('/>\n') + + def addSkip(self, test, reason): + try: + self.__super.addSkip(test, reason) + except AttributeError: + # Python < 2.7|3.1 + pass + self._test_case_string(test) + self._results.append('>\n') + self._results.append('\n\n'% _escape_attr(reason)) + + def addUnexpectedSuccess(self, test): + try: + self.__super.addUnexpectedSuccess(test) + except AttributeError: + # Python < 2.7|3.1 + pass + self._test_case_string(test) + self._results.append('>\n') + self._results.append('\n\n') + + def addExpectedFailure(self, test, error): + try: + self.__super.addExpectedFailure(test, error) + except AttributeError: + # Python < 2.7|3.1 + pass + self._test_case_string(test) + self._results.append('/>\n') + diff --git a/pyinstaller/PyInstaller/lib/junitxml/tests/.svn/entries b/pyinstaller/PyInstaller/lib/junitxml/tests/.svn/entries new file mode 100644 index 0000000..ff7eda7 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/junitxml/tests/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/lib/junitxml/tests +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test_junitxml.py +file + + + +add + +__init__.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/lib/junitxml/tests/__init__.py b/pyinstaller/PyInstaller/lib/junitxml/tests/__init__.py new file mode 100644 index 0000000..2bde05d --- /dev/null +++ b/pyinstaller/PyInstaller/lib/junitxml/tests/__init__.py @@ -0,0 +1,16 @@ +# +# junitxml: extensions to Python unittest to get output junitxml +# Copyright (C) 2009 Robert Collins +# +# Copying permitted under the LGPL-3 licence, included with this library. + +import unittest as unittest2 + +from junitxml.tests import ( + test_junitxml, + ) + +def test_suite(): + return unittest.TestLoader().loadTestsFromNames([ + 'junitxml.tests.test_junitxml', + ]) diff --git a/pyinstaller/PyInstaller/lib/junitxml/tests/test_junitxml.py b/pyinstaller/PyInstaller/lib/junitxml/tests/test_junitxml.py new file mode 100644 index 0000000..738bc27 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/junitxml/tests/test_junitxml.py @@ -0,0 +1,327 @@ +# +# junitxml: extensions to Python unittest to get output junitxml +# Copyright (C) 2009 Robert Collins +# +# Copying permitted under the LGPL-3 licence, included with this library. + + +try: + from cStringIO import StringIO +except ImportError: + from io import StringIO +import datetime +import re +import sys +import unittest2 as unittest +import xml.dom.minidom + +import junitxml + +class TestImports(unittest.TestCase): + + def test_result(self): + from junitxml import JUnitXmlResult + + +class TestJUnitXmlResult__init__(unittest.TestCase): + + def test_with_stream(self): + result = junitxml.JUnitXmlResult(StringIO()) + + +class TestJUnitXmlResult(unittest.TestCase): + + def setUp(self): + self.output = StringIO() + self.result = junitxml.JUnitXmlResult(self.output) + + def get_output(self): + output = self.output.getvalue() + # Collapse detailed regions into specific strings we can match on + return re.sub(r'(?s).*?', + r'failure', re.sub( + r'(?s).*?', r'error', + re.sub(r'time="\d+\.\d+"', 'time="0.000"', output))) + + def run_test_or_simulate(self, test, method_name, manual_method, + *manual_args): + if getattr(test, method_name, None): + test.run(self.result) + else: + # older python - manually execute + self.result.startTest(test) + manual_method(test, *manual_args) + self.result.stopTest(test) + + def test_run_duration_handles_datestamping_in_the_past(self): + # When used via subunit2junitxml, startTestRun is called before + # any tz info in the test stream has been seen. + # So, we use the earliest reported timestamp as the start time, + # replacing _test_start if needed. + self.result.startTestRun() # the time is now. + # Lose an hour (peeks inside, a little naughty but not very). + self.result.time(self.result._run_start - datetime.timedelta(0, 3600)) + self.result.stopTestRun() + self.assertEqual(""" + +""", self.get_output()) + + def test_startTestRun_no_output(self): + # startTestRun doesn't output anything, because JUnit wants an up-front + # summary. + self.result.startTestRun() + self.assertEqual('', self.get_output()) + + def test_stopTestRun_outputs(self): + # When stopTestRun is called, everything is output. + self.result.startTestRun() + self.result.stopTestRun() + self.assertEqual(""" + +""", self.get_output()) + + def test_test_count(self): + class Passes(unittest.TestCase): + def test_me(self): + pass + self.result.startTestRun() + Passes("test_me").run(self.result) + Passes("test_me").run(self.result) + self.result.stopTestRun() + # When tests are run, the number of tests is counted. + output = self.get_output() + self.assertTrue('tests="2"' in output) + + def test_test_id_with_parameter(self): + class Passes(unittest.TestCase): + def id(self): + return unittest.TestCase.id(self) + '(version_1.6)' + def test_me(self): + pass + self.result.startTestRun() + Passes("test_me").run(self.result) + self.result.stopTestRun() + output = self.get_output() + self.assertTrue('Passes" name="test_me(version_1.6)"' in output) + + def test_erroring_test(self): + class Errors(unittest.TestCase): + def test_me(self): + 1/0 + self.result.startTestRun() + Errors("test_me").run(self.result) + self.result.stopTestRun() + self.assertEqual(""" + +error + + +""", self.get_output()) + + def test_failing_test(self): + class Fails(unittest.TestCase): + def test_me(self): + self.fail() + self.result.startTestRun() + Fails("test_me").run(self.result) + self.result.stopTestRun() + self.assertEqual(""" + +failure + + +""", self.get_output()) + + def test_successful_test(self): + class Passes(unittest.TestCase): + def test_me(self): + pass + self.result.startTestRun() + Passes("test_me").run(self.result) + self.result.stopTestRun() + self.assertEqual(""" + + +""", self.get_output()) + + def test_skip_test(self): + class Skips(unittest.TestCase): + def test_me(self): + self.skipTest("yo") + self.result.startTestRun() + test = Skips("test_me") + self.run_test_or_simulate(test, 'skipTest', self.result.addSkip, 'yo') + self.result.stopTestRun() + output = self.get_output() + expected = """ + +yo + + +""" + self.assertEqual(expected, output) + + def test_unexpected_success_test(self): + class Succeeds(unittest.TestCase): + def test_me(self): + pass + try: + test_me = unittest.expectedFailure(test_me) + except AttributeError: + pass # Older python - just let the test pass + self.result.startTestRun() + Succeeds("test_me").run(self.result) + self.result.stopTestRun() + output = self.get_output() + expected = """ + + + + +""" + expected_old = """ + + +""" + if output != expected_old: + self.assertEqual(expected, output) + + def test_expected_failure_test(self): + expected_failure_support = [True] + class ExpectedFail(unittest.TestCase): + def test_me(self): + self.fail("fail") + try: + test_me = unittest.expectedFailure(test_me) + except AttributeError: + # Older python - just let the test fail + expected_failure_support[0] = False + self.result.startTestRun() + ExpectedFail("test_me").run(self.result) + self.result.stopTestRun() + output = self.get_output() + expected = """ + + +""" + expected_old = """ + +failure + + +""" + if expected_failure_support[0]: + self.assertEqual(expected, output) + else: + self.assertEqual(expected_old, output) + + +class TestWellFormedXml(unittest.TestCase): + """XML created should always be well formed even with odd test cases""" + + def _run_and_parse_test(self, case): + output = StringIO() + result = junitxml.JUnitXmlResult(output) + result.startTestRun() + case.run(result) + result.stopTestRun() + return xml.dom.minidom.parseString(output.getvalue()) + + def test_failure_with_amp(self): + """Check the failure element content is escaped""" + class FailWithAmp(unittest.TestCase): + def runTest(self): + self.fail("& should be escaped as &") + doc = self._run_and_parse_test(FailWithAmp()) + self.assertTrue( + doc.getElementsByTagName("failure")[0].firstChild.nodeValue + .endswith("AssertionError: & should be escaped as &\n")) + + def test_quotes_in_test_case_id(self): + """Check that quotes in an attribute are escaped""" + class QuoteId(unittest.TestCase): + def id(self): + return unittest.TestCase.id(self) + '("quotes")' + def runTest(self): + pass + doc = self._run_and_parse_test(QuoteId()) + self.assertEqual('runTest("quotes")', + doc.getElementsByTagName("testcase")[0].getAttribute("name")) + + def test_skip_reason(self): + """Check the skip element content is escaped""" + class SkipWithLt(unittest.TestCase): + def runTest(self): + self.fail("version < 2.7") + try: + runTest = unittest.skip("2.7 <= version")(runTest) + except AttributeError: + self.has_skip = False + else: + self.has_skip = True + doc = self._run_and_parse_test(SkipWithLt()) + if self.has_skip: + self.assertEqual('2.7 <= version', + doc.getElementsByTagName("skip")[0].firstChild.nodeValue) + else: + self.assertTrue( + doc.getElementsByTagName("failure")[0].firstChild.nodeValue + .endswith("AssertionError: version < 2.7\n")) + + def test_error_with_control_characters(self): + """Check C0 control characters are stripped rather than output""" + class ErrorWithC0(unittest.TestCase): + def runTest(self): + raise ValueError("\x1F\x0E\x0C\x0B\x08\x01\x00lost control") + doc = self._run_and_parse_test(ErrorWithC0()) + self.assertTrue( + doc.getElementsByTagName("error")[0].firstChild.nodeValue + .endswith("ValueError: lost control\n")) + + def test_error_with_invalid_cdata(self): + """Check unicode outside the valid cdata range is stripped""" + if len("\uffff") == 1: + # Basic str type supports unicode + exception = ValueError("\ufffe\uffffEOF") + else: + class UTF8_Error(Exception): + def __unicode__(self): + return str(self).decode("UTF-8") + exception = UTF8_Error("\xef\xbf\xbe\xef\xbf\xbfEOF") + class ErrorWithBadUnicode(unittest.TestCase): + def runTest(self): + raise exception + doc = self._run_and_parse_test(ErrorWithBadUnicode()) + self.assertTrue( + doc.getElementsByTagName("error")[0].firstChild.nodeValue + .endswith("Error: EOF\n")) + + def test_error_with_surrogates(self): + """Check unicode surrogates are handled properly, paired or otherwise + + This is a pain due to suboptimal unicode support in Python and the + various changes in Python 3. On UCS-2 builds there is no easy way of + getting rid of unpaired surrogates while leaving valid pairs alone, so + this test doesn't require astral characters are kept there. + """ + if len("\uffff") == 1: + exception = ValueError("paired: \U000201a2" + " unpaired: "+chr(0xD800)+"-"+chr(0xDFFF)) + astral_char = "\U000201a2" + else: + class UTF8_Error(Exception): + def __unicode__(self): + return str(self).decode("UTF-8") + exception = UTF8_Error("paired: \xf0\xa0\x86\xa2" + " unpaired: \xed\xa0\x80-\xed\xbf\xbf") + astral_char = "\U000201a2".decode("unicode-escape") + class ErrorWithSurrogates(unittest.TestCase): + def runTest(self): + raise exception + doc = self._run_and_parse_test(ErrorWithSurrogates()) + traceback = doc.getElementsByTagName("error")[0].firstChild.nodeValue + if sys.maxunicode == 0xFFFF: + pass # would be nice to handle astral characters properly even so + else: + self.assertTrue(astral_char in traceback) + self.assertTrue(traceback.endswith(" unpaired: -\n")) diff --git a/pyinstaller/PyInstaller/lib/macholib/.svn/entries b/pyinstaller/PyInstaller/lib/macholib/.svn/entries new file mode 100644 index 0000000..919c482 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/.svn/entries @@ -0,0 +1,140 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/lib/macholib +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +dyld.py +file + + + +add + +framework.py +file + + + +add + +macho_standalone.py +file + + + +add + +__init__.py +file + + + +add + +MachOStandalone.py +file + + + +add + +dylib.py +file + + + +add + +MachOGraph.py +file + + + +add + +_cmdline.py +file + + + +add + +macho_dump.py +file + + + +add + +SymbolTable.py +file + + + +add + +util.py +file + + + +add + +itergraphreport.py +file + + + +add + +ptypes.py +file + + + +add + +mach_o.py +file + + + +add + +macho_find.py +file + + + +add + +MachO.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/lib/macholib/MachO.py b/pyinstaller/PyInstaller/lib/macholib/MachO.py new file mode 100644 index 0000000..acffc65 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/MachO.py @@ -0,0 +1,361 @@ +""" +Utilities for reading and writing Mach-O headers +""" +from __future__ import print_function + +import sys +import struct + +from macholib.mach_o import * +from macholib.dyld import dyld_find, framework_info +from macholib.util import fileview +try: + from macholib.compat import bytes +except ImportError: + pass + +try: + unicode +except NameError: + unicode = str + +__all__ = ['MachO'] + +_RELOCATABLE = set(( + # relocatable commands that should be used for dependency walking + LC_LOAD_DYLIB, + LC_LOAD_WEAK_DYLIB, + LC_PREBOUND_DYLIB, + LC_REEXPORT_DYLIB, +)) + +_RELOCATABLE_NAMES = { + LC_LOAD_DYLIB: 'load_dylib', + LC_LOAD_WEAK_DYLIB: 'load_weak_dylib', + LC_PREBOUND_DYLIB: 'prebound_dylib', + LC_REEXPORT_DYLIB: 'reexport_dylib', +} + +def _shouldRelocateCommand(cmd): + """ + Should this command id be investigated for relocation? + """ + return cmd in _RELOCATABLE + +class MachO(object): + """ + Provides reading/writing the Mach-O header of a specific existing file + """ + # filename - the original filename of this mach-o + # sizediff - the current deviation from the initial mach-o size + # header - the mach-o header + # commands - a list of (load_command, somecommand, data) + # data is either a str, or a list of segment structures + # total_size - the current mach-o header size (including header) + # low_offset - essentially, the maximum mach-o header size + # id_cmd - the index of my id command, or None + + + def __init__(self, filename): + + # supports the ObjectGraph protocol + self.graphident = filename + self.filename = filename + + # initialized by load + self.fat = None + self.headers = [] + with open(filename, 'rb') as fp: + self.load(fp) + + def __repr__(self): + return "" % (self.filename,) + + def load(self, fh): + assert fh.tell() == 0 + header = struct.unpack('>I', fh.read(4))[0] + fh.seek(0) + if header == FAT_MAGIC: + self.load_fat(fh) + else: + fh.seek(0, 2) + size = fh.tell() + fh.seek(0) + self.load_header(fh, 0, size) + + def load_fat(self, fh): + self.fat = fat_header.from_fileobj(fh) + archs = [fat_arch.from_fileobj(fh) for i in range(self.fat.nfat_arch)] + for arch in archs: + self.load_header(fh, arch.offset, arch.size) + + def rewriteLoadCommands(self, *args, **kw): + changed = False + for header in self.headers: + if header.rewriteLoadCommands(*args, **kw): + changed = True + return changed + + def load_header(self, fh, offset, size): + fh.seek(offset) + header = struct.unpack('>I', fh.read(4))[0] + fh.seek(offset) + if header == MH_MAGIC: + magic, hdr, endian = MH_MAGIC, mach_header, '>' + elif header == MH_CIGAM: + magic, hdr, endian = MH_MAGIC, mach_header, '<' + elif header == MH_MAGIC_64: + magic, hdr, endian = MH_MAGIC_64, mach_header_64, '>' + elif header == MH_CIGAM_64: + magic, hdr, endian = MH_MAGIC_64, mach_header_64, '<' + else: + raise ValueError("Unknown Mach-O header: 0x%08x in %r" % ( + header, fh)) + hdr = MachOHeader(self, fh, offset, size, magic, hdr, endian) + self.headers.append(hdr) + + def write(self, f): + for header in self.headers: + header.write(f) + +class MachOHeader(object): + """ + Provides reading/writing the Mach-O header of a specific existing file + """ + # filename - the original filename of this mach-o + # sizediff - the current deviation from the initial mach-o size + # header - the mach-o header + # commands - a list of (load_command, somecommand, data) + # data is either a str, or a list of segment structures + # total_size - the current mach-o header size (including header) + # low_offset - essentially, the maximum mach-o header size + # id_cmd - the index of my id command, or None + + + def __init__(self, parent, fh, offset, size, magic, hdr, endian): + self.MH_MAGIC = magic + self.mach_header = hdr + + # These are all initialized by self.load() + self.parent = parent + self.offset = offset + self.size = size + + self.endian = endian + self.header = None + self.commands = None + self.id_cmd = None + self.sizediff = None + self.total_size = None + self.low_offset = None + self.filetype = None + self.headers = [] + + self.load(fh) + + def __repr__(self): + return "<%s filename=%r offset=%d size=%d endian=%r>" % ( + type(self).__name__, self.parent.filename, self.offset, self.size, + self.endian) + + def load(self, fh): + fh = fileview(fh, self.offset, self.size) + fh.seek(0) + + self.sizediff = 0 + kw = {'_endian_': self.endian} + header = self.mach_header.from_fileobj(fh, **kw) + self.header = header + if header.magic != self.MH_MAGIC: + raise ValueError("header has magic %08x, expecting %08x" % ( + header.magic, self.MH_MAGIC)) + + cmd = self.commands = [] + + self.filetype = MH_FILETYPE_SHORTNAMES[header.filetype] + + read_bytes = 0 + low_offset = sys.maxsize + for i in range(header.ncmds): + # read the load command + cmd_load = load_command.from_fileobj(fh, **kw) + + # read the specific command + klass = LC_REGISTRY.get(cmd_load.cmd, None) + if klass is None: + raise ValueError("Unknown load command: %d" % (cmd_load.cmd,)) + cmd_cmd = klass.from_fileobj(fh, **kw) + + if cmd_load.cmd == LC_ID_DYLIB: + # remember where this command was + if self.id_cmd is not None: + raise ValueError("This dylib already has an id") + self.id_cmd = i + + if cmd_load.cmd in (LC_SEGMENT, LC_SEGMENT_64): + # for segment commands, read the list of segments + segs = [] + # assert that the size makes sense + if cmd_load.cmd == LC_SEGMENT: + section_cls = section + else: # LC_SEGMENT_64 + section_cls = section_64 + + expected_size = ( + sizeof(klass) + sizeof(load_command) + + (sizeof(section_cls) * cmd_cmd.nsects) + ) + if cmd_load.cmdsize != expected_size: + raise ValueError("Segment size mismatch") + # this is a zero block or something + # so the beginning is wherever the fileoff of this command is + if cmd_cmd.nsects == 0: + if cmd_cmd.filesize != 0: + low_offset = min(low_offset, cmd_cmd.fileoff) + else: + # this one has multiple segments + for j in range(cmd_cmd.nsects): + # read the segment + seg = section_cls.from_fileobj(fh, **kw) + # if the segment has a size and is not zero filled + # then its beginning is the offset of this segment + not_zerofill = ((seg.flags & S_ZEROFILL) != S_ZEROFILL) + if seg.offset > 0 and seg.size > 0 and not_zerofill: + low_offset = min(low_offset, seg.offset) + segs.append(seg) + # data is a list of segments + cmd_data = segs + else: + # data is a raw str + data_size = ( + cmd_load.cmdsize - sizeof(klass) - sizeof(load_command) + ) + cmd_data = fh.read(data_size) + cmd.append((cmd_load, cmd_cmd, cmd_data)) + read_bytes += cmd_load.cmdsize + + # make sure the header made sense + if read_bytes != header.sizeofcmds: + raise ValueError("Read %d bytes, header reports %d bytes" % ( + read_bytes, header.sizeofcmds)) + self.total_size = sizeof(self.mach_header) + read_bytes + self.low_offset = low_offset + + # this header overwrites a segment, what the heck? + if self.total_size > low_offset: + raise ValueError("total_size > low_offset (%d > %d)" % ( + self.total_size, low_offset)) + + def walkRelocatables(self, shouldRelocateCommand=_shouldRelocateCommand): + """ + for all relocatable commands + yield (command_index, command_name, filename) + """ + for (idx, (lc, cmd, data)) in enumerate(self.commands): + if shouldRelocateCommand(lc.cmd): + name = _RELOCATABLE_NAMES[lc.cmd] + ofs = cmd.name - sizeof(lc.__class__) - sizeof(cmd.__class__) + yield idx, name, data[ofs:data.find(b'\x00', ofs)].decode( + sys.getfilesystemencoding()) + + def rewriteInstallNameCommand(self, loadcmd): + """Rewrite the load command of this dylib""" + if self.id_cmd is not None: + self.rewriteDataForCommand(self.id_cmd, loadcmd) + return True + return False + + def changedHeaderSizeBy(self, bytes): + self.sizediff += bytes + if (self.total_size + self.sizediff) > self.low_offset: + print("WARNING: Mach-O header may be too large to relocate") + + def rewriteLoadCommands(self, changefunc): + """ + Rewrite the load commands based upon a change dictionary + """ + data = changefunc(self.parent.filename) + changed = False + if data is not None: + if self.rewriteInstallNameCommand( + data.encode(sys.getfilesystemencoding())): + changed = True + for idx, name, filename in self.walkRelocatables(): + data = changefunc(filename) + if data is not None: + if self.rewriteDataForCommand(idx, data.encode( + sys.getfilesystemencoding())): + changed = True + return changed + + def rewriteDataForCommand(self, idx, data): + lc, cmd, old_data = self.commands[idx] + hdrsize = sizeof(lc.__class__) + sizeof(cmd.__class__) + align = struct.calcsize('L') + data = data + (b'\x00' * (align - (len(data) % align))) + newsize = hdrsize + len(data) + self.commands[idx] = (lc, cmd, data) + self.changedHeaderSizeBy(newsize - lc.cmdsize) + lc.cmdsize, cmd.name = newsize, hdrsize + return True + + def synchronize_size(self): + if (self.total_size + self.sizediff) > self.low_offset: + raise ValueError("New Mach-O header is too large to relocate") + self.header.sizeofcmds += self.sizediff + self.total_size = sizeof(self.mach_header) + self.header.sizeofcmds + self.sizediff = 0 + + def write(self, fileobj): + fileobj = fileview(fileobj, self.offset, self.size) + fileobj.seek(0) + + # serialize all the mach-o commands + self.synchronize_size() + + self.header.to_fileobj(fileobj) + for lc, cmd, data in self.commands: + lc.to_fileobj(fileobj) + cmd.to_fileobj(fileobj) + + if isinstance(data, unicode): + fileobj.write(data.encode(sys.getfilesystemencoding())) + + elif isinstance(data, (bytes, str)): + fileobj.write(data) + else: + # segments.. + for obj in data: + obj.to_fileobj(fileobj) + + # zero out the unused space, doubt this is strictly necessary + # and is generally probably already the case + fileobj.write(b'\x00' * (self.low_offset - fileobj.tell())) + + def getSymbolTableCommand(self): + for lc, cmd, data in self.commands: + if lc.cmd == LC_SYMTAB: + return cmd + return None + + def getDynamicSymbolTableCommand(self): + for lc, cmd, data in self.commands: + if lc.cmd == LC_DYSYMTAB: + return cmd + return None + +def main(fn): + m = MachO(fn) + seen = set() + for header in m.headers: + for idx, name, other in header.walkRelocatables(): + if other not in seen: + seen.add(other) + print('\t' + name + ": " + other) + +if __name__ == '__main__': + import sys + files = sys.argv[1:] or ['/bin/ls'] + for fn in files: + print(fn) + main(fn) diff --git a/pyinstaller/PyInstaller/lib/macholib/MachOGraph.py b/pyinstaller/PyInstaller/lib/macholib/MachOGraph.py new file mode 100644 index 0000000..379c887 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/MachOGraph.py @@ -0,0 +1,119 @@ +""" +Utilities for reading and writing Mach-O headers +""" + +import os +import sys + +from altgraph.Graph import Graph +from altgraph.ObjectGraph import ObjectGraph + +from macholib.mach_o import * +from macholib.dyld import dyld_find +from macholib.MachO import MachO +from macholib.itergraphreport import itergraphreport + +__all__ = ['MachOGraph'] + +try: + unicode +except NameError: + unicode = str + +class MissingMachO(object): + def __init__(self, filename): + self.graphident = filename + self.headers = () + + def __repr__(self): + return '<%s graphident=%r>' % (type(self).__name__, self.graphident) + +class MachOGraph(ObjectGraph): + """ + Graph data structure of Mach-O dependencies + """ + def __init__(self, debug=0, graph=None, env=None, executable_path=None): + super(MachOGraph, self).__init__(debug=debug, graph=graph) + self.env = env + self.trans_table = {} + self.executable_path = executable_path + + def locate(self, filename): + assert isinstance(filename, (str, unicode)) + fn = self.trans_table.get(filename) + if fn is None: + try: + fn = dyld_find(filename, env=self.env, + executable_path=self.executable_path) + self.trans_table[filename] = fn + except ValueError: + return None + return fn + + def findNode(self, name): + assert isinstance(name, (str, unicode)) + data = super(MachOGraph, self).findNode(name) + if data is not None: + return data + newname = self.locate(name) + if newname is not None and newname != name: + return self.findNode(newname) + return None + + def run_file(self, pathname, caller=None): + assert isinstance(pathname, (str, unicode)) + self.msgin(2, "run_file", pathname) + m = self.findNode(pathname) + if m is None: + if not os.path.exists(pathname): + raise ValueError('%r does not exist' % (pathname,)) + m = self.createNode(MachO, pathname) + self.createReference(caller, m, edge_data='run_file') + self.scan_node(m) + self.msgout(2, '') + return m + + def load_file(self, name, caller=None): + assert isinstance(name, (str, unicode)) + self.msgin(2, "load_file", name) + m = self.findNode(name) + if m is None: + newname = self.locate(name) + if newname is not None and newname != name: + return self.load_file(newname, caller=caller) + if os.path.exists(name): + m = self.createNode(MachO, name) + self.scan_node(m) + else: + m = self.createNode(MissingMachO, name) + self.msgout(2, '') + return m + + def scan_node(self, node): + self.msgin(2, 'scan_node', node) + for header in node.headers: + for idx, name, filename in header.walkRelocatables(): + assert isinstance(name, (str, unicode)) + assert isinstance(filename, (str, unicode)) + m = self.load_file(filename, caller=node) + self.createReference(node, m, edge_data=name) + self.msgout(2, '', node) + + def itergraphreport(self, name='G'): + nodes = map(self.graph.describe_node, self.graph.iterdfs(self)) + describe_edge = self.graph.describe_edge + return itergraphreport(nodes, describe_edge, name=name) + + def graphreport(self, fileobj=None): + if fileobj is None: + fileobj = sys.stdout + fileobj.writelines(self.itergraphreport()) + +def main(args): + g = MachOGraph() + for arg in args: + g.run_file(arg) + g.graphreport() + +if __name__ == '__main__': + main(sys.argv[1:] or ['/bin/ls']) diff --git a/pyinstaller/PyInstaller/lib/macholib/MachOStandalone.py b/pyinstaller/PyInstaller/lib/macholib/MachOStandalone.py new file mode 100644 index 0000000..e110259 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/MachOStandalone.py @@ -0,0 +1,140 @@ +import os + +from macholib.MachOGraph import MachOGraph, MissingMachO +from macholib.util import iter_platform_files, in_system_path, mergecopy, \ + mergetree, flipwritable, has_filename_filter +from macholib.dyld import framework_info +from collections import deque + +class ExcludedMachO(MissingMachO): + pass + +class FilteredMachOGraph(MachOGraph): + def __init__(self, delegate, *args, **kwargs): + super(FilteredMachOGraph, self).__init__(*args, **kwargs) + self.delegate = delegate + + def createNode(self, cls, name): + cls = self.delegate.getClass(name, cls) + res = super(FilteredMachOGraph, self).createNode(cls, name) + return res + + def locate(self, filename): + newname = super(FilteredMachOGraph, self).locate(filename) + if newname is None: + return None + return self.delegate.locate(newname) + +class MachOStandalone(object): + def __init__(self, base, dest=None, graph=None, env=None, + executable_path=None): + self.base = os.path.join(os.path.abspath(base), '') + if dest is None: + dest = os.path.join(self.base, 'Contents', 'Frameworks') + self.dest = dest + self.mm = FilteredMachOGraph(self, graph=graph, env=env, + executable_path=executable_path) + self.changemap = {} + self.excludes = [] + self.pending = deque() + + def getClass(self, name, cls): + if in_system_path(name): + return ExcludedMachO + for base in self.excludes: + if name.startswith(base): + return ExcludedMachO + return cls + + def locate(self, filename): + if in_system_path(filename): + return filename + if filename.startswith(self.base): + return filename + for base in self.excludes: + if filename.startswith(base): + return filename + if filename in self.changemap: + return self.changemap[filename] + info = framework_info(filename) + if info is None: + res = self.copy_dylib(filename) + self.changemap[filename] = res + return res + else: + res = self.copy_framework(info) + self.changemap[filename] = res + return res + + def copy_dylib(self, filename): + dest = os.path.join(self.dest, os.path.basename(filename)) + if not os.path.exists(dest): + self.mergecopy(filename, dest) + return dest + + def mergecopy(self, src, dest): + return mergecopy(src, dest) + + def mergetree(self, src, dest): + return mergetree(src, dest) + + def copy_framework(self, info): + dest = os.path.join(self.dest, info['shortname'] + '.framework') + destfn = os.path.join(self.dest, info['name']) + src = os.path.join(info['location'], info['shortname'] + '.framework') + if not os.path.exists(dest): + self.mergetree(src, dest) + self.pending.append((destfn, iter_platform_files(dest))) + return destfn + + def run(self, platfiles=None, contents=None): + mm = self.mm + if contents is None: + contents = '@executable_path/..' + if platfiles is None: + platfiles = iter_platform_files(self.base) + + for fn in platfiles: + mm.run_file(fn) + + while self.pending: + fmwk, files = self.pending.popleft() + ref = mm.findNode(fmwk) + for fn in files: + mm.run_file(fn, caller=ref) + + changemap = {} + skipcontents = os.path.join(os.path.dirname(self.dest), '') + machfiles = [] + + for node in mm.flatten(has_filename_filter): + machfiles.append(node) + dest = os.path.join(contents, node.filename[len(skipcontents):]) + changemap[node.filename] = dest + + def changefunc(path): + res = mm.locate(path) + return changemap.get(res) + + for node in machfiles: + fn = mm.locate(node.filename) + if fn is None: + continue + rewroteAny = False + for header in node.headers: + if node.rewriteLoadCommands(changefunc): + rewroteAny = True + if rewroteAny: + old_mode = flipwritable(fn) + try: + with open(fn, 'rb+') as f: + for header in node.headers: + f.seek(0) + node.write(f) + f.seek(0, 2) + f.flush() + finally: + flipwritable(fn, old_mode) + + allfiles = [mm.locate(node.filename) for node in machfiles] + return set(filter(None, allfiles)) diff --git a/pyinstaller/PyInstaller/lib/macholib/SymbolTable.py b/pyinstaller/PyInstaller/lib/macholib/SymbolTable.py new file mode 100644 index 0000000..62fb172 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/SymbolTable.py @@ -0,0 +1,95 @@ +""" +Class to read the symbol table from a Mach-O header +""" + +from macholib.mach_o import * + +__all__ = ['SymbolTable'] + +# XXX: Does not support 64-bit, probably broken anyway + +class SymbolTable(object): + def __init__(self, macho, openfile=None): + if openfile is None: + openfile = open + self.macho = macho.headers[0] + self.symtab = macho.getSymbolTableCommand() + self.dysymtab = macho.getDynamicSymbolTableCommand() + fh = openfile(self.macho.filename, 'rb') + try: + if self.symtab is not None: + self.readSymbolTable(fh) + if self.dysymtab is not None: + self.readDynamicSymbolTable(fh) + finally: + fh.close() + + def readSymbolTable(self, fh): + cmd = self.symtab + fh.seek(cmd.stroff) + strtab = fh.read(cmd.strsize) + fh.seek(cmd.symoff) + nlists = [] + for i in xrange(cmd.nsyms): + cmd = nlist.from_fileobj(fh) + if cmd.n_un == 0: + nlists.append((cmd, '')) + else: + nlists.append((cmd, strtab[cmd.n_un:strtab.find(b'\x00', cmd.n_un)])) + self.nlists = nlists + + def readDynamicSymbolTable(self, fh): + cmd = self.dysymtab + nlists = self.nlists + self.localsyms = nlists[cmd.ilocalsym:cmd.ilocalsym+cmd.nlocalsym] + self.extdefsyms = nlists[cmd.iextdefsym:cmd.iextdefsym+cmd.nextdefsym] + self.undefsyms = nlists[cmd.iundefsym:cmd.iundefsym+cmd.nundefsym] + #if cmd.tocoff == 0: + # self.toc = None + #else: + # self.toc = self.readtoc(fh, cmd.tocoff, cmd.ntoc) + #if cmd.modtaboff == 0: + # self.modtab = None + #else: + # self.modtab = self.readmodtab(fh, cmd.modtaboff, cmd.nmodtab) + if cmd.extrefsymoff == 0: + self.extrefsym = None + else: + self.extrefsym = self.readsym(fh, cmd.extrefsymoff, cmd.nextrefsyms) + #if cmd.indirectsymoff == 0: + # self.indirectsym = None + #else: + # self.indirectsym = self.readsym(fh, cmd.indirectsymoff, cmd.nindirectsyms) + #if cmd.extreloff == 0: + # self.extrel = None + #else: + # self.extrel = self.readrel(fh, cmd.extreloff, cmd.nextrel) + #if cmd.locreloff == 0: + # self.locrel = None + #else: + # self.locrel = self.readrel(fh, cmd.locreloff, cmd.nlocrel) + + def readtoc(self, fh, off, n): + #print 'toc', off, n + fh.seek(off) + return [dylib_table_of_contents.from_fileobj(fh) for i in xrange(n)] + + def readmodtab(self, fh, off, n): + #print 'modtab', off, n + fh.seek(off) + return [dylib_module.from_fileobj(fh) for i in xrange(n)] + + def readsym(self, fh, off, n): + #print 'sym', off, n + fh.seek(off) + refs = [] + for i in xrange(n): + ref = dylib_reference.from_fileobj(fh) + isym, flags = divmod(ref.isym_flags, 256) + refs.append((self.nlists[isym], flags)) + return refs + + def readrel(self, fh, off, n): + #print 'rel', off, n + fh.seek(off) + return [relocation_info.from_fileobj(fh) for i in xrange(n)] diff --git a/pyinstaller/PyInstaller/lib/macholib/__init__.py b/pyinstaller/PyInstaller/lib/macholib/__init__.py new file mode 100644 index 0000000..138751f --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/__init__.py @@ -0,0 +1,9 @@ +""" +Enough Mach-O to make your head spin. + +See the relevant header files in /usr/include/mach-o + +And also Apple's documentation. +""" +__version__ = '1.4.3' + diff --git a/pyinstaller/PyInstaller/lib/macholib/_cmdline.py b/pyinstaller/PyInstaller/lib/macholib/_cmdline.py new file mode 100644 index 0000000..eb44824 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/_cmdline.py @@ -0,0 +1,44 @@ +""" +Internal helpers for basic commandline tools +""" +from __future__ import print_function, absolute_import +import os +import sys + +from macholib.util import is_platform_file + +def check_file(fp, path, callback): + if not os.path.exists(path): + print('%s: %s: No such file or directory' % (sys.argv[0], path), file=sys.stderr) + return 1 + + try: + is_plat = is_platform_file(path) + + except IOError as msg: + print('%s: %s: %s' % (sys.argv[0], path, msg), file=sys.stderr) + return 1 + + else: + if is_plat: + callback(fp, path) + return 0 + +def main(callback): + args = sys.argv[1:] + name = os.path.basename(sys.argv[0]) + err = 0 + + if not args: + print("Usage: %s filename..."%(name,), file=sys.stderr) + return 1 + + for base in args: + if os.path.isdir(base): + for root, dirs, files in os.walk(base): + for fn in files: + err |= check_file(sys.stdout, os.path.join(root, fn), callback) + else: + err |= check_file(sys.stdout, base, callback) + + return err diff --git a/pyinstaller/PyInstaller/lib/macholib/dyld.py b/pyinstaller/PyInstaller/lib/macholib/dyld.py new file mode 100644 index 0000000..6badc92 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/dyld.py @@ -0,0 +1,168 @@ +""" +dyld emulation +""" + +from itertools import chain + +import os, sys + +from macholib.framework import framework_info +from macholib.dylib import dylib_info + +__all__ = [ + 'dyld_find', 'framework_find', + 'framework_info', 'dylib_info', +] + +# These are the defaults as per man dyld(1) +# +_DEFAULT_FRAMEWORK_FALLBACK = [ + os.path.expanduser("~/Library/Frameworks"), + "/Library/Frameworks", + "/Network/Library/Frameworks", + "/System/Library/Frameworks", +] + +_DEFAULT_LIBRARY_FALLBACK = [ + os.path.expanduser("~/lib"), + "/usr/local/lib", + "/lib", + "/usr/lib", +] + +# XXX: Is this function still needed? +if sys.version_info[0] == 2: + def _ensure_utf8(s): + """Not all of PyObjC and Python understand unicode paths very well yet""" + if isinstance(s, unicode): + return s.encode('utf8') + return s +else: + def _ensure_utf8(s): + if s is not None and not isinstance(s, str): + raise ValueError(s) + return s + + +def _dyld_env(env, var): + if env is None: + env = os.environ + rval = env.get(var) + if rval is None or rval == '': + return [] + return rval.split(':') + +def dyld_image_suffix(env=None): + if env is None: + env = os.environ + return env.get('DYLD_IMAGE_SUFFIX') + +def dyld_framework_path(env=None): + return _dyld_env(env, 'DYLD_FRAMEWORK_PATH') + +def dyld_library_path(env=None): + return _dyld_env(env, 'DYLD_LIBRARY_PATH') + +def dyld_fallback_framework_path(env=None): + return _dyld_env(env, 'DYLD_FALLBACK_FRAMEWORK_PATH') + +def dyld_fallback_library_path(env=None): + return _dyld_env(env, 'DYLD_FALLBACK_LIBRARY_PATH') + +def dyld_image_suffix_search(iterator, env=None): + """For a potential path iterator, add DYLD_IMAGE_SUFFIX semantics""" + suffix = dyld_image_suffix(env) + if suffix is None: + return iterator + def _inject(iterator=iterator, suffix=suffix): + for path in iterator: + if path.endswith('.dylib'): + yield path[:-len('.dylib')] + suffix + '.dylib' + else: + yield path + suffix + yield path + return _inject() + +def dyld_override_search(name, env=None): + # If DYLD_FRAMEWORK_PATH is set and this dylib_name is a + # framework name, use the first file that exists in the framework + # path if any. If there is none go on to search the DYLD_LIBRARY_PATH + # if any. + + framework = framework_info(name) + + if framework is not None: + for path in dyld_framework_path(env): + yield os.path.join(path, framework['name']) + + # If DYLD_LIBRARY_PATH is set then use the first file that exists + # in the path. If none use the original name. + for path in dyld_library_path(env): + yield os.path.join(path, os.path.basename(name)) + +def dyld_executable_path_search(name, executable_path=None): + # If we haven't done any searching and found a library and the + # dylib_name starts with "@executable_path/" then construct the + # library name. + if name.startswith('@executable_path/') and executable_path is not None: + yield os.path.join(executable_path, name[len('@executable_path/'):]) + +def dyld_default_search(name, env=None): + yield name + + framework = framework_info(name) + + if framework is not None: + fallback_framework_path = dyld_fallback_framework_path(env) + + if fallback_framework_path: + for path in fallback_framework_path: + yield os.path.join(path, framework['name']) + + else: + for path in _DEFAULT_FRAMEWORK_FALLBACK: + yield os.path.join(path, framework['name']) + + fallback_library_path = dyld_fallback_library_path(env) + if fallback_library_path: + for path in fallback_library_path: + yield os.path.join(path, os.path.basename(name)) + + else: + for path in _DEFAULT_LIBRARY_FALLBACK: + yield os.path.join(path, os.path.basename(name)) + +def dyld_find(name, executable_path=None, env=None): + """ + Find a library or framework using dyld semantics + """ + name = _ensure_utf8(name) + executable_path = _ensure_utf8(executable_path) + for path in dyld_image_suffix_search(chain( + dyld_override_search(name, env), + dyld_executable_path_search(name, executable_path), + dyld_default_search(name, env), + ), env): + if os.path.isfile(path): + return path + raise ValueError("dylib %s could not be found" % (name,)) + +def framework_find(fn, executable_path=None, env=None): + """ + Find a framework using dyld semantics in a very loose manner. + + Will take input such as: + Python + Python.framework + Python.framework/Versions/Current + """ + try: + return dyld_find(fn, executable_path=executable_path, env=env) + except ValueError: + pass + fmwk_index = fn.rfind('.framework') + if fmwk_index == -1: + fmwk_index = len(fn) + fn += '.framework' + fn = os.path.join(fn, os.path.basename(fn[:fmwk_index])) + return dyld_find(fn, executable_path=executable_path, env=env) diff --git a/pyinstaller/PyInstaller/lib/macholib/dylib.py b/pyinstaller/PyInstaller/lib/macholib/dylib.py new file mode 100644 index 0000000..cea6a95 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/dylib.py @@ -0,0 +1,42 @@ +""" +Generic dylib path manipulation +""" + +import re + +__all__ = ['dylib_info'] + +_DYLIB_RE = re.compile(r"""(?x) +(?P^.*)(?:^|/) +(?P + (?P\w+?) + (?:\.(?P[^._]+))? + (?:_(?P[^._]+))? + \.dylib$ +) +""") + +def dylib_info(filename): + """ + A dylib name can take one of the following four forms: + Location/Name.SomeVersion_Suffix.dylib + Location/Name.SomeVersion.dylib + Location/Name_Suffix.dylib + Location/Name.dylib + + returns None if not found or a mapping equivalent to: + dict( + location='Location', + name='Name.SomeVersion_Suffix.dylib', + shortname='Name', + version='SomeVersion', + suffix='Suffix', + ) + + Note that SomeVersion and Suffix are optional and may be None + if not present. + """ + is_dylib = _DYLIB_RE.match(filename) + if not is_dylib: + return None + return is_dylib.groupdict() diff --git a/pyinstaller/PyInstaller/lib/macholib/framework.py b/pyinstaller/PyInstaller/lib/macholib/framework.py new file mode 100644 index 0000000..da09957 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/framework.py @@ -0,0 +1,42 @@ +""" +Generic framework path manipulation +""" + +import re + +__all__ = ['framework_info'] + +_STRICT_FRAMEWORK_RE = re.compile(r"""(?x) +(?P^.*)(?:^|/) +(?P + (?P[-_A-Za-z0-9]+).framework/ + (?:Versions/(?P[^/]+)/)? + (?P=shortname) + (?:_(?P[^_]+))? +)$ +""") + +def framework_info(filename): + """ + A framework name can take one of the following four forms: + Location/Name.framework/Versions/SomeVersion/Name_Suffix + Location/Name.framework/Versions/SomeVersion/Name + Location/Name.framework/Name_Suffix + Location/Name.framework/Name + + returns None if not found, or a mapping equivalent to: + dict( + location='Location', + name='Name.framework/Versions/SomeVersion/Name_Suffix', + shortname='Name', + version='SomeVersion', + suffix='Suffix', + ) + + Note that SomeVersion and Suffix are optional and may be None + if not present + """ + is_framework = _STRICT_FRAMEWORK_RE.match(filename) + if not is_framework: + return None + return is_framework.groupdict() diff --git a/pyinstaller/PyInstaller/lib/macholib/itergraphreport.py b/pyinstaller/PyInstaller/lib/macholib/itergraphreport.py new file mode 100644 index 0000000..453c96e --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/itergraphreport.py @@ -0,0 +1,73 @@ +""" +Utilities for creating dot output from a MachOGraph + +XXX: need to rewrite this based on altgraph.Dot +""" + +from collections import deque + +try: + from itertools import imap +except ImportError: + imap = map + +__all__ = ['itergraphreport'] + +def itergraphreport(nodes, describe_edge, name='G'): + edges = deque() + nodetoident = {} + mainedges = set() + + def nodevisitor(node, data, outgoing, incoming): + return {'label': str(node)} + + def edgevisitor(edge, data, head, tail): + return {} + + yield 'digraph %s {\n' % (name,) + attr = dict(rankdir='LR', concentrate='true') + cpatt = '%s="%s"' + for item in attr.iteritems(): + yield '\t%s;\n' % (cpatt % item,) + + # find all packages (subgraphs) + for (node, data, outgoing, incoming) in nodes: + nodetoident[node] = getattr(data, 'identifier', node) + + # create sets for subgraph, write out descriptions + for (node, data, outgoing, incoming) in nodes: + # update edges + for edge in imap(describe_edge, outgoing): + edges.append(edge) + + # describe node + yield '\t"%s" [%s];\n' % ( + node, + ','.join([ + (cpatt % item) for item in + nodevisitor(node, data, outgoing, incoming).iteritems() + ]), + ) + + graph = [] + + while edges: + edge, data, head, tail = edges.popleft() + if data in ('run_file', 'load_dylib'): + graph.append((edge, data, head, tail)) + + def do_graph(edges, tabs): + edgestr = tabs + '"%s" -> "%s" [%s];\n' + # describe edge + for (edge, data, head, tail) in edges: + attribs = edgevisitor(edge, data, head, tail) + yield edgestr % ( + head, + tail, + ','.join([(cpatt % item) for item in attribs.iteritems()]), + ) + + for s in do_graph(graph, '\t'): + yield s + + yield '}\n' diff --git a/pyinstaller/PyInstaller/lib/macholib/mach_o.py b/pyinstaller/PyInstaller/lib/macholib/mach_o.py new file mode 100644 index 0000000..523b659 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/mach_o.py @@ -0,0 +1,666 @@ +""" +Other than changing the load commands in such a way that they do not +contain the load command itself, this is largely a by-hand conversion +of the C headers. Hopefully everything in here should be at least as +obvious as the C headers, and you should be using the C headers as a real +reference because the documentation didn't come along for the ride. + +Doing much of anything with the symbol tables or segments is really +not covered at this point. + +See /usr/include/mach-o and friends. +""" +import time + +from macholib.ptypes import * + + +_CPU_ARCH_ABI64 = 0x01000000 + +CPU_TYPE_NAMES = { + -1: 'ANY', + 1: 'VAX', + 6: 'MC680x0', + 7: 'i386', + _CPU_ARCH_ABI64 | 7: 'x86_64', + 8: 'MIPS', + 10: 'MC98000', + 11: 'HPPA', + 12: 'ARM', + 13: 'MC88000', + 14: 'SPARC', + 15: 'i860', + 16: 'Alpha', + 18: 'PowerPC', + _CPU_ARCH_ABI64 | 18: 'PowerPC64', +} + +_MH_EXECUTE_SYM = "__mh_execute_header" +MH_EXECUTE_SYM = "_mh_execute_header" +_MH_BUNDLE_SYM = "__mh_bundle_header" +MH_BUNDLE_SYM = "_mh_bundle_header" +_MH_DYLIB_SYM = "__mh_dylib_header" +MH_DYLIB_SYM = "_mh_dylib_header" +_MH_DYLINKER_SYM = "__mh_dylinker_header" +MH_DYLINKER_SYM = "_mh_dylinker_header" + +( + MH_OBJECT, MH_EXECUTE, MH_FVMLIB, MH_CORE, MH_PRELOAD, MH_DYLIB, + MH_DYLINKER, MH_BUNDLE, MH_DYLIB_STUB, MH_DSYM +) = range(0x1, 0xb) + +( + MH_NOUNDEFS, MH_INCRLINK, MH_DYLDLINK, MH_BINDATLOAD, MH_PREBOUND, + MH_SPLIT_SEGS, MH_LAZY_INIT, MH_TWOLEVEL, MH_FORCE_FLAT, MH_NOMULTIDEFS, + MH_NOFIXPREBINDING +) = map((1).__lshift__, range(11)) + +MH_MAGIC = 0xfeedface +MH_CIGAM = 0xcefaedfe +MH_MAGIC_64 = 0xfeedfacf +MH_CIGAM_64 = 0xcffaedfe + +integer_t = p_int32 +cpu_type_t = integer_t +cpu_subtype_t = integer_t + +MH_FILETYPE_NAMES = { + MH_OBJECT: 'relocatable object', + MH_EXECUTE: 'demand paged executable', + MH_FVMLIB: 'fixed vm shared library', + MH_CORE: 'core', + MH_PRELOAD: 'preloaded executable', + MH_DYLIB: 'dynamically bound shared library', + MH_DYLINKER: 'dynamic link editor', + MH_BUNDLE: 'dynamically bound bundle', + MH_DYLIB_STUB: 'shared library stub for static linking', + MH_DSYM: 'symbol information', +} + +MH_FILETYPE_SHORTNAMES = { + MH_OBJECT: 'object', + MH_EXECUTE: 'execute', + MH_FVMLIB: 'fvmlib', + MH_CORE: 'core', + MH_PRELOAD: 'preload', + MH_DYLIB: 'dylib', + MH_DYLINKER: 'dylinker', + MH_BUNDLE: 'bundle', + MH_DYLIB_STUB: 'dylib_stub', + MH_DSYM: 'dsym', +} + +MH_FLAGS_NAMES = { + MH_NOUNDEFS: 'no undefined references', + MH_INCRLINK: 'output of an incremental link', + MH_DYLDLINK: 'input for the dynamic linker', + MH_BINDATLOAD: 'undefined references bound dynamically when loaded', + MH_PREBOUND: 'dynamic undefined references prebound', + MH_SPLIT_SEGS: 'split read-only and read-write segments', + MH_LAZY_INIT: '(obsolete)', + MH_TWOLEVEL: 'using two-level name space bindings', + MH_FORCE_FLAT: 'forcing all imagges to use flat name space bindings', + MH_NOMULTIDEFS: 'umbrella guarantees no multiple definitions', + MH_NOFIXPREBINDING: 'do not notify prebinding agent about this executable', +} + +class mach_version_helper(Structure): + _fields_ = ( + ('major', p_ushort), + ('minor', p_uint8), + ('rev', p_uint8), + ) + def __str__(self): + return '%s.%s.%s' % (self.major, self.minor, self.rev) + +class mach_timestamp_helper(p_uint32): + def __str__(self): + return time.ctime(self) + +def read_struct(f, s, **kw): + return s.from_fileobj(f, **kw) + +class mach_header(Structure): + _fields_ = ( + ('magic', p_uint32), + ('cputype', cpu_type_t), + ('cpusubtype', cpu_subtype_t), + ('filetype', p_uint32), + ('ncmds', p_uint32), + ('sizeofcmds', p_uint32), + ('flags', p_uint32), + ) + def _describe(self): + bit = 1 + flags = self.flags + dflags = [] + while flags and bit < (1<<32): + if flags & bit: + dflags.append(MH_FLAGS_NAMES.get(bit, str(bit))) + flags = flags ^ bit + bit <<= 1 + return ( + ('magic', '0x%08X' % self.magic), + ('cputype', CPU_TYPE_NAMES.get(self.cputype, self.cputype)), + ('cpusubtype', self.cpusubtype), + ('filetype', MH_FILETYPE_NAMES.get(self.filetype, self.filetype)), + ('ncmds', self.ncmds), + ('sizeofcmds', self.sizeofcmds), + ('flags', dflags), + ) + +class mach_header_64(mach_header): + _fields_ = mach_header._fields_ + (('reserved', p_uint32),) + +class load_command(Structure): + _fields_ = ( + ('cmd', p_uint32), + ('cmdsize', p_uint32), + ) + +LC_REQ_DYLD = 0x80000000 + +( + LC_SEGMENT, LC_SYMTAB, LC_SYMSEG, LC_THREAD, LC_UNIXTHREAD, LC_LOADFVMLIB, + LC_IDFVMLIB, LC_IDENT, LC_FVMFILE, LC_PREPAGE, LC_DYSYMTAB, LC_LOAD_DYLIB, + LC_ID_DYLIB, LC_LOAD_DYLINKER, LC_ID_DYLINKER, LC_PREBOUND_DYLIB, + LC_ROUTINES, LC_SUB_FRAMEWORK, LC_SUB_UMBRELLA, LC_SUB_CLIENT, + LC_SUB_LIBRARY, LC_TWOLEVEL_HINTS, LC_PREBIND_CKSUM +) = range(0x1, 0x18) + +LC_LOAD_WEAK_DYLIB = LC_REQ_DYLD | 0x18 + +LC_SEGMENT_64 = 0x19 +LC_ROUTINES_64 = 0x1a +LC_UUID = 0x1b +LC_RPATH = (0x1c | LC_REQ_DYLD) +LC_CODE_SIGNATURE = 0x1d +LC_CODE_SEGMENT_SPLIT_INFO = 0x1e +LC_REEXPORT_DYLIB = 0x1f | LC_REQ_DYLD +LC_LAZY_LOAD_DYLIB = 0x20 +LC_ENCRYPTION_INFO = 0x21 +LC_DYLD_INFO = 0x22 +LC_DYLD_INFO_ONLY = 0x22 | LC_REQ_DYLD +LC_LOAD_UPWARD_DYLIB = 0x23 | LC_REQ_DYLD +LC_VERSION_MIN_MACOSX = 0x24 +LC_VERSION_MIN_IPHONEOS = 0x25 +LC_FUNCTION_STARTS = 0x26 +LC_DYLD_ENVIRONMENT = 0x27 +LC_MAIN = 0x28 | LC_REQ_DYLD +LC_DATA_IN_CODE = 0x29 +LC_SOURCE_VERSION = 0x2a +LC_DYLIB_CODE_SIGN_DRS = 0x2b + +# this is really a union.. but whatever +class lc_str(p_uint32): + pass + +p_str16 = pypackable('p_str16', bytes, '16s') + +vm_prot_t = p_int32 +class segment_command(Structure): + _fields_ = ( + ('segname', p_str16), + ('vmaddr', p_uint32), + ('vmsize', p_uint32), + ('fileoff', p_uint32), + ('filesize', p_uint32), + ('maxprot', vm_prot_t), + ('initprot', vm_prot_t), + ('nsects', p_uint32), # read the section structures ? + ('flags', p_uint32), + ) + +class segment_command_64(Structure): + _fields_ = ( + ('segname', p_str16), + ('vmaddr', p_uint64), + ('vmsize', p_uint64), + ('fileoff', p_uint64), + ('filesize', p_uint64), + ('maxprot', vm_prot_t), + ('initprot', vm_prot_t), + ('nsects', p_uint32), # read the section structures ? + ('flags', p_uint32), + ) + +SG_HIGHVM = 0x1 +SG_FVMLIB = 0x2 +SG_NORELOC = 0x4 + +class section(Structure): + _fields_ = ( + ('sectname', p_str16), + ('segname', p_str16), + ('addr', p_uint32), + ('size', p_uint32), + ('offset', p_uint32), + ('align', p_uint32), + ('reloff', p_uint32), + ('nreloc', p_uint32), + ('flags', p_uint32), + ('reserved1', p_uint32), + ('reserved2', p_uint32), + ) + +class section_64(Structure): + _fields_ = ( + ('sectname', p_str16), + ('segname', p_str16), + ('addr', p_uint64), + ('size', p_uint64), + ('offset', p_uint32), + ('align', p_uint32), + ('reloff', p_uint32), + ('nreloc', p_uint32), + ('flags', p_uint32), + ('reserved1', p_uint32), + ('reserved2', p_uint32), + ('reserved3', p_uint32), + ) + +SECTION_TYPE = 0xff +SECTION_ATTRIBUTES = 0xffffff00 +S_REGULAR = 0x0 +S_ZEROFILL = 0x1 +S_CSTRING_LITERALS = 0x2 +S_4BYTE_LITERALS = 0x3 +S_8BYTE_LITERALS = 0x4 +S_LITERAL_POINTERS = 0x5 +S_NON_LAZY_SYMBOL_POINTERS = 0x6 +S_LAZY_SYMBOL_POINTERS = 0x7 +S_SYMBOL_STUBS = 0x8 +S_MOD_INIT_FUNC_POINTERS = 0x9 +S_MOD_TERM_FUNC_POINTERS = 0xa +S_COALESCED = 0xb + +SECTION_ATTRIBUTES_USR = 0xff000000 +S_ATTR_PURE_INSTRUCTIONS = 0x80000000 +S_ATTR_NO_TOC = 0x40000000 +S_ATTR_STRIP_STATIC_SYMS = 0x20000000 +SECTION_ATTRIBUTES_SYS = 0x00ffff00 +S_ATTR_SOME_INSTRUCTIONS = 0x00000400 +S_ATTR_EXT_RELOC = 0x00000200 +S_ATTR_LOC_RELOC = 0x00000100 + + +SEG_PAGEZERO = "__PAGEZERO" +SEG_TEXT = "__TEXT" +SECT_TEXT = "__text" +SECT_FVMLIB_INIT0 = "__fvmlib_init0" +SECT_FVMLIB_INIT1 = "__fvmlib_init1" +SEG_DATA = "__DATA" +SECT_DATA = "__data" +SECT_BSS = "__bss" +SECT_COMMON = "__common" +SEG_OBJC = "__OBJC" +SECT_OBJC_SYMBOLS = "__symbol_table" +SECT_OBJC_MODULES = "__module_info" +SECT_OBJC_STRINGS = "__selector_strs" +SECT_OBJC_REFS = "__selector_refs" +SEG_ICON = "__ICON" +SECT_ICON_HEADER = "__header" +SECT_ICON_TIFF = "__tiff" +SEG_LINKEDIT = "__LINKEDIT" +SEG_UNIXSTACK = "__UNIXSTACK" + +# +# I really should remove all these _command classes because they +# are no different. I decided to keep the load commands separate, +# so classes like fvmlib and fvmlib_command are equivalent. +# + +class fvmlib(Structure): + _fields_ = ( + ('name', lc_str), + ('minor_version', mach_version_helper), + ('header_addr', p_uint32), + ) + +class fvmlib_command(Structure): + _fields_ = fvmlib._fields_ + +class dylib(Structure): + _fields_ = ( + ('name', lc_str), + ('timestamp', mach_timestamp_helper), + ('current_version', mach_version_helper), + ('compatibility_version', mach_version_helper), + ) + +# merged dylib structure +class dylib_command(Structure): + _fields_ = dylib._fields_ + +class sub_framework_command(Structure): + _fields_ = ( + ('umbrella', lc_str), + ) + +class sub_client_command(Structure): + _fields_ = ( + ('client', lc_str), + ) + +class sub_umbrella_command(Structure): + _fields_ = ( + ('sub_umbrella', lc_str), + ) + +class sub_library_command(Structure): + _fields_ = ( + ('sub_library', lc_str), + ) + +class prebound_dylib_command(Structure): + _fields_ = ( + ('name', lc_str), + ('nmodules', p_uint32), + ('linked_modules', lc_str), + ) + +class dylinker_command(Structure): + _fields_ = ( + ('name', lc_str), + ) + +class thread_command(Structure): + _fields_ = ( + ) + +class entry_point_command(Structure): + _fields_ = ( + ('entryoff', p_uint64), + ('stacksize', p_uint64), + ) + +class routines_command(Structure): + _fields_ = ( + ('init_address', p_uint32), + ('init_module', p_uint32), + ('reserved1', p_uint32), + ('reserved2', p_uint32), + ('reserved3', p_uint32), + ('reserved4', p_uint32), + ('reserved5', p_uint32), + ('reserved6', p_uint32), + ) + +class routines_command_64(Structure): + _fields_ = ( + ('init_address', p_uint64), + ('init_module', p_uint64), + ('reserved1', p_uint64), + ('reserved2', p_uint64), + ('reserved3', p_uint64), + ('reserved4', p_uint64), + ('reserved5', p_uint64), + ('reserved6', p_uint64), + ) + +class symtab_command(Structure): + _fields_ = ( + ('symoff', p_uint32), + ('nsyms', p_uint32), + ('stroff', p_uint32), + ('strsize', p_uint32), + ) + +class dysymtab_command(Structure): + _fields_ = ( + ('ilocalsym', p_uint32), + ('nlocalsym', p_uint32), + ('iextdefsym', p_uint32), + ('nextdefsym', p_uint32), + ('iundefsym', p_uint32), + ('nundefsym', p_uint32), + ('tocoff', p_uint32), + ('ntoc', p_uint32), + ('modtaboff', p_uint32), + ('nmodtab', p_uint32), + ('extrefsymoff', p_uint32), + ('nextrefsyms', p_uint32), + ('indirectsymoff', p_uint32), + ('nindirectsyms', p_uint32), + ('extreloff', p_uint32), + ('nextrel', p_uint32), + ('locreloff', p_uint32), + ('nlocrel', p_uint32), + ) + +INDIRECT_SYMBOL_LOCAL = 0x80000000 +INDIRECT_SYMBOL_ABS = 0x40000000 + +class dylib_table_of_contents(Structure): + _fields_ = ( + ('symbol_index', p_uint32), + ('module_index', p_uint32), + ) + +class dylib_module(Structure): + _fields_ = ( + ('module_name', p_uint32), + ('iextdefsym', p_uint32), + ('nextdefsym', p_uint32), + ('irefsym', p_uint32), + ('nrefsym', p_uint32), + ('ilocalsym', p_uint32), + ('nlocalsym', p_uint32), + ('iextrel', p_uint32), + ('nextrel', p_uint32), + ('iinit_iterm', p_uint32), + ('ninit_nterm', p_uint32), + ('objc_module_info_addr', p_uint32), + ('objc_module_info_size', p_uint32), + ) + +class dylib_module_64(Structure): + _fields_ = ( + ('module_name', p_uint32), + ('iextdefsym', p_uint32), + ('nextdefsym', p_uint32), + ('irefsym', p_uint32), + ('nrefsym', p_uint32), + ('ilocalsym', p_uint32), + ('nlocalsym', p_uint32), + ('iextrel', p_uint32), + ('nextrel', p_uint32), + ('iinit_iterm', p_uint32), + ('ninit_nterm', p_uint32), + ('objc_module_info_size', p_uint32), + ('objc_module_info_addr', p_uint64), + ) + +class dylib_reference(Structure): + _fields_ = ( + # XXX - ick, fix + ('isym_flags', p_uint32), + #('isym', p_uint8 * 3), + #('flags', p_uint8), + ) + +class twolevel_hints_command(Structure): + _fields_ = ( + ('offset', p_uint32), + ('nhints', p_uint32), + ) + +class twolevel_hint(Structure): + _fields_ = ( + # XXX - ick, fix + ('isub_image_itoc', p_uint32), + #('isub_image', p_uint8), + #('itoc', p_uint8 * 3), + ) + +class prebind_cksum_command(Structure): + _fields_ = ( + ('cksum', p_uint32), + ) + +class symseg_command(Structure): + _fields_ = ( + ('offset', p_uint32), + ('size', p_uint32), + ) + +class ident_command(Structure): + _fields_ = ( + ) + +class fvmfile_command(Structure): + _fields_ = ( + ('name', lc_str), + ('header_addr', p_uint32), + ) + +class uuid_command (Structure): + _fields_ = ( + ('uuid', p_str16), + ) + +class rpath_command (Structure): + _fields_ = ( + ('path', lc_str), + ) + +class linkedit_data_command (Structure): + _fields_ = ( + ('dataoff', p_uint32), + ('datassize', p_uint32), + ) + +class version_min_command (Structure): + _fields_ = ( + ('version', p_uint32), # X.Y.Z is encoded in nibbles xxxx.yy.zz + ('reserved', p_uint32), + ) + +class source_version_command (Structure): + _fields_ = ( + ('version', p_uint64), + ) + +LC_REGISTRY = { + LC_SEGMENT: segment_command, + LC_IDFVMLIB: fvmlib_command, + LC_LOADFVMLIB: fvmlib_command, + LC_ID_DYLIB: dylib_command, + LC_LOAD_DYLIB: dylib_command, + LC_LOAD_WEAK_DYLIB: dylib_command, + LC_SUB_FRAMEWORK: sub_framework_command, + LC_SUB_CLIENT: sub_client_command, + LC_SUB_UMBRELLA: sub_umbrella_command, + LC_SUB_LIBRARY: sub_library_command, + LC_PREBOUND_DYLIB: prebound_dylib_command, + LC_ID_DYLINKER: dylinker_command, + LC_LOAD_DYLINKER: dylinker_command, + LC_THREAD: thread_command, + LC_UNIXTHREAD: thread_command, + LC_ROUTINES: routines_command, + LC_SYMTAB: symtab_command, + LC_DYSYMTAB: dysymtab_command, + LC_TWOLEVEL_HINTS: twolevel_hints_command, + LC_PREBIND_CKSUM: prebind_cksum_command, + LC_SYMSEG: symseg_command, + LC_IDENT: ident_command, + LC_FVMFILE: fvmfile_command, + LC_SEGMENT_64: segment_command_64, + LC_ROUTINES_64: routines_command_64, + LC_UUID: uuid_command, + LC_RPATH: rpath_command, + LC_CODE_SIGNATURE: linkedit_data_command, + LC_CODE_SEGMENT_SPLIT_INFO: linkedit_data_command, + LC_REEXPORT_DYLIB: dylib_command, + LC_LAZY_LOAD_DYLIB: dylib_command, + LC_ENCRYPTION_INFO: dylib_command, + LC_DYLD_INFO: dylib_command, + LC_DYLD_INFO_ONLY: dylib_command, + LC_LOAD_UPWARD_DYLIB: dylib_command, + LC_VERSION_MIN_MACOSX: version_min_command, + LC_VERSION_MIN_IPHONEOS: version_min_command, + LC_FUNCTION_STARTS: linkedit_data_command, + LC_DYLD_ENVIRONMENT: dylinker_command, + LC_MAIN: entry_point_command, + LC_DATA_IN_CODE: dylib_command, + LC_SOURCE_VERSION: source_version_command, + LC_DYLIB_CODE_SIGN_DRS: linkedit_data_command, +} + +#this is another union. +class n_un(p_int32): + pass + +class nlist(Structure): + _fields_ = ( + ('n_un', n_un), + ('n_type', p_uint8), + ('n_sect', p_uint8), + ('n_desc', p_short), + ('n_value', p_uint32), + ) + +class nlist_64(Structure): + _fields_ = [ + ('n_un', n_un), + ('n_type', p_uint8), + ('n_sect', p_uint8), + ('n_desc', p_short), + ('n_value', p_int64), + ] + +N_STAB = 0xe0 +N_PEXT = 0x10 +N_TYPE = 0x0e +N_EXT = 0x01 + +N_UNDF = 0x0 +N_ABS = 0x2 +N_SECT = 0xe +N_PBUD = 0xc +N_INDR = 0xa + +NO_SECT = 0 +MAX_SECT = 255 + +REFERENCE_TYPE = 0xf +REFERENCE_FLAG_UNDEFINED_NON_LAZY = 0 +REFERENCE_FLAG_UNDEFINED_LAZY = 1 +REFERENCE_FLAG_DEFINED = 2 +REFERENCE_FLAG_PRIVATE_DEFINED = 3 +REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY = 4 +REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY = 5 + +REFERENCED_DYNAMICALLY = 0x0010 + +def GET_LIBRARY_ORDINAL(n_desc): + return (((n_desc) >> 8) & 0xff) + +def SET_LIBRARY_ORDINAL(n_desc, ordinal): + return (((n_desc) & 0x00ff) | (((ordinal & 0xff) << 8))) + +SELF_LIBRARY_ORDINAL = 0x0 +MAX_LIBRARY_ORDINAL = 0xfd +DYNAMIC_LOOKUP_ORDINAL = 0xfe +EXECUTABLE_ORDINAL = 0xff + +N_DESC_DISCARDED = 0x0020 +N_WEAK_REF = 0x0040 +N_WEAK_DEF = 0x0080 + +# /usr/include/mach-o/fat.h +FAT_MAGIC = 0xcafebabe +class fat_header(Structure): + _fields_ = ( + ('magic', p_uint32), + ('nfat_arch', p_uint32), + ) + +class fat_arch(Structure): + _fields_ = ( + ('cputype', cpu_type_t), + ('cpusubtype', cpu_subtype_t), + ('offset', p_uint32), + ('size', p_uint32), + ('align', p_uint32), + ) diff --git a/pyinstaller/PyInstaller/lib/macholib/macho_dump.py b/pyinstaller/PyInstaller/lib/macholib/macho_dump.py new file mode 100644 index 0000000..a0c8a3d --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/macho_dump.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import os +import sys + +from macholib._cmdline import main as _main +from macholib.MachO import MachO +from macholib.mach_o import * + +ARCH_MAP={ + ('<', '64-bit'): 'x86_64', + ('<', '32-bit'): 'i386', + ('>', '64-bit'): 'pp64', + ('>', '32-bit'): 'ppc', +} + +def print_file(fp, path): + print(path, file=fp) + m = MachO(path) + for header in m.headers: + seen = set() + if header.MH_MAGIC == MH_MAGIC_64: + sz = '64-bit' + else: + sz = '32-bit' + + print(' [%s endian=%r size=%r arch=%r]' % (header.__class__.__name__, + header.endian, sz, ARCH_MAP[(header.endian, sz)]), file=fp) + for idx, name, other in header.walkRelocatables(): + if other not in seen: + seen.add(other) + print('\t' + other, file=fp) + +def main(): + _main(print_file) + +if __name__ == '__main__': + try: + sys.exit(main(print_file)) + except KeyboardInterrupt: + pass diff --git a/pyinstaller/PyInstaller/lib/macholib/macho_find.py b/pyinstaller/PyInstaller/lib/macholib/macho_find.py new file mode 100644 index 0000000..86c3b10 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/macho_find.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +from __future__ import print_function +from macholib._cmdline import main as _main + + +def print_file(fp, path): + print(path, file=fp) + +def main(): + _main(print_file) + +if __name__ == '__main__': + try: + main(print_file) + except KeyboardInterrupt: + pass diff --git a/pyinstaller/PyInstaller/lib/macholib/macho_standalone.py b/pyinstaller/PyInstaller/lib/macholib/macho_standalone.py new file mode 100644 index 0000000..05b7cc8 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/macho_standalone.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +import os +import sys + +from macholib.MachOStandalone import MachOStandalone +from macholib.util import strip_files + +def standaloneApp(path): + if not os.path.isdir(path) and os.path.exists( + os.path.join(path, 'Contents')): + raise SystemExit('%s: %s does not look like an app bundle' + % (sys.argv[0], path)) + files = MachOStandalone(path).run() + strip_files(files) + +def main(): + if not sys.argv[1:]: + raise SystemExit, 'usage: %s [appbundle ...]' % (sys.argv[0],) + for fn in sys.argv[1:]: + standaloneApp(fn) + +if __name__ == '__main__': + main() diff --git a/pyinstaller/PyInstaller/lib/macholib/ptypes.py b/pyinstaller/PyInstaller/lib/macholib/ptypes.py new file mode 100644 index 0000000..f1457c9 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/ptypes.py @@ -0,0 +1,290 @@ +""" +This module defines packable types, that is types than can be easily converted to a binary format +as used in MachO headers. +""" +import struct +import sys + +try: + from itertools import izip, imap +except ImportError: + izip, imap = zip, map +from itertools import chain, starmap +import warnings + +__all__ = """ +sizeof +BasePackable +Structure +pypackable +p_char +p_byte +p_ubyte +p_short +p_ushort +p_int +p_uint +p_long +p_ulong +p_longlong +p_ulonglong +p_int8 +p_uint8 +p_int16 +p_uint16 +p_int32 +p_uint32 +p_int64 +p_uint64 +p_float +p_double +""".split() + +def sizeof(s): + """ + Return the size of an object when packed + """ + if hasattr(s, '_size_'): + return s._size_ + + elif isinstance(s, bytes): + return len(s) + + raise ValueError(s) + +class MetaPackable(type): + """ + Fixed size struct.unpack-able types use from_tuple as their designated initializer + """ + def from_mmap(cls, mm, ptr, **kw): + return cls.from_str(mm[ptr:ptr+cls._size_], **kw) + + def from_fileobj(cls, f, **kw): + return cls.from_str(f.read(cls._size_), **kw) + + def from_str(cls, s, **kw): + endian = kw.get('_endian_', cls._endian_) + return cls.from_tuple(struct.unpack(endian + cls._format_, s), **kw) + + def from_tuple(cls, tpl, **kw): + return cls(tpl[0], **kw) + +class BasePackable(object): + _endian_ = '>' + + def to_str(self): + raise NotImplementedError + + def to_fileobj(self, f): + f.write(self.to_str()) + + def to_mmap(self, mm, ptr): + mm[ptr:ptr+self._size_] = self.to_str() + + +# This defines a class with a custom metaclass, we'd normally +# use "class Packable(BasePackable, metaclass=MetaPackage)", +# but that syntax is not valid in Python 2 (and likewise the +# python 2 syntax is not valid in Python 3) +def _make(): + def to_str(self): + cls = type(self) + endian = getattr(self, '_endian_', cls._endian_) + return struct.pack(endian + cls._format_, self) + return MetaPackable("Packable", (BasePackable,), {'to_str': to_str}) +Packable = _make() +del _make + +def pypackable(name, pytype, format): + """ + Create a "mix-in" class with a python type and a + Packable with the given struct format + """ + size, items = _formatinfo(format) + return type(Packable)(name, (pytype, Packable), { + '_format_': format, + '_size_': size, + '_items_': items, + }) + +def _formatinfo(format): + """ + Calculate the size and number of items in a struct format. + """ + size = struct.calcsize(format) + return size, len(struct.unpack(format, b'\x00' * size)) + +class MetaStructure(MetaPackable): + """ + The metaclass of Structure objects that does all the magic. + + Since we can assume that all Structures have a fixed size, + we can do a bunch of calculations up front and pack or + unpack the whole thing in one struct call. + """ + def __new__(cls, clsname, bases, dct): + fields = dct['_fields_'] + names = [] + types = [] + structmarks = [] + format = '' + items = 0 + size = 0 + + def struct_property(name, typ): + def _get(self): + return self._objects_[name] + def _set(self, obj): + if type(obj) is not typ: + obj = typ(obj) + self._objects_[name] = obj + return property(_get, _set, typ.__name__) + + for name, typ in fields: + dct[name] = struct_property(name, typ) + names.append(name) + types.append(typ) + format += typ._format_ + size += typ._size_ + if (typ._items_ > 1): + structmarks.append((items, typ._items_, typ)) + items += typ._items_ + + dct['_structmarks_'] = structmarks + dct['_names_'] = names + dct['_types_'] = types + dct['_size_'] = size + dct['_items_'] = items + dct['_format_'] = format + return super(MetaStructure, cls).__new__(cls, clsname, bases, dct) + + def from_tuple(cls, tpl, **kw): + values = [] + current = 0 + for begin, length, typ in cls._structmarks_: + if begin > current: + values.extend(tpl[current:begin]) + current = begin + length + values.append(typ.from_tuple(tpl[begin:current], **kw)) + values.extend(tpl[current:]) + return cls(*values, **kw) + +# See metaclass discussion earlier in this file +def _make(): + class_dict={} + class_dict['_fields_'] = () + + def as_method(function): + class_dict[function.__name__] = function + + @as_method + def __init__(self, *args, **kwargs): + if len(args) == 1 and not kwargs and type(args[0]) is type(self): + kwargs = args[0]._objects_ + args = () + self._objects_ = {} + iargs = chain(izip(self._names_, args), kwargs.items()) + for key, value in iargs: + if key not in self._names_ and key != "_endian_": + raise TypeError + setattr(self, key, value) + for key, typ in izip(self._names_, self._types_): + if key not in self._objects_: + self._objects_[key] = typ() + + @as_method + def _get_packables(self): + for obj in imap(self._objects_.__getitem__, self._names_): + if obj._items_ == 1: + yield obj + else: + for obj in obj._get_packables(): + yield obj + + @as_method + def to_str(self): + return struct.pack(self._endian_ + self._format_, *self._get_packables()) + + @as_method + def __cmp__(self, other): + if type(other) is not type(self): + raise TypeError('Cannot compare objects of type %r to objects of type %r' % (type(other), type(self))) + if sys.version_info[0] == 2: + _cmp = cmp + else: + def _cmp(a, b): + if a < b: + return -1 + elif a > b: + return 1 + elif a == b: + return 0 + else: + raise TypeError() + + for cmpval in starmap(_cmp, izip(self._get_packables(), other._get_packables())): + if cmpval != 0: + return cmpval + return 0 + + @as_method + def __eq__(self, other): + r = self.__cmp__(other) + return r == 0 + + @as_method + def __ne__(self, other): + r = self.__cmp__(other) + return r != 0 + + @as_method + def __lt__(self, other): + r = self.__cmp__(other) + return r < 0 + + @as_method + def __le__(self, other): + r = self.__cmp__(other) + return r <= 0 + + @as_method + def __gt__(self, other): + r = self.__cmp__(other) + return r > 0 + + @as_method + def __ge__(self, other): + r = self.__cmp__(other) + return r >= 0 + + return MetaStructure("Structure", (BasePackable,), class_dict) +Structure = _make() +del _make + +try: + long +except NameError: + long = int + +# export common packables with predictable names +p_char = pypackable('p_char', bytes, 'c') +p_int8 = pypackable('p_int8', int, 'b') +p_uint8 = pypackable('p_uint8', int, 'B') +p_int16 = pypackable('p_int16', int, 'h') +p_uint16 = pypackable('p_uint16', int, 'H') +p_int32 = pypackable('p_int32', int, 'i') +p_uint32 = pypackable('p_uint32', long, 'I') +p_int64 = pypackable('p_int64', long, 'q') +p_uint64 = pypackable('p_uint64', long, 'Q') +p_float = pypackable('p_float', float, 'f') +p_double = pypackable('p_double', float, 'd') + +# Deprecated names, need trick to emit deprecation warning. +p_byte = p_int8 +p_ubyte = p_uint8 +p_short = p_int16 +p_ushort = p_uint16 +p_int = p_long = p_int32 +p_uint = p_ulong = p_uint32 +p_longlong = p_int64 +p_ulonglong = p_uint64 diff --git a/pyinstaller/PyInstaller/lib/macholib/util.py b/pyinstaller/PyInstaller/lib/macholib/util.py new file mode 100644 index 0000000..d491598 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/macholib/util.py @@ -0,0 +1,236 @@ +import os +import sys +import stat +import operator +import struct +import shutil + +from macholib import mach_o + +MAGIC = [ + struct.pack('!L', getattr(mach_o, 'MH_' + _)) + for _ in ['MAGIC', 'CIGAM', 'MAGIC_64', 'CIGAM_64'] +] +FAT_MAGIC_BYTES = struct.pack('!L', mach_o.FAT_MAGIC) +MAGIC_LEN = 4 +STRIPCMD = ['/usr/bin/strip', '-x', '-S', '-'] + + +def fsencoding(s, encoding=sys.getfilesystemencoding()): + """ + Ensure the given argument is in filesystem encoding (not unicode) + """ + if isinstance(s, unicode): + s = s.encode(encoding) + return s + +def move(src, dst): + """ + move that ensures filesystem encoding of paths + """ + shutil.move(fsencoding(src), fsencoding(dst)) + +def copy2(src, dst): + """ + copy2 that ensures filesystem encoding of paths + """ + shutil.copy2(fsencoding(src), fsencoding(dst)) + +def flipwritable(fn, mode=None): + """ + Flip the writability of a file and return the old mode. Returns None + if the file is already writable. + """ + if os.access(fn, os.W_OK): + return None + old_mode = os.stat(fn).st_mode + os.chmod(fn, stat.S_IWRITE | old_mode) + return old_mode + +class fileview(object): + """ + A proxy for file-like objects that exposes a given view of a file + """ + + def __init__(self, fileobj, start, size): + self._fileobj = fileobj + self._start = start + self._end = start + size + + def __repr__(self): + return '' % ( + self._start, self._end, self._fileobj) + + def tell(self): + return self._fileobj.tell() - self._start + + def _checkwindow(self, seekto, op): + if not (self._start <= seekto <= self._end): + raise IOError("%s to offset %d is outside window [%d, %d]" % ( + op, seekto, self._start, self._end)) + + def seek(self, offset, whence=0): + seekto = offset + if whence == 0: + seekto += self._start + elif whence == 1: + seekto += self._fileobj.tell() + elif whence == 2: + seekto += self._end + else: + raise IOError("Invalid whence argument to seek: %r" % (whence,)) + self._checkwindow(seekto, 'seek') + self._fileobj.seek(seekto) + + def write(self, bytes): + here = self._fileobj.tell() + self._checkwindow(here, 'write') + self._checkwindow(here + len(bytes), 'write') + self._fileobj.write(bytes) + + def read(self, size=sys.maxsize): + assert size >= 0 + here = self._fileobj.tell() + self._checkwindow(here, 'read') + bytes = min(size, self._end - here) + return self._fileobj.read(bytes) + + +def mergecopy(src, dest): + """ + copy2, but only if the destination isn't up to date + """ + if os.path.exists(dest) and os.stat(dest).st_mtime >= os.stat(src).st_mtime: + return + copy2(src, dest) + +def mergetree(src, dst, condition=None, copyfn=mergecopy, srcbase=None): + """ + Recursively merge a directory tree using mergecopy(). + """ + src = fsencoding(src) + dst = fsencoding(dst) + if srcbase is None: + srcbase = src + names = map(fsencoding, os.listdir(src)) + try: + os.makedirs(dst) + except OSError: + pass + errors = [] + for name in names: + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + if condition is not None and not condition(srcname): + continue + try: + if os.path.islink(srcname): + # XXX: This is naive at best, should check srcbase(?) + realsrc = os.readlink(srcname) + os.symlink(realsrc, dstname) + elif os.path.isdir(srcname): + mergetree(srcname, dstname, + condition=condition, copyfn=copyfn, srcbase=srcbase) + else: + copyfn(srcname, dstname) + except (IOError, os.error) as why: + errors.append((srcname, dstname, why)) + if errors: + raise IOError(errors) + +def sdk_normalize(filename): + """ + Normalize a path to strip out the SDK portion, normally so that it + can be decided whether it is in a system path or not. + """ + if filename.startswith('/Developer/SDKs/'): + pathcomp = filename.split('/') + del pathcomp[1:4] + filename = '/'.join(pathcomp) + return filename + +NOT_SYSTEM_FILES=[] + +def in_system_path(filename): + """ + Return True if the file is in a system path + """ + fn = sdk_normalize(os.path.realpath(filename)) + if fn.startswith('/usr/local/'): + return False + elif fn.startswith('/System/') or fn.startswith('/usr/'): + if fn in NOT_SYSTEM_FILES: + return False + return True + else: + return False + +def has_filename_filter(module): + """ + Return False if the module does not have a filename attribute + """ + return getattr(module, 'filename', None) is not None + +def get_magic(): + """ + Get a list of valid Mach-O header signatures, not including the fat header + """ + return MAGIC + +def is_platform_file(path): + """ + Return True if the file is Mach-O + """ + if not os.path.exists(path) or os.path.islink(path): + return False + # If the header is fat, we need to read into the first arch + with open(path, 'rb') as fileobj: + bytes = fileobj.read(MAGIC_LEN) + if bytes == FAT_MAGIC_BYTES: + # Read in the fat header + fileobj.seek(0) + header = mach_o.fat_header.from_fileobj(fileobj, _endian_='>') + if header.nfat_arch < 1: + return False + # Read in the first fat arch header + arch = mach_o.fat_arch.from_fileobj(fileobj, _endian_='>') + fileobj.seek(arch.offset) + # Read magic off the first header + bytes = fileobj.read(MAGIC_LEN) + for magic in MAGIC: + if bytes == magic: + return True + return False + +def iter_platform_files(dst): + """ + Walk a directory and yield each full path that is a Mach-O file + """ + for root, dirs, files in os.walk(dst): + for fn in files: + fn = os.path.join(root, fn) + if is_platform_file(fn): + yield fn + +def strip_files(files, argv_max=(256 * 1024)): + """ + Strip a list of files + """ + tostrip = [(fn, flipwritable(fn)) for fn in files] + while tostrip: + cmd = list(STRIPCMD) + flips = [] + pathlen = sum([len(s) + 1 for s in cmd]) + while pathlen < argv_max: + if not tostrip: + break + added, flip = tostrip.pop() + pathlen += len(added) + 1 + cmd.append(added) + flips.append((added, flip)) + else: + cmd.pop() + tostrip.append(flips.pop()) + os.spawnv(os.P_WAIT, cmd[0], cmd) + for args in flips: + flipwritable(*args) diff --git a/pyinstaller/PyInstaller/lib/pefile.py b/pyinstaller/PyInstaller/lib/pefile.py new file mode 100644 index 0000000..22df521 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/pefile.py @@ -0,0 +1,4651 @@ +# -*- coding: latin-1 -*- +"""pefile, Portable Executable reader module + + +All the PE file basic structures are available with their default names +as attributes of the instance returned. + +Processed elements such as the import table are made available with lowercase +names, to differentiate them from the upper case basic structure names. + +pefile has been tested against the limits of valid PE headers, that is, malware. +Lots of packed malware attempt to abuse the format way beyond its standard use. +To the best of my knowledge most of the abuses are handled gracefully. + +Copyright (c) 2005-2011 Ero Carrera + +All rights reserved. + +For detailed copyright information see the file COPYING in +the root of the distribution archive. +""" + +__revision__ = "$LastChangedRevision: 102 $" +__author__ = 'Ero Carrera' +__version__ = '1.2.10-%d' % int( __revision__[21:-2] ) +__contact__ = 'ero.carrera@gmail.com' + + +import os +import struct +import time +import math +import re +import exceptions +import string +import array +import mmap + +sha1, sha256, sha512, md5 = None, None, None, None + +try: + import hashlib + sha1 = hashlib.sha1 + sha256 = hashlib.sha256 + sha512 = hashlib.sha512 + md5 = hashlib.md5 +except ImportError: + try: + import sha + sha1 = sha.new + except ImportError: + pass + try: + import md5 + md5 = md5.new + except ImportError: + pass + +try: + enumerate +except NameError: + def enumerate(iter): + L = list(iter) + return zip(range(0, len(L)), L) + + +fast_load = False + +# This will set a maximum length of a string to be retrieved from the file. +# It's there to prevent loading massive amounts of data from memory mapped +# files. Strings longer than 1MB should be rather rare. +MAX_STRING_LENGTH = 0x100000 # 2^20 + +IMAGE_DOS_SIGNATURE = 0x5A4D +IMAGE_DOSZM_SIGNATURE = 0x4D5A +IMAGE_NE_SIGNATURE = 0x454E +IMAGE_LE_SIGNATURE = 0x454C +IMAGE_LX_SIGNATURE = 0x584C + +IMAGE_NT_SIGNATURE = 0x00004550 +IMAGE_NUMBEROF_DIRECTORY_ENTRIES= 16 +IMAGE_ORDINAL_FLAG = 0x80000000L +IMAGE_ORDINAL_FLAG64 = 0x8000000000000000L +OPTIONAL_HEADER_MAGIC_PE = 0x10b +OPTIONAL_HEADER_MAGIC_PE_PLUS = 0x20b + + +directory_entry_types = [ + ('IMAGE_DIRECTORY_ENTRY_EXPORT', 0), + ('IMAGE_DIRECTORY_ENTRY_IMPORT', 1), + ('IMAGE_DIRECTORY_ENTRY_RESOURCE', 2), + ('IMAGE_DIRECTORY_ENTRY_EXCEPTION', 3), + ('IMAGE_DIRECTORY_ENTRY_SECURITY', 4), + ('IMAGE_DIRECTORY_ENTRY_BASERELOC', 5), + ('IMAGE_DIRECTORY_ENTRY_DEBUG', 6), + ('IMAGE_DIRECTORY_ENTRY_COPYRIGHT', 7), + ('IMAGE_DIRECTORY_ENTRY_GLOBALPTR', 8), + ('IMAGE_DIRECTORY_ENTRY_TLS', 9), + ('IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG', 10), + ('IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT', 11), + ('IMAGE_DIRECTORY_ENTRY_IAT', 12), + ('IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT', 13), + ('IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR',14), + ('IMAGE_DIRECTORY_ENTRY_RESERVED', 15) ] + +DIRECTORY_ENTRY = dict([(e[1], e[0]) for e in directory_entry_types]+directory_entry_types) + + +image_characteristics = [ + ('IMAGE_FILE_RELOCS_STRIPPED', 0x0001), + ('IMAGE_FILE_EXECUTABLE_IMAGE', 0x0002), + ('IMAGE_FILE_LINE_NUMS_STRIPPED', 0x0004), + ('IMAGE_FILE_LOCAL_SYMS_STRIPPED', 0x0008), + ('IMAGE_FILE_AGGRESIVE_WS_TRIM', 0x0010), + ('IMAGE_FILE_LARGE_ADDRESS_AWARE', 0x0020), + ('IMAGE_FILE_16BIT_MACHINE', 0x0040), + ('IMAGE_FILE_BYTES_REVERSED_LO', 0x0080), + ('IMAGE_FILE_32BIT_MACHINE', 0x0100), + ('IMAGE_FILE_DEBUG_STRIPPED', 0x0200), + ('IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP', 0x0400), + ('IMAGE_FILE_NET_RUN_FROM_SWAP', 0x0800), + ('IMAGE_FILE_SYSTEM', 0x1000), + ('IMAGE_FILE_DLL', 0x2000), + ('IMAGE_FILE_UP_SYSTEM_ONLY', 0x4000), + ('IMAGE_FILE_BYTES_REVERSED_HI', 0x8000) ] + +IMAGE_CHARACTERISTICS = dict([(e[1], e[0]) for e in + image_characteristics]+image_characteristics) + + +section_characteristics = [ + ('IMAGE_SCN_CNT_CODE', 0x00000020), + ('IMAGE_SCN_CNT_INITIALIZED_DATA', 0x00000040), + ('IMAGE_SCN_CNT_UNINITIALIZED_DATA', 0x00000080), + ('IMAGE_SCN_LNK_OTHER', 0x00000100), + ('IMAGE_SCN_LNK_INFO', 0x00000200), + ('IMAGE_SCN_LNK_REMOVE', 0x00000800), + ('IMAGE_SCN_LNK_COMDAT', 0x00001000), + ('IMAGE_SCN_MEM_FARDATA', 0x00008000), + ('IMAGE_SCN_MEM_PURGEABLE', 0x00020000), + ('IMAGE_SCN_MEM_16BIT', 0x00020000), + ('IMAGE_SCN_MEM_LOCKED', 0x00040000), + ('IMAGE_SCN_MEM_PRELOAD', 0x00080000), + ('IMAGE_SCN_ALIGN_1BYTES', 0x00100000), + ('IMAGE_SCN_ALIGN_2BYTES', 0x00200000), + ('IMAGE_SCN_ALIGN_4BYTES', 0x00300000), + ('IMAGE_SCN_ALIGN_8BYTES', 0x00400000), + ('IMAGE_SCN_ALIGN_16BYTES', 0x00500000), + ('IMAGE_SCN_ALIGN_32BYTES', 0x00600000), + ('IMAGE_SCN_ALIGN_64BYTES', 0x00700000), + ('IMAGE_SCN_ALIGN_128BYTES', 0x00800000), + ('IMAGE_SCN_ALIGN_256BYTES', 0x00900000), + ('IMAGE_SCN_ALIGN_512BYTES', 0x00A00000), + ('IMAGE_SCN_ALIGN_1024BYTES', 0x00B00000), + ('IMAGE_SCN_ALIGN_2048BYTES', 0x00C00000), + ('IMAGE_SCN_ALIGN_4096BYTES', 0x00D00000), + ('IMAGE_SCN_ALIGN_8192BYTES', 0x00E00000), + ('IMAGE_SCN_ALIGN_MASK', 0x00F00000), + ('IMAGE_SCN_LNK_NRELOC_OVFL', 0x01000000), + ('IMAGE_SCN_MEM_DISCARDABLE', 0x02000000), + ('IMAGE_SCN_MEM_NOT_CACHED', 0x04000000), + ('IMAGE_SCN_MEM_NOT_PAGED', 0x08000000), + ('IMAGE_SCN_MEM_SHARED', 0x10000000), + ('IMAGE_SCN_MEM_EXECUTE', 0x20000000), + ('IMAGE_SCN_MEM_READ', 0x40000000), + ('IMAGE_SCN_MEM_WRITE', 0x80000000L) ] + +SECTION_CHARACTERISTICS = dict([(e[1], e[0]) for e in + section_characteristics]+section_characteristics) + + +debug_types = [ + ('IMAGE_DEBUG_TYPE_UNKNOWN', 0), + ('IMAGE_DEBUG_TYPE_COFF', 1), + ('IMAGE_DEBUG_TYPE_CODEVIEW', 2), + ('IMAGE_DEBUG_TYPE_FPO', 3), + ('IMAGE_DEBUG_TYPE_MISC', 4), + ('IMAGE_DEBUG_TYPE_EXCEPTION', 5), + ('IMAGE_DEBUG_TYPE_FIXUP', 6), + ('IMAGE_DEBUG_TYPE_OMAP_TO_SRC', 7), + ('IMAGE_DEBUG_TYPE_OMAP_FROM_SRC', 8), + ('IMAGE_DEBUG_TYPE_BORLAND', 9), + ('IMAGE_DEBUG_TYPE_RESERVED10', 10) ] + +DEBUG_TYPE = dict([(e[1], e[0]) for e in debug_types]+debug_types) + + +subsystem_types = [ + ('IMAGE_SUBSYSTEM_UNKNOWN', 0), + ('IMAGE_SUBSYSTEM_NATIVE', 1), + ('IMAGE_SUBSYSTEM_WINDOWS_GUI', 2), + ('IMAGE_SUBSYSTEM_WINDOWS_CUI', 3), + ('IMAGE_SUBSYSTEM_OS2_CUI', 5), + ('IMAGE_SUBSYSTEM_POSIX_CUI', 7), + ('IMAGE_SUBSYSTEM_WINDOWS_CE_GUI', 9), + ('IMAGE_SUBSYSTEM_EFI_APPLICATION', 10), + ('IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER', 11), + ('IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER', 12), + ('IMAGE_SUBSYSTEM_EFI_ROM', 13), + ('IMAGE_SUBSYSTEM_XBOX', 14)] + +SUBSYSTEM_TYPE = dict([(e[1], e[0]) for e in subsystem_types]+subsystem_types) + + +machine_types = [ + ('IMAGE_FILE_MACHINE_UNKNOWN', 0), + ('IMAGE_FILE_MACHINE_AM33', 0x1d3), + ('IMAGE_FILE_MACHINE_AMD64', 0x8664), + ('IMAGE_FILE_MACHINE_ARM', 0x1c0), + ('IMAGE_FILE_MACHINE_EBC', 0xebc), + ('IMAGE_FILE_MACHINE_I386', 0x14c), + ('IMAGE_FILE_MACHINE_IA64', 0x200), + ('IMAGE_FILE_MACHINE_MR32', 0x9041), + ('IMAGE_FILE_MACHINE_MIPS16', 0x266), + ('IMAGE_FILE_MACHINE_MIPSFPU', 0x366), + ('IMAGE_FILE_MACHINE_MIPSFPU16',0x466), + ('IMAGE_FILE_MACHINE_POWERPC', 0x1f0), + ('IMAGE_FILE_MACHINE_POWERPCFP',0x1f1), + ('IMAGE_FILE_MACHINE_R4000', 0x166), + ('IMAGE_FILE_MACHINE_SH3', 0x1a2), + ('IMAGE_FILE_MACHINE_SH3DSP', 0x1a3), + ('IMAGE_FILE_MACHINE_SH4', 0x1a6), + ('IMAGE_FILE_MACHINE_SH5', 0x1a8), + ('IMAGE_FILE_MACHINE_THUMB', 0x1c2), + ('IMAGE_FILE_MACHINE_WCEMIPSV2',0x169), + ] + +MACHINE_TYPE = dict([(e[1], e[0]) for e in machine_types]+machine_types) + + +relocation_types = [ + ('IMAGE_REL_BASED_ABSOLUTE', 0), + ('IMAGE_REL_BASED_HIGH', 1), + ('IMAGE_REL_BASED_LOW', 2), + ('IMAGE_REL_BASED_HIGHLOW', 3), + ('IMAGE_REL_BASED_HIGHADJ', 4), + ('IMAGE_REL_BASED_MIPS_JMPADDR', 5), + ('IMAGE_REL_BASED_SECTION', 6), + ('IMAGE_REL_BASED_REL', 7), + ('IMAGE_REL_BASED_MIPS_JMPADDR16', 9), + ('IMAGE_REL_BASED_IA64_IMM64', 9), + ('IMAGE_REL_BASED_DIR64', 10), + ('IMAGE_REL_BASED_HIGH3ADJ', 11) ] + +RELOCATION_TYPE = dict([(e[1], e[0]) for e in relocation_types]+relocation_types) + + +dll_characteristics = [ + ('IMAGE_DLL_CHARACTERISTICS_RESERVED_0x0001', 0x0001), + ('IMAGE_DLL_CHARACTERISTICS_RESERVED_0x0002', 0x0002), + ('IMAGE_DLL_CHARACTERISTICS_RESERVED_0x0004', 0x0004), + ('IMAGE_DLL_CHARACTERISTICS_RESERVED_0x0008', 0x0008), + ('IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE', 0x0040), + ('IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY', 0x0080), + ('IMAGE_DLL_CHARACTERISTICS_NX_COMPAT', 0x0100), + ('IMAGE_DLL_CHARACTERISTICS_NO_ISOLATION', 0x0200), + ('IMAGE_DLL_CHARACTERISTICS_NO_SEH', 0x0400), + ('IMAGE_DLL_CHARACTERISTICS_NO_BIND', 0x0800), + ('IMAGE_DLL_CHARACTERISTICS_RESERVED_0x1000', 0x1000), + ('IMAGE_DLL_CHARACTERISTICS_WDM_DRIVER', 0x2000), + ('IMAGE_DLL_CHARACTERISTICS_TERMINAL_SERVER_AWARE', 0x8000) ] + +DLL_CHARACTERISTICS = dict([(e[1], e[0]) for e in dll_characteristics]+dll_characteristics) + + +# Resource types +resource_type = [ + ('RT_CURSOR', 1), + ('RT_BITMAP', 2), + ('RT_ICON', 3), + ('RT_MENU', 4), + ('RT_DIALOG', 5), + ('RT_STRING', 6), + ('RT_FONTDIR', 7), + ('RT_FONT', 8), + ('RT_ACCELERATOR', 9), + ('RT_RCDATA', 10), + ('RT_MESSAGETABLE', 11), + ('RT_GROUP_CURSOR', 12), + ('RT_GROUP_ICON', 14), + ('RT_VERSION', 16), + ('RT_DLGINCLUDE', 17), + ('RT_PLUGPLAY', 19), + ('RT_VXD', 20), + ('RT_ANICURSOR', 21), + ('RT_ANIICON', 22), + ('RT_HTML', 23), + ('RT_MANIFEST', 24) ] + +RESOURCE_TYPE = dict([(e[1], e[0]) for e in resource_type]+resource_type) + + +# Language definitions +lang = [ + ('LANG_NEUTRAL', 0x00), + ('LANG_INVARIANT', 0x7f), + ('LANG_AFRIKAANS', 0x36), + ('LANG_ALBANIAN', 0x1c), + ('LANG_ARABIC', 0x01), + ('LANG_ARMENIAN', 0x2b), + ('LANG_ASSAMESE', 0x4d), + ('LANG_AZERI', 0x2c), + ('LANG_BASQUE', 0x2d), + ('LANG_BELARUSIAN', 0x23), + ('LANG_BENGALI', 0x45), + ('LANG_BULGARIAN', 0x02), + ('LANG_CATALAN', 0x03), + ('LANG_CHINESE', 0x04), + ('LANG_CROATIAN', 0x1a), + ('LANG_CZECH', 0x05), + ('LANG_DANISH', 0x06), + ('LANG_DIVEHI', 0x65), + ('LANG_DUTCH', 0x13), + ('LANG_ENGLISH', 0x09), + ('LANG_ESTONIAN', 0x25), + ('LANG_FAEROESE', 0x38), + ('LANG_FARSI', 0x29), + ('LANG_FINNISH', 0x0b), + ('LANG_FRENCH', 0x0c), + ('LANG_GALICIAN', 0x56), + ('LANG_GEORGIAN', 0x37), + ('LANG_GERMAN', 0x07), + ('LANG_GREEK', 0x08), + ('LANG_GUJARATI', 0x47), + ('LANG_HEBREW', 0x0d), + ('LANG_HINDI', 0x39), + ('LANG_HUNGARIAN', 0x0e), + ('LANG_ICELANDIC', 0x0f), + ('LANG_INDONESIAN', 0x21), + ('LANG_ITALIAN', 0x10), + ('LANG_JAPANESE', 0x11), + ('LANG_KANNADA', 0x4b), + ('LANG_KASHMIRI', 0x60), + ('LANG_KAZAK', 0x3f), + ('LANG_KONKANI', 0x57), + ('LANG_KOREAN', 0x12), + ('LANG_KYRGYZ', 0x40), + ('LANG_LATVIAN', 0x26), + ('LANG_LITHUANIAN', 0x27), + ('LANG_MACEDONIAN', 0x2f), + ('LANG_MALAY', 0x3e), + ('LANG_MALAYALAM', 0x4c), + ('LANG_MANIPURI', 0x58), + ('LANG_MARATHI', 0x4e), + ('LANG_MONGOLIAN', 0x50), + ('LANG_NEPALI', 0x61), + ('LANG_NORWEGIAN', 0x14), + ('LANG_ORIYA', 0x48), + ('LANG_POLISH', 0x15), + ('LANG_PORTUGUESE', 0x16), + ('LANG_PUNJABI', 0x46), + ('LANG_ROMANIAN', 0x18), + ('LANG_RUSSIAN', 0x19), + ('LANG_SANSKRIT', 0x4f), + ('LANG_SERBIAN', 0x1a), + ('LANG_SINDHI', 0x59), + ('LANG_SLOVAK', 0x1b), + ('LANG_SLOVENIAN', 0x24), + ('LANG_SPANISH', 0x0a), + ('LANG_SWAHILI', 0x41), + ('LANG_SWEDISH', 0x1d), + ('LANG_SYRIAC', 0x5a), + ('LANG_TAMIL', 0x49), + ('LANG_TATAR', 0x44), + ('LANG_TELUGU', 0x4a), + ('LANG_THAI', 0x1e), + ('LANG_TURKISH', 0x1f), + ('LANG_UKRAINIAN', 0x22), + ('LANG_URDU', 0x20), + ('LANG_UZBEK', 0x43), + ('LANG_VIETNAMESE', 0x2a), + ('LANG_GAELIC', 0x3c), + ('LANG_MALTESE', 0x3a), + ('LANG_MAORI', 0x28), + ('LANG_RHAETO_ROMANCE',0x17), + ('LANG_SAAMI', 0x3b), + ('LANG_SORBIAN', 0x2e), + ('LANG_SUTU', 0x30), + ('LANG_TSONGA', 0x31), + ('LANG_TSWANA', 0x32), + ('LANG_VENDA', 0x33), + ('LANG_XHOSA', 0x34), + ('LANG_ZULU', 0x35), + ('LANG_ESPERANTO', 0x8f), + ('LANG_WALON', 0x90), + ('LANG_CORNISH', 0x91), + ('LANG_WELSH', 0x92), + ('LANG_BRETON', 0x93) ] + +LANG = dict(lang+[(e[1], e[0]) for e in lang]) + + +# Sublanguage definitions +sublang = [ + ('SUBLANG_NEUTRAL', 0x00), + ('SUBLANG_DEFAULT', 0x01), + ('SUBLANG_SYS_DEFAULT', 0x02), + ('SUBLANG_ARABIC_SAUDI_ARABIA', 0x01), + ('SUBLANG_ARABIC_IRAQ', 0x02), + ('SUBLANG_ARABIC_EGYPT', 0x03), + ('SUBLANG_ARABIC_LIBYA', 0x04), + ('SUBLANG_ARABIC_ALGERIA', 0x05), + ('SUBLANG_ARABIC_MOROCCO', 0x06), + ('SUBLANG_ARABIC_TUNISIA', 0x07), + ('SUBLANG_ARABIC_OMAN', 0x08), + ('SUBLANG_ARABIC_YEMEN', 0x09), + ('SUBLANG_ARABIC_SYRIA', 0x0a), + ('SUBLANG_ARABIC_JORDAN', 0x0b), + ('SUBLANG_ARABIC_LEBANON', 0x0c), + ('SUBLANG_ARABIC_KUWAIT', 0x0d), + ('SUBLANG_ARABIC_UAE', 0x0e), + ('SUBLANG_ARABIC_BAHRAIN', 0x0f), + ('SUBLANG_ARABIC_QATAR', 0x10), + ('SUBLANG_AZERI_LATIN', 0x01), + ('SUBLANG_AZERI_CYRILLIC', 0x02), + ('SUBLANG_CHINESE_TRADITIONAL', 0x01), + ('SUBLANG_CHINESE_SIMPLIFIED', 0x02), + ('SUBLANG_CHINESE_HONGKONG', 0x03), + ('SUBLANG_CHINESE_SINGAPORE', 0x04), + ('SUBLANG_CHINESE_MACAU', 0x05), + ('SUBLANG_DUTCH', 0x01), + ('SUBLANG_DUTCH_BELGIAN', 0x02), + ('SUBLANG_ENGLISH_US', 0x01), + ('SUBLANG_ENGLISH_UK', 0x02), + ('SUBLANG_ENGLISH_AUS', 0x03), + ('SUBLANG_ENGLISH_CAN', 0x04), + ('SUBLANG_ENGLISH_NZ', 0x05), + ('SUBLANG_ENGLISH_EIRE', 0x06), + ('SUBLANG_ENGLISH_SOUTH_AFRICA', 0x07), + ('SUBLANG_ENGLISH_JAMAICA', 0x08), + ('SUBLANG_ENGLISH_CARIBBEAN', 0x09), + ('SUBLANG_ENGLISH_BELIZE', 0x0a), + ('SUBLANG_ENGLISH_TRINIDAD', 0x0b), + ('SUBLANG_ENGLISH_ZIMBABWE', 0x0c), + ('SUBLANG_ENGLISH_PHILIPPINES', 0x0d), + ('SUBLANG_FRENCH', 0x01), + ('SUBLANG_FRENCH_BELGIAN', 0x02), + ('SUBLANG_FRENCH_CANADIAN', 0x03), + ('SUBLANG_FRENCH_SWISS', 0x04), + ('SUBLANG_FRENCH_LUXEMBOURG', 0x05), + ('SUBLANG_FRENCH_MONACO', 0x06), + ('SUBLANG_GERMAN', 0x01), + ('SUBLANG_GERMAN_SWISS', 0x02), + ('SUBLANG_GERMAN_AUSTRIAN', 0x03), + ('SUBLANG_GERMAN_LUXEMBOURG', 0x04), + ('SUBLANG_GERMAN_LIECHTENSTEIN', 0x05), + ('SUBLANG_ITALIAN', 0x01), + ('SUBLANG_ITALIAN_SWISS', 0x02), + ('SUBLANG_KASHMIRI_SASIA', 0x02), + ('SUBLANG_KASHMIRI_INDIA', 0x02), + ('SUBLANG_KOREAN', 0x01), + ('SUBLANG_LITHUANIAN', 0x01), + ('SUBLANG_MALAY_MALAYSIA', 0x01), + ('SUBLANG_MALAY_BRUNEI_DARUSSALAM', 0x02), + ('SUBLANG_NEPALI_INDIA', 0x02), + ('SUBLANG_NORWEGIAN_BOKMAL', 0x01), + ('SUBLANG_NORWEGIAN_NYNORSK', 0x02), + ('SUBLANG_PORTUGUESE', 0x02), + ('SUBLANG_PORTUGUESE_BRAZILIAN', 0x01), + ('SUBLANG_SERBIAN_LATIN', 0x02), + ('SUBLANG_SERBIAN_CYRILLIC', 0x03), + ('SUBLANG_SPANISH', 0x01), + ('SUBLANG_SPANISH_MEXICAN', 0x02), + ('SUBLANG_SPANISH_MODERN', 0x03), + ('SUBLANG_SPANISH_GUATEMALA', 0x04), + ('SUBLANG_SPANISH_COSTA_RICA', 0x05), + ('SUBLANG_SPANISH_PANAMA', 0x06), + ('SUBLANG_SPANISH_DOMINICAN_REPUBLIC', 0x07), + ('SUBLANG_SPANISH_VENEZUELA', 0x08), + ('SUBLANG_SPANISH_COLOMBIA', 0x09), + ('SUBLANG_SPANISH_PERU', 0x0a), + ('SUBLANG_SPANISH_ARGENTINA', 0x0b), + ('SUBLANG_SPANISH_ECUADOR', 0x0c), + ('SUBLANG_SPANISH_CHILE', 0x0d), + ('SUBLANG_SPANISH_URUGUAY', 0x0e), + ('SUBLANG_SPANISH_PARAGUAY', 0x0f), + ('SUBLANG_SPANISH_BOLIVIA', 0x10), + ('SUBLANG_SPANISH_EL_SALVADOR', 0x11), + ('SUBLANG_SPANISH_HONDURAS', 0x12), + ('SUBLANG_SPANISH_NICARAGUA', 0x13), + ('SUBLANG_SPANISH_PUERTO_RICO', 0x14), + ('SUBLANG_SWEDISH', 0x01), + ('SUBLANG_SWEDISH_FINLAND', 0x02), + ('SUBLANG_URDU_PAKISTAN', 0x01), + ('SUBLANG_URDU_INDIA', 0x02), + ('SUBLANG_UZBEK_LATIN', 0x01), + ('SUBLANG_UZBEK_CYRILLIC', 0x02), + ('SUBLANG_DUTCH_SURINAM', 0x03), + ('SUBLANG_ROMANIAN', 0x01), + ('SUBLANG_ROMANIAN_MOLDAVIA', 0x02), + ('SUBLANG_RUSSIAN', 0x01), + ('SUBLANG_RUSSIAN_MOLDAVIA', 0x02), + ('SUBLANG_CROATIAN', 0x01), + ('SUBLANG_LITHUANIAN_CLASSIC', 0x02), + ('SUBLANG_GAELIC', 0x01), + ('SUBLANG_GAELIC_SCOTTISH', 0x02), + ('SUBLANG_GAELIC_MANX', 0x03) ] + +SUBLANG = dict(sublang+[(e[1], e[0]) for e in sublang]) + +# Initialize the dictionary with all the name->value pairs +SUBLANG = dict( sublang ) +# Now add all the value->name information, handling duplicates appropriately +for sublang_name, sublang_value in sublang: + if SUBLANG.has_key( sublang_value ): + SUBLANG[ sublang_value ].append( sublang_name ) + else: + SUBLANG[ sublang_value ] = [ sublang_name ] + +# Resolve a sublang name given the main lang name +# +def get_sublang_name_for_lang( lang_value, sublang_value ): + lang_name = LANG.get(lang_value, '*unknown*') + for sublang_name in SUBLANG.get(sublang_value, list()): + # if the main language is a substring of sublang's name, then + # return that + if lang_name in sublang_name: + return sublang_name + # otherwise return the first sublang name + return SUBLANG.get(sublang_value, ['*unknown*'])[0] + + +# Ange Albertini's code to process resources' strings +# +def parse_strings(data, counter, l): + i = 0 + error_count = 0 + while i < len(data): + + data_slice = data[i:i + 2] + if len(data_slice) < 2: + break + + len_ = struct.unpack("= 3: + break + i += len_ * 2 + counter += 1 + + +def retrieve_flags(flag_dict, flag_filter): + """Read the flags from a dictionary and return them in a usable form. + + Will return a list of (flag, value) for all flags in "flag_dict" + matching the filter "flag_filter". + """ + + return [(f[0], f[1]) for f in flag_dict.items() if + isinstance(f[0], str) and f[0].startswith(flag_filter)] + + +def set_flags(obj, flag_field, flags): + """Will process the flags and set attributes in the object accordingly. + + The object "obj" will gain attributes named after the flags provided in + "flags" and valued True/False, matching the results of applying each + flag value from "flags" to flag_field. + """ + + for flag in flags: + if flag[1] & flag_field: + #setattr(obj, flag[0], True) + obj.__dict__[flag[0]] = True + else: + #setattr(obj, flag[0], False) + obj.__dict__[flag[0]] = False + + +# According to http://corkami.blogspot.com/2010/01/parce-que-la-planche-aura-brule.html +# if PointerToRawData is less that 0x200 it's rounded to zero. Loading the test file +# in a debugger it's easy to verify that the PointerToRawData value of 1 is rounded +# to zero. Hence we reproduce the behabior +# +# According to the document: +# [ Microsoft Portable Executable and Common Object File Format Specification ] +# "The alignment factor (in bytes) that is used to align the raw data of sections in +# the image file. The value should be a power of 2 between 512 and 64 K, inclusive. +# The default is 512. If the SectionAlignment is less than the architecture’s page +# size, then FileAlignment must match SectionAlignment." +# +def adjust_FileAlignment( val, file_aligment ): + + #if file_aligment and val % file_aligment: + # return file_aligment * ( val / file_aligment ) + return val + + +# According to the document: +# [ Microsoft Portable Executable and Common Object File Format Specification ] +# "The alignment (in bytes) of sections when they are loaded into memory. It must be +# greater than or equal to FileAlignment. The default is the page size for the +# architecture." +# +def adjust_SectionAlignment( val, section_alignment, file_aligment ): + + if section_alignment < 0x1000: # page size + section_alignment = file_aligment + + # 0x200 is the minimum valid FileAlignment according to the documentation + # although ntoskrnl.exe has an alignment of 0x80 in some Windows versions + # + #elif section_alignment < 0x80: + # section_alignment = 0x80 + + if section_alignment and val % section_alignment: + return section_alignment * ( val / section_alignment ) + return val + + +class UnicodeStringWrapperPostProcessor: + """This class attempts to help the process of identifying strings + that might be plain Unicode or Pascal. A list of strings will be + wrapped on it with the hope the overlappings will help make the + decision about their type.""" + + def __init__(self, pe, rva_ptr): + self.pe = pe + self.rva_ptr = rva_ptr + self.string = None + + + def get_rva(self): + """Get the RVA of the string.""" + + return self.rva_ptr + + + def __str__(self): + """Return the escaped ASCII representation of the string.""" + + def convert_char(char): + if char in string.printable: + return char + else: + return r'\x%02x' % ord(char) + + if self.string: + return ''.join([convert_char(c) for c in self.string]) + + return '' + + + def invalidate(self): + """Make this instance None, to express it's no known string type.""" + + self = None + + + def render_pascal_16(self): + + self.string = self.pe.get_string_u_at_rva( + self.rva_ptr+2, + max_length=self.__get_pascal_16_length()) + + + def ask_pascal_16(self, next_rva_ptr): + """The next RVA is taken to be the one immediately following this one. + + Such RVA could indicate the natural end of the string and will be checked + with the possible length contained in the first word. + """ + + length = self.__get_pascal_16_length() + + if length == (next_rva_ptr - (self.rva_ptr+2)) / 2: + self.length = length + return True + + return False + + + def __get_pascal_16_length(self): + + return self.__get_word_value_at_rva(self.rva_ptr) + + + def __get_word_value_at_rva(self, rva): + + try: + data = self.pe.get_data(self.rva_ptr, 2) + except PEFormatError, e: + return False + + if len(data)<2: + return False + + return struct.unpack(' self.__format_length__: + data = data[:self.__format_length__] + + # OC Patch: + # Some malware have incorrect header lengths. + # Fail gracefully if this occurs + # Buggy malware: a29b0118af8b7408444df81701ad5a7f + # + elif len(data) < self.__format_length__: + raise PEFormatError('Data length less than expected header length.') + + + if data.count(chr(0)) == len(data): + self.__all_zeroes__ = True + + self.__unpacked_data_elms__ = struct.unpack(self.__format__, data) + for i in xrange(len(self.__unpacked_data_elms__)): + for key in self.__keys__[i]: + #self.values[key] = self.__unpacked_data_elms__[i] + setattr(self, key, self.__unpacked_data_elms__[i]) + + + def __pack__(self): + + new_values = [] + + for i in xrange(len(self.__unpacked_data_elms__)): + + for key in self.__keys__[i]: + new_val = getattr(self, key) + old_val = self.__unpacked_data_elms__[i] + + # In the case of Unions, when the first changed value + # is picked the loop is exited + if new_val != old_val: + break + + new_values.append(new_val) + + return struct.pack(self.__format__, *new_values) + + + def __str__(self): + return '\n'.join( self.dump() ) + + def __repr__(self): + return '' % (' '.join( [' '.join(s.split()) for s in self.dump()] )) + + + def dump(self, indentation=0): + """Returns a string representation of the structure.""" + + dump = [] + + dump.append('[%s]' % self.name) + + # Refer to the __set_format__ method for an explanation + # of the following construct. + for keys in self.__keys__: + for key in keys: + + val = getattr(self, key) + if isinstance(val, int) or isinstance(val, long): + val_str = '0x%-8X' % (val) + if key == 'TimeDateStamp' or key == 'dwTimeStamp': + try: + val_str += ' [%s UTC]' % time.asctime(time.gmtime(val)) + except exceptions.ValueError, e: + val_str += ' [INVALID TIME]' + else: + val_str = ''.join(filter(lambda c:c != '\0', str(val))) + + dump.append('0x%-8X 0x%-3X %-30s %s' % ( + self.__field_offsets__[key] + self.__file_offset__, + self.__field_offsets__[key], key+':', val_str)) + + return dump + + + +class SectionStructure(Structure): + """Convenience section handling class.""" + + def __init__(self, *argl, **argd): + if 'pe' in argd: + self.pe = argd['pe'] + del argd['pe'] + + Structure.__init__(self, *argl, **argd) + + def get_data(self, start=None, length=None): + """Get data chunk from a section. + + Allows to query data from the section by passing the + addresses where the PE file would be loaded by default. + It is then possible to retrieve code and data by its real + addresses as it would be if loaded. + """ + + PointerToRawData_adj = adjust_FileAlignment( self.PointerToRawData, + self.pe.OPTIONAL_HEADER.FileAlignment ) + VirtualAddress_adj = adjust_SectionAlignment( self.VirtualAddress, + self.pe.OPTIONAL_HEADER.SectionAlignment, self.pe.OPTIONAL_HEADER.FileAlignment ) + + if start is None: + offset = PointerToRawData_adj + else: + offset = ( start - VirtualAddress_adj ) + PointerToRawData_adj + + if length is not None: + end = offset + length + else: + end = offset + self.SizeOfRawData + + # PointerToRawData is not adjusted here as we might want to read any possible extra bytes + # that might get cut off by aligning the start (and hence cutting something off the end) + # + if end > self.PointerToRawData + self.SizeOfRawData: + end = self.PointerToRawData + self.SizeOfRawData + + return self.pe.__data__[offset:end] + + + def __setattr__(self, name, val): + + if name == 'Characteristics': + section_flags = retrieve_flags(SECTION_CHARACTERISTICS, 'IMAGE_SCN_') + + # Set the section's flags according the the Characteristics member + set_flags(self, val, section_flags) + + elif 'IMAGE_SCN_' in name and hasattr(self, name): + if val: + self.__dict__['Characteristics'] |= SECTION_CHARACTERISTICS[name] + else: + self.__dict__['Characteristics'] ^= SECTION_CHARACTERISTICS[name] + + self.__dict__[name] = val + + + def get_rva_from_offset(self, offset): + return offset - adjust_FileAlignment( self.PointerToRawData, + self.pe.OPTIONAL_HEADER.FileAlignment ) + adjust_SectionAlignment( self.VirtualAddress, + self.pe.OPTIONAL_HEADER.SectionAlignment, self.pe.OPTIONAL_HEADER.FileAlignment ) + + + def get_offset_from_rva(self, rva): + return (rva - + adjust_SectionAlignment( + self.VirtualAddress, + self.pe.OPTIONAL_HEADER.SectionAlignment, + self.pe.OPTIONAL_HEADER.FileAlignment ) + ) + adjust_FileAlignment( + self.PointerToRawData, + self.pe.OPTIONAL_HEADER.FileAlignment ) + + + def contains_offset(self, offset): + """Check whether the section contains the file offset provided.""" + + if self.PointerToRawData is None: + # bss and other sections containing only uninitialized data must have 0 + # and do not take space in the file + return False + return ( adjust_FileAlignment( self.PointerToRawData, + self.pe.OPTIONAL_HEADER.FileAlignment ) <= + offset < + adjust_FileAlignment( self.PointerToRawData, + self.pe.OPTIONAL_HEADER.FileAlignment ) + + self.SizeOfRawData ) + + + def contains_rva(self, rva): + """Check whether the section contains the address provided.""" + + # Check if the SizeOfRawData is realistic. If it's bigger than the size of + # the whole PE file minus the start address of the section it could be + # either truncated or the SizeOfRawData contain a misleading value. + # In either of those cases we take the VirtualSize + # + if len(self.pe.__data__) - adjust_FileAlignment( self.PointerToRawData, + self.pe.OPTIONAL_HEADER.FileAlignment ) < self.SizeOfRawData: + # PECOFF documentation v8 says: + # VirtualSize: The total size of the section when loaded into memory. + # If this value is greater than SizeOfRawData, the section is zero-padded. + # This field is valid only for executable images and should be set to zero + # for object files. + # + size = self.Misc_VirtualSize + else: + size = max(self.SizeOfRawData, self.Misc_VirtualSize) + + VirtualAddress_adj = adjust_SectionAlignment( self.VirtualAddress, + self.pe.OPTIONAL_HEADER.SectionAlignment, self.pe.OPTIONAL_HEADER.FileAlignment ) + + return VirtualAddress_adj <= rva < VirtualAddress_adj + size + + + def contains(self, rva): + #print "DEPRECATION WARNING: you should use contains_rva() instead of contains()" + return self.contains_rva(rva) + + + #def set_data(self, data): + # """Set the data belonging to the section.""" + # + # self.data = data + + + def get_entropy(self): + """Calculate and return the entropy for the section.""" + + return self.entropy_H( self.get_data() ) + + + def get_hash_sha1(self): + """Get the SHA-1 hex-digest of the section's data.""" + + if sha1 is not None: + return sha1( self.get_data() ).hexdigest() + + + def get_hash_sha256(self): + """Get the SHA-256 hex-digest of the section's data.""" + + if sha256 is not None: + return sha256( self.get_data() ).hexdigest() + + + def get_hash_sha512(self): + """Get the SHA-512 hex-digest of the section's data.""" + + if sha512 is not None: + return sha512( self.get_data() ).hexdigest() + + + def get_hash_md5(self): + """Get the MD5 hex-digest of the section's data.""" + + if md5 is not None: + return md5( self.get_data() ).hexdigest() + + + def entropy_H(self, data): + """Calculate the entropy of a chunk of data.""" + + if len(data) == 0: + return 0.0 + + occurences = array.array('L', [0]*256) + + for x in data: + occurences[ord(x)] += 1 + + entropy = 0 + for x in occurences: + if x: + p_x = float(x) / len(data) + entropy -= p_x*math.log(p_x, 2) + + return entropy + + + +class DataContainer: + """Generic data container.""" + + def __init__(self, **args): + for key, value in args.items(): + setattr(self, key, value) + + + +class ImportDescData(DataContainer): + """Holds import descriptor information. + + dll: name of the imported DLL + imports: list of imported symbols (ImportData instances) + struct: IMAGE_IMPORT_DESCRIPTOR structure + """ + +class ImportData(DataContainer): + """Holds imported symbol's information. + + ordinal: Ordinal of the symbol + name: Name of the symbol + bound: If the symbol is bound, this contains + the address. + """ + + + def __setattr__(self, name, val): + + # If the instance doesn't yet have an ordinal attribute + # it's not fully initialized so can't do any of the + # following + # + if hasattr(self, 'ordinal') and hasattr(self, 'bound') and hasattr(self, 'name'): + + if name == 'ordinal': + + if self.pe.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE: + ordinal_flag = IMAGE_ORDINAL_FLAG + elif self.pe.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS: + ordinal_flag = IMAGE_ORDINAL_FLAG64 + + # Set the ordinal and flag the entry as imporing by ordinal + self.struct_table.Ordinal = ordinal_flag | (val & 0xffff) + self.struct_table.AddressOfData = self.struct_table.Ordinal + self.struct_table.Function = self.struct_table.Ordinal + self.struct_table.ForwarderString = self.struct_table.Ordinal + elif name == 'bound': + if self.struct_iat is not None: + self.struct_iat.AddressOfData = val + self.struct_iat.AddressOfData = self.struct_iat.AddressOfData + self.struct_iat.Function = self.struct_iat.AddressOfData + self.struct_iat.ForwarderString = self.struct_iat.AddressOfData + elif name == 'address': + self.struct_table.AddressOfData = val + self.struct_table.Ordinal = self.struct_table.AddressOfData + self.struct_table.Function = self.struct_table.AddressOfData + self.struct_table.ForwarderString = self.struct_table.AddressOfData + elif name == 'name': + # Make sure we reset the entry in case the import had been set to import by ordinal + if self.name_offset: + + name_rva = self.pe.get_rva_from_offset( self.name_offset ) + self.pe.set_dword_at_offset( self.ordinal_offset, (0<<31) | name_rva ) + + # Complain if the length of the new name is longer than the existing one + if len(val) > len(self.name): + #raise Exception('The export name provided is longer than the existing one.') + pass + self.pe.set_bytes_at_offset( self.name_offset, val ) + + self.__dict__[name] = val + + +class ExportDirData(DataContainer): + """Holds export directory information. + + struct: IMAGE_EXPORT_DIRECTORY structure + symbols: list of exported symbols (ExportData instances) +""" + +class ExportData(DataContainer): + """Holds exported symbols' information. + + ordinal: ordinal of the symbol + address: address of the symbol + name: name of the symbol (None if the symbol is + exported by ordinal only) + forwarder: if the symbol is forwarded it will + contain the name of the target symbol, + None otherwise. + """ + + def __setattr__(self, name, val): + + # If the instance doesn't yet have an ordinal attribute + # it's not fully initialized so can't do any of the + # following + # + if hasattr(self, 'ordinal') and hasattr(self, 'address') and hasattr(self, 'forwarder') and hasattr(self, 'name'): + + if name == 'ordinal': + self.pe.set_word_at_offset( self.ordinal_offset, val ) + elif name == 'address': + self.pe.set_dword_at_offset( self.address_offset, val ) + elif name == 'name': + # Complain if the length of the new name is longer than the existing one + if len(val) > len(self.name): + #raise Exception('The export name provided is longer than the existing one.') + pass + self.pe.set_bytes_at_offset( self.name_offset, val ) + elif name == 'forwarder': + # Complain if the length of the new name is longer than the existing one + if len(val) > len(self.forwarder): + #raise Exception('The forwarder name provided is longer than the existing one.') + pass + self.pe.set_bytes_at_offset( self.forwarder_offset, val ) + + self.__dict__[name] = val + + +class ResourceDirData(DataContainer): + """Holds resource directory information. + + struct: IMAGE_RESOURCE_DIRECTORY structure + entries: list of entries (ResourceDirEntryData instances) + """ + +class ResourceDirEntryData(DataContainer): + """Holds resource directory entry data. + + struct: IMAGE_RESOURCE_DIRECTORY_ENTRY structure + name: If the resource is identified by name this + attribute will contain the name string. None + otherwise. If identified by id, the id is + available at 'struct.Id' + id: the id, also in struct.Id + directory: If this entry has a lower level directory + this attribute will point to the + ResourceDirData instance representing it. + data: If this entry has no further lower directories + and points to the actual resource data, this + attribute will reference the corresponding + ResourceDataEntryData instance. + (Either of the 'directory' or 'data' attribute will exist, + but not both.) + """ + +class ResourceDataEntryData(DataContainer): + """Holds resource data entry information. + + struct: IMAGE_RESOURCE_DATA_ENTRY structure + lang: Primary language ID + sublang: Sublanguage ID + """ + +class DebugData(DataContainer): + """Holds debug information. + + struct: IMAGE_DEBUG_DIRECTORY structure + """ + +class BaseRelocationData(DataContainer): + """Holds base relocation information. + + struct: IMAGE_BASE_RELOCATION structure + entries: list of relocation data (RelocationData instances) + """ + +class RelocationData(DataContainer): + """Holds relocation information. + + type: Type of relocation + The type string is can be obtained by + RELOCATION_TYPE[type] + rva: RVA of the relocation + """ + def __setattr__(self, name, val): + + # If the instance doesn't yet have a struct attribute + # it's not fully initialized so can't do any of the + # following + # + if hasattr(self, 'struct'): + # Get the word containing the type and data + # + word = self.struct.Data + + if name == 'type': + word = (val << 12) | (word & 0xfff) + elif name == 'rva': + offset = val-self.base_rva + if offset < 0: + offset = 0 + word = ( word & 0xf000) | ( offset & 0xfff) + + # Store the modified data + # + self.struct.Data = word + + self.__dict__[name] = val + +class TlsData(DataContainer): + """Holds TLS information. + + struct: IMAGE_TLS_DIRECTORY structure + """ + +class BoundImportDescData(DataContainer): + """Holds bound import descriptor data. + + This directory entry will provide with information on the + DLLs this PE files has been bound to (if bound at all). + The structure will contain the name and timestamp of the + DLL at the time of binding so that the loader can know + whether it differs from the one currently present in the + system and must, therefore, re-bind the PE's imports. + + struct: IMAGE_BOUND_IMPORT_DESCRIPTOR structure + name: DLL name + entries: list of entries (BoundImportRefData instances) + the entries will exist if this DLL has forwarded + symbols. If so, the destination DLL will have an + entry in this list. + """ + +class LoadConfigData(DataContainer): + """Holds Load Config data. + + struct: IMAGE_LOAD_CONFIG_DIRECTORY structure + name: dll name + """ + +class BoundImportRefData(DataContainer): + """Holds bound import forwarder reference data. + + Contains the same information as the bound descriptor but + for forwarded DLLs, if any. + + struct: IMAGE_BOUND_FORWARDER_REF structure + name: dll name + """ + + +# Valid FAT32 8.3 short filename characters according to: +# http://en.wikipedia.org/wiki/8.3_filename +# This will help decide whether DLL ASCII names are likely +# to be valid of otherwise corruted data +# +# The flename length is not checked because the DLLs filename +# can be longer that the 8.3 +allowed_filename = string.lowercase + string.uppercase + string.digits + "!#$%&'()-@^_`{}~+,.;=[]" + ''.join( [chr(i) for i in range(128, 256)] ) +def is_valid_dos_filename(s): + if s is None or not isinstance(s, str): + return False + for c in s: + if c not in allowed_filename: + return False + return True + + +# Check if a imported name uses the valid accepted characters expected in mangled +# function names. If the symbol's characters don't fall within this charset +# we will assume the name is invalid +# +allowed_function_name = string.lowercase + string.uppercase + string.digits + '_?@$()' +def is_valid_function_name(s): + if s is None or not isinstance(s, str): + return False + for c in s: + if c not in allowed_function_name: + return False + return True + + + +class PE: + """A Portable Executable representation. + + This class provides access to most of the information in a PE file. + + It expects to be supplied the name of the file to load or PE data + to process and an optional argument 'fast_load' (False by default) + which controls whether to load all the directories information, + which can be quite time consuming. + + pe = pefile.PE('module.dll') + pe = pefile.PE(name='module.dll') + + would load 'module.dll' and process it. If the data would be already + available in a buffer the same could be achieved with: + + pe = pefile.PE(data=module_dll_data) + + The "fast_load" can be set to a default by setting its value in the + module itself by means,for instance, of a "pefile.fast_load = True". + That will make all the subsequent instances not to load the + whole PE structure. The "full_load" method can be used to parse + the missing data at a later stage. + + Basic headers information will be available in the attributes: + + DOS_HEADER + NT_HEADERS + FILE_HEADER + OPTIONAL_HEADER + + All of them will contain among their attributes the members of the + corresponding structures as defined in WINNT.H + + The raw data corresponding to the header (from the beginning of the + file up to the start of the first section) will be available in the + instance's attribute 'header' as a string. + + The sections will be available as a list in the 'sections' attribute. + Each entry will contain as attributes all the structure's members. + + Directory entries will be available as attributes (if they exist): + (no other entries are processed at this point) + + DIRECTORY_ENTRY_IMPORT (list of ImportDescData instances) + DIRECTORY_ENTRY_EXPORT (ExportDirData instance) + DIRECTORY_ENTRY_RESOURCE (ResourceDirData instance) + DIRECTORY_ENTRY_DEBUG (list of DebugData instances) + DIRECTORY_ENTRY_BASERELOC (list of BaseRelocationData instances) + DIRECTORY_ENTRY_TLS + DIRECTORY_ENTRY_BOUND_IMPORT (list of BoundImportData instances) + + The following dictionary attributes provide ways of mapping different + constants. They will accept the numeric value and return the string + representation and the opposite, feed in the string and get the + numeric constant: + + DIRECTORY_ENTRY + IMAGE_CHARACTERISTICS + SECTION_CHARACTERISTICS + DEBUG_TYPE + SUBSYSTEM_TYPE + MACHINE_TYPE + RELOCATION_TYPE + RESOURCE_TYPE + LANG + SUBLANG + """ + + # + # Format specifications for PE structures. + # + + __IMAGE_DOS_HEADER_format__ = ('IMAGE_DOS_HEADER', + ('H,e_magic', 'H,e_cblp', 'H,e_cp', + 'H,e_crlc', 'H,e_cparhdr', 'H,e_minalloc', + 'H,e_maxalloc', 'H,e_ss', 'H,e_sp', 'H,e_csum', + 'H,e_ip', 'H,e_cs', 'H,e_lfarlc', 'H,e_ovno', '8s,e_res', + 'H,e_oemid', 'H,e_oeminfo', '20s,e_res2', + 'I,e_lfanew')) + + __IMAGE_FILE_HEADER_format__ = ('IMAGE_FILE_HEADER', + ('H,Machine', 'H,NumberOfSections', + 'I,TimeDateStamp', 'I,PointerToSymbolTable', + 'I,NumberOfSymbols', 'H,SizeOfOptionalHeader', + 'H,Characteristics')) + + __IMAGE_DATA_DIRECTORY_format__ = ('IMAGE_DATA_DIRECTORY', + ('I,VirtualAddress', 'I,Size')) + + + __IMAGE_OPTIONAL_HEADER_format__ = ('IMAGE_OPTIONAL_HEADER', + ('H,Magic', 'B,MajorLinkerVersion', + 'B,MinorLinkerVersion', 'I,SizeOfCode', + 'I,SizeOfInitializedData', 'I,SizeOfUninitializedData', + 'I,AddressOfEntryPoint', 'I,BaseOfCode', 'I,BaseOfData', + 'I,ImageBase', 'I,SectionAlignment', 'I,FileAlignment', + 'H,MajorOperatingSystemVersion', 'H,MinorOperatingSystemVersion', + 'H,MajorImageVersion', 'H,MinorImageVersion', + 'H,MajorSubsystemVersion', 'H,MinorSubsystemVersion', + 'I,Reserved1', 'I,SizeOfImage', 'I,SizeOfHeaders', + 'I,CheckSum', 'H,Subsystem', 'H,DllCharacteristics', + 'I,SizeOfStackReserve', 'I,SizeOfStackCommit', + 'I,SizeOfHeapReserve', 'I,SizeOfHeapCommit', + 'I,LoaderFlags', 'I,NumberOfRvaAndSizes' )) + + + __IMAGE_OPTIONAL_HEADER64_format__ = ('IMAGE_OPTIONAL_HEADER64', + ('H,Magic', 'B,MajorLinkerVersion', + 'B,MinorLinkerVersion', 'I,SizeOfCode', + 'I,SizeOfInitializedData', 'I,SizeOfUninitializedData', + 'I,AddressOfEntryPoint', 'I,BaseOfCode', + 'Q,ImageBase', 'I,SectionAlignment', 'I,FileAlignment', + 'H,MajorOperatingSystemVersion', 'H,MinorOperatingSystemVersion', + 'H,MajorImageVersion', 'H,MinorImageVersion', + 'H,MajorSubsystemVersion', 'H,MinorSubsystemVersion', + 'I,Reserved1', 'I,SizeOfImage', 'I,SizeOfHeaders', + 'I,CheckSum', 'H,Subsystem', 'H,DllCharacteristics', + 'Q,SizeOfStackReserve', 'Q,SizeOfStackCommit', + 'Q,SizeOfHeapReserve', 'Q,SizeOfHeapCommit', + 'I,LoaderFlags', 'I,NumberOfRvaAndSizes' )) + + + __IMAGE_NT_HEADERS_format__ = ('IMAGE_NT_HEADERS', ('I,Signature',)) + + __IMAGE_SECTION_HEADER_format__ = ('IMAGE_SECTION_HEADER', + ('8s,Name', 'I,Misc,Misc_PhysicalAddress,Misc_VirtualSize', + 'I,VirtualAddress', 'I,SizeOfRawData', 'I,PointerToRawData', + 'I,PointerToRelocations', 'I,PointerToLinenumbers', + 'H,NumberOfRelocations', 'H,NumberOfLinenumbers', + 'I,Characteristics')) + + __IMAGE_DELAY_IMPORT_DESCRIPTOR_format__ = ('IMAGE_DELAY_IMPORT_DESCRIPTOR', + ('I,grAttrs', 'I,szName', 'I,phmod', 'I,pIAT', 'I,pINT', + 'I,pBoundIAT', 'I,pUnloadIAT', 'I,dwTimeStamp')) + + __IMAGE_IMPORT_DESCRIPTOR_format__ = ('IMAGE_IMPORT_DESCRIPTOR', + ('I,OriginalFirstThunk,Characteristics', + 'I,TimeDateStamp', 'I,ForwarderChain', 'I,Name', 'I,FirstThunk')) + + __IMAGE_EXPORT_DIRECTORY_format__ = ('IMAGE_EXPORT_DIRECTORY', + ('I,Characteristics', + 'I,TimeDateStamp', 'H,MajorVersion', 'H,MinorVersion', 'I,Name', + 'I,Base', 'I,NumberOfFunctions', 'I,NumberOfNames', + 'I,AddressOfFunctions', 'I,AddressOfNames', 'I,AddressOfNameOrdinals')) + + __IMAGE_RESOURCE_DIRECTORY_format__ = ('IMAGE_RESOURCE_DIRECTORY', + ('I,Characteristics', + 'I,TimeDateStamp', 'H,MajorVersion', 'H,MinorVersion', + 'H,NumberOfNamedEntries', 'H,NumberOfIdEntries')) + + __IMAGE_RESOURCE_DIRECTORY_ENTRY_format__ = ('IMAGE_RESOURCE_DIRECTORY_ENTRY', + ('I,Name', + 'I,OffsetToData')) + + __IMAGE_RESOURCE_DATA_ENTRY_format__ = ('IMAGE_RESOURCE_DATA_ENTRY', + ('I,OffsetToData', 'I,Size', 'I,CodePage', 'I,Reserved')) + + __VS_VERSIONINFO_format__ = ( 'VS_VERSIONINFO', + ('H,Length', 'H,ValueLength', 'H,Type' )) + + __VS_FIXEDFILEINFO_format__ = ( 'VS_FIXEDFILEINFO', + ('I,Signature', 'I,StrucVersion', 'I,FileVersionMS', 'I,FileVersionLS', + 'I,ProductVersionMS', 'I,ProductVersionLS', 'I,FileFlagsMask', 'I,FileFlags', + 'I,FileOS', 'I,FileType', 'I,FileSubtype', 'I,FileDateMS', 'I,FileDateLS')) + + __StringFileInfo_format__ = ( 'StringFileInfo', + ('H,Length', 'H,ValueLength', 'H,Type' )) + + __StringTable_format__ = ( 'StringTable', + ('H,Length', 'H,ValueLength', 'H,Type' )) + + __String_format__ = ( 'String', + ('H,Length', 'H,ValueLength', 'H,Type' )) + + __Var_format__ = ( 'Var', ('H,Length', 'H,ValueLength', 'H,Type' )) + + __IMAGE_THUNK_DATA_format__ = ('IMAGE_THUNK_DATA', + ('I,ForwarderString,Function,Ordinal,AddressOfData',)) + + __IMAGE_THUNK_DATA64_format__ = ('IMAGE_THUNK_DATA', + ('Q,ForwarderString,Function,Ordinal,AddressOfData',)) + + __IMAGE_DEBUG_DIRECTORY_format__ = ('IMAGE_DEBUG_DIRECTORY', + ('I,Characteristics', 'I,TimeDateStamp', 'H,MajorVersion', + 'H,MinorVersion', 'I,Type', 'I,SizeOfData', 'I,AddressOfRawData', + 'I,PointerToRawData')) + + __IMAGE_BASE_RELOCATION_format__ = ('IMAGE_BASE_RELOCATION', + ('I,VirtualAddress', 'I,SizeOfBlock') ) + + __IMAGE_BASE_RELOCATION_ENTRY_format__ = ('IMAGE_BASE_RELOCATION_ENTRY', + ('H,Data',) ) + + __IMAGE_TLS_DIRECTORY_format__ = ('IMAGE_TLS_DIRECTORY', + ('I,StartAddressOfRawData', 'I,EndAddressOfRawData', + 'I,AddressOfIndex', 'I,AddressOfCallBacks', + 'I,SizeOfZeroFill', 'I,Characteristics' ) ) + + __IMAGE_TLS_DIRECTORY64_format__ = ('IMAGE_TLS_DIRECTORY', + ('Q,StartAddressOfRawData', 'Q,EndAddressOfRawData', + 'Q,AddressOfIndex', 'Q,AddressOfCallBacks', + 'I,SizeOfZeroFill', 'I,Characteristics' ) ) + + __IMAGE_LOAD_CONFIG_DIRECTORY_format__ = ('IMAGE_LOAD_CONFIG_DIRECTORY', + ('I,Size', 'I,TimeDateStamp', + 'H,MajorVersion', 'H,MinorVersion', + 'I,GlobalFlagsClear', 'I,GlobalFlagsSet', + 'I,CriticalSectionDefaultTimeout', + 'I,DeCommitFreeBlockThreshold', + 'I,DeCommitTotalFreeThreshold', + 'I,LockPrefixTable', + 'I,MaximumAllocationSize', + 'I,VirtualMemoryThreshold', + 'I,ProcessHeapFlags', + 'I,ProcessAffinityMask', + 'H,CSDVersion', 'H,Reserved1', + 'I,EditList', 'I,SecurityCookie', + 'I,SEHandlerTable', 'I,SEHandlerCount' ) ) + + __IMAGE_LOAD_CONFIG_DIRECTORY64_format__ = ('IMAGE_LOAD_CONFIG_DIRECTORY', + ('I,Size', 'I,TimeDateStamp', + 'H,MajorVersion', 'H,MinorVersion', + 'I,GlobalFlagsClear', 'I,GlobalFlagsSet', + 'I,CriticalSectionDefaultTimeout', + 'Q,DeCommitFreeBlockThreshold', + 'Q,DeCommitTotalFreeThreshold', + 'Q,LockPrefixTable', + 'Q,MaximumAllocationSize', + 'Q,VirtualMemoryThreshold', + 'Q,ProcessAffinityMask', + 'I,ProcessHeapFlags', + 'H,CSDVersion', 'H,Reserved1', + 'Q,EditList', 'Q,SecurityCookie', + 'Q,SEHandlerTable', 'Q,SEHandlerCount' ) ) + + __IMAGE_BOUND_IMPORT_DESCRIPTOR_format__ = ('IMAGE_BOUND_IMPORT_DESCRIPTOR', + ('I,TimeDateStamp', 'H,OffsetModuleName', 'H,NumberOfModuleForwarderRefs')) + + __IMAGE_BOUND_FORWARDER_REF_format__ = ('IMAGE_BOUND_FORWARDER_REF', + ('I,TimeDateStamp', 'H,OffsetModuleName', 'H,Reserved') ) + + + def __init__(self, name=None, data=None, fast_load=None): + + self.sections = [] + + self.__warnings = [] + + self.PE_TYPE = None + + if not name and not data: + return + + # This list will keep track of all the structures created. + # That will allow for an easy iteration through the list + # in order to save the modifications made + self.__structures__ = [] + + if not fast_load: + fast_load = globals()['fast_load'] + self.__parse__(name, data, fast_load) + + + + def __unpack_data__(self, format, data, file_offset): + """Apply structure format to raw data. + + Returns and unpacked structure object if successful, None otherwise. + """ + + structure = Structure(format, file_offset=file_offset) + + try: + structure.__unpack__(data) + except PEFormatError, err: + self.__warnings.append( + 'Corrupt header "%s" at file offset %d. Exception: %s' % ( + format[0], file_offset, str(err)) ) + return None + + self.__structures__.append(structure) + + return structure + + + def __parse__(self, fname, data, fast_load): + """Parse a Portable Executable file. + + Loads a PE file, parsing all its structures and making them available + through the instance's attributes. + """ + + if fname: + fd = file(fname, 'rb') + self.fileno = fd.fileno() + self.__data__ = mmap.mmap( self.fileno, 0, access = mmap.ACCESS_READ ) + fd.close() + elif data: + self.__data__ = data + + dos_header_data = self.__data__[:64] + if len(dos_header_data) != 64: + raise PEFormatError('Unable to read the DOS Header, possibly a truncated file.') + + self.DOS_HEADER = self.__unpack_data__( + self.__IMAGE_DOS_HEADER_format__, + dos_header_data, file_offset=0) + + if self.DOS_HEADER.e_magic == IMAGE_DOSZM_SIGNATURE: + raise PEFormatError('Probably a ZM Executable (not a PE file).') + if not self.DOS_HEADER or self.DOS_HEADER.e_magic != IMAGE_DOS_SIGNATURE: + raise PEFormatError('DOS Header magic not found.') + + # OC Patch: + # Check for sane value in e_lfanew + # + if self.DOS_HEADER.e_lfanew > len(self.__data__): + raise PEFormatError('Invalid e_lfanew value, probably not a PE file') + + nt_headers_offset = self.DOS_HEADER.e_lfanew + + self.NT_HEADERS = self.__unpack_data__( + self.__IMAGE_NT_HEADERS_format__, + self.__data__[nt_headers_offset:nt_headers_offset+8], + file_offset = nt_headers_offset) + + # We better check the signature right here, before the file screws + # around with sections: + # OC Patch: + # Some malware will cause the Signature value to not exist at all + if not self.NT_HEADERS or not self.NT_HEADERS.Signature: + raise PEFormatError('NT Headers not found.') + + if (0xFFFF & self.NT_HEADERS.Signature) == IMAGE_NE_SIGNATURE: + raise PEFormatError('Invalid NT Headers signature. Probably a NE file') + if (0xFFFF & self.NT_HEADERS.Signature) == IMAGE_LE_SIGNATURE: + raise PEFormatError('Invalid NT Headers signature. Probably a LE file') + if (0xFFFF & self.NT_HEADERS.Signature) == IMAGE_LX_SIGNATURE: + raise PEFormatError('Invalid NT Headers signature. Probably a LX file') + if self.NT_HEADERS.Signature != IMAGE_NT_SIGNATURE: + raise PEFormatError('Invalid NT Headers signature.') + + self.FILE_HEADER = self.__unpack_data__( + self.__IMAGE_FILE_HEADER_format__, + self.__data__[nt_headers_offset+4:nt_headers_offset+4+32], + file_offset = nt_headers_offset+4) + image_flags = retrieve_flags(IMAGE_CHARACTERISTICS, 'IMAGE_FILE_') + + if not self.FILE_HEADER: + raise PEFormatError('File Header missing') + + # Set the image's flags according the the Characteristics member + set_flags(self.FILE_HEADER, self.FILE_HEADER.Characteristics, image_flags) + + optional_header_offset = nt_headers_offset+4+self.FILE_HEADER.sizeof() + + # Note: location of sections can be controlled from PE header: + sections_offset = optional_header_offset + self.FILE_HEADER.SizeOfOptionalHeader + + self.OPTIONAL_HEADER = self.__unpack_data__( + self.__IMAGE_OPTIONAL_HEADER_format__, + self.__data__[optional_header_offset:], + file_offset = optional_header_offset) + + # According to solardesigner's findings for his + # Tiny PE project, the optional header does not + # need fields beyond "Subsystem" in order to be + # loadable by the Windows loader (given that zeroes + # are acceptable values and the header is loaded + # in a zeroed memory page) + # If trying to parse a full Optional Header fails + # we try to parse it again with some 0 padding + # + MINIMUM_VALID_OPTIONAL_HEADER_RAW_SIZE = 69 + + if ( self.OPTIONAL_HEADER is None and + len(self.__data__[optional_header_offset:optional_header_offset+0x200]) + >= MINIMUM_VALID_OPTIONAL_HEADER_RAW_SIZE ): + + # Add enough zeroes to make up for the unused fields + # + padding_length = 128 + + # Create padding + # + padded_data = self.__data__[optional_header_offset:optional_header_offset+0x200] + ( + '\0' * padding_length) + + self.OPTIONAL_HEADER = self.__unpack_data__( + self.__IMAGE_OPTIONAL_HEADER_format__, + padded_data, + file_offset = optional_header_offset) + + + # Check the Magic in the OPTIONAL_HEADER and set the PE file + # type accordingly + # + if self.OPTIONAL_HEADER is not None: + + if self.OPTIONAL_HEADER.Magic == OPTIONAL_HEADER_MAGIC_PE: + + self.PE_TYPE = OPTIONAL_HEADER_MAGIC_PE + + elif self.OPTIONAL_HEADER.Magic == OPTIONAL_HEADER_MAGIC_PE_PLUS: + + self.PE_TYPE = OPTIONAL_HEADER_MAGIC_PE_PLUS + + self.OPTIONAL_HEADER = self.__unpack_data__( + self.__IMAGE_OPTIONAL_HEADER64_format__, + self.__data__[optional_header_offset:optional_header_offset+0x200], + file_offset = optional_header_offset) + + # Again, as explained above, we try to parse + # a reduced form of the Optional Header which + # is still valid despite not including all + # structure members + # + MINIMUM_VALID_OPTIONAL_HEADER_RAW_SIZE = 69+4 + + if ( self.OPTIONAL_HEADER is None and + len(self.__data__[optional_header_offset:optional_header_offset+0x200]) + >= MINIMUM_VALID_OPTIONAL_HEADER_RAW_SIZE ): + + padding_length = 128 + padded_data = self.__data__[optional_header_offset:optional_header_offset+0x200] + ( + '\0' * padding_length) + self.OPTIONAL_HEADER = self.__unpack_data__( + self.__IMAGE_OPTIONAL_HEADER64_format__, + padded_data, + file_offset = optional_header_offset) + + + if not self.FILE_HEADER: + raise PEFormatError('File Header missing') + + + # OC Patch: + # Die gracefully if there is no OPTIONAL_HEADER field + # 975440f5ad5e2e4a92c4d9a5f22f75c1 + if self.PE_TYPE is None or self.OPTIONAL_HEADER is None: + raise PEFormatError("No Optional Header found, invalid PE32 or PE32+ file") + + dll_characteristics_flags = retrieve_flags(DLL_CHARACTERISTICS, 'IMAGE_DLL_CHARACTERISTICS_') + + # Set the Dll Characteristics flags according the the DllCharacteristics member + set_flags( + self.OPTIONAL_HEADER, + self.OPTIONAL_HEADER.DllCharacteristics, + dll_characteristics_flags) + + + self.OPTIONAL_HEADER.DATA_DIRECTORY = [] + #offset = (optional_header_offset + self.FILE_HEADER.SizeOfOptionalHeader) + offset = (optional_header_offset + self.OPTIONAL_HEADER.sizeof()) + + + self.NT_HEADERS.FILE_HEADER = self.FILE_HEADER + self.NT_HEADERS.OPTIONAL_HEADER = self.OPTIONAL_HEADER + + + # The NumberOfRvaAndSizes is sanitized to stay within + # reasonable limits so can be casted to an int + # + if self.OPTIONAL_HEADER.NumberOfRvaAndSizes > 0x10: + self.__warnings.append( + 'Suspicious NumberOfRvaAndSizes in the Optional Header. ' + + 'Normal values are never larger than 0x10, the value is: 0x%x' % + self.OPTIONAL_HEADER.NumberOfRvaAndSizes ) + + MAX_ASSUMED_VALID_NUMBER_OF_RVA_AND_SIZES = 0x100 + for i in xrange(int(0x7fffffffL & self.OPTIONAL_HEADER.NumberOfRvaAndSizes)): + + if len(self.__data__) - offset == 0: + break + + if len(self.__data__) - offset < 8: + data = self.__data__[offset:] + '\0'*8 + else: + data = self.__data__[offset:offset+MAX_ASSUMED_VALID_NUMBER_OF_RVA_AND_SIZES] + + dir_entry = self.__unpack_data__( + self.__IMAGE_DATA_DIRECTORY_format__, + data, + file_offset = offset) + + if dir_entry is None: + break + + # Would fail if missing an entry + # 1d4937b2fa4d84ad1bce0309857e70ca offending sample + try: + dir_entry.name = DIRECTORY_ENTRY[i] + except (KeyError, AttributeError): + break + + offset += dir_entry.sizeof() + + self.OPTIONAL_HEADER.DATA_DIRECTORY.append(dir_entry) + + # If the offset goes outside the optional header, + # the loop is broken, regardless of how many directories + # NumberOfRvaAndSizes says there are + # + # We assume a normally sized optional header, hence that we do + # a sizeof() instead of reading SizeOfOptionalHeader. + # Then we add a default number of directories times their size, + # if we go beyond that, we assume the number of directories + # is wrong and stop processing + if offset >= (optional_header_offset + + self.OPTIONAL_HEADER.sizeof() + 8*16) : + + break + + + offset = self.parse_sections(sections_offset) + + # OC Patch: + # There could be a problem if there are no raw data sections + # greater than 0 + # fc91013eb72529da005110a3403541b6 example + # Should this throw an exception in the minimum header offset + # can't be found? + # + rawDataPointers = [ + adjust_FileAlignment( s.PointerToRawData, + self.OPTIONAL_HEADER.FileAlignment ) + for s in self.sections if s.PointerToRawData>0 ] + + if len(rawDataPointers) > 0: + lowest_section_offset = min(rawDataPointers) + else: + lowest_section_offset = None + + if not lowest_section_offset or lowest_section_offset < offset: + self.header = self.__data__[:offset] + else: + self.header = self.__data__[:lowest_section_offset] + + + # Check whether the entry point lies within a section + # + if self.get_section_by_rva(self.OPTIONAL_HEADER.AddressOfEntryPoint) is not None: + + # Check whether the entry point lies within the file + # + ep_offset = self.get_offset_from_rva(self.OPTIONAL_HEADER.AddressOfEntryPoint) + if ep_offset > len(self.__data__): + + self.__warnings.append( + 'Possibly corrupt file. AddressOfEntryPoint lies outside the file. ' + + 'AddressOfEntryPoint: 0x%x' % + self.OPTIONAL_HEADER.AddressOfEntryPoint ) + + else: + + self.__warnings.append( + 'AddressOfEntryPoint lies outside the sections\' boundaries. ' + + 'AddressOfEntryPoint: 0x%x' % + self.OPTIONAL_HEADER.AddressOfEntryPoint ) + + + if not fast_load: + self.parse_data_directories() + + class RichHeader: + pass + rich_header = self.parse_rich_header() + if rich_header: + self.RICH_HEADER = RichHeader() + self.RICH_HEADER.checksum = rich_header.get('checksum', None) + self.RICH_HEADER.values = rich_header.get('values', None) + else: + self.RICH_HEADER = None + + + def parse_rich_header(self): + """Parses the rich header + see http://www.ntcore.com/files/richsign.htm for more information + + Structure: + 00 DanS ^ checksum, checksum, checksum, checksum + 10 Symbol RVA ^ checksum, Symbol size ^ checksum... + ... + XX Rich, checksum, 0, 0,... + """ + + # Rich Header constants + # + DANS = 0x536E6144 # 'DanS' as dword + RICH = 0x68636952 # 'Rich' as dword + + # Read a block of data + # + try: + data = list(struct.unpack("<32I", self.get_data(0x80, 0x80))) + except: + # In the cases where there's not enough data to contain the Rich header + # we abort its parsing + return None + + # the checksum should be present 3 times after the DanS signature + # + checksum = data[1] + if (data[0] ^ checksum != DANS + or data[2] != checksum + or data[3] != checksum): + return None + + result = {"checksum": checksum} + headervalues = [] + result ["values"] = headervalues + + data = data[4:] + for i in xrange(len(data) / 2): + + # Stop until the Rich footer signature is found + # + if data[2 * i] == RICH: + + # it should be followed by the checksum + # + if data[2 * i + 1] != checksum: + self.__warnings.append('Rich Header corrupted') + break + + # header values come by pairs + # + headervalues += [data[2 * i] ^ checksum, data[2 * i + 1] ^ checksum] + return result + + + def get_warnings(self): + """Return the list of warnings. + + Non-critical problems found when parsing the PE file are + appended to a list of warnings. This method returns the + full list. + """ + + return self.__warnings + + + def show_warnings(self): + """Print the list of warnings. + + Non-critical problems found when parsing the PE file are + appended to a list of warnings. This method prints the + full list to standard output. + """ + + for warning in self.__warnings: + print '>', warning + + + def full_load(self): + """Process the data directories. + + This method will load the data directories which might not have + been loaded if the "fast_load" option was used. + """ + + self.parse_data_directories() + + + def write(self, filename=None): + """Write the PE file. + + This function will process all headers and components + of the PE file and include all changes made (by just + assigning to attributes in the PE objects) and write + the changes back to a file whose name is provided as + an argument. The filename is optional, if not + provided the data will be returned as a 'str' object. + """ + + file_data = list(self.__data__) + for structure in self.__structures__: + + struct_data = list(structure.__pack__()) + offset = structure.get_file_offset() + + file_data[offset:offset+len(struct_data)] = struct_data + + if hasattr(self, 'VS_VERSIONINFO'): + if hasattr(self, 'FileInfo'): + for entry in self.FileInfo: + if hasattr(entry, 'StringTable'): + for st_entry in entry.StringTable: + for key, entry in st_entry.entries.items(): + + offsets = st_entry.entries_offsets[key] + lengths = st_entry.entries_lengths[key] + + if len( entry ) > lengths[1]: + + l = list() + for idx, c in enumerate(entry): + if ord(c) > 256: + l.extend( [ chr(ord(c) & 0xff), chr( (ord(c) & 0xff00) >>8) ] ) + else: + l.extend( [chr( ord(c) ), '\0'] ) + + file_data[ + offsets[1] : offsets[1] + lengths[1]*2 ] = l + + else: + + l = list() + for idx, c in enumerate(entry): + if ord(c) > 256: + l.extend( [ chr(ord(c) & 0xff), chr( (ord(c) & 0xff00) >>8) ] ) + else: + l.extend( [chr( ord(c) ), '\0'] ) + + file_data[ + offsets[1] : offsets[1] + len(entry)*2 ] = l + + remainder = lengths[1] - len(entry) + file_data[ + offsets[1] + len(entry)*2 : + offsets[1] + lengths[1]*2 ] = [ + u'\0' ] * remainder*2 + + new_file_data = ''.join( [ chr(ord(c)) for c in file_data] ) + + if filename: + f = file(filename, 'wb+') + f.write(new_file_data) + f.close() + else: + return new_file_data + + + def parse_sections(self, offset): + """Fetch the PE file sections. + + The sections will be readily available in the "sections" attribute. + Its attributes will contain all the section information plus "data" + a buffer containing the section's data. + + The "Characteristics" member will be processed and attributes + representing the section characteristics (with the 'IMAGE_SCN_' + string trimmed from the constant's names) will be added to the + section instance. + + Refer to the SectionStructure class for additional info. + """ + + self.sections = [] + + for i in xrange(self.FILE_HEADER.NumberOfSections): + section = SectionStructure( self.__IMAGE_SECTION_HEADER_format__, pe=self ) + if not section: + break + section_offset = offset + section.sizeof() * i + section.set_file_offset(section_offset) + section.__unpack__(self.__data__[section_offset : section_offset + section.sizeof()]) + self.__structures__.append(section) + + if section.SizeOfRawData > len(self.__data__): + self.__warnings.append( + ('Error parsing section %d. ' % i) + + 'SizeOfRawData is larger than file.') + + if adjust_FileAlignment( section.PointerToRawData, + self.OPTIONAL_HEADER.FileAlignment ) > len(self.__data__): + + self.__warnings.append( + ('Error parsing section %d. ' % i) + + 'PointerToRawData points beyond the end of the file.') + + if section.Misc_VirtualSize > 0x10000000: + self.__warnings.append( + ('Suspicious value found parsing section %d. ' % i) + + 'VirtualSize is extremely large > 256MiB.') + + if adjust_SectionAlignment( section.VirtualAddress, + self.OPTIONAL_HEADER.SectionAlignment, self.OPTIONAL_HEADER.FileAlignment ) > 0x10000000: + self.__warnings.append( + ('Suspicious value found parsing section %d. ' % i) + + 'VirtualAddress is beyond 0x10000000.') + + # + # Some packer used a non-aligned PointerToRawData in the sections, + # which causes several common tools not to load the section data + # properly as they blindly read from the indicated offset. + # It seems that Windows will round the offset down to the largest + # offset multiple of FileAlignment which is smaller than + # PointerToRawData. The following code will do the same. + # + + #alignment = self.OPTIONAL_HEADER.FileAlignment + #self.update_section_data(section) + + if ( self.OPTIONAL_HEADER.FileAlignment != 0 and + ( section.PointerToRawData % self.OPTIONAL_HEADER.FileAlignment) != 0): + self.__warnings.append( + ('Error parsing section %d. ' % i) + + 'Suspicious value for FileAlignment in the Optional Header. ' + + 'Normally the PointerToRawData entry of the sections\' structures ' + + 'is a multiple of FileAlignment, this might imply the file ' + + 'is trying to confuse tools which parse this incorrectly') + + + section_flags = retrieve_flags(SECTION_CHARACTERISTICS, 'IMAGE_SCN_') + + # Set the section's flags according the the Characteristics member + set_flags(section, section.Characteristics, section_flags) + + if ( section.__dict__.get('IMAGE_SCN_MEM_WRITE', False) and + section.__dict__.get('IMAGE_SCN_MEM_EXECUTE', False) ): + + self.__warnings.append( + ('Suspicious flags set for section %d. ' % i) + + 'Both IMAGE_SCN_MEM_WRITE and IMAGE_SCN_MEM_EXECUTE are set. ' + + 'This might indicate a packed executable.') + + self.sections.append(section) + + if self.FILE_HEADER.NumberOfSections > 0 and self.sections: + return offset + self.sections[0].sizeof()*self.FILE_HEADER.NumberOfSections + else: + return offset + + + + def parse_data_directories(self, directories=None): + """Parse and process the PE file's data directories. + + If the optional argument 'directories' is given, only + the directories at the specified indices will be parsed. + Such functionality allows parsing of areas of interest + without the burden of having to parse all others. + The directories can then be specified as: + + For export/import only: + + directories = [ 0, 1 ] + + or (more verbosely): + + directories = [ DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT'], + DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT'] ] + + If 'directories' is a list, the ones that are processed will be removed, + leaving only the ones that are not present in the image. + """ + + directory_parsing = ( + ('IMAGE_DIRECTORY_ENTRY_IMPORT', self.parse_import_directory), + ('IMAGE_DIRECTORY_ENTRY_EXPORT', self.parse_export_directory), + ('IMAGE_DIRECTORY_ENTRY_RESOURCE', self.parse_resources_directory), + ('IMAGE_DIRECTORY_ENTRY_DEBUG', self.parse_debug_directory), + ('IMAGE_DIRECTORY_ENTRY_BASERELOC', self.parse_relocations_directory), + ('IMAGE_DIRECTORY_ENTRY_TLS', self.parse_directory_tls), + ('IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG', self.parse_directory_load_config), + ('IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT', self.parse_delay_import_directory), + ('IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT', self.parse_directory_bound_imports) ) + + if directories is not None: + if not isinstance(directories, (tuple, list)): + directories = [directories] + + for entry in directory_parsing: + # OC Patch: + # + try: + directory_index = DIRECTORY_ENTRY[entry[0]] + dir_entry = self.OPTIONAL_HEADER.DATA_DIRECTORY[directory_index] + except IndexError: + break + + # Only process all the directories if no individual ones have + # been chosen + # + if directories is None or directory_index in directories: + + if dir_entry.VirtualAddress: + value = entry[1](dir_entry.VirtualAddress, dir_entry.Size) + if value: + setattr(self, entry[0][6:], value) + + if (directories is not None) and isinstance(directories, list) and (entry[0] in directories): + directories.remove(directory_index) + + + + def parse_directory_bound_imports(self, rva, size): + """""" + + bnd_descr = Structure(self.__IMAGE_BOUND_IMPORT_DESCRIPTOR_format__) + bnd_descr_size = bnd_descr.sizeof() + start = rva + + bound_imports = [] + while True: + + bnd_descr = self.__unpack_data__( + self.__IMAGE_BOUND_IMPORT_DESCRIPTOR_format__, + self.__data__[rva:rva+bnd_descr_size], + file_offset = rva) + if bnd_descr is None: + # If can't parse directory then silently return. + # This directory does not necessarily have to be valid to + # still have a valid PE file + + self.__warnings.append( + 'The Bound Imports directory exists but can\'t be parsed.') + + return + + if bnd_descr.all_zeroes(): + break + + rva += bnd_descr.sizeof() + + forwarder_refs = [] + for idx in xrange(bnd_descr.NumberOfModuleForwarderRefs): + # Both structures IMAGE_BOUND_IMPORT_DESCRIPTOR and + # IMAGE_BOUND_FORWARDER_REF have the same size. + bnd_frwd_ref = self.__unpack_data__( + self.__IMAGE_BOUND_FORWARDER_REF_format__, + self.__data__[rva:rva+bnd_descr_size], + file_offset = rva) + # OC Patch: + if not bnd_frwd_ref: + raise PEFormatError( + "IMAGE_BOUND_FORWARDER_REF cannot be read") + rva += bnd_frwd_ref.sizeof() + + offset = start+bnd_frwd_ref.OffsetModuleName + name_str = self.get_string_from_data( + 0, self.__data__[offset : offset + MAX_STRING_LENGTH]) + + if not name_str: + break + forwarder_refs.append(BoundImportRefData( + struct = bnd_frwd_ref, + name = name_str)) + + offset = start+bnd_descr.OffsetModuleName + name_str = self.get_string_from_data( + 0, self.__data__[offset : offset + MAX_STRING_LENGTH]) + + if not name_str: + break + bound_imports.append( + BoundImportDescData( + struct = bnd_descr, + name = name_str, + entries = forwarder_refs)) + + return bound_imports + + + def parse_directory_tls(self, rva, size): + """""" + + if self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE: + format = self.__IMAGE_TLS_DIRECTORY_format__ + + elif self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS: + format = self.__IMAGE_TLS_DIRECTORY64_format__ + + try: + tls_struct = self.__unpack_data__( + format, + self.get_data( rva, Structure(format).sizeof() ), + file_offset = self.get_offset_from_rva(rva)) + except PEFormatError: + self.__warnings.append( + 'Invalid TLS information. Can\'t read ' + + 'data at RVA: 0x%x' % rva) + tls_struct = None + + if not tls_struct: + return None + + return TlsData( struct = tls_struct ) + + + def parse_directory_load_config(self, rva, size): + """""" + + if self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE: + format = self.__IMAGE_LOAD_CONFIG_DIRECTORY_format__ + + elif self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS: + format = self.__IMAGE_LOAD_CONFIG_DIRECTORY64_format__ + + try: + load_config_struct = self.__unpack_data__( + format, + self.get_data( rva, Structure(format).sizeof() ), + file_offset = self.get_offset_from_rva(rva)) + except PEFormatError: + self.__warnings.append( + 'Invalid LOAD_CONFIG information. Can\'t read ' + + 'data at RVA: 0x%x' % rva) + load_config_struct = None + + if not load_config_struct: + return None + + return LoadConfigData( struct = load_config_struct ) + + + def parse_relocations_directory(self, rva, size): + """""" + + rlc_size = Structure(self.__IMAGE_BASE_RELOCATION_format__).sizeof() + end = rva+size + + relocations = [] + while rva < end: + + # OC Patch: + # Malware that has bad RVA entries will cause an error. + # Just continue on after an exception + # + try: + rlc = self.__unpack_data__( + self.__IMAGE_BASE_RELOCATION_format__, + self.get_data(rva, rlc_size), + file_offset = self.get_offset_from_rva(rva) ) + except PEFormatError: + self.__warnings.append( + 'Invalid relocation information. Can\'t read ' + + 'data at RVA: 0x%x' % rva) + rlc = None + + if not rlc: + break + + reloc_entries = self.parse_relocations( + rva+rlc_size, rlc.VirtualAddress, rlc.SizeOfBlock-rlc_size ) + + relocations.append( + BaseRelocationData( + struct = rlc, + entries = reloc_entries)) + + if not rlc.SizeOfBlock: + break + rva += rlc.SizeOfBlock + + return relocations + + + def parse_relocations(self, data_rva, rva, size): + """""" + + data = self.get_data(data_rva, size) + file_offset = self.get_offset_from_rva(data_rva) + + entries = [] + for idx in xrange( len(data) / 2 ): + + entry = self.__unpack_data__( + self.__IMAGE_BASE_RELOCATION_ENTRY_format__, + data[idx*2:(idx+1)*2], + file_offset = file_offset ) + + if not entry: + break + word = entry.Data + + reloc_type = (word>>12) + reloc_offset = (word & 0x0fff) + entries.append( + RelocationData( + struct = entry, + type = reloc_type, + base_rva = rva, + rva = reloc_offset+rva)) + file_offset += entry.sizeof() + + return entries + + + def parse_debug_directory(self, rva, size): + """""" + + dbg_size = Structure(self.__IMAGE_DEBUG_DIRECTORY_format__).sizeof() + + debug = [] + for idx in xrange(size/dbg_size): + try: + data = self.get_data(rva+dbg_size*idx, dbg_size) + except PEFormatError, e: + self.__warnings.append( + 'Invalid debug information. Can\'t read ' + + 'data at RVA: 0x%x' % rva) + return None + + dbg = self.__unpack_data__( + self.__IMAGE_DEBUG_DIRECTORY_format__, + data, file_offset = self.get_offset_from_rva(rva+dbg_size*idx)) + + if not dbg: + return None + + debug.append( + DebugData( + struct = dbg)) + + return debug + + + def parse_resources_directory(self, rva, size=0, base_rva = None, level = 0, dirs=None): + """Parse the resources directory. + + Given the RVA of the resources directory, it will process all + its entries. + + The root will have the corresponding member of its structure, + IMAGE_RESOURCE_DIRECTORY plus 'entries', a list of all the + entries in the directory. + + Those entries will have, correspondingly, all the structure's + members (IMAGE_RESOURCE_DIRECTORY_ENTRY) and an additional one, + "directory", pointing to the IMAGE_RESOURCE_DIRECTORY structure + representing upper layers of the tree. This one will also have + an 'entries' attribute, pointing to the 3rd, and last, level. + Another directory with more entries. Those last entries will + have a new attribute (both 'leaf' or 'data_entry' can be used to + access it). This structure finally points to the resource data. + All the members of this structure, IMAGE_RESOURCE_DATA_ENTRY, + are available as its attributes. + """ + + # OC Patch: + if dirs is None: + dirs = [rva] + + if base_rva is None: + base_rva = rva + + resources_section = self.get_section_by_rva(rva) + + try: + # If the RVA is invalid all would blow up. Some EXEs seem to be + # specially nasty and have an invalid RVA. + data = self.get_data(rva, Structure(self.__IMAGE_RESOURCE_DIRECTORY_format__).sizeof() ) + except PEFormatError, e: + self.__warnings.append( + 'Invalid resources directory. Can\'t read ' + + 'directory data at RVA: 0x%x' % rva) + return None + + # Get the resource directory structure, that is, the header + # of the table preceding the actual entries + # + resource_dir = self.__unpack_data__( + self.__IMAGE_RESOURCE_DIRECTORY_format__, data, + file_offset = self.get_offset_from_rva(rva) ) + if resource_dir is None: + # If can't parse resources directory then silently return. + # This directory does not necessarily have to be valid to + # still have a valid PE file + self.__warnings.append( + 'Invalid resources directory. Can\'t parse ' + + 'directory data at RVA: 0x%x' % rva) + return None + + dir_entries = [] + + # Advance the RVA to the positon immediately following the directory + # table header and pointing to the first entry in the table + # + rva += resource_dir.sizeof() + + number_of_entries = ( + resource_dir.NumberOfNamedEntries + + resource_dir.NumberOfIdEntries ) + + # Set a hard limit on the maximum resonable number of entries + MAX_ALLOWED_ENTRIES = 4096 + if number_of_entries > MAX_ALLOWED_ENTRIES: + self.__warnings.append( + 'Error parsing the resources directory, ' + 'The directory contains %d entries (>%s)' % + (number_of_entries, MAX_ALLOWED_ENTRIES) ) + return None + + strings_to_postprocess = list() + + for idx in xrange(number_of_entries): + + + res = self.parse_resource_entry(rva) + if res is None: + self.__warnings.append( + 'Error parsing the resources directory, ' + 'Entry %d is invalid, RVA = 0x%x. ' % + (idx, rva) ) + break + + entry_name = None + entry_id = None + + # If all named entries have been processed, only Id ones + # remain + + if idx >= resource_dir.NumberOfNamedEntries: + entry_id = res.Name + else: + ustr_offset = base_rva+res.NameOffset + try: + #entry_name = self.get_string_u_at_rva(ustr_offset, max_length=16) + entry_name = UnicodeStringWrapperPostProcessor(self, ustr_offset) + strings_to_postprocess.append(entry_name) + + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the resources directory, ' + 'attempting to read entry name. ' + 'Can\'t read unicode string at offset 0x%x' % + (ustr_offset) ) + + + if res.DataIsDirectory: + # OC Patch: + # + # One trick malware can do is to recursively reference + # the next directory. This causes hilarity to ensue when + # trying to parse everything correctly. + # If the original RVA given to this function is equal to + # the next one to parse, we assume that it's a trick. + # Instead of raising a PEFormatError this would skip some + # reasonable data so we just break. + # + # 9ee4d0a0caf095314fd7041a3e4404dc is the offending sample + if (base_rva + res.OffsetToDirectory) in dirs: + + break + + else: + entry_directory = self.parse_resources_directory( + base_rva+res.OffsetToDirectory, + size-(rva-base_rva), # size + base_rva=base_rva, level = level+1, + dirs=dirs + [base_rva + res.OffsetToDirectory]) + + if not entry_directory: + break + + # Ange Albertini's code to process resources' strings + # + strings = None + if entry_id == RESOURCE_TYPE['RT_STRING']: + strings = dict() + for resource_id in entry_directory.entries: + if hasattr(resource_id, 'directory'): + for resource_lang in resource_id.directory.entries: + + resource_strings = dict() + + string_entry_rva = resource_lang.data.struct.OffsetToData + string_entry_size = resource_lang.data.struct.Size + string_entry_id = resource_id.id + + if resource_lang.data.struct.Size is None or resource_id.id is None: + continue + + string_entry_data = self.get_data(string_entry_rva, string_entry_size) + parse_strings( string_entry_data, (int(string_entry_id) - 1) * 16, resource_strings ) + strings.update(resource_strings) + + resource_id.directory.strings = resource_strings + + dir_entries.append( + ResourceDirEntryData( + struct = res, + name = entry_name, + id = entry_id, + directory = entry_directory)) + + else: + struct = self.parse_resource_data_entry( + base_rva + res.OffsetToDirectory) + + if struct: + entry_data = ResourceDataEntryData( + struct = struct, + lang = res.Name & 0x3ff, + sublang = res.Name >> 10 ) + + dir_entries.append( + ResourceDirEntryData( + struct = res, + name = entry_name, + id = entry_id, + data = entry_data)) + + else: + break + + + + # Check if this entry contains version information + # + if level == 0 and res.Id == RESOURCE_TYPE['RT_VERSION']: + if len(dir_entries)>0: + last_entry = dir_entries[-1] + + rt_version_struct = None + try: + rt_version_struct = last_entry.directory.entries[0].directory.entries[0].data.struct + except: + # Maybe a malformed directory structure...? + # Lets ignore it + pass + + if rt_version_struct is not None: + self.parse_version_information(rt_version_struct) + + rva += res.sizeof() + + + string_rvas = [s.get_rva() for s in strings_to_postprocess] + string_rvas.sort() + + for idx, s in enumerate(strings_to_postprocess): + s.render_pascal_16() + + + resource_directory_data = ResourceDirData( + struct = resource_dir, + entries = dir_entries) + + return resource_directory_data + + + def parse_resource_data_entry(self, rva): + """Parse a data entry from the resources directory.""" + + try: + # If the RVA is invalid all would blow up. Some EXEs seem to be + # specially nasty and have an invalid RVA. + data = self.get_data(rva, Structure(self.__IMAGE_RESOURCE_DATA_ENTRY_format__).sizeof() ) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing a resource directory data entry, ' + + 'the RVA is invalid: 0x%x' % ( rva ) ) + return None + + data_entry = self.__unpack_data__( + self.__IMAGE_RESOURCE_DATA_ENTRY_format__, data, + file_offset = self.get_offset_from_rva(rva) ) + + return data_entry + + + def parse_resource_entry(self, rva): + """Parse a directory entry from the resources directory.""" + + try: + data = self.get_data( rva, Structure(self.__IMAGE_RESOURCE_DIRECTORY_ENTRY_format__).sizeof() ) + except PEFormatError, excp: + # A warning will be added by the caller if this method returns None + return None + + resource = self.__unpack_data__( + self.__IMAGE_RESOURCE_DIRECTORY_ENTRY_format__, data, + file_offset = self.get_offset_from_rva(rva) ) + + if resource is None: + return None + + #resource.NameIsString = (resource.Name & 0x80000000L) >> 31 + resource.NameOffset = resource.Name & 0x7FFFFFFFL + + resource.__pad = resource.Name & 0xFFFF0000L + resource.Id = resource.Name & 0x0000FFFFL + + resource.DataIsDirectory = (resource.OffsetToData & 0x80000000L) >> 31 + resource.OffsetToDirectory = resource.OffsetToData & 0x7FFFFFFFL + + return resource + + + def parse_version_information(self, version_struct): + """Parse version information structure. + + The date will be made available in three attributes of the PE object. + + VS_VERSIONINFO will contain the first three fields of the main structure: + 'Length', 'ValueLength', and 'Type' + + VS_FIXEDFILEINFO will hold the rest of the fields, accessible as sub-attributes: + 'Signature', 'StrucVersion', 'FileVersionMS', 'FileVersionLS', + 'ProductVersionMS', 'ProductVersionLS', 'FileFlagsMask', 'FileFlags', + 'FileOS', 'FileType', 'FileSubtype', 'FileDateMS', 'FileDateLS' + + FileInfo is a list of all StringFileInfo and VarFileInfo structures. + + StringFileInfo structures will have a list as an attribute named 'StringTable' + containing all the StringTable structures. Each of those structures contains a + dictionary 'entries' with all the key/value version information string pairs. + + VarFileInfo structures will have a list as an attribute named 'Var' containing + all Var structures. Each Var structure will have a dictionary as an attribute + named 'entry' which will contain the name and value of the Var. + """ + + + # Retrieve the data for the version info resource + # + start_offset = self.get_offset_from_rva( version_struct.OffsetToData ) + raw_data = self.__data__[ start_offset : start_offset+version_struct.Size ] + + + # Map the main structure and the subsequent string + # + versioninfo_struct = self.__unpack_data__( + self.__VS_VERSIONINFO_format__, raw_data, + file_offset = start_offset ) + + if versioninfo_struct is None: + return + + ustr_offset = version_struct.OffsetToData + versioninfo_struct.sizeof() + try: + versioninfo_string = self.get_string_u_at_rva( ustr_offset ) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the version information, ' + + 'attempting to read VS_VERSION_INFO string. Can\'t ' + + 'read unicode string at offset 0x%x' % ( + ustr_offset ) ) + + versioninfo_string = None + + # If the structure does not contain the expected name, it's assumed to be invalid + # + if versioninfo_string != u'VS_VERSION_INFO': + + self.__warnings.append('Invalid VS_VERSION_INFO block') + return + + + # Set the PE object's VS_VERSIONINFO to this one + # + self.VS_VERSIONINFO = versioninfo_struct + + # The the Key attribute to point to the unicode string identifying the structure + # + self.VS_VERSIONINFO.Key = versioninfo_string + + + # Process the fixed version information, get the offset and structure + # + fixedfileinfo_offset = self.dword_align( + versioninfo_struct.sizeof() + 2 * (len(versioninfo_string) + 1), + version_struct.OffsetToData) + fixedfileinfo_struct = self.__unpack_data__( + self.__VS_FIXEDFILEINFO_format__, + raw_data[fixedfileinfo_offset:], + file_offset = start_offset+fixedfileinfo_offset ) + + if not fixedfileinfo_struct: + return + + # Set the PE object's VS_FIXEDFILEINFO to this one + # + self.VS_FIXEDFILEINFO = fixedfileinfo_struct + + + # Start parsing all the StringFileInfo and VarFileInfo structures + # + + # Get the first one + # + stringfileinfo_offset = self.dword_align( + fixedfileinfo_offset + fixedfileinfo_struct.sizeof(), + version_struct.OffsetToData) + original_stringfileinfo_offset = stringfileinfo_offset + + + # Set the PE object's attribute that will contain them all. + # + self.FileInfo = list() + + + while True: + + # Process the StringFileInfo/VarFileInfo struct + # + stringfileinfo_struct = self.__unpack_data__( + self.__StringFileInfo_format__, + raw_data[stringfileinfo_offset:], + file_offset = start_offset+stringfileinfo_offset ) + + if stringfileinfo_struct is None: + self.__warnings.append( + 'Error parsing StringFileInfo/VarFileInfo struct' ) + return None + + # Get the subsequent string defining the structure. + # + ustr_offset = ( version_struct.OffsetToData + + stringfileinfo_offset + versioninfo_struct.sizeof() ) + try: + stringfileinfo_string = self.get_string_u_at_rva( ustr_offset ) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the version information, ' + + 'attempting to read StringFileInfo string. Can\'t ' + + 'read unicode string at offset 0x%x' % ( ustr_offset ) ) + break + + # Set such string as the Key attribute + # + stringfileinfo_struct.Key = stringfileinfo_string + + + # Append the structure to the PE object's list + # + self.FileInfo.append(stringfileinfo_struct) + + + # Parse a StringFileInfo entry + # + if stringfileinfo_string and stringfileinfo_string.startswith(u'StringFileInfo'): + + if stringfileinfo_struct.Type == 1 and stringfileinfo_struct.ValueLength == 0: + + stringtable_offset = self.dword_align( + stringfileinfo_offset + stringfileinfo_struct.sizeof() + + 2*(len(stringfileinfo_string)+1), + version_struct.OffsetToData) + + stringfileinfo_struct.StringTable = list() + + # Process the String Table entries + # + while True: + + stringtable_struct = self.__unpack_data__( + self.__StringTable_format__, + raw_data[stringtable_offset:], + file_offset = start_offset+stringtable_offset ) + + if not stringtable_struct: + break + + ustr_offset = ( version_struct.OffsetToData + stringtable_offset + + stringtable_struct.sizeof() ) + try: + stringtable_string = self.get_string_u_at_rva( ustr_offset ) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the version information, ' + + 'attempting to read StringTable string. Can\'t ' + + 'read unicode string at offset 0x%x' % ( ustr_offset ) ) + break + + stringtable_struct.LangID = stringtable_string + stringtable_struct.entries = dict() + stringtable_struct.entries_offsets = dict() + stringtable_struct.entries_lengths = dict() + stringfileinfo_struct.StringTable.append(stringtable_struct) + + entry_offset = self.dword_align( + stringtable_offset + stringtable_struct.sizeof() + + 2*(len(stringtable_string)+1), + version_struct.OffsetToData) + + # Process all entries in the string table + # + + while entry_offset < stringtable_offset + stringtable_struct.Length: + + string_struct = self.__unpack_data__( + self.__String_format__, raw_data[entry_offset:], + file_offset = start_offset+entry_offset ) + + if not string_struct: + break + + ustr_offset = ( version_struct.OffsetToData + entry_offset + + string_struct.sizeof() ) + try: + key = self.get_string_u_at_rva( ustr_offset ) + key_offset = self.get_offset_from_rva( ustr_offset ) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the version information, ' + + 'attempting to read StringTable Key string. Can\'t ' + + 'read unicode string at offset 0x%x' % ( ustr_offset ) ) + break + + value_offset = self.dword_align( + 2*(len(key)+1) + entry_offset + string_struct.sizeof(), + version_struct.OffsetToData) + + ustr_offset = version_struct.OffsetToData + value_offset + try: + value = self.get_string_u_at_rva( ustr_offset, + max_length = string_struct.ValueLength ) + value_offset = self.get_offset_from_rva( ustr_offset ) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the version information, ' + + 'attempting to read StringTable Value string. ' + + 'Can\'t read unicode string at offset 0x%x' % ( + ustr_offset ) ) + break + + if string_struct.Length == 0: + entry_offset = stringtable_offset + stringtable_struct.Length + else: + entry_offset = self.dword_align( + string_struct.Length+entry_offset, version_struct.OffsetToData) + + key_as_char = [] + for c in key: + if ord(c)>128: + key_as_char.append('\\x%02x' %ord(c)) + else: + key_as_char.append(c) + + key_as_char = ''.join(key_as_char) + + setattr(stringtable_struct, key_as_char, value) + stringtable_struct.entries[key] = value + stringtable_struct.entries_offsets[key] = (key_offset, value_offset) + stringtable_struct.entries_lengths[key] = (len(key), len(value)) + + + new_stringtable_offset = self.dword_align( + stringtable_struct.Length + stringtable_offset, + version_struct.OffsetToData) + + # check if the entry is crafted in a way that would lead to an infinite + # loop and break if so + # + if new_stringtable_offset == stringtable_offset: + break + stringtable_offset = new_stringtable_offset + + if stringtable_offset >= stringfileinfo_struct.Length: + break + + # Parse a VarFileInfo entry + # + elif stringfileinfo_string and stringfileinfo_string.startswith( u'VarFileInfo' ): + + varfileinfo_struct = stringfileinfo_struct + varfileinfo_struct.name = 'VarFileInfo' + + if varfileinfo_struct.Type == 1 and varfileinfo_struct.ValueLength == 0: + + var_offset = self.dword_align( + stringfileinfo_offset + varfileinfo_struct.sizeof() + + 2*(len(stringfileinfo_string)+1), + version_struct.OffsetToData) + + varfileinfo_struct.Var = list() + + # Process all entries + # + + while True: + var_struct = self.__unpack_data__( + self.__Var_format__, + raw_data[var_offset:], + file_offset = start_offset+var_offset ) + + if not var_struct: + break + + ustr_offset = ( version_struct.OffsetToData + var_offset + + var_struct.sizeof() ) + try: + var_string = self.get_string_u_at_rva( ustr_offset ) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the version information, ' + + 'attempting to read VarFileInfo Var string. ' + + 'Can\'t read unicode string at offset 0x%x' % (ustr_offset)) + break + + + varfileinfo_struct.Var.append(var_struct) + + varword_offset = self.dword_align( + 2*(len(var_string)+1) + var_offset + var_struct.sizeof(), + version_struct.OffsetToData) + orig_varword_offset = varword_offset + + while varword_offset < orig_varword_offset + var_struct.ValueLength: + word1 = self.get_word_from_data( + raw_data[varword_offset:varword_offset+2], 0) + word2 = self.get_word_from_data( + raw_data[varword_offset+2:varword_offset+4], 0) + varword_offset += 4 + + if isinstance(word1, (int, long)) and isinstance(word1, (int, long)): + var_struct.entry = {var_string: '0x%04x 0x%04x' % (word1, word2)} + + var_offset = self.dword_align( + var_offset+var_struct.Length, version_struct.OffsetToData) + + if var_offset <= var_offset+var_struct.Length: + break + + + # Increment and align the offset + # + stringfileinfo_offset = self.dword_align( + stringfileinfo_struct.Length+stringfileinfo_offset, + version_struct.OffsetToData) + + # Check if all the StringFileInfo and VarFileInfo items have been processed + # + if stringfileinfo_struct.Length == 0 or stringfileinfo_offset >= versioninfo_struct.Length: + break + + + + def parse_export_directory(self, rva, size): + """Parse the export directory. + + Given the RVA of the export directory, it will process all + its entries. + + The exports will be made available through a list "exports" + containing a tuple with the following elements: + + (ordinal, symbol_address, symbol_name) + + And also through a dictionary "exports_by_ordinal" whose keys + will be the ordinals and the values tuples of the from: + + (symbol_address, symbol_name) + + The symbol addresses are relative, not absolute. + """ + + try: + export_dir = self.__unpack_data__( + self.__IMAGE_EXPORT_DIRECTORY_format__, + self.get_data( rva, Structure(self.__IMAGE_EXPORT_DIRECTORY_format__).sizeof() ), + file_offset = self.get_offset_from_rva(rva) ) + except PEFormatError: + self.__warnings.append( + 'Error parsing export directory at RVA: 0x%x' % ( rva ) ) + return + + if not export_dir: + return + + # We keep track of the bytes left in the file and use it to set a upper + # bound in the number of items that can be read from the different + # arrays + # + def length_until_eof(rva): + return len(self.__data__) - self.get_offset_from_rva(rva) + + try: + address_of_names = self.get_data( + export_dir.AddressOfNames, min( length_until_eof(export_dir.AddressOfNames), export_dir.NumberOfNames*4)) + address_of_name_ordinals = self.get_data( + export_dir.AddressOfNameOrdinals, min( length_until_eof(export_dir.AddressOfNameOrdinals), export_dir.NumberOfNames*4) ) + address_of_functions = self.get_data( + export_dir.AddressOfFunctions, min( length_until_eof(export_dir.AddressOfFunctions), export_dir.NumberOfFunctions*4) ) + except PEFormatError: + self.__warnings.append( + 'Error parsing export directory at RVA: 0x%x' % ( rva ) ) + return + + exports = [] + + max_failed_entries_before_giving_up = 10 + + for i in xrange( min( export_dir.NumberOfNames, length_until_eof(export_dir.AddressOfNames)/4) ): + + symbol_name_address = self.get_dword_from_data(address_of_names, i) + + symbol_name = self.get_string_at_rva( symbol_name_address ) + try: + symbol_name_offset = self.get_offset_from_rva( symbol_name_address ) + except PEFormatError: + max_failed_entries_before_giving_up -= 1 + if max_failed_entries_before_giving_up <= 0: + break + continue + + symbol_ordinal = self.get_word_from_data( + address_of_name_ordinals, i) + + + if symbol_ordinal*4 < len(address_of_functions): + symbol_address = self.get_dword_from_data( + address_of_functions, symbol_ordinal) + else: + # Corrupt? a bad pointer... we assume it's all + # useless, no exports + return None + + if symbol_address is None or symbol_address == 0: + continue + + # If the funcion's RVA points within the export directory + # it will point to a string with the forwarded symbol's string + # instead of pointing the the function start address. + + if symbol_address >= rva and symbol_address < rva+size: + forwarder_str = self.get_string_at_rva(symbol_address) + try: + forwarder_offset = self.get_offset_from_rva( symbol_address ) + except PEFormatError: + continue + else: + forwarder_str = None + forwarder_offset = None + + exports.append( + ExportData( + pe = self, + ordinal = export_dir.Base+symbol_ordinal, + ordinal_offset = self.get_offset_from_rva( export_dir.AddressOfNameOrdinals + 2*i ), + address = symbol_address, + address_offset = self.get_offset_from_rva( export_dir.AddressOfFunctions + 4*symbol_ordinal ), + name = symbol_name, + name_offset = symbol_name_offset, + forwarder = forwarder_str, + forwarder_offset = forwarder_offset )) + + ordinals = [exp.ordinal for exp in exports] + + max_failed_entries_before_giving_up = 10 + + for idx in xrange( min(export_dir.NumberOfFunctions, length_until_eof(export_dir.AddressOfFunctions)/4) ): + + if not idx+export_dir.Base in ordinals: + try: + symbol_address = self.get_dword_from_data( + address_of_functions, idx) + except PEFormatError: + symbol_address = None + + if symbol_address is None: + max_failed_entries_before_giving_up -= 1 + if max_failed_entries_before_giving_up <= 0: + break + + if symbol_address == 0: + continue + # + # Checking for forwarder again. + # + if symbol_address >= rva and symbol_address < rva+size: + forwarder_str = self.get_string_at_rva(symbol_address) + else: + forwarder_str = None + + exports.append( + ExportData( + ordinal = export_dir.Base+idx, + address = symbol_address, + name = None, + forwarder = forwarder_str)) + + return ExportDirData( + struct = export_dir, + symbols = exports) + + + def dword_align(self, offset, base): + return ((offset+base+3) & 0xfffffffcL) - (base & 0xfffffffcL) + + + def parse_delay_import_directory(self, rva, size): + """Walk and parse the delay import directory.""" + + import_descs = [] + while True: + try: + # If the RVA is invalid all would blow up. Some PEs seem to be + # specially nasty and have an invalid RVA. + data = self.get_data( rva, Structure(self.__IMAGE_DELAY_IMPORT_DESCRIPTOR_format__).sizeof() ) + except PEFormatError, e: + self.__warnings.append( + 'Error parsing the Delay import directory at RVA: 0x%x' % ( rva ) ) + break + + import_desc = self.__unpack_data__( + self.__IMAGE_DELAY_IMPORT_DESCRIPTOR_format__, + data, file_offset = self.get_offset_from_rva(rva) ) + + + # If the structure is all zeroes, we reached the end of the list + if not import_desc or import_desc.all_zeroes(): + break + + + rva += import_desc.sizeof() + + try: + import_data = self.parse_imports( + import_desc.pINT, + import_desc.pIAT, + None) + except PEFormatError, e: + self.__warnings.append( + 'Error parsing the Delay import directory. ' + + 'Invalid import data at RVA: 0x%x' % ( rva ) ) + break + + if not import_data: + continue + + + dll = self.get_string_at_rva(import_desc.szName) + if not is_valid_dos_filename(dll): + dll = '*invalid*' + + if dll: + import_descs.append( + ImportDescData( + struct = import_desc, + imports = import_data, + dll = dll)) + + return import_descs + + + + def parse_import_directory(self, rva, size): + """Walk and parse the import directory.""" + + import_descs = [] + while True: + try: + # If the RVA is invalid all would blow up. Some EXEs seem to be + # specially nasty and have an invalid RVA. + data = self.get_data(rva, Structure(self.__IMAGE_IMPORT_DESCRIPTOR_format__).sizeof() ) + except PEFormatError, e: + self.__warnings.append( + 'Error parsing the import directory at RVA: 0x%x' % ( rva ) ) + break + + import_desc = self.__unpack_data__( + self.__IMAGE_IMPORT_DESCRIPTOR_format__, + data, file_offset = self.get_offset_from_rva(rva) ) + + # If the structure is all zeroes, we reached the end of the list + if not import_desc or import_desc.all_zeroes(): + break + + rva += import_desc.sizeof() + + try: + import_data = self.parse_imports( + import_desc.OriginalFirstThunk, + import_desc.FirstThunk, + import_desc.ForwarderChain) + except PEFormatError, excp: + self.__warnings.append( + 'Error parsing the import directory. ' + + 'Invalid Import data at RVA: 0x%x (%s)' % ( rva, str(excp) ) ) + break + #raise excp + + if not import_data: + continue + + dll = self.get_string_at_rva(import_desc.Name) + if not is_valid_dos_filename(dll): + dll = '*invalid*' + + if dll: + import_descs.append( + ImportDescData( + struct = import_desc, + imports = import_data, + dll = dll)) + + suspicious_imports = set([ 'LoadLibrary', 'GetProcAddress' ]) + suspicious_imports_count = 0 + total_symbols = 0 + for imp_dll in import_descs: + for symbol in imp_dll.imports: + for suspicious_symbol in suspicious_imports: + if symbol and symbol.name and symbol.name.startswith( suspicious_symbol ): + suspicious_imports_count += 1 + break + total_symbols += 1 + if suspicious_imports_count == len(suspicious_imports) and total_symbols < 20: + self.__warnings.append( + 'Imported symbols contain entries typical of packed executables.' ) + + + + return import_descs + + + + def parse_imports(self, original_first_thunk, first_thunk, forwarder_chain): + """Parse the imported symbols. + + It will fill a list, which will be available as the dictionary + attribute "imports". Its keys will be the DLL names and the values + all the symbols imported from that object. + """ + + imported_symbols = [] + + # The following has been commented as a PE does not + # need to have the import data necessarily witin + # a section, it can keep it in gaps between sections + # or overlapping other data. + # + #imports_section = self.get_section_by_rva(first_thunk) + #if not imports_section: + # raise PEFormatError, 'Invalid/corrupt imports.' + + # Import Lookup Table. Contains ordinals or pointers to strings. + ilt = self.get_import_table(original_first_thunk) + # Import Address Table. May have identical content to ILT if + # PE file is not bounded, Will contain the address of the + # imported symbols once the binary is loaded or if it is already + # bound. + iat = self.get_import_table(first_thunk) + + # OC Patch: + # Would crash if IAT or ILT had None type + if (not iat or len(iat)==0) and (not ilt or len(ilt)==0): + raise PEFormatError( + 'Invalid Import Table information. ' + + 'Both ILT and IAT appear to be broken.') + + table = None + if ilt: + table = ilt + elif iat: + table = iat + else: + return None + + if self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE: + ordinal_flag = IMAGE_ORDINAL_FLAG + elif self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS: + ordinal_flag = IMAGE_ORDINAL_FLAG64 + + for idx in xrange(len(table)): + + imp_ord = None + imp_hint = None + imp_name = None + name_offset = None + hint_name_table_rva = None + + if table[idx].AddressOfData: + + # If imported by ordinal, we will append the ordinal number + # + if table[idx].AddressOfData & ordinal_flag: + import_by_ordinal = True + imp_ord = table[idx].AddressOfData & 0xffff + imp_name = None + name_offset = None + else: + import_by_ordinal = False + try: + hint_name_table_rva = table[idx].AddressOfData & 0x7fffffff + data = self.get_data(hint_name_table_rva, 2) + # Get the Hint + imp_hint = self.get_word_from_data(data, 0) + imp_name = self.get_string_at_rva(table[idx].AddressOfData+2) + if not is_valid_function_name(imp_name): + imp_name = '*invalid*' + + name_offset = self.get_offset_from_rva(table[idx].AddressOfData+2) + except PEFormatError, e: + pass + + # by nriva: we want the ThunkRVA and ThunkOffset + thunk_offset = table[idx].get_file_offset() + thunk_rva = self.get_rva_from_offset(thunk_offset) + + imp_address = first_thunk + self.OPTIONAL_HEADER.ImageBase + idx * 4 + + struct_iat = None + try: + + if iat and ilt and ilt[idx].AddressOfData != iat[idx].AddressOfData: + imp_bound = iat[idx].AddressOfData + struct_iat = iat[idx] + else: + imp_bound = None + except IndexError: + imp_bound = None + + # The file with hashes: + # + # MD5: bfe97192e8107d52dd7b4010d12b2924 + # SHA256: 3d22f8b001423cb460811ab4f4789f277b35838d45c62ec0454c877e7c82c7f5 + # + # has an invalid table built in a way that it's parseable but contains invalid + # entries that lead pefile to take extremely long amounts of time to + # parse. It also leads to extreme memory consumption. + # To prevent similar cases, if invalid entries are found in the middle of a + # table the parsing will be aborted + # + if imp_ord == None and imp_name == None: + raise PEFormatError( 'Invalid entries in the Import Table. Aborting parsing.' ) + + if imp_name != '' and (imp_ord or imp_name): + imported_symbols.append( + ImportData( + pe = self, + struct_table = table[idx], + struct_iat = struct_iat, # for bound imports if any + import_by_ordinal = import_by_ordinal, + ordinal = imp_ord, + ordinal_offset = table[idx].get_file_offset(), + hint = imp_hint, + name = imp_name, + name_offset = name_offset, + bound = imp_bound, + address = imp_address, + hint_name_table_rva = hint_name_table_rva, + thunk_offset = thunk_offset, + thunk_rva = thunk_rva )) + + return imported_symbols + + + + def get_import_table(self, rva): + + table = [] + + # We need the ordinal flag for a simple heuristic + # we're implementing within the loop + # + if self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE: + ordinal_flag = IMAGE_ORDINAL_FLAG + format = self.__IMAGE_THUNK_DATA_format__ + elif self.PE_TYPE == OPTIONAL_HEADER_MAGIC_PE_PLUS: + ordinal_flag = IMAGE_ORDINAL_FLAG64 + format = self.__IMAGE_THUNK_DATA64_format__ + + while True and rva: + + try: + data = self.get_data( rva, Structure(format).sizeof() ) + except PEFormatError, e: + self.__warnings.append( + 'Error parsing the import table. ' + + 'Invalid data at RVA: 0x%x' % ( rva ) ) + return None + + thunk_data = self.__unpack_data__( + format, data, file_offset=self.get_offset_from_rva(rva) ) + + if thunk_data and thunk_data.AddressOfData: + # If the entry looks like could be an ordinal... + if thunk_data.AddressOfData & ordinal_flag: + # But its value is beyond 2^16, we will assume it's a + # corrupted and ignore it altogether + if thunk_data.AddressOfData & 0x7fffffff > 0xffff: + return [] + + if not thunk_data or thunk_data.all_zeroes(): + break + + rva += thunk_data.sizeof() + + table.append(thunk_data) + + return table + + + def get_memory_mapped_image(self, max_virtual_address=0x10000000, ImageBase=None): + """Returns the data corresponding to the memory layout of the PE file. + + The data includes the PE header and the sections loaded at offsets + corresponding to their relative virtual addresses. (the VirtualAddress + section header member). + Any offset in this data corresponds to the absolute memory address + ImageBase+offset. + + The optional argument 'max_virtual_address' provides with means of limiting + which section are processed. + Any section with their VirtualAddress beyond this value will be skipped. + Normally, sections with values beyond this range are just there to confuse + tools. It's a common trick to see in packed executables. + + If the 'ImageBase' optional argument is supplied, the file's relocations + will be applied to the image by calling the 'relocate_image()' method. Beware + that the relocation information is applied permanently. + """ + + # Rebase if requested + # + if ImageBase is not None: + + # Keep a copy of the image's data before modifying it by rebasing it + # + original_data = self.__data__ + + self.relocate_image(ImageBase) + + # Collect all sections in one code block + #mapped_data = self.header + mapped_data = ''+ self.__data__[:] + for section in self.sections: + + # Miscellaneous integrity tests. + # Some packer will set these to bogus values to + # make tools go nuts. + # + if section.Misc_VirtualSize == 0 or section.SizeOfRawData == 0: + continue + + if section.SizeOfRawData > len(self.__data__): + continue + + if adjust_FileAlignment( section.PointerToRawData, + self.OPTIONAL_HEADER.FileAlignment ) > len(self.__data__): + + continue + + VirtualAddress_adj = adjust_SectionAlignment( section.VirtualAddress, + self.OPTIONAL_HEADER.SectionAlignment, self.OPTIONAL_HEADER.FileAlignment ) + + if VirtualAddress_adj >= max_virtual_address: + continue + + padding_length = VirtualAddress_adj - len(mapped_data) + + if padding_length>0: + mapped_data += '\0'*padding_length + elif padding_length<0: + mapped_data = mapped_data[:padding_length] + + mapped_data += section.get_data() + + # If the image was rebased, restore it to its original form + # + if ImageBase is not None: + self.__data__ = original_data + + return mapped_data + + + def get_resources_strings(self): + """Returns a list of all the strings found withing the resources (if any). + + This method will scan all entries in the resources directory of the PE, if + there is one, and will return a list() with the strings. + + An empty list will be returned otherwise. + """ + + resources_strings = list() + + if hasattr(self, 'DIRECTORY_ENTRY_RESOURCE'): + + for resource_type in self.DIRECTORY_ENTRY_RESOURCE.entries: + if hasattr(resource_type, 'directory'): + for resource_id in resource_type.directory.entries: + if hasattr(resource_id, 'directory'): + if hasattr(resource_id.directory, 'strings') and resource_id.directory.strings: + for res_string in resource_id.directory.strings.values(): + resources_strings.append( res_string ) + + return resources_strings + + + def get_data(self, rva=0, length=None): + """Get data regardless of the section where it lies on. + + Given a RVA and the size of the chunk to retrieve, this method + will find the section where the data lies and return the data. + """ + + s = self.get_section_by_rva(rva) + + if length: + end = rva + length + else: + end = None + + if not s: + if rva < len(self.header): + return self.header[rva:end] + + # Before we give up we check whether the file might + # contain the data anyway. There are cases of PE files + # without sections that rely on windows loading the first + # 8291 bytes into memory and assume the data will be + # there + # A functional file with these characteristics is: + # MD5: 0008892cdfbc3bda5ce047c565e52295 + # SHA-1: c7116b9ff950f86af256defb95b5d4859d4752a9 + # + if rva < len(self.__data__): + return self.__data__[rva:end] + + raise PEFormatError, 'data at RVA can\'t be fetched. Corrupt header?' + + return s.get_data(rva, length) + + + def get_rva_from_offset(self, offset): + """Get the RVA corresponding to this file offset. """ + + s = self.get_section_by_offset(offset) + if not s: + if self.sections: + lowest_rva = min( [ adjust_SectionAlignment( s.VirtualAddress, + self.OPTIONAL_HEADER.SectionAlignment, self.OPTIONAL_HEADER.FileAlignment ) for s in self.sections] ) + if offset < lowest_rva: + # We will assume that the offset lies within the headers, or + # at least points before where the earliest section starts + # and we will simply return the offset as the RVA + # + # The case illustrating this behavior can be found at: + # http://corkami.blogspot.com/2010/01/hey-hey-hey-whats-in-your-head.html + # where the import table is not contained by any section + # hence the RVA needs to be resolved to a raw offset + return offset + else: + return offset + #raise PEFormatError("specified offset (0x%x) doesn't belong to any section." % offset) + return s.get_rva_from_offset(offset) + + def get_offset_from_rva(self, rva): + """Get the file offset corresponding to this RVA. + + Given a RVA , this method will find the section where the + data lies and return the offset within the file. + """ + + s = self.get_section_by_rva(rva) + if not s: + + # If not found within a section assume it might + # point to overlay data or otherwise data present + # but not contained in any section. In those + # cases the RVA should equal the offset + if rva len(data): + return None + + return struct.unpack(' len(self.__data__): + return None + + return self.get_dword_from_data(self.__data__[offset:offset+4], 0) + + + def set_dword_at_rva(self, rva, dword): + """Set the double word value at the file offset corresponding to the given RVA.""" + return self.set_bytes_at_rva(rva, self.get_data_from_dword(dword)) + + + def set_dword_at_offset(self, offset, dword): + """Set the double word value at the given file offset.""" + return self.set_bytes_at_offset(offset, self.get_data_from_dword(dword)) + + + + ## + # Word get/set + ## + + def get_data_from_word(self, word): + """Return a two byte string representing the word value. (little endian).""" + return struct.pack(' len(data): + return None + + return struct.unpack(' len(self.__data__): + return None + + return self.get_word_from_data(self.__data__[offset:offset+2], 0) + + + def set_word_at_rva(self, rva, word): + """Set the word value at the file offset corresponding to the given RVA.""" + return self.set_bytes_at_rva(rva, self.get_data_from_word(word)) + + + def set_word_at_offset(self, offset, word): + """Set the word value at the given file offset.""" + return self.set_bytes_at_offset(offset, self.get_data_from_word(word)) + + + ## + # Quad-Word get/set + ## + + def get_data_from_qword(self, word): + """Return a eight byte string representing the quad-word value. (little endian).""" + return struct.pack(' len(data): + return None + + return struct.unpack(' len(self.__data__): + return None + + return self.get_qword_from_data(self.__data__[offset:offset+8], 0) + + + def set_qword_at_rva(self, rva, qword): + """Set the quad-word value at the file offset corresponding to the given RVA.""" + return self.set_bytes_at_rva(rva, self.get_data_from_qword(qword)) + + + def set_qword_at_offset(self, offset, qword): + """Set the quad-word value at the given file offset.""" + return self.set_bytes_at_offset(offset, self.get_data_from_qword(qword)) + + + + ## + # Set bytes + ## + + + def set_bytes_at_rva(self, rva, data): + """Overwrite, with the given string, the bytes at the file offset corresponding to the given RVA. + + Return True if successful, False otherwise. It can fail if the + offset is outside the file's boundaries. + """ + + offset = self.get_physical_by_rva(rva) + if not offset: + raise False + + return self.set_bytes_at_offset(offset, data) + + + def set_bytes_at_offset(self, offset, data): + """Overwrite the bytes at the given file offset with the given string. + + Return True if successful, False otherwise. It can fail if the + offset is outside the file's boundaries. + """ + + if not isinstance(data, str): + raise TypeError('data should be of type: str') + + if offset >= 0 and offset < len(self.__data__): + self.__data__ = ( self.__data__[:offset] + data + self.__data__[offset+len(data):] ) + else: + return False + + return True + + + def merge_modified_section_data(self): + """Update the PE image content with any individual section data that has been modified.""" + + for section in self.sections: + section_data_start = adjust_FileAlignment( section.PointerToRawData, + self.OPTIONAL_HEADER.FileAlignment ) + section_data_end = section_data_start+section.SizeOfRawData + if section_data_start < len(self.__data__) and section_data_end < len(self.__data__): + self.__data__ = self.__data__[:section_data_start] + section.get_data() + self.__data__[section_data_end:] + + + def relocate_image(self, new_ImageBase): + """Apply the relocation information to the image using the provided new image base. + + This method will apply the relocation information to the image. Given the new base, + all the relocations will be processed and both the raw data and the section's data + will be fixed accordingly. + The resulting image can be retrieved as well through the method: + + get_memory_mapped_image() + + In order to get something that would more closely match what could be found in memory + once the Windows loader finished its work. + """ + + relocation_difference = new_ImageBase - self.OPTIONAL_HEADER.ImageBase + + + for reloc in self.DIRECTORY_ENTRY_BASERELOC: + + virtual_address = reloc.struct.VirtualAddress + size_of_block = reloc.struct.SizeOfBlock + + # We iterate with an index because if the relocation is of type + # IMAGE_REL_BASED_HIGHADJ we need to also process the next entry + # at once and skip it for the next iteration + # + entry_idx = 0 + while entry_idx>16)&0xffff ) + + elif entry.type == RELOCATION_TYPE['IMAGE_REL_BASED_LOW']: + # Fix the low 16bits of a relocation + # + # Add low 16 bits of relocation_difference to the 16bit value + # at RVA=entry.rva + + self.set_word_at_rva( + entry.rva, + ( self.get_word_at_rva(entry.rva) + relocation_difference)&0xffff) + + elif entry.type == RELOCATION_TYPE['IMAGE_REL_BASED_HIGHLOW']: + # Handle all high and low parts of a 32bit relocation + # + # Add relocation_difference to the value at RVA=entry.rva + + self.set_dword_at_rva( + entry.rva, + self.get_dword_at_rva(entry.rva)+relocation_difference) + + elif entry.type == RELOCATION_TYPE['IMAGE_REL_BASED_HIGHADJ']: + # Fix the high 16bits of a relocation and adjust + # + # Add high 16bits of relocation_difference to the 32bit value + # composed from the (16bit value at RVA=entry.rva)<<16 plus + # the 16bit value at the next relocation entry. + # + + # If the next entry is beyond the array's limits, + # abort... the table is corrupt + # + if entry_idx == len(reloc.entries): + break + + next_entry = reloc.entries[entry_idx] + entry_idx += 1 + self.set_word_at_rva( entry.rva, + ((self.get_word_at_rva(entry.rva)<<16) + next_entry.rva + + relocation_difference & 0xffff0000) >> 16 ) + + elif entry.type == RELOCATION_TYPE['IMAGE_REL_BASED_DIR64']: + # Apply the difference to the 64bit value at the offset + # RVA=entry.rva + + self.set_qword_at_rva( + entry.rva, + self.get_qword_at_rva(entry.rva) + relocation_difference) + + + def verify_checksum(self): + + return self.OPTIONAL_HEADER.CheckSum == self.generate_checksum() + + + def generate_checksum(self): + + # This will make sure that the data representing the PE image + # is updated with any changes that might have been made by + # assigning values to header fields as those are not automatically + # updated upon assignment. + # + self.__data__ = self.write() + + # Get the offset to the CheckSum field in the OptionalHeader + # + checksum_offset = self.OPTIONAL_HEADER.__file_offset__ + 0x40 # 64 + + checksum = 0 + + # Verify the data is dword-aligned. Add padding if needed + # + remainder = len(self.__data__) % 4 + data = self.__data__ + ( '\0' * ((4-remainder) * ( remainder != 0 )) ) + + for i in range( len( data ) / 4 ): + + # Skip the checksum field + # + if i == checksum_offset / 4: + continue + + dword = struct.unpack('I', data[ i*4 : i*4+4 ])[0] + checksum = (checksum & 0xffffffff) + dword + (checksum>>32) + if checksum > 2**32: + checksum = (checksum & 0xffffffff) + (checksum >> 32) + + checksum = (checksum & 0xffff) + (checksum >> 16) + checksum = (checksum) + (checksum >> 16) + checksum = checksum & 0xffff + + # The length is the one of the original data, not the padded one + # + return checksum + len(self.__data__) + + + def is_exe(self): + """Check whether the file is a standard executable. + + This will return true only if the file has the IMAGE_FILE_EXECUTABLE_IMAGE flag set + and the IMAGE_FILE_DLL not set and the file does not appear to be a driver either. + """ + + EXE_flag = IMAGE_CHARACTERISTICS['IMAGE_FILE_EXECUTABLE_IMAGE'] + + if (not self.is_dll()) and (not self.is_driver()) and ( + EXE_flag & self.FILE_HEADER.Characteristics) == EXE_flag: + return True + + return False + + + def is_dll(self): + """Check whether the file is a standard DLL. + + This will return true only if the image has the IMAGE_FILE_DLL flag set. + """ + + DLL_flag = IMAGE_CHARACTERISTICS['IMAGE_FILE_DLL'] + + if ( DLL_flag & self.FILE_HEADER.Characteristics) == DLL_flag: + return True + + return False + + + def is_driver(self): + """Check whether the file is a Windows driver. + + This will return true only if there are reliable indicators of the image + being a driver. + """ + + # Checking that the ImageBase field of the OptionalHeader is above or + # equal to 0x80000000 (that is, whether it lies in the upper 2GB of + # the address space, normally belonging to the kernel) is not a + # reliable enough indicator. For instance, PEs that play the invalid + # ImageBase trick to get relocated could be incorrectly assumed to be + # drivers. + + # This is not reliable either... + # + # if any( (section.Characteristics & SECTION_CHARACTERISTICS['IMAGE_SCN_MEM_NOT_PAGED']) for section in self.sections ): + # return True + + if hasattr(self, 'DIRECTORY_ENTRY_IMPORT'): + + # If it imports from "ntoskrnl.exe" or other kernel components it should be a driver + # + if set( ('ntoskrnl.exe', 'hal.dll', 'ndis.sys', 'bootvid.dll', 'kdcom.dll' ) ).intersection( [ imp.dll.lower() for imp in self.DIRECTORY_ENTRY_IMPORT ] ): + return True + + return False + + + def get_overlay_data_start_offset(self): + """Get the offset of data appended to the file and not contained within the area described in the headers.""" + + highest_PointerToRawData = 0 + highest_SizeOfRawData = 0 + for section in self.sections: + + # If a section seems to fall outside the boundaries of the file we assume it's either + # because of intentionally misleading values or because the file is truncated + # In either case we skip it + if section.PointerToRawData + section.SizeOfRawData > len(self.__data__): + continue + + if section.PointerToRawData + section.SizeOfRawData > highest_PointerToRawData + highest_SizeOfRawData: + highest_PointerToRawData = section.PointerToRawData + highest_SizeOfRawData = section.SizeOfRawData + + if len(self.__data__) > highest_PointerToRawData + highest_SizeOfRawData: + return highest_PointerToRawData + highest_SizeOfRawData + + return None + + + def get_overlay(self): + """Get the data appended to the file and not contained within the area described in the headers.""" + + overlay_data_offset = self.get_overlay_data_start_offset() + + if overlay_data_offset is not None: + return self.__data__[ overlay_data_offset : ] + + return None + + + def trim(self): + """Return the just data defined by the PE headers, removing any overlayed data.""" + + overlay_data_offset = self.get_overlay_data_start_offset() + + if overlay_data_offset is not None: + return self.__data__[ : overlay_data_offset ] + + return self.__data__[:] diff --git a/pyinstaller/PyInstaller/lib/six.py b/pyinstaller/PyInstaller/lib/six.py new file mode 100644 index 0000000..36748f2 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/six.py @@ -0,0 +1,332 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.0.0" + + +# True if we are running on Python 3. +PY3 = sys.version_info[0] == 3 + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + MAXSIZE = sys.maxint + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) + # This is a bit ugly, but it avoids running this again. + delattr(tp, self.name) + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + + +class _MovedItems(types.ModuleType): + """Lazy loading of moved objects""" + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("reload_module", "__builtin__", "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("winreg", "_winreg"), +] +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) +del attr + +moves = sys.modules["six.moves"] = _MovedItems("moves") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_code = "__code__" + _func_defaults = "__defaults__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_code = "func_code" + _func_defaults = "func_defaults" + + +if PY3: + def get_unbound_function(unbound): + return unbound + + + advance_iterator = next + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) +else: + def get_unbound_function(unbound): + return unbound.im_func + + + def advance_iterator(it): + return it.next() + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +def get_method_function(meth): + """Get the underlying function of a bound method.""" + return getattr(meth, _meth_func) + + +def get_method_self(meth): + """Get the self of a bound method.""" + return getattr(meth, _meth_self) + + +def get_function_code(func): + """Get code object of a function.""" + return getattr(func, _func_code) + + +def get_function_defaults(func): + """Get defaults of a function.""" + return getattr(func, _func_defaults) + + +if PY3: + def b(s): + return s.encode("latin-1") + def u(s): + return s + import io + StringIO = io.StringIO + BytesIO = io.BytesIO +else: + def b(s): + return s + def u(s): + return unicode(s, "unicode_escape") + import StringIO + StringIO = BytesIO = StringIO.StringIO +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +if PY3: + exec_ = eval("exec") + + + def reraise(tp, value, tb=None): + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + + + print_ = eval("print") + + + def with_metaclass(meta, base=object): + ns = dict(base=base, meta=meta) + exec_("""class NewBase(base, metaclass=meta): + pass""", ns) + return ns["NewBase"] + + +else: + def exec_(code, globs=None, locs=None): + """Execute code in a namespace.""" + if globs is None: + frame = sys._getframe(1) + globs = frame.f_globals + if locs is None: + locs = frame.f_locals + del frame + elif locs is None: + locs = globs + exec("""exec code in globs, locs""") + + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + + def print_(*args, **kwargs): + """The new-style print function.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + def write(data): + if not isinstance(data, basestring): + data = str(data) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + + + def with_metaclass(meta, base=object): + class NewBase(base): + __metaclass__ = meta + return NewBase + + +_add_doc(reraise, """Reraise an exception.""") +_add_doc(with_metaclass, """Create a base class with a metaclass""") diff --git a/pyinstaller/PyInstaller/lib/unittest2/.svn/entries b/pyinstaller/PyInstaller/lib/unittest2/.svn/entries new file mode 100644 index 0000000..3ca1128 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/.svn/entries @@ -0,0 +1,119 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/lib/unittest2 +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test +dir + + + +add + +compatibility.py +file + + + +add + +runner.py +file + + + +add + +suite.py +file + + + +add + +case.py +file + + + +add + +util.py +file + + + +add + +result.py +file + + + +add + +__init__.py +file + + + +add + +__main__.py +file + + + +add + +signals.py +file + + + +add + +main.py +file + + + +add + +collector.py +file + + + +add + +loader.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/lib/unittest2/__init__.py b/pyinstaller/PyInstaller/lib/unittest2/__init__.py new file mode 100644 index 0000000..ba6fc31 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/__init__.py @@ -0,0 +1,67 @@ +""" +unittest2 + +unittest2 is a backport of the new features added to the unittest testing +framework in Python 2.7. It is tested to run on Python 2.4 - 2.6. + +To use unittest2 instead of unittest simply replace ``import unittest`` with +``import unittest2``. + + +Copyright (c) 1999-2003 Steve Purcell +Copyright (c) 2003-2010 Python Software Foundation +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +""" + +__all__ = ['TestResult', 'TestCase', 'TestSuite', + 'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main', + 'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless', + 'expectedFailure', 'TextTestResult', '__version__', 'collector'] + +__version__ = '0.5.1' + +# Expose obsolete functions for backwards compatibility +__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) + + +from unittest2.collector import collector +from unittest2.result import TestResult +from unittest2.case import \ + TestCase, FunctionTestCase, SkipTest, skip, skipIf,\ + skipUnless, expectedFailure + +from unittest2.suite import BaseTestSuite, TestSuite +from unittest2.loader import \ + TestLoader, defaultTestLoader, makeSuite, getTestCaseNames,\ + findTestCases + +from unittest2.main import TestProgram, main, main_ +from unittest2.runner import TextTestRunner, TextTestResult + +try: + from unittest2.signals import\ + installHandler, registerResult, removeResult, removeHandler +except ImportError: + # Compatibility with platforms that don't have the signal module + pass +else: + __all__.extend(['installHandler', 'registerResult', 'removeResult', + 'removeHandler']) + +# deprecated +_TextTestResult = TextTestResult + +__unittest = True diff --git a/pyinstaller/PyInstaller/lib/unittest2/__main__.py b/pyinstaller/PyInstaller/lib/unittest2/__main__.py new file mode 100644 index 0000000..04ed982 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/__main__.py @@ -0,0 +1,10 @@ +"""Main entry point""" + +import sys +if sys.argv[0].endswith("__main__.py"): + sys.argv[0] = "unittest2" + +__unittest = True + +from unittest2.main import main_ +main_() diff --git a/pyinstaller/PyInstaller/lib/unittest2/case.py b/pyinstaller/PyInstaller/lib/unittest2/case.py new file mode 100644 index 0000000..dc94463 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/case.py @@ -0,0 +1,1095 @@ +"""Test case implementation""" + +import sys +import difflib +import pprint +import re +import unittest +import warnings + +if sys.version_info[:2] == (2,3): + from sets import Set as set + from sets import ImmutableSet as frozenset + +from unittest2 import result +from unittest2.util import\ + safe_repr, safe_str, strclass,\ + unorderable_list_difference + +from unittest2.compatibility import wraps + +__unittest = True + + +DIFF_OMITTED = ('\nDiff is %s characters long. ' + 'Set self.maxDiff to None to see it.') + +class SkipTest(Exception): + """ + Raise this exception in a test to skip it. + + Usually you can use TestResult.skip() or one of the skipping decorators + instead of raising this directly. + """ + +class _ExpectedFailure(Exception): + """ + Raise this when a test is expected to fail. + + This is an implementation detail. + """ + + def __init__(self, exc_info): + # can't use super because Python 2.4 exceptions are old style + Exception.__init__(self) + self.exc_info = exc_info + +class _UnexpectedSuccess(Exception): + """ + The test was supposed to fail, but it didn't! + """ + +def _id(obj): + return obj + +def skip(reason): + """ + Unconditionally skip a test. + """ + def decorator(test_item): + if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): + def skip_wrapper(*args, **kwargs): + raise SkipTest(reason) + skip_wrapper = wraps(test_item)(skip_wrapper) + test_item = skip_wrapper + + test_item.__unittest_skip__ = True + test_item.__unittest_skip_why__ = reason + return test_item + return decorator + +def skipIf(condition, reason): + """ + Skip a test if the condition is true. + """ + if condition: + return skip(reason) + return _id + +def skipUnless(condition, reason): + """ + Skip a test unless the condition is true. + """ + if not condition: + return skip(reason) + return _id + + +def expectedFailure(func): + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + except Exception: + raise _ExpectedFailure(sys.exc_info()) + raise _UnexpectedSuccess + wrapper = wraps(func)(wrapper) + return wrapper + + +class _AssertRaisesContext(object): + """A context manager used to implement TestCase.assertRaises* methods.""" + + def __init__(self, expected, test_case, expected_regexp=None): + self.expected = expected + self.failureException = test_case.failureException + self.expected_regexp = expected_regexp + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if exc_type is None: + try: + exc_name = self.expected.__name__ + except AttributeError: + exc_name = str(self.expected) + raise self.failureException( + "%s not raised" % (exc_name,)) + if not issubclass(exc_type, self.expected): + # let unexpected exceptions pass through + return False + self.exception = exc_value # store for later retrieval + if self.expected_regexp is None: + return True + + expected_regexp = self.expected_regexp + if isinstance(expected_regexp, basestring): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(str(exc_value)): + raise self.failureException('"%s" does not match "%s"' % + (expected_regexp.pattern, str(exc_value))) + return True + + +class _TypeEqualityDict(object): + + def __init__(self, testcase): + self.testcase = testcase + self._store = {} + + def __setitem__(self, key, value): + self._store[key] = value + + def __getitem__(self, key): + value = self._store[key] + if isinstance(value, basestring): + return getattr(self.testcase, value) + return value + + def get(self, key, default=None): + if key in self._store: + return self[key] + return default + + +class TestCase(unittest.TestCase): + """A class whose instances are single test cases. + + By default, the test code itself should be placed in a method named + 'runTest'. + + If the fixture may be used for many test cases, create as + many test methods as are needed. When instantiating such a TestCase + subclass, specify in the constructor arguments the name of the test method + that the instance is to execute. + + Test authors should subclass TestCase for their own tests. Construction + and deconstruction of the test's environment ('fixture') can be + implemented by overriding the 'setUp' and 'tearDown' methods respectively. + + If it is necessary to override the __init__ method, the base class + __init__ method must always be called. It is important that subclasses + should not change the signature of their __init__ method, since instances + of the classes are instantiated automatically by parts of the framework + in order to be run. + """ + + # This attribute determines which exception will be raised when + # the instance's assertion methods fail; test methods raising this + # exception will be deemed to have 'failed' rather than 'errored' + + failureException = AssertionError + + # This attribute sets the maximum length of a diff in failure messages + # by assert methods using difflib. It is looked up as an instance attribute + # so can be configured by individual tests if required. + + maxDiff = 80*8 + + # This attribute determines whether long messages (including repr of + # objects used in assert methods) will be printed on failure in *addition* + # to any explicit message passed. + + longMessage = True + + # Attribute used by TestSuite for classSetUp + + _classSetupFailed = False + + def __init__(self, methodName='runTest'): + """Create an instance of the class that will use the named test + method when executed. Raises a ValueError if the instance does + not have a method with the specified name. + """ + self._testMethodName = methodName + self._resultForDoCleanups = None + try: + testMethod = getattr(self, methodName) + except AttributeError: + raise ValueError("no such test method in %s: %s" % \ + (self.__class__, methodName)) + self._testMethodDoc = testMethod.__doc__ + self._cleanups = [] + + # Map types to custom assertEqual functions that will compare + # instances of said type in more detail to generate a more useful + # error message. + self._type_equality_funcs = _TypeEqualityDict(self) + self.addTypeEqualityFunc(dict, 'assertDictEqual') + self.addTypeEqualityFunc(list, 'assertListEqual') + self.addTypeEqualityFunc(tuple, 'assertTupleEqual') + self.addTypeEqualityFunc(set, 'assertSetEqual') + self.addTypeEqualityFunc(frozenset, 'assertSetEqual') + self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual') + + def addTypeEqualityFunc(self, typeobj, function): + """Add a type specific assertEqual style function to compare a type. + + This method is for use by TestCase subclasses that need to register + their own type equality functions to provide nicer error messages. + + Args: + typeobj: The data type to call this function on when both values + are of the same type in assertEqual(). + function: The callable taking two arguments and an optional + msg= argument that raises self.failureException with a + useful error message when the two arguments are not equal. + """ + self._type_equality_funcs[typeobj] = function + + def addCleanup(self, function, *args, **kwargs): + """Add a function, with arguments, to be called when the test is + completed. Functions added are called on a LIFO basis and are + called after tearDown on test failure or success. + + Cleanup items are called even if setUp fails (unlike tearDown).""" + self._cleanups.append((function, args, kwargs)) + + def setUp(self): + "Hook method for setting up the test fixture before exercising it." + + def setUpClass(cls): + "Hook method for setting up class fixture before running tests in the class." + setUpClass = classmethod(setUpClass) + + def tearDownClass(cls): + "Hook method for deconstructing the class fixture after running all tests in the class." + tearDownClass = classmethod(tearDownClass) + + def tearDown(self): + "Hook method for deconstructing the test fixture after testing it." + + def countTestCases(self): + return 1 + + def defaultTestResult(self): + return result.TestResult() + + def shortDescription(self): + """Returns a one-line description of the test, or None if no + description has been provided. + + The default implementation of this method returns the first line of + the specified test method's docstring. + """ + doc = self._testMethodDoc + return doc and doc.split("\n")[0].strip() or None + + + def id(self): + return "%s.%s" % (strclass(self.__class__), self._testMethodName) + + def __eq__(self, other): + if type(self) is not type(other): + return NotImplemented + + return self._testMethodName == other._testMethodName + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((type(self), self._testMethodName)) + + def __str__(self): + return "%s (%s)" % (self._testMethodName, strclass(self.__class__)) + + def __repr__(self): + return "<%s testMethod=%s>" % \ + (strclass(self.__class__), self._testMethodName) + + def _addSkip(self, result, reason): + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None: + addSkip(self, reason) + else: + warnings.warn("Use of a TestResult without an addSkip method is deprecated", + DeprecationWarning, 2) + result.addSuccess(self) + + def run(self, result=None): + orig_result = result + if result is None: + result = self.defaultTestResult() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + + self._resultForDoCleanups = result + result.startTest(self) + + testMethod = getattr(self, self._testMethodName) + + if (getattr(self.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. + try: + skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') + or getattr(testMethod, '__unittest_skip_why__', '')) + self._addSkip(result, skip_why) + finally: + result.stopTest(self) + return + try: + success = False + try: + self.setUp() + except SkipTest, e: + self._addSkip(result, str(e)) + except Exception: + result.addError(self, sys.exc_info()) + else: + try: + testMethod() + except self.failureException: + result.addFailure(self, sys.exc_info()) + except _ExpectedFailure, e: + addExpectedFailure = getattr(result, 'addExpectedFailure', None) + if addExpectedFailure is not None: + addExpectedFailure(self, e.exc_info) + else: + warnings.warn("Use of a TestResult without an addExpectedFailure method is deprecated", + DeprecationWarning) + result.addSuccess(self) + except _UnexpectedSuccess: + addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None) + if addUnexpectedSuccess is not None: + addUnexpectedSuccess(self) + else: + warnings.warn("Use of a TestResult without an addUnexpectedSuccess method is deprecated", + DeprecationWarning) + result.addFailure(self, sys.exc_info()) + except SkipTest, e: + self._addSkip(result, str(e)) + except Exception: + result.addError(self, sys.exc_info()) + else: + success = True + + try: + self.tearDown() + except Exception: + result.addError(self, sys.exc_info()) + success = False + + cleanUpSuccess = self.doCleanups() + success = success and cleanUpSuccess + if success: + result.addSuccess(self) + finally: + result.stopTest(self) + if orig_result is None: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + + def doCleanups(self): + """Execute all cleanup functions. Normally called for you after + tearDown.""" + result = self._resultForDoCleanups + ok = True + while self._cleanups: + function, args, kwargs = self._cleanups.pop(-1) + try: + function(*args, **kwargs) + except Exception: + ok = False + result.addError(self, sys.exc_info()) + return ok + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the test without collecting errors in a TestResult""" + self.setUp() + getattr(self, self._testMethodName)() + self.tearDown() + while self._cleanups: + function, args, kwargs = self._cleanups.pop(-1) + function(*args, **kwargs) + + def skipTest(self, reason): + """Skip this test.""" + raise SkipTest(reason) + + def fail(self, msg=None): + """Fail immediately, with the given message.""" + raise self.failureException(msg) + + def assertFalse(self, expr, msg=None): + "Fail the test if the expression is true." + if expr: + msg = self._formatMessage(msg, "%s is not False" % safe_repr(expr)) + raise self.failureException(msg) + + def assertTrue(self, expr, msg=None): + """Fail the test unless the expression is true.""" + if not expr: + msg = self._formatMessage(msg, "%s is not True" % safe_repr(expr)) + raise self.failureException(msg) + + def _formatMessage(self, msg, standardMsg): + """Honour the longMessage attribute when generating failure messages. + If longMessage is False this means: + * Use only an explicit message if it is provided + * Otherwise use the standard message for the assert + + If longMessage is True: + * Use the standard message + * If an explicit message is provided, plus ' : ' and the explicit message + """ + if not self.longMessage: + return msg or standardMsg + if msg is None: + return standardMsg + try: + return '%s : %s' % (standardMsg, msg) + except UnicodeDecodeError: + return '%s : %s' % (safe_str(standardMsg), safe_str(msg)) + + + def assertRaises(self, excClass, callableObj=None, *args, **kwargs): + """Fail unless an exception of class excClass is thrown + by callableObj when invoked with arguments args and keyword + arguments kwargs. If a different type of exception is + thrown, it will not be caught, and the test case will be + deemed to have suffered an error, exactly as for an + unexpected exception. + + If called with callableObj omitted or None, will return a + context object used like this:: + + with self.assertRaises(SomeException): + do_something() + + The context manager keeps a reference to the exception as + the 'exception' attribute. This allows you to inspect the + exception after the assertion:: + + with self.assertRaises(SomeException) as cm: + do_something() + the_exception = cm.exception + self.assertEqual(the_exception.error_code, 3) + """ + if callableObj is None: + return _AssertRaisesContext(excClass, self) + try: + callableObj(*args, **kwargs) + except excClass: + return + + if hasattr(excClass,'__name__'): + excName = excClass.__name__ + else: + excName = str(excClass) + raise self.failureException, "%s not raised" % excName + + def _getAssertEqualityFunc(self, first, second): + """Get a detailed comparison function for the types of the two args. + + Returns: A callable accepting (first, second, msg=None) that will + raise a failure exception if first != second with a useful human + readable error message for those types. + """ + # + # NOTE(gregory.p.smith): I considered isinstance(first, type(second)) + # and vice versa. I opted for the conservative approach in case + # subclasses are not intended to be compared in detail to their super + # class instances using a type equality func. This means testing + # subtypes won't automagically use the detailed comparison. Callers + # should use their type specific assertSpamEqual method to compare + # subclasses if the detailed comparison is desired and appropriate. + # See the discussion in http://bugs.python.org/issue2578. + # + if type(first) is type(second): + asserter = self._type_equality_funcs.get(type(first)) + if asserter is not None: + return asserter + + return self._baseAssertEqual + + def _baseAssertEqual(self, first, second, msg=None): + """The default assertEqual implementation, not type specific.""" + if not first == second: + standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second)) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertEqual(self, first, second, msg=None): + """Fail if the two objects are unequal as determined by the '==' + operator. + """ + assertion_func = self._getAssertEqualityFunc(first, second) + assertion_func(first, second, msg=msg) + + def assertNotEqual(self, first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' + operator. + """ + if not first != second: + msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first), + safe_repr(second))) + raise self.failureException(msg) + + def assertAlmostEqual(self, first, second, places=None, msg=None, delta=None): + """Fail if the two objects are unequal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero, or by comparing that the + between the two objects is more than the given delta. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most signficant digit). + + If the two objects compare equal then they will automatically + compare almost equal. + """ + if first == second: + # shortcut + return + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + + if delta is not None: + if abs(first - second) <= delta: + return + + standardMsg = '%s != %s within %s delta' % (safe_repr(first), + safe_repr(second), + safe_repr(delta)) + else: + if places is None: + places = 7 + + if round(abs(second-first), places) == 0: + return + + standardMsg = '%s != %s within %r places' % (safe_repr(first), + safe_repr(second), + places) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertNotAlmostEqual(self, first, second, places=None, msg=None, delta=None): + """Fail if the two objects are equal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero, or by comparing that the + between the two objects is less than the given delta. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most signficant digit). + + Objects that are equal automatically fail. + """ + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + if delta is not None: + if not (first == second) and abs(first - second) > delta: + return + standardMsg = '%s == %s within %s delta' % (safe_repr(first), + safe_repr(second), + safe_repr(delta)) + else: + if places is None: + places = 7 + if not (first == second) and round(abs(second-first), places) != 0: + return + standardMsg = '%s == %s within %r places' % (safe_repr(first), + safe_repr(second), + places) + + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + # Synonyms for assertion methods + + # The plurals are undocumented. Keep them that way to discourage use. + # Do not add more. Do not remove. + # Going through a deprecation cycle on these would annoy many people. + assertEquals = assertEqual + assertNotEquals = assertNotEqual + assertAlmostEquals = assertAlmostEqual + assertNotAlmostEquals = assertNotAlmostEqual + assert_ = assertTrue + + # These fail* assertion method names are pending deprecation and will + # be a DeprecationWarning in 3.2; http://bugs.python.org/issue2578 + def _deprecate(original_func): + def deprecated_func(*args, **kwargs): + warnings.warn( + ('Please use %s instead.' % original_func.__name__), + PendingDeprecationWarning, 2) + return original_func(*args, **kwargs) + return deprecated_func + + failUnlessEqual = _deprecate(assertEqual) + failIfEqual = _deprecate(assertNotEqual) + failUnlessAlmostEqual = _deprecate(assertAlmostEqual) + failIfAlmostEqual = _deprecate(assertNotAlmostEqual) + failUnless = _deprecate(assertTrue) + failUnlessRaises = _deprecate(assertRaises) + failIf = _deprecate(assertFalse) + + def assertSequenceEqual(self, seq1, seq2, + msg=None, seq_type=None, max_diff=80*8): + """An equality assertion for ordered sequences (like lists and tuples). + + For the purposes of this function, a valid ordered sequence type is one + which can be indexed, has a length, and has an equality operator. + + Args: + seq1: The first sequence to compare. + seq2: The second sequence to compare. + seq_type: The expected datatype of the sequences, or None if no + datatype should be enforced. + msg: Optional message to use on failure instead of a list of + differences. + max_diff: Maximum size off the diff, larger diffs are not shown + """ + if seq_type is not None: + seq_type_name = seq_type.__name__ + if not isinstance(seq1, seq_type): + raise self.failureException('First sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq1))) + if not isinstance(seq2, seq_type): + raise self.failureException('Second sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq2))) + else: + seq_type_name = "sequence" + + differing = None + try: + len1 = len(seq1) + except (TypeError, NotImplementedError): + differing = 'First %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + try: + len2 = len(seq2) + except (TypeError, NotImplementedError): + differing = 'Second %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + if seq1 == seq2: + return + + seq1_repr = repr(seq1) + seq2_repr = repr(seq2) + if len(seq1_repr) > 30: + seq1_repr = seq1_repr[:30] + '...' + if len(seq2_repr) > 30: + seq2_repr = seq2_repr[:30] + '...' + elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr) + differing = '%ss differ: %s != %s\n' % elements + + for i in xrange(min(len1, len2)): + try: + item1 = seq1[i] + except (TypeError, IndexError, NotImplementedError): + differing += ('\nUnable to index element %d of first %s\n' % + (i, seq_type_name)) + break + + try: + item2 = seq2[i] + except (TypeError, IndexError, NotImplementedError): + differing += ('\nUnable to index element %d of second %s\n' % + (i, seq_type_name)) + break + + if item1 != item2: + differing += ('\nFirst differing element %d:\n%s\n%s\n' % + (i, item1, item2)) + break + else: + if (len1 == len2 and seq_type is None and + type(seq1) != type(seq2)): + # The sequences are the same, but have differing types. + return + + if len1 > len2: + differing += ('\nFirst %s contains %d additional ' + 'elements.\n' % (seq_type_name, len1 - len2)) + try: + differing += ('First extra element %d:\n%s\n' % + (len2, seq1[len2])) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of first %s\n' % (len2, seq_type_name)) + elif len1 < len2: + differing += ('\nSecond %s contains %d additional ' + 'elements.\n' % (seq_type_name, len2 - len1)) + try: + differing += ('First extra element %d:\n%s\n' % + (len1, seq2[len1])) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of second %s\n' % (len1, seq_type_name)) + standardMsg = differing + diffMsg = '\n' + '\n'.join( + difflib.ndiff(pprint.pformat(seq1).splitlines(), + pprint.pformat(seq2).splitlines())) + + standardMsg = self._truncateMessage(standardMsg, diffMsg) + msg = self._formatMessage(msg, standardMsg) + self.fail(msg) + + def _truncateMessage(self, message, diff): + max_diff = self.maxDiff + if max_diff is None or len(diff) <= max_diff: + return message + diff + return message + (DIFF_OMITTED % len(diff)) + + def assertListEqual(self, list1, list2, msg=None): + """A list-specific equality assertion. + + Args: + list1: The first list to compare. + list2: The second list to compare. + msg: Optional message to use on failure instead of a list of + differences. + + """ + self.assertSequenceEqual(list1, list2, msg, seq_type=list) + + def assertTupleEqual(self, tuple1, tuple2, msg=None): + """A tuple-specific equality assertion. + + Args: + tuple1: The first tuple to compare. + tuple2: The second tuple to compare. + msg: Optional message to use on failure instead of a list of + differences. + """ + self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple) + + def assertSetEqual(self, set1, set2, msg=None): + """A set-specific equality assertion. + + Args: + set1: The first set to compare. + set2: The second set to compare. + msg: Optional message to use on failure instead of a list of + differences. + + assertSetEqual uses ducktyping to support + different types of sets, and is optimized for sets specifically + (parameters must support a difference method). + """ + try: + difference1 = set1.difference(set2) + except TypeError, e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError, e: + self.fail('first argument does not support set difference: %s' % e) + + try: + difference2 = set2.difference(set1) + except TypeError, e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError, e: + self.fail('second argument does not support set difference: %s' % e) + + if not (difference1 or difference2): + return + + lines = [] + if difference1: + lines.append('Items in the first set but not the second:') + for item in difference1: + lines.append(repr(item)) + if difference2: + lines.append('Items in the second set but not the first:') + for item in difference2: + lines.append(repr(item)) + + standardMsg = '\n'.join(lines) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIn(self, member, container, msg=None): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '%s not found in %s' % (safe_repr(member), + safe_repr(container)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIn(self, member, container, msg=None): + """Just like self.assertTrue(a not in b), but with a nicer default message.""" + if member in container: + standardMsg = '%s unexpectedly found in %s' % (safe_repr(member), + safe_repr(container)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIs(self, expr1, expr2, msg=None): + """Just like self.assertTrue(a is b), but with a nicer default message.""" + if expr1 is not expr2: + standardMsg = '%s is not %s' % (safe_repr(expr1), safe_repr(expr2)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNot(self, expr1, expr2, msg=None): + """Just like self.assertTrue(a is not b), but with a nicer default message.""" + if expr1 is expr2: + standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictEqual(self, d1, d2, msg=None): + self.assert_(isinstance(d1, dict), 'First argument is not a dictionary') + self.assert_(isinstance(d2, dict), 'Second argument is not a dictionary') + + if d1 != d2: + standardMsg = '%s != %s' % (safe_repr(d1, True), safe_repr(d2, True)) + diff = ('\n' + '\n'.join(difflib.ndiff( + pprint.pformat(d1).splitlines(), + pprint.pformat(d2).splitlines()))) + standardMsg = self._truncateMessage(standardMsg, diff) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictContainsSubset(self, expected, actual, msg=None): + """Checks whether actual is a superset of expected.""" + missing = [] + mismatched = [] + for key, value in expected.iteritems(): + if key not in actual: + missing.append(key) + else: + try: + are_equal = (value == actual[key]) + except UnicodeDecodeError: + are_equal = False + if not are_equal: + mismatched.append('%s, expected: %s, actual: %s' % + (safe_repr(key), safe_repr(value), + safe_repr(actual[key]))) + + if not (missing or mismatched): + return + + standardMsg = '' + if missing: + standardMsg = 'Missing: %s' % ','.join([safe_repr(m) for m in + missing]) + if mismatched: + if standardMsg: + standardMsg += '; ' + standardMsg += 'Mismatched values: %s' % ','.join(mismatched) + + self.fail(self._formatMessage(msg, standardMsg)) + + def assertItemsEqual(self, expected_seq, actual_seq, msg=None): + """An unordered sequence specific comparison. It asserts that + expected_seq and actual_seq contain the same elements. It is + the equivalent of:: + + self.assertEqual(sorted(expected_seq), sorted(actual_seq)) + + Raises with an error message listing which elements of expected_seq + are missing from actual_seq and vice versa if any. + + Asserts that each element has the same count in both sequences. + Example: + - [0, 1, 1] and [1, 0, 1] compare equal. + - [0, 0, 1] and [0, 1] compare unequal. + """ + try: + + expected = expected_seq[:] + expected.sort() + actual = actual_seq[:] + actual.sort() + except TypeError: + # Unsortable items (example: set(), complex(), ...) + expected = list(expected_seq) + actual = list(actual_seq) + missing, unexpected = unorderable_list_difference( + expected, actual, ignore_duplicate=False + ) + else: + return self.assertSequenceEqual(expected, actual, msg=msg) + + errors = [] + if missing: + errors.append('Expected, but missing:\n %s' % + safe_repr(missing)) + if unexpected: + errors.append('Unexpected, but present:\n %s' % + safe_repr(unexpected)) + if errors: + standardMsg = '\n'.join(errors) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertMultiLineEqual(self, first, second, msg=None): + """Assert that two multi-line strings are equal.""" + self.assert_(isinstance(first, basestring), ( + 'First argument is not a string')) + self.assert_(isinstance(second, basestring), ( + 'Second argument is not a string')) + + if first != second: + standardMsg = '%s != %s' % (safe_repr(first, True), safe_repr(second, True)) + diff = '\n' + ''.join(difflib.ndiff(first.splitlines(True), + second.splitlines(True))) + standardMsg = self._truncateMessage(standardMsg, diff) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLess(self, a, b, msg=None): + """Just like self.assertTrue(a < b), but with a nicer default message.""" + if not a < b: + standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLessEqual(self, a, b, msg=None): + """Just like self.assertTrue(a <= b), but with a nicer default message.""" + if not a <= b: + standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreater(self, a, b, msg=None): + """Just like self.assertTrue(a > b), but with a nicer default message.""" + if not a > b: + standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreaterEqual(self, a, b, msg=None): + """Just like self.assertTrue(a >= b), but with a nicer default message.""" + if not a >= b: + standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNone(self, obj, msg=None): + """Same as self.assertTrue(obj is None), with a nicer default message.""" + if obj is not None: + standardMsg = '%s is not None' % (safe_repr(obj),) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNotNone(self, obj, msg=None): + """Included for symmetry with assertIsNone.""" + if obj is None: + standardMsg = 'unexpectedly None' + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsInstance(self, obj, cls, msg=None): + """Same as self.assertTrue(isinstance(obj, cls)), with a nicer + default message.""" + if not isinstance(obj, cls): + standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIsInstance(self, obj, cls, msg=None): + """Included for symmetry with assertIsInstance.""" + if isinstance(obj, cls): + standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertRaisesRegexp(self, expected_exception, expected_regexp, + callable_obj=None, *args, **kwargs): + """Asserts that the message in a raised exception matches a regexp. + + Args: + expected_exception: Exception class expected to be raised. + expected_regexp: Regexp (re pattern object or string) expected + to be found in error message. + callable_obj: Function to be called. + args: Extra args. + kwargs: Extra kwargs. + """ + if callable_obj is None: + return _AssertRaisesContext(expected_exception, self, expected_regexp) + try: + callable_obj(*args, **kwargs) + except expected_exception, exc_value: + if isinstance(expected_regexp, basestring): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(str(exc_value)): + raise self.failureException('"%s" does not match "%s"' % + (expected_regexp.pattern, str(exc_value))) + else: + if hasattr(expected_exception, '__name__'): + excName = expected_exception.__name__ + else: + excName = str(expected_exception) + raise self.failureException, "%s not raised" % excName + + + def assertRegexpMatches(self, text, expected_regexp, msg=None): + """Fail the test unless the text matches the regular expression.""" + if isinstance(expected_regexp, basestring): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(text): + msg = msg or "Regexp didn't match" + msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text) + raise self.failureException(msg) + + def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None): + """Fail the test if the text matches the regular expression.""" + if isinstance(unexpected_regexp, basestring): + unexpected_regexp = re.compile(unexpected_regexp) + match = unexpected_regexp.search(text) + if match: + msg = msg or "Regexp matched" + msg = '%s: %r matches %r in %r' % (msg, + text[match.start():match.end()], + unexpected_regexp.pattern, + text) + raise self.failureException(msg) + +class FunctionTestCase(TestCase): + """A test case that wraps a test function. + + This is useful for slipping pre-existing test functions into the + unittest framework. Optionally, set-up and tidy-up functions can be + supplied. As with TestCase, the tidy-up ('tearDown') function will + always be called if the set-up ('setUp') function ran successfully. + """ + + def __init__(self, testFunc, setUp=None, tearDown=None, description=None): + super(FunctionTestCase, self).__init__() + self._setUpFunc = setUp + self._tearDownFunc = tearDown + self._testFunc = testFunc + self._description = description + + def setUp(self): + if self._setUpFunc is not None: + self._setUpFunc() + + def tearDown(self): + if self._tearDownFunc is not None: + self._tearDownFunc() + + def runTest(self): + self._testFunc() + + def id(self): + return self._testFunc.__name__ + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self._setUpFunc == other._setUpFunc and \ + self._tearDownFunc == other._tearDownFunc and \ + self._testFunc == other._testFunc and \ + self._description == other._description + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((type(self), self._setUpFunc, self._tearDownFunc, + self._testFunc, self._description)) + + def __str__(self): + return "%s (%s)" % (strclass(self.__class__), + self._testFunc.__name__) + + def __repr__(self): + return "<%s testFunc=%s>" % (strclass(self.__class__), + self._testFunc) + + def shortDescription(self): + if self._description is not None: + return self._description + doc = self._testFunc.__doc__ + return doc and doc.split("\n")[0].strip() or None diff --git a/pyinstaller/PyInstaller/lib/unittest2/collector.py b/pyinstaller/PyInstaller/lib/unittest2/collector.py new file mode 100644 index 0000000..28ff3f8 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/collector.py @@ -0,0 +1,9 @@ +import os +import sys +from unittest2.loader import defaultTestLoader + +def collector(): + # import __main__ triggers code re-execution + __main__ = sys.modules['__main__'] + setupDir = os.path.abspath(os.path.dirname(__main__.__file__)) + return defaultTestLoader.discover(setupDir) diff --git a/pyinstaller/PyInstaller/lib/unittest2/compatibility.py b/pyinstaller/PyInstaller/lib/unittest2/compatibility.py new file mode 100644 index 0000000..b8f15dd --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/compatibility.py @@ -0,0 +1,64 @@ +import os +import sys + +try: + from functools import wraps +except ImportError: + # only needed for Python 2.4 + def wraps(_): + def _wraps(func): + return func + return _wraps + +__unittest = True + +def _relpath_nt(path, start=os.path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + start_list = os.path.abspath(start).split(os.path.sep) + path_list = os.path.abspath(path).split(os.path.sep) + if start_list[0].lower() != path_list[0].lower(): + unc_path, rest = os.path.splitunc(path) + unc_start, rest = os.path.splitunc(start) + if bool(unc_path) ^ bool(unc_start): + raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" + % (path, start)) + else: + raise ValueError("path is on drive %s, start on drive %s" + % (path_list[0], start_list[0])) + # Work out how much of the filepath is shared by start and path. + for i in range(min(len(start_list), len(path_list))): + if start_list[i].lower() != path_list[i].lower(): + break + else: + i += 1 + + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return os.path.curdir + return os.path.join(*rel_list) + +# default to posixpath definition +def _relpath_posix(path, start=os.path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + start_list = os.path.abspath(start).split(os.path.sep) + path_list = os.path.abspath(path).split(os.path.sep) + + # Work out how much of the filepath is shared by start and path. + i = len(os.path.commonprefix([start_list, path_list])) + + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return os.path.curdir + return os.path.join(*rel_list) + +if os.path is sys.modules.get('ntpath'): + relpath = _relpath_nt +else: + relpath = _relpath_posix diff --git a/pyinstaller/PyInstaller/lib/unittest2/loader.py b/pyinstaller/PyInstaller/lib/unittest2/loader.py new file mode 100644 index 0000000..8f2753f --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/loader.py @@ -0,0 +1,323 @@ +"""Loading unittests.""" + +import os +import re +import sys +import traceback +import types +import unittest + +from fnmatch import fnmatch + +from unittest2 import case, suite + +try: + from os.path import relpath +except ImportError: + from unittest2.compatibility import relpath + +__unittest = True + + +def _CmpToKey(mycmp): + 'Convert a cmp= function into a key= function' + class K(object): + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) == -1 + return K + + +# what about .pyc or .pyo (etc) +# we would need to avoid loading the same tests multiple times +# from '.py', '.pyc' *and* '.pyo' +VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE) + + +def _make_failed_import_test(name, suiteClass): + message = 'Failed to import test module: %s' % name + if hasattr(traceback, 'format_exc'): + # Python 2.3 compatibility + # format_exc returns two frames of discover.py as well + message += '\n%s' % traceback.format_exc() + return _make_failed_test('ModuleImportFailure', name, ImportError(message), + suiteClass) + +def _make_failed_load_tests(name, exception, suiteClass): + return _make_failed_test('LoadTestsFailure', name, exception, suiteClass) + +def _make_failed_test(classname, methodname, exception, suiteClass): + def testFailure(self): + raise exception + attrs = {methodname: testFailure} + TestClass = type(classname, (case.TestCase,), attrs) + return suiteClass((TestClass(methodname),)) + + +class TestLoader(unittest.TestLoader): + """ + This class is responsible for loading tests according to various criteria + and returning them wrapped in a TestSuite + """ + testMethodPrefix = 'test' + sortTestMethodsUsing = cmp + suiteClass = suite.TestSuite + _top_level_dir = None + + def loadTestsFromTestCase(self, testCaseClass): + """Return a suite of all tests cases contained in testCaseClass""" + if issubclass(testCaseClass, suite.TestSuite): + raise TypeError("Test cases should not be derived from TestSuite." + " Maybe you meant to derive from TestCase?") + testCaseNames = self.getTestCaseNames(testCaseClass) + if not testCaseNames and hasattr(testCaseClass, 'runTest'): + testCaseNames = ['runTest'] + loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) + return loaded_suite + + def loadTestsFromModule(self, module, use_load_tests=True): + """Return a suite of all tests cases contained in the given module""" + tests = [] + for name in dir(module): + obj = getattr(module, name) + if isinstance(obj, type) and issubclass(obj, unittest.TestCase): + tests.append(self.loadTestsFromTestCase(obj)) + + load_tests = getattr(module, 'load_tests', None) + tests = self.suiteClass(tests) + if use_load_tests and load_tests is not None: + try: + return load_tests(self, tests, None) + except Exception, e: + return _make_failed_load_tests(module.__name__, e, + self.suiteClass) + return tests + + def loadTestsFromName(self, name, module=None): + """Return a suite of all tests cases given a string specifier. + + The name may resolve either to a module, a test case class, a + test method within a test case class, or a callable object which + returns a TestCase or TestSuite instance. + + The method optionally resolves the names relative to a given module. + """ + parts = name.split('.') + if module is None: + parts_copy = parts[:] + while parts_copy: + try: + module = __import__('.'.join(parts_copy)) + break + except ImportError: + del parts_copy[-1] + if not parts_copy: + raise + parts = parts[1:] + obj = module + for part in parts: + parent, obj = obj, getattr(obj, part) + + if isinstance(obj, types.ModuleType): + return self.loadTestsFromModule(obj) + elif isinstance(obj, type) and issubclass(obj, unittest.TestCase): + return self.loadTestsFromTestCase(obj) + elif (isinstance(obj, types.UnboundMethodType) and + isinstance(parent, type) and + issubclass(parent, case.TestCase)): + return self.suiteClass([parent(obj.__name__)]) + elif isinstance(obj, unittest.TestSuite): + return obj + elif hasattr(obj, '__call__'): + test = obj() + if isinstance(test, unittest.TestSuite): + return test + elif isinstance(test, unittest.TestCase): + return self.suiteClass([test]) + else: + raise TypeError("calling %s returned %s, not a test" % + (obj, test)) + else: + raise TypeError("don't know how to make test from: %s" % obj) + + def loadTestsFromNames(self, names, module=None): + """Return a suite of all tests cases found using the given sequence + of string specifiers. See 'loadTestsFromName()'. + """ + suites = [self.loadTestsFromName(name, module) for name in names] + return self.suiteClass(suites) + + def getTestCaseNames(self, testCaseClass): + """Return a sorted sequence of method names found within testCaseClass + """ + def isTestMethod(attrname, testCaseClass=testCaseClass, + prefix=self.testMethodPrefix): + return attrname.startswith(prefix) and \ + hasattr(getattr(testCaseClass, attrname), '__call__') + testFnNames = filter(isTestMethod, dir(testCaseClass)) + if self.sortTestMethodsUsing: + # testFnNames.sort(key=_CmpToKey(self.sortTestMethodsUsing)) + testFnNames.sort(self.sortTestMethodsUsing) + return testFnNames + + def discover(self, start_dir, pattern='test*.py', top_level_dir=None): + """Find and return all test modules from the specified start + directory, recursing into subdirectories to find them. Only test files + that match the pattern will be loaded. (Using shell style pattern + matching.) + + All test modules must be importable from the top level of the project. + If the start directory is not the top level directory then the top + level directory must be specified separately. + + If a test package name (directory with '__init__.py') matches the + pattern then the package will be checked for a 'load_tests' function. If + this exists then it will be called with loader, tests, pattern. + + If load_tests exists then discovery does *not* recurse into the package, + load_tests is responsible for loading all tests in the package. + + The pattern is deliberately not stored as a loader attribute so that + packages can continue discovery themselves. top_level_dir is stored so + load_tests does not need to pass this argument in to loader.discover(). + """ + set_implicit_top = False + if top_level_dir is None and self._top_level_dir is not None: + # make top_level_dir optional if called from load_tests in a package + top_level_dir = self._top_level_dir + elif top_level_dir is None: + set_implicit_top = True + top_level_dir = start_dir + + top_level_dir = os.path.abspath(top_level_dir) + + if not top_level_dir in sys.path: + # all test modules must be importable from the top level directory + # should we *unconditionally* put the start directory in first + # in sys.path to minimise likelihood of conflicts between installed + # modules and development versions? + sys.path.insert(0, top_level_dir) + self._top_level_dir = top_level_dir + + is_not_importable = False + if os.path.isdir(os.path.abspath(start_dir)): + start_dir = os.path.abspath(start_dir) + if start_dir != top_level_dir: + is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py')) + else: + # support for discovery from dotted module names + try: + __import__(start_dir) + except ImportError: + is_not_importable = True + else: + the_module = sys.modules[start_dir] + top_part = start_dir.split('.')[0] + start_dir = os.path.abspath(os.path.dirname((the_module.__file__))) + if set_implicit_top: + self._top_level_dir = os.path.abspath(os.path.dirname(os.path.dirname(sys.modules[top_part].__file__))) + sys.path.remove(top_level_dir) + + if is_not_importable: + raise ImportError('Start directory is not importable: %r' % start_dir) + + tests = list(self._find_tests(start_dir, pattern)) + return self.suiteClass(tests) + + def _get_name_from_path(self, path): + path = os.path.splitext(os.path.normpath(path))[0] + + _relpath = relpath(path, self._top_level_dir) + assert not os.path.isabs(_relpath), "Path must be within the project" + assert not _relpath.startswith('..'), "Path must be within the project" + + name = _relpath.replace(os.path.sep, '.') + return name + + def _get_module_from_name(self, name): + __import__(name) + return sys.modules[name] + + def _match_path(self, path, full_path, pattern): + # override this method to use alternative matching strategy + return fnmatch(path, pattern) + + def _find_tests(self, start_dir, pattern): + """Used by discovery. Yields test suites it loads.""" + paths = os.listdir(start_dir) + + for path in paths: + full_path = os.path.join(start_dir, path) + if os.path.isfile(full_path): + if not VALID_MODULE_NAME.match(path): + # valid Python identifiers only + continue + if not self._match_path(path, full_path, pattern): + continue + # if the test file matches, load it + name = self._get_name_from_path(full_path) + try: + module = self._get_module_from_name(name) + except: + yield _make_failed_import_test(name, self.suiteClass) + else: + mod_file = os.path.abspath(getattr(module, '__file__', full_path)) + realpath = os.path.splitext(mod_file)[0] + fullpath_noext = os.path.splitext(full_path)[0] + if realpath.lower() != fullpath_noext.lower(): + module_dir = os.path.dirname(realpath) + mod_name = os.path.splitext(os.path.basename(full_path))[0] + expected_dir = os.path.dirname(full_path) + msg = ("%r module incorrectly imported from %r. Expected %r. " + "Is this module globally installed?") + raise ImportError(msg % (mod_name, module_dir, expected_dir)) + yield self.loadTestsFromModule(module) + elif os.path.isdir(full_path): + if not os.path.isfile(os.path.join(full_path, '__init__.py')): + continue + + load_tests = None + tests = None + if fnmatch(path, pattern): + # only check load_tests if the package directory itself matches the filter + name = self._get_name_from_path(full_path) + package = self._get_module_from_name(name) + load_tests = getattr(package, 'load_tests', None) + tests = self.loadTestsFromModule(package, use_load_tests=False) + + if load_tests is None: + if tests is not None: + # tests loaded from package file + yield tests + # recurse into the package + for test in self._find_tests(full_path, pattern): + yield test + else: + try: + yield load_tests(self, tests, pattern) + except Exception, e: + yield _make_failed_load_tests(package.__name__, e, + self.suiteClass) + +defaultTestLoader = TestLoader() + + +def _makeLoader(prefix, sortUsing, suiteClass=None): + loader = TestLoader() + loader.sortTestMethodsUsing = sortUsing + loader.testMethodPrefix = prefix + if suiteClass: + loader.suiteClass = suiteClass + return loader + +def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp): + return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) + +def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, + suiteClass=suite.TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) + +def findTestCases(module, prefix='test', sortUsing=cmp, + suiteClass=suite.TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) diff --git a/pyinstaller/PyInstaller/lib/unittest2/main.py b/pyinstaller/PyInstaller/lib/unittest2/main.py new file mode 100644 index 0000000..9db1d30 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/main.py @@ -0,0 +1,241 @@ +"""Unittest main program""" + +import sys +import os +import types + +from unittest2 import loader, runner +try: + from unittest2.signals import installHandler +except ImportError: + installHandler = None + +__unittest = True + +FAILFAST = " -f, --failfast Stop on first failure\n" +CATCHBREAK = " -c, --catch Catch control-C and display results\n" +BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n" + +USAGE_AS_MAIN = """\ +Usage: %(progName)s [options] [tests] + +Options: + -h, --help Show this message + -v, --verbose Verbose output + -q, --quiet Minimal output +%(failfast)s%(catchbreak)s%(buffer)s +Examples: + %(progName)s test_module - run tests from test_module + %(progName)s test_module.TestClass - run tests from + test_module.TestClass + %(progName)s test_module.TestClass.test_method - run specified test method + +[tests] can be a list of any number of test modules, classes and test +methods. + +Alternative Usage: %(progName)s discover [options] + +Options: + -v, --verbose Verbose output +%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default) + -p pattern Pattern to match test files ('test*.py' default) + -t directory Top level directory of project (default to + start directory) + +For test discovery all test modules must be importable from the top +level directory of the project. +""" + +USAGE_FROM_MODULE = """\ +Usage: %(progName)s [options] [test] [...] + +Options: + -h, --help Show this message + -v, --verbose Verbose output + -q, --quiet Minimal output +%(failfast)s%(catchbreak)s%(buffer)s +Examples: + %(progName)s - run default set of tests + %(progName)s MyTestSuite - run suite 'MyTestSuite' + %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething + %(progName)s MyTestCase - run all 'test*' test methods + in MyTestCase +""" + + +class TestProgram(object): + """A command-line program that runs a set of tests; this is primarily + for making test modules conveniently executable. + """ + USAGE = USAGE_FROM_MODULE + + # defaults for testing + failfast = catchbreak = buffer = progName = None + + def __init__(self, module='__main__', defaultTest=None, + argv=None, testRunner=None, + testLoader=loader.defaultTestLoader, exit=True, + verbosity=1, failfast=None, catchbreak=None, buffer=None): + if isinstance(module, basestring): + self.module = __import__(module) + for part in module.split('.')[1:]: + self.module = getattr(self.module, part) + else: + self.module = module + if argv is None: + argv = sys.argv + + self.exit = exit + self.verbosity = verbosity + self.failfast = failfast + self.catchbreak = catchbreak + self.buffer = buffer + self.defaultTest = defaultTest + self.testRunner = testRunner + self.testLoader = testLoader + self.progName = os.path.basename(argv[0]) + self.parseArgs(argv) + self.runTests() + + def usageExit(self, msg=None): + if msg: + print msg + usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', + 'buffer': ''} + if self.failfast != False: + usage['failfast'] = FAILFAST + if self.catchbreak != False and installHandler is not None: + usage['catchbreak'] = CATCHBREAK + if self.buffer != False: + usage['buffer'] = BUFFEROUTPUT + print self.USAGE % usage + sys.exit(2) + + def parseArgs(self, argv): + if len(argv) > 1 and argv[1].lower() == 'discover': + self._do_discovery(argv[2:]) + return + + import getopt + long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer'] + try: + options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts) + for opt, value in options: + if opt in ('-h','-H','--help'): + self.usageExit() + if opt in ('-q','--quiet'): + self.verbosity = 0 + if opt in ('-v','--verbose'): + self.verbosity = 2 + if opt in ('-f','--failfast'): + if self.failfast is None: + self.failfast = True + # Should this raise an exception if -f is not valid? + if opt in ('-c','--catch'): + if self.catchbreak is None and installHandler is not None: + self.catchbreak = True + # Should this raise an exception if -c is not valid? + if opt in ('-b','--buffer'): + if self.buffer is None: + self.buffer = True + # Should this raise an exception if -b is not valid? + if len(args) == 0 and self.defaultTest is None: + # createTests will load tests from self.module + self.testNames = None + elif len(args) > 0: + self.testNames = args + if __name__ == '__main__': + # to support python -m unittest ... + self.module = None + else: + self.testNames = (self.defaultTest,) + self.createTests() + except getopt.error, msg: + self.usageExit(msg) + + def createTests(self): + if self.testNames is None: + self.test = self.testLoader.loadTestsFromModule(self.module) + else: + self.test = self.testLoader.loadTestsFromNames(self.testNames, + self.module) + + def _do_discovery(self, argv, Loader=loader.TestLoader): + # handle command line args for test discovery + self.progName = '%s discover' % self.progName + import optparse + parser = optparse.OptionParser() + parser.prog = self.progName + parser.add_option('-v', '--verbose', dest='verbose', default=False, + help='Verbose output', action='store_true') + if self.failfast != False: + parser.add_option('-f', '--failfast', dest='failfast', default=False, + help='Stop on first fail or error', + action='store_true') + if self.catchbreak != False and installHandler is not None: + parser.add_option('-c', '--catch', dest='catchbreak', default=False, + help='Catch ctrl-C and display results so far', + action='store_true') + if self.buffer != False: + parser.add_option('-b', '--buffer', dest='buffer', default=False, + help='Buffer stdout and stderr during tests', + action='store_true') + parser.add_option('-s', '--start-directory', dest='start', default='.', + help="Directory to start discovery ('.' default)") + parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', + help="Pattern to match tests ('test*.py' default)") + parser.add_option('-t', '--top-level-directory', dest='top', default=None, + help='Top level directory of project (defaults to start directory)') + + options, args = parser.parse_args(argv) + if len(args) > 3: + self.usageExit() + + for name, value in zip(('start', 'pattern', 'top'), args): + setattr(options, name, value) + + # only set options from the parsing here + # if they weren't set explicitly in the constructor + if self.failfast is None: + self.failfast = options.failfast + if self.catchbreak is None and installHandler is not None: + self.catchbreak = options.catchbreak + if self.buffer is None: + self.buffer = options.buffer + + if options.verbose: + self.verbosity = 2 + + start_dir = options.start + pattern = options.pattern + top_level_dir = options.top + + loader = Loader() + self.test = loader.discover(start_dir, pattern, top_level_dir) + + def runTests(self): + if self.catchbreak: + installHandler() + if self.testRunner is None: + self.testRunner = runner.TextTestRunner + if isinstance(self.testRunner, (type, types.ClassType)): + try: + testRunner = self.testRunner(verbosity=self.verbosity, + failfast=self.failfast, + buffer=self.buffer) + except TypeError: + # didn't accept the verbosity, buffer or failfast arguments + testRunner = self.testRunner() + else: + # it is assumed to be a TestRunner instance + testRunner = self.testRunner + self.result = testRunner.run(self.test) + if self.exit: + sys.exit(not self.result.wasSuccessful()) + +main = TestProgram + +def main_(): + TestProgram.USAGE = USAGE_AS_MAIN + main(module=None) + diff --git a/pyinstaller/PyInstaller/lib/unittest2/result.py b/pyinstaller/PyInstaller/lib/unittest2/result.py new file mode 100644 index 0000000..e238c75 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/result.py @@ -0,0 +1,184 @@ +"""Test result object""" + +import sys +import traceback +import unittest + +from StringIO import StringIO + +from unittest2 import util +from unittest2.compatibility import wraps + +__unittest = True + +def failfast(method): + def inner(self, *args, **kw): + if getattr(self, 'failfast', False): + self.stop() + return method(self, *args, **kw) + inner = wraps(method)(inner) + return inner + + +STDOUT_LINE = '\nStdout:\n%s' +STDERR_LINE = '\nStderr:\n%s' + +class TestResult(unittest.TestResult): + """Holder for test result information. + + Test results are automatically managed by the TestCase and TestSuite + classes, and do not need to be explicitly manipulated by writers of tests. + + Each instance holds the total number of tests run, and collections of + failures and errors that occurred among those test runs. The collections + contain tuples of (testcase, exceptioninfo), where exceptioninfo is the + formatted traceback of the error that occurred. + """ + _previousTestClass = None + _moduleSetUpFailed = False + + def __init__(self): + self.failfast = False + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.skipped = [] + self.expectedFailures = [] + self.unexpectedSuccesses = [] + self.shouldStop = False + self.buffer = False + self._stdout_buffer = None + self._stderr_buffer = None + self._original_stdout = sys.stdout + self._original_stderr = sys.stderr + self._mirrorOutput = False + + def startTest(self, test): + "Called when the given test is about to be run" + self.testsRun += 1 + self._mirrorOutput = False + if self.buffer: + if self._stderr_buffer is None: + self._stderr_buffer = StringIO() + self._stdout_buffer = StringIO() + sys.stdout = self._stdout_buffer + sys.stderr = self._stderr_buffer + + def startTestRun(self): + """Called once before any tests are executed. + + See startTest for a method called before each test. + """ + + def stopTest(self, test): + """Called when the given test has been run""" + if self.buffer: + if self._mirrorOutput: + output = sys.stdout.getvalue() + error = sys.stderr.getvalue() + if output: + if not output.endswith('\n'): + output += '\n' + self._original_stdout.write(STDOUT_LINE % output) + if error: + if not error.endswith('\n'): + error += '\n' + self._original_stderr.write(STDERR_LINE % error) + + sys.stdout = self._original_stdout + sys.stderr = self._original_stderr + self._stdout_buffer.seek(0) + self._stdout_buffer.truncate() + self._stderr_buffer.seek(0) + self._stderr_buffer.truncate() + self._mirrorOutput = False + + + def stopTestRun(self): + """Called once after all tests are executed. + + See stopTest for a method called after each test. + """ + + + def addError(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info(). + """ + self.errors.append((test, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + addError = failfast(addError) + + def addFailure(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info().""" + self.failures.append((test, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + addFailure = failfast(addFailure) + + def addSuccess(self, test): + "Called when a test has completed successfully" + pass + + def addSkip(self, test, reason): + """Called when a test is skipped.""" + self.skipped.append((test, reason)) + + def addExpectedFailure(self, test, err): + """Called when an expected failure/error occured.""" + self.expectedFailures.append( + (test, self._exc_info_to_string(err, test))) + + def addUnexpectedSuccess(self, test): + """Called when a test was expected to fail, but succeed.""" + self.unexpectedSuccesses.append(test) + addUnexpectedSuccess = failfast(addUnexpectedSuccess) + + def wasSuccessful(self): + "Tells whether or not this result was a success" + return (len(self.failures) + len(self.errors) == 0) + + def stop(self): + "Indicates that the tests should be aborted" + self.shouldStop = True + + def _exc_info_to_string(self, err, test): + """Converts a sys.exc_info()-style tuple of values into a string.""" + exctype, value, tb = err + # Skip test runner traceback levels + while tb and self._is_relevant_tb_level(tb): + tb = tb.tb_next + if exctype is test.failureException: + # Skip assert*() traceback levels + length = self._count_relevant_tb_levels(tb) + msgLines = traceback.format_exception(exctype, value, tb, length) + else: + msgLines = traceback.format_exception(exctype, value, tb) + + if self.buffer: + output = sys.stdout.getvalue() + error = sys.stderr.getvalue() + if output: + if not output.endswith('\n'): + output += '\n' + msgLines.append(STDOUT_LINE % output) + if error: + if not error.endswith('\n'): + error += '\n' + msgLines.append(STDERR_LINE % error) + return ''.join(msgLines) + + def _is_relevant_tb_level(self, tb): + return '__unittest' in tb.tb_frame.f_globals + + def _count_relevant_tb_levels(self, tb): + length = 0 + while tb and not self._is_relevant_tb_level(tb): + length += 1 + tb = tb.tb_next + return length + + def __repr__(self): + return "<%s run=%i errors=%i failures=%i>" % \ + (util.strclass(self.__class__), self.testsRun, len(self.errors), + len(self.failures)) diff --git a/pyinstaller/PyInstaller/lib/unittest2/runner.py b/pyinstaller/PyInstaller/lib/unittest2/runner.py new file mode 100644 index 0000000..15a6f88 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/runner.py @@ -0,0 +1,206 @@ +"""Running tests""" + +import sys +import time +import unittest + +from unittest2 import result + +try: + from unittest2.signals import registerResult +except ImportError: + def registerResult(_): + pass + +__unittest = True + + +class _WritelnDecorator(object): + """Used to decorate file-like objects with a handy 'writeln' method""" + def __init__(self,stream): + self.stream = stream + + def __getattr__(self, attr): + if attr in ('stream', '__getstate__'): + raise AttributeError(attr) + return getattr(self.stream,attr) + + def writeln(self, arg=None): + if arg: + self.write(arg) + self.write('\n') # text-mode streams translate to \r\n if needed + + +class TextTestResult(result.TestResult): + """A test result class that can print formatted text results to a stream. + + Used by TextTestRunner. + """ + separator1 = '=' * 70 + separator2 = '-' * 70 + + def __init__(self, stream, descriptions, verbosity): + super(TextTestResult, self).__init__() + self.stream = stream + self.showAll = verbosity > 1 + self.dots = verbosity == 1 + self.descriptions = descriptions + + def getDescription(self, test): + doc_first_line = test.shortDescription() + if self.descriptions and doc_first_line: + return '\n'.join((str(test), doc_first_line)) + else: + return str(test) + + def startTest(self, test): + super(TextTestResult, self).startTest(test) + if self.showAll: + self.stream.write(self.getDescription(test)) + self.stream.write(" ... ") + self.stream.flush() + + def addSuccess(self, test): + super(TextTestResult, self).addSuccess(test) + if self.showAll: + self.stream.writeln("ok") + elif self.dots: + self.stream.write('.') + self.stream.flush() + + def addError(self, test, err): + super(TextTestResult, self).addError(test, err) + if self.showAll: + self.stream.writeln("ERROR") + elif self.dots: + self.stream.write('E') + self.stream.flush() + + def addFailure(self, test, err): + super(TextTestResult, self).addFailure(test, err) + if self.showAll: + self.stream.writeln("FAIL") + elif self.dots: + self.stream.write('F') + self.stream.flush() + + def addSkip(self, test, reason): + super(TextTestResult, self).addSkip(test, reason) + if self.showAll: + self.stream.writeln("skipped %r" % (reason,)) + elif self.dots: + self.stream.write("s") + self.stream.flush() + + def addExpectedFailure(self, test, err): + super(TextTestResult, self).addExpectedFailure(test, err) + if self.showAll: + self.stream.writeln("expected failure") + elif self.dots: + self.stream.write("x") + self.stream.flush() + + def addUnexpectedSuccess(self, test): + super(TextTestResult, self).addUnexpectedSuccess(test) + if self.showAll: + self.stream.writeln("unexpected success") + elif self.dots: + self.stream.write("u") + self.stream.flush() + + def printErrors(self): + if self.dots or self.showAll: + self.stream.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def printErrorList(self, flavour, errors): + for test, err in errors: + self.stream.writeln(self.separator1) + self.stream.writeln("%s: %s" % (flavour, self.getDescription(test))) + self.stream.writeln(self.separator2) + self.stream.writeln("%s" % err) + + def stopTestRun(self): + super(TextTestResult, self).stopTestRun() + self.printErrors() + + +class TextTestRunner(unittest.TextTestRunner): + """A test runner class that displays results in textual form. + + It prints out the names of tests as they are run, errors as they + occur, and a summary of the results at the end of the test run. + """ + resultclass = TextTestResult + + def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1, + failfast=False, buffer=False, resultclass=None): + self.stream = _WritelnDecorator(stream) + self.descriptions = descriptions + self.verbosity = verbosity + self.failfast = failfast + self.buffer = buffer + if resultclass is not None: + self.resultclass = resultclass + + def _makeResult(self): + return self.resultclass(self.stream, self.descriptions, self.verbosity) + + def run(self, test): + "Run the given test case or test suite." + result = self._makeResult() + result.failfast = self.failfast + result.buffer = self.buffer + registerResult(result) + + startTime = time.time() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + try: + test(result) + finally: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + else: + result.printErrors() + stopTime = time.time() + timeTaken = stopTime - startTime + if hasattr(result, 'separator2'): + self.stream.writeln(result.separator2) + run = result.testsRun + self.stream.writeln("Ran %d test%s in %.3fs" % + (run, run != 1 and "s" or "", timeTaken)) + self.stream.writeln() + + expectedFails = unexpectedSuccesses = skipped = 0 + try: + results = map(len, (result.expectedFailures, + result.unexpectedSuccesses, + result.skipped)) + expectedFails, unexpectedSuccesses, skipped = results + except AttributeError: + pass + infos = [] + if not result.wasSuccessful(): + self.stream.write("FAILED") + failed, errored = map(len, (result.failures, result.errors)) + if failed: + infos.append("failures=%d" % failed) + if errored: + infos.append("errors=%d" % errored) + else: + self.stream.write("OK") + if skipped: + infos.append("skipped=%d" % skipped) + if expectedFails: + infos.append("expected failures=%d" % expectedFails) + if unexpectedSuccesses: + infos.append("unexpected successes=%d" % unexpectedSuccesses) + if infos: + self.stream.writeln(" (%s)" % (", ".join(infos),)) + else: + self.stream.write("\n") + return result diff --git a/pyinstaller/PyInstaller/lib/unittest2/signals.py b/pyinstaller/PyInstaller/lib/unittest2/signals.py new file mode 100644 index 0000000..5a3128b --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/signals.py @@ -0,0 +1,57 @@ +import signal +import weakref + +from unittest2.compatibility import wraps + +__unittest = True + + +class _InterruptHandler(object): + def __init__(self, default_handler): + self.called = False + self.default_handler = default_handler + + def __call__(self, signum, frame): + installed_handler = signal.getsignal(signal.SIGINT) + if installed_handler is not self: + # if we aren't the installed handler, then delegate immediately + # to the default handler + self.default_handler(signum, frame) + + if self.called: + self.default_handler(signum, frame) + self.called = True + for result in _results.keys(): + result.stop() + +_results = weakref.WeakKeyDictionary() +def registerResult(result): + _results[result] = 1 + +def removeResult(result): + return bool(_results.pop(result, None)) + +_interrupt_handler = None +def installHandler(): + global _interrupt_handler + if _interrupt_handler is None: + default_handler = signal.getsignal(signal.SIGINT) + _interrupt_handler = _InterruptHandler(default_handler) + signal.signal(signal.SIGINT, _interrupt_handler) + + +def removeHandler(method=None): + if method is not None: + def inner(*args, **kwargs): + initial = signal.getsignal(signal.SIGINT) + removeHandler() + try: + return method(*args, **kwargs) + finally: + signal.signal(signal.SIGINT, initial) + inner = wraps(method)(inner) + return inner + + global _interrupt_handler + if _interrupt_handler is not None: + signal.signal(signal.SIGINT, _interrupt_handler.default_handler) diff --git a/pyinstaller/PyInstaller/lib/unittest2/suite.py b/pyinstaller/PyInstaller/lib/unittest2/suite.py new file mode 100644 index 0000000..370ca5f --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/suite.py @@ -0,0 +1,287 @@ +"""TestSuite""" + +import sys +import unittest +from unittest2 import case, util + +__unittest = True + + +class BaseTestSuite(unittest.TestSuite): + """A simple test suite that doesn't provide class or module shared fixtures. + """ + def __init__(self, tests=()): + self._tests = [] + self.addTests(tests) + + def __repr__(self): + return "<%s tests=%s>" % (util.strclass(self.__class__), list(self)) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return list(self) == list(other) + + def __ne__(self, other): + return not self == other + + # Can't guarantee hash invariant, so flag as unhashable + __hash__ = None + + def __iter__(self): + return iter(self._tests) + + def countTestCases(self): + cases = 0 + for test in self: + cases += test.countTestCases() + return cases + + def addTest(self, test): + # sanity checks + if not hasattr(test, '__call__'): + raise TypeError("%r is not callable" % (repr(test),)) + if isinstance(test, type) and issubclass(test, + (case.TestCase, TestSuite)): + raise TypeError("TestCases and TestSuites must be instantiated " + "before passing them to addTest()") + self._tests.append(test) + + def addTests(self, tests): + if isinstance(tests, basestring): + raise TypeError("tests must be an iterable of tests, not a string") + for test in tests: + self.addTest(test) + + def run(self, result): + for test in self: + if result.shouldStop: + break + test(result) + return result + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + for test in self: + test.debug() + + +class TestSuite(BaseTestSuite): + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + + + def run(self, result): + self._wrapped_run(result) + self._tearDownPreviousClass(None, result) + self._handleModuleTearDown(result) + return result + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + debug = _DebugResult() + self._wrapped_run(debug, True) + self._tearDownPreviousClass(None, debug) + self._handleModuleTearDown(debug) + + ################################ + # private methods + def _wrapped_run(self, result, debug=False): + for test in self: + if result.shouldStop: + break + + if _isnotsuite(test): + self._tearDownPreviousClass(test, result) + self._handleModuleFixture(test, result) + self._handleClassSetUp(test, result) + result._previousTestClass = test.__class__ + + if (getattr(test.__class__, '_classSetupFailed', False) or + getattr(result, '_moduleSetUpFailed', False)): + continue + + if hasattr(test, '_wrapped_run'): + test._wrapped_run(result, debug) + elif not debug: + test(result) + else: + test.debug() + + def _handleClassSetUp(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if result._moduleSetUpFailed: + return + if getattr(currentClass, "__unittest_skip__", False): + return + + try: + currentClass._classSetupFailed = False + except TypeError: + # test may actually be a function + # so its class will be a builtin-type + pass + + setUpClass = getattr(currentClass, 'setUpClass', None) + if setUpClass is not None: + try: + setUpClass() + except Exception, e: + if isinstance(result, _DebugResult): + raise + currentClass._classSetupFailed = True + className = util.strclass(currentClass) + errorName = 'setUpClass (%s)' % className + self._addClassOrModuleLevelException(result, e, errorName) + + def _get_previous_module(self, result): + previousModule = None + previousClass = getattr(result, '_previousTestClass', None) + if previousClass is not None: + previousModule = previousClass.__module__ + return previousModule + + + def _handleModuleFixture(self, test, result): + previousModule = self._get_previous_module(result) + currentModule = test.__class__.__module__ + if currentModule == previousModule: + return + + self._handleModuleTearDown(result) + + + result._moduleSetUpFailed = False + try: + module = sys.modules[currentModule] + except KeyError: + return + setUpModule = getattr(module, 'setUpModule', None) + if setUpModule is not None: + try: + setUpModule() + except Exception, e: + if isinstance(result, _DebugResult): + raise + result._moduleSetUpFailed = True + errorName = 'setUpModule (%s)' % currentModule + self._addClassOrModuleLevelException(result, e, errorName) + + def _addClassOrModuleLevelException(self, result, exception, errorName): + error = _ErrorHolder(errorName) + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None and isinstance(exception, case.SkipTest): + addSkip(error, str(exception)) + else: + result.addError(error, sys.exc_info()) + + def _handleModuleTearDown(self, result): + previousModule = self._get_previous_module(result) + if previousModule is None: + return + if result._moduleSetUpFailed: + return + + try: + module = sys.modules[previousModule] + except KeyError: + return + + tearDownModule = getattr(module, 'tearDownModule', None) + if tearDownModule is not None: + try: + tearDownModule() + except Exception, e: + if isinstance(result, _DebugResult): + raise + errorName = 'tearDownModule (%s)' % previousModule + self._addClassOrModuleLevelException(result, e, errorName) + + def _tearDownPreviousClass(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if getattr(previousClass, '_classSetupFailed', False): + return + if getattr(result, '_moduleSetUpFailed', False): + return + if getattr(previousClass, "__unittest_skip__", False): + return + + tearDownClass = getattr(previousClass, 'tearDownClass', None) + if tearDownClass is not None: + try: + tearDownClass() + except Exception, e: + if isinstance(result, _DebugResult): + raise + className = util.strclass(previousClass) + errorName = 'tearDownClass (%s)' % className + self._addClassOrModuleLevelException(result, e, errorName) + + +class _ErrorHolder(object): + """ + Placeholder for a TestCase inside a result. As far as a TestResult + is concerned, this looks exactly like a unit test. Used to insert + arbitrary errors into a test suite run. + """ + # Inspired by the ErrorHolder from Twisted: + # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py + + # attribute used by TestResult._exc_info_to_string + failureException = None + + def __init__(self, description): + self.description = description + + def id(self): + return self.description + + def shortDescription(self): + return None + + def __repr__(self): + return "" % (self.description,) + + def __str__(self): + return self.id() + + def run(self, result): + # could call result.addError(...) - but this test-like object + # shouldn't be run anyway + pass + + def __call__(self, result): + return self.run(result) + + def countTestCases(self): + return 0 + +def _isnotsuite(test): + "A crude way to tell apart testcases and suites with duck-typing" + try: + iter(test) + except TypeError: + return True + return False + + +class _DebugResult(object): + "Used by the TestSuite to hold previous class when running in debug." + _previousTestClass = None + _moduleSetUpFailed = False + shouldStop = False diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/.svn/entries b/pyinstaller/PyInstaller/lib/unittest2/test/.svn/entries new file mode 100644 index 0000000..a50c4d6 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/.svn/entries @@ -0,0 +1,147 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/lib/unittest2/test +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test_suite.py +file + + + +add + +test_assertions.py +file + + + +add + +dummy.py +file + + + +add + +test_unittest2_with.py +file + + + +add + +__init__.py +file + + + +add + +test_loader.py +file + + + +add + +test_program.py +file + + + +add + +test_discovery.py +file + + + +add + +test_runner.py +file + + + +add + +test_new_tests.py +file + + + +add + +test_case.py +file + + + +add + +support.py +file + + + +add + +test_result.py +file + + + +add + +test_functiontestcase.py +file + + + +add + +test_setups.py +file + + + +add + +test_skipping.py +file + + + +add + +test_break.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/__init__.py b/pyinstaller/PyInstaller/lib/unittest2/test/__init__.py new file mode 100644 index 0000000..4287ca8 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/__init__.py @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/dummy.py b/pyinstaller/PyInstaller/lib/unittest2/test/dummy.py new file mode 100644 index 0000000..e69de29 diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/support.py b/pyinstaller/PyInstaller/lib/unittest2/test/support.py new file mode 100644 index 0000000..a006b45 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/support.py @@ -0,0 +1,177 @@ +import sys +import warnings + +import unittest2 + + +def resultFactory(*_): + return unittest2.TestResult() + +class OldTestResult(object): + """An object honouring TestResult before startTestRun/stopTestRun.""" + + def __init__(self, *_): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.shouldStop = False + + def startTest(self, test): + pass + + def stopTest(self, test): + pass + + def addError(self, test, err): + self.errors.append((test, err)) + + def addFailure(self, test, err): + self.failures.append((test, err)) + + def addSuccess(self, test): + pass + + def wasSuccessful(self): + return True + + def printErrors(self): + pass + +class LoggingResult(unittest2.TestResult): + def __init__(self, log): + self._events = log + super(LoggingResult, self).__init__() + + def startTest(self, test): + self._events.append('startTest') + super(LoggingResult, self).startTest(test) + + def startTestRun(self): + self._events.append('startTestRun') + super(LoggingResult, self).startTestRun() + + def stopTest(self, test): + self._events.append('stopTest') + super(LoggingResult, self).stopTest(test) + + def stopTestRun(self): + self._events.append('stopTestRun') + super(LoggingResult, self).stopTestRun() + + def addFailure(self, *args): + self._events.append('addFailure') + super(LoggingResult, self).addFailure(*args) + + def addSuccess(self, *args): + self._events.append('addSuccess') + super(LoggingResult, self).addSuccess(*args) + + def addError(self, *args): + self._events.append('addError') + super(LoggingResult, self).addError(*args) + + def addSkip(self, *args): + self._events.append('addSkip') + super(LoggingResult, self).addSkip(*args) + + def addExpectedFailure(self, *args): + self._events.append('addExpectedFailure') + super(LoggingResult, self).addExpectedFailure(*args) + + def addUnexpectedSuccess(self, *args): + self._events.append('addUnexpectedSuccess') + super(LoggingResult, self).addUnexpectedSuccess(*args) + + +class EqualityMixin(object): + """Used as a mixin for TestCase""" + + # Check for a valid __eq__ implementation + def test_eq(self): + for obj_1, obj_2 in self.eq_pairs: + self.assertEqual(obj_1, obj_2) + self.assertEqual(obj_2, obj_1) + + # Check for a valid __ne__ implementation + def test_ne(self): + for obj_1, obj_2 in self.ne_pairs: + self.assertNotEqual(obj_1, obj_2) + self.assertNotEqual(obj_2, obj_1) + +class HashingMixin(object): + """Used as a mixin for TestCase""" + + # Check for a valid __hash__ implementation + def test_hash(self): + for obj_1, obj_2 in self.eq_pairs: + try: + if not hash(obj_1) == hash(obj_2): + self.fail("%r and %r do not hash equal" % (obj_1, obj_2)) + except KeyboardInterrupt: + raise + except Exception, e: + self.fail("Problem hashing %r and %r: %s" % (obj_1, obj_2, e)) + + for obj_1, obj_2 in self.ne_pairs: + try: + if hash(obj_1) == hash(obj_2): + self.fail("%s and %s hash equal, but shouldn't" % + (obj_1, obj_2)) + except KeyboardInterrupt: + raise + except Exception, e: + self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) + + + +# copied from Python 2.6 +try: + from warnings import catch_warnings +except ImportError: + class catch_warnings(object): + def __init__(self, record=False, module=None): + self._record = record + self._module = sys.modules['warnings'] + self._entered = False + + def __repr__(self): + args = [] + if self._record: + args.append("record=True") + name = type(self).__name__ + return "%s(%s)" % (name, ", ".join(args)) + + def __enter__(self): + if self._entered: + raise RuntimeError("Cannot enter %r twice" % self) + self._entered = True + self._filters = self._module.filters + self._module.filters = self._filters[:] + self._showwarning = self._module.showwarning + if self._record: + log = [] + def showwarning(*args, **kwargs): + log.append(WarningMessage(*args, **kwargs)) + self._module.showwarning = showwarning + return log + else: + return None + + def __exit__(self, *exc_info): + if not self._entered: + raise RuntimeError("Cannot exit %r without entering first" % self) + self._module.filters = self._filters + self._module.showwarning = self._showwarning + + class WarningMessage(object): + _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", + "line") + def __init__(self, message, category, filename, lineno, file=None, + line=None): + local_values = locals() + for attr in self._WARNING_DETAILS: + setattr(self, attr, local_values[attr]) + self._category_name = None + if category.__name__: + self._category_name = category.__name__ + diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_assertions.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_assertions.py new file mode 100644 index 0000000..63abbc4 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_assertions.py @@ -0,0 +1,259 @@ + +import sys +import datetime +if sys.version_info[:2] == (2,3): + from sets import Set as set + from sets import ImmutableSet as frozenset + +import unittest2 + + +class Test_Assertions(unittest2.TestCase): + def test_AlmostEqual(self): + self.assertAlmostEqual(1.00000001, 1.0) + self.assertNotAlmostEqual(1.0000001, 1.0) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 1.0000001, 1.0) + self.assertRaises(self.failureException, + self.assertNotAlmostEqual, 1.00000001, 1.0) + + self.assertAlmostEqual(1.1, 1.0, places=0) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 1.1, 1.0, places=1) + + self.assertAlmostEqual(0, .1+.1j, places=0) + self.assertNotAlmostEqual(0, .1+.1j, places=1) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 0, .1+.1j, places=1) + self.assertRaises(self.failureException, + self.assertNotAlmostEqual, 0, .1+.1j, places=0) + + try: + self.assertAlmostEqual(float('inf'), float('inf')) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + float('inf'), float('inf')) + except ValueError: + # float('inf') is invalid on Windows in Python 2.4 / 2.5 + x = object() + self.assertAlmostEqual(x, x) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + x, x) + + + def test_AmostEqualWithDelta(self): + self.assertAlmostEqual(1.1, 1.0, delta=0.5) + self.assertAlmostEqual(1.0, 1.1, delta=0.5) + self.assertNotAlmostEqual(1.1, 1.0, delta=0.05) + self.assertNotAlmostEqual(1.0, 1.1, delta=0.05) + + self.assertRaises(self.failureException, self.assertAlmostEqual, + 1.1, 1.0, delta=0.05) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + 1.1, 1.0, delta=0.5) + + self.assertRaises(TypeError, self.assertAlmostEqual, + 1.1, 1.0, places=2, delta=2) + self.assertRaises(TypeError, self.assertNotAlmostEqual, + 1.1, 1.0, places=2, delta=2) + + first = datetime.datetime.now() + second = first + datetime.timedelta(seconds=10) + self.assertAlmostEqual(first, second, + delta=datetime.timedelta(seconds=20)) + self.assertNotAlmostEqual(first, second, + delta=datetime.timedelta(seconds=5)) + + def testAssertNotRegexpMatches(self): + self.assertNotRegexpMatches('Ala ma kota', r'r+') + try: + self.assertNotRegexpMatches('Ala ma kota', r'k.t', 'Message') + except self.failureException, e: + self.assertIn("'kot'", e.args[0]) + self.assertIn('Message', e.args[0]) + else: + self.fail('assertNotRegexpMatches should have failed.') + + +class TestLongMessage(unittest2.TestCase): + """Test that the individual asserts honour longMessage. + This actually tests all the message behaviour for + asserts that use longMessage.""" + + def setUp(self): + class TestableTestFalse(unittest2.TestCase): + longMessage = False + failureException = self.failureException + + def testTest(self): + pass + + class TestableTestTrue(unittest2.TestCase): + longMessage = True + failureException = self.failureException + + def testTest(self): + pass + + self.testableTrue = TestableTestTrue('testTest') + self.testableFalse = TestableTestFalse('testTest') + + def testDefault(self): + self.assertTrue(unittest2.TestCase.longMessage) + + def test_formatMsg(self): + self.assertEquals(self.testableFalse._formatMessage(None, "foo"), "foo") + self.assertEquals(self.testableFalse._formatMessage("foo", "bar"), "foo") + + self.assertEquals(self.testableTrue._formatMessage(None, "foo"), "foo") + self.assertEquals(self.testableTrue._formatMessage("foo", "bar"), "bar : foo") + + # This blows up if _formatMessage uses string concatenation + self.testableTrue._formatMessage(object(), 'foo') + + def assertMessages(self, methodName, args, errors): + def getMethod(i): + useTestableFalse = i < 2 + if useTestableFalse: + test = self.testableFalse + else: + test = self.testableTrue + return getattr(test, methodName) + + for i, expected_regexp in enumerate(errors): + testMethod = getMethod(i) + kwargs = {} + withMsg = i % 2 + if withMsg: + kwargs = {"msg": "oops"} + + self.assertRaisesRegexp(self.failureException, + expected_regexp, + lambda: testMethod(*args, **kwargs)) + + def testAssertTrue(self): + self.assertMessages('assertTrue', (False,), + ["^False is not True$", "^oops$", "^False is not True$", + "^False is not True : oops$"]) + + def testAssertFalse(self): + self.assertMessages('assertFalse', (True,), + ["^True is not False$", "^oops$", "^True is not False$", + "^True is not False : oops$"]) + + def testNotEqual(self): + self.assertMessages('assertNotEqual', (1, 1), + ["^1 == 1$", "^oops$", "^1 == 1$", + "^1 == 1 : oops$"]) + + def testAlmostEqual(self): + self.assertMessages('assertAlmostEqual', (1, 2), + ["^1 != 2 within 7 places$", "^oops$", + "^1 != 2 within 7 places$", "^1 != 2 within 7 places : oops$"]) + + def testNotAlmostEqual(self): + self.assertMessages('assertNotAlmostEqual', (1, 1), + ["^1 == 1 within 7 places$", "^oops$", + "^1 == 1 within 7 places$", "^1 == 1 within 7 places : oops$"]) + + def test_baseAssertEqual(self): + self.assertMessages('_baseAssertEqual', (1, 2), + ["^1 != 2$", "^oops$", "^1 != 2$", "^1 != 2 : oops$"]) + + def testAssertSequenceEqual(self): + # Error messages are multiline so not testing on full message + # assertTupleEqual and assertListEqual delegate to this method + self.assertMessages('assertSequenceEqual', ([], [None]), + ["\+ \[None\]$", "^oops$", r"\+ \[None\]$", + r"\+ \[None\] : oops$"]) + + def testAssertSetEqual(self): + self.assertMessages('assertSetEqual', (set(), set([None])), + ["None$", "^oops$", "None$", + "None : oops$"]) + + def testAssertIn(self): + self.assertMessages('assertIn', (None, []), + ['^None not found in \[\]$', "^oops$", + '^None not found in \[\]$', + '^None not found in \[\] : oops$']) + + def testAssertNotIn(self): + self.assertMessages('assertNotIn', (None, [None]), + ['^None unexpectedly found in \[None\]$', "^oops$", + '^None unexpectedly found in \[None\]$', + '^None unexpectedly found in \[None\] : oops$']) + + def testAssertDictEqual(self): + self.assertMessages('assertDictEqual', ({}, {'key': 'value'}), + [r"\+ \{'key': 'value'\}$", "^oops$", + "\+ \{'key': 'value'\}$", + "\+ \{'key': 'value'\} : oops$"]) + + def testAssertDictContainsSubset(self): + self.assertMessages('assertDictContainsSubset', ({'key': 'value'}, {}), + ["^Missing: 'key'$", "^oops$", + "^Missing: 'key'$", + "^Missing: 'key' : oops$"]) + + def testAssertItemsEqual(self): + self.assertMessages('assertItemsEqual', ([], [None]), + [r"\[None\]$", "^oops$", + r"\[None\]$", + r"\[None\] : oops$"]) + + def testAssertMultiLineEqual(self): + self.assertMessages('assertMultiLineEqual', ("", "foo"), + [r"\+ foo$", "^oops$", + r"\+ foo$", + r"\+ foo : oops$"]) + + def testAssertLess(self): + self.assertMessages('assertLess', (2, 1), + ["^2 not less than 1$", "^oops$", + "^2 not less than 1$", "^2 not less than 1 : oops$"]) + + def testAssertLessEqual(self): + self.assertMessages('assertLessEqual', (2, 1), + ["^2 not less than or equal to 1$", "^oops$", + "^2 not less than or equal to 1$", + "^2 not less than or equal to 1 : oops$"]) + + def testAssertGreater(self): + self.assertMessages('assertGreater', (1, 2), + ["^1 not greater than 2$", "^oops$", + "^1 not greater than 2$", + "^1 not greater than 2 : oops$"]) + + def testAssertGreaterEqual(self): + self.assertMessages('assertGreaterEqual', (1, 2), + ["^1 not greater than or equal to 2$", "^oops$", + "^1 not greater than or equal to 2$", + "^1 not greater than or equal to 2 : oops$"]) + + def testAssertIsNone(self): + self.assertMessages('assertIsNone', ('not None',), + ["^'not None' is not None$", "^oops$", + "^'not None' is not None$", + "^'not None' is not None : oops$"]) + + def testAssertIsNotNone(self): + self.assertMessages('assertIsNotNone', (None,), + ["^unexpectedly None$", "^oops$", + "^unexpectedly None$", + "^unexpectedly None : oops$"]) + + def testAssertIs(self): + self.assertMessages('assertIs', (None, 'foo'), + ["^None is not 'foo'$", "^oops$", + "^None is not 'foo'$", + "^None is not 'foo' : oops$"]) + + def testAssertIsNot(self): + self.assertMessages('assertIsNot', (None, None), + ["^unexpectedly identical: None$", "^oops$", + "^unexpectedly identical: None$", + "^unexpectedly identical: None : oops$"]) + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_break.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_break.py new file mode 100644 index 0000000..0c5b5ab --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_break.py @@ -0,0 +1,260 @@ +import gc +import os +import weakref + +from cStringIO import StringIO + +try: + import signal +except ImportError: + signal = None + +import unittest2 + + +class TestBreak(unittest2.TestCase): + + def setUp(self): + self._default_handler = signal.getsignal(signal.SIGINT) + + def tearDown(self): + signal.signal(signal.SIGINT, self._default_handler) + unittest2.signals._results = weakref.WeakKeyDictionary() + unittest2.signals._interrupt_handler = None + + + def testInstallHandler(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest2.installHandler() + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + + self.assertTrue(unittest2.signals._interrupt_handler.called) + + def testRegisterResult(self): + result = unittest2.TestResult() + unittest2.registerResult(result) + + for ref in unittest2.signals._results: + if ref is result: + break + elif ref is not result: + self.fail("odd object in result set") + else: + self.fail("result not found") + + + def testInterruptCaught(self): + default_handler = signal.getsignal(signal.SIGINT) + + result = unittest2.TestResult() + unittest2.installHandler() + unittest2.registerResult(result) + + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + result.breakCaught = True + self.assertTrue(result.shouldStop) + + try: + test(result) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + self.assertTrue(result.breakCaught) + + + def testSecondInterrupt(self): + result = unittest2.TestResult() + unittest2.installHandler() + unittest2.registerResult(result) + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + result.breakCaught = True + self.assertTrue(result.shouldStop) + os.kill(pid, signal.SIGINT) + self.fail("Second KeyboardInterrupt not raised") + + try: + test(result) + except KeyboardInterrupt: + pass + else: + self.fail("Second KeyboardInterrupt not raised") + self.assertTrue(result.breakCaught) + + + def testTwoResults(self): + unittest2.installHandler() + + result = unittest2.TestResult() + unittest2.registerResult(result) + new_handler = signal.getsignal(signal.SIGINT) + + result2 = unittest2.TestResult() + unittest2.registerResult(result2) + self.assertEqual(signal.getsignal(signal.SIGINT), new_handler) + + result3 = unittest2.TestResult() + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + + try: + test(result) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + + self.assertTrue(result.shouldStop) + self.assertTrue(result2.shouldStop) + self.assertFalse(result3.shouldStop) + + + def testHandlerReplacedButCalled(self): + # If our handler has been replaced (is no longer installed) but is + # called by the *new* handler, then it isn't safe to delay the + # SIGINT and we should immediately delegate to the default handler + unittest2.installHandler() + + handler = signal.getsignal(signal.SIGINT) + def new_handler(frame, signum): + handler(frame, signum) + signal.signal(signal.SIGINT, new_handler) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + pass + else: + self.fail("replaced but delegated handler doesn't raise interrupt") + + def testRunner(self): + # Creating a TextTestRunner with the appropriate argument should + # register the TextTestResult it creates + runner = unittest2.TextTestRunner(stream=StringIO()) + + result = runner.run(unittest2.TestSuite()) + self.assertIn(result, unittest2.signals._results) + + def testWeakReferences(self): + # Calling registerResult on a result should not keep it alive + result = unittest2.TestResult() + unittest2.registerResult(result) + + ref = weakref.ref(result) + del result + + # For non-reference counting implementations + gc.collect();gc.collect() + self.assertIsNone(ref()) + + + def testRemoveResult(self): + result = unittest2.TestResult() + unittest2.registerResult(result) + + unittest2.installHandler() + self.assertTrue(unittest2.removeResult(result)) + + # Should this raise an error instead? + self.assertFalse(unittest2.removeResult(unittest2.TestResult())) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + pass + + self.assertFalse(result.shouldStop) + + def testMainInstallsHandler(self): + failfast = object() + test = object() + verbosity = object() + result = object() + default_handler = signal.getsignal(signal.SIGINT) + + class FakeRunner(object): + initArgs = [] + runArgs = [] + def __init__(self, *args, **kwargs): + self.initArgs.append((args, kwargs)) + def run(self, test): + self.runArgs.append(test) + return result + + class Program(unittest2.TestProgram): + def __init__(self, catchbreak): + self.exit = False + self.verbosity = verbosity + self.failfast = failfast + self.catchbreak = catchbreak + self.testRunner = FakeRunner + self.test = test + self.result = None + + p = Program(False) + p.runTests() + + self.assertEqual(FakeRunner.initArgs, [((), {'verbosity': verbosity, + 'failfast': failfast, + 'buffer': None})]) + self.assertEqual(FakeRunner.runArgs, [test]) + self.assertEqual(p.result, result) + + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + FakeRunner.initArgs = [] + FakeRunner.runArgs = [] + p = Program(True) + p.runTests() + + self.assertEqual(FakeRunner.initArgs, [((), {'verbosity': verbosity, + 'failfast': failfast, + 'buffer': None})]) + self.assertEqual(FakeRunner.runArgs, [test]) + self.assertEqual(p.result, result) + + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + + def testRemoveHandler(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest2.installHandler() + unittest2.removeHandler() + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + # check that calling removeHandler multiple times has no ill-effect + unittest2.removeHandler() + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + def testRemoveHandlerAsDecorator(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest2.installHandler() + + def test(): + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + test = unittest2.removeHandler(test) + + test() + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + +# Should also skip some tests on Jython +skipper = unittest2.skipUnless(hasattr(os, 'kill') and signal is not None, + "test uses os.kill(...) and the signal module") +TestBreak = skipper(TestBreak) + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_case.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_case.py new file mode 100644 index 0000000..e68ce69 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_case.py @@ -0,0 +1,1070 @@ +import difflib +import pprint +import re +import sys + +from copy import deepcopy + +import unittest2 + +from unittest2.test.support import\ + OldTestResult, EqualityMixin, HashingMixin, LoggingResult + +if sys.version_info[:2] == (2,3): + from sets import Set as set + from sets import ImmutableSet as frozenset + +class MyException(Exception): + pass + + +class Test(object): + "Keep these TestCase classes out of the main namespace" + + class Foo(unittest2.TestCase): + def runTest(self): pass + def test1(self): pass + + class Bar(Foo): + def test2(self): pass + + class LoggingTestCase(unittest2.TestCase): + """A test case which logs its calls.""" + + def __init__(self, events): + super(Test.LoggingTestCase, self).__init__('test') + self.events = events + + def setUp(self): + self.events.append('setUp') + + def test(self): + self.events.append('test') + + def tearDown(self): + self.events.append('tearDown') + + + +class TestCleanUp(unittest2.TestCase): + + def testCleanUp(self): + class TestableTest(unittest2.TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + self.assertEqual(test._cleanups, []) + + cleanups = [] + + def cleanup1(*args, **kwargs): + cleanups.append((1, args, kwargs)) + + def cleanup2(*args, **kwargs): + cleanups.append((2, args, kwargs)) + + test.addCleanup(cleanup1, 1, 2, 3, four='hello', five='goodbye') + test.addCleanup(cleanup2) + + self.assertEqual(test._cleanups, + [(cleanup1, (1, 2, 3), dict(four='hello', five='goodbye')), + (cleanup2, (), {})]) + + result = test.doCleanups() + self.assertTrue(result) + + self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), dict(four='hello', five='goodbye'))]) + + def testCleanUpWithErrors(self): + class TestableTest(unittest2.TestCase): + def testNothing(self): + pass + + class MockResult(object): + errors = [] + def addError(self, test, exc_info): + self.errors.append((test, exc_info)) + + result = MockResult() + test = TestableTest('testNothing') + test._resultForDoCleanups = result + + exc1 = Exception('foo') + exc2 = Exception('bar') + def cleanup1(): + raise exc1 + + def cleanup2(): + raise exc2 + + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + self.assertFalse(test.doCleanups()) + + errors = MockResult.errors[:] + errors.reverse() + (test1, (Type1, instance1, _)), (test2, (Type2, instance2, _)) = errors + self.assertEqual((test1, Type1, instance1), (test, Exception, exc1)) + self.assertEqual((test2, Type2, instance2), (test, Exception, exc2)) + + def testCleanupInRun(self): + blowUp = False + ordering = [] + + class TestableTest(unittest2.TestCase): + def setUp(self): + ordering.append('setUp') + if blowUp: + raise Exception('foo') + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + def cleanup2(): + ordering.append('cleanup2') + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + def success(some_test): + self.assertEqual(some_test, test) + ordering.append('success') + + result = unittest2.TestResult() + result.addSuccess = success + + test.run(result) + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', + 'cleanup2', 'cleanup1', 'success']) + + blowUp = True + ordering = [] + test = TestableTest('testNothing') + test.addCleanup(cleanup1) + test.run(result) + self.assertEqual(ordering, ['setUp', 'cleanup1']) + + def testTestCaseDebugExecutesCleanups(self): + ordering = [] + + class TestableTest(unittest2.TestCase): + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup1) + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + test.addCleanup(cleanup2) + def cleanup2(): + ordering.append('cleanup2') + + test.debug() + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', 'cleanup1', 'cleanup2']) + + +class Test_TestCase(unittest2.TestCase, EqualityMixin, HashingMixin): + + ### Set up attributes used by inherited tests + ################################################################ + + # Used by HashingMixin.test_hash and EqualityMixin.test_eq + eq_pairs = [(Test.Foo('test1'), Test.Foo('test1'))] + + # Used by EqualityMixin.test_ne + ne_pairs = [(Test.Foo('test1'), Test.Foo('runTest')), + (Test.Foo('test1'), Test.Bar('test1')), + (Test.Foo('test1'), Test.Bar('test2'))] + + ################################################################ + ### /Set up attributes used by inherited tests + + + # "class TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + # ... + # "methodName defaults to "runTest"." + # + # Make sure it really is optional, and that it defaults to the proper + # thing. + def test_init__no_test_name(self): + class Test(unittest2.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + self.assertEqual(Test().id()[-13:], '.Test.runTest') + + # "class TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + def test_init__test_name__valid(self): + class Test(unittest2.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + self.assertEqual(Test('test').id()[-10:], '.Test.test') + + # "class unittest2.TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + def test_init__test_name__invalid(self): + class Test(unittest2.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + try: + Test('testfoo') + except ValueError: + pass + else: + self.fail("Failed to raise ValueError") + + # "Return the number of tests represented by the this test object. For + # TestCase instances, this will always be 1" + def test_countTestCases(self): + class Foo(unittest2.TestCase): + def test(self): pass + + self.assertEqual(Foo('test').countTestCases(), 1) + + # "Return the default type of test result object to be used to run this + # test. For TestCase instances, this will always be + # unittest2.TestResult; subclasses of TestCase should + # override this as necessary." + def test_defaultTestResult(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + result = Foo().defaultTestResult() + self.assertEqual(type(result), unittest2.TestResult) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if setUp() raises + # an exception. + def test_run_call_order__error_in_setUp(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def setUp(self): + super(Foo, self).setUp() + raise RuntimeError('raised by Foo.setUp') + + Foo(events).run(result) + expected = ['startTest', 'setUp', 'addError', 'stopTest'] + self.assertEqual(events, expected) + + # "With a temporary result stopTestRun is called when setUp errors. + def test_run_call_order__error_in_setUp_default_result(self): + events = [] + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def setUp(self): + super(Foo, self).setUp() + raise RuntimeError('raised by Foo.setUp') + + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'addError', + 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test raises + # an error (as opposed to a failure). + def test_run_call_order__error_in_test(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + raise RuntimeError('raised by Foo.test') + + expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown', + 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + + # "With a default result, an error in the test still results in stopTestRun + # being called." + def test_run_call_order__error_in_test_default_result(self): + events = [] + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def test(self): + super(Foo, self).test() + raise RuntimeError('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addError', + 'tearDown', 'stopTest', 'stopTestRun'] + Foo(events).run() + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test signals + # a failure (as opposed to an error). + def test_run_call_order__failure_in_test(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + self.fail('raised by Foo.test') + + expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown', + 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + + # "When a test fails with a default result stopTestRun is still called." + def test_run_call_order__failure_in_test_default_result(self): + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def test(self): + super(Foo, self).test() + self.fail('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addFailure', + 'tearDown', 'stopTest', 'stopTestRun'] + events = [] + Foo(events).run() + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if tearDown() raises + # an exception. + def test_run_call_order__error_in_tearDown(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def tearDown(self): + super(Foo, self).tearDown() + raise RuntimeError('raised by Foo.tearDown') + + Foo(events).run(result) + expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', + 'stopTest'] + self.assertEqual(events, expected) + + # "When tearDown errors with a default result stopTestRun is still called." + def test_run_call_order__error_in_tearDown_default_result(self): + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def tearDown(self): + super(Foo, self).tearDown() + raise RuntimeError('raised by Foo.tearDown') + + events = [] + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'tearDown', + 'addError', 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + # "TestCase.run() still works when the defaultTestResult is a TestResult + # that does not support startTestRun and stopTestRun. + def test_run_call_order_default_result(self): + + class Foo(unittest2.TestCase): + def defaultTestResult(self): + return OldTestResult() + def test(self): + pass + + Foo('test').run() + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework. The initial value of this + # attribute is AssertionError" + def test_failureException__default(self): + class Foo(unittest2.TestCase): + def test(self): + pass + + self.assertTrue(Foo('test').failureException is AssertionError) + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework." + # + # Make sure TestCase.run() respects the designated failureException + def test_failureException__subclassing__explicit_raise(self): + events = [] + result = LoggingResult(events) + + class Foo(unittest2.TestCase): + def test(self): + raise RuntimeError() + + failureException = RuntimeError + + self.assertTrue(Foo('test').failureException is RuntimeError) + + + Foo('test').run(result) + expected = ['startTest', 'addFailure', 'stopTest'] + self.assertEqual(events, expected) + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework." + # + # Make sure TestCase.run() respects the designated failureException + def test_failureException__subclassing__implicit_raise(self): + events = [] + result = LoggingResult(events) + + class Foo(unittest2.TestCase): + def test(self): + self.fail("foo") + + failureException = RuntimeError + + self.assertTrue(Foo('test').failureException is RuntimeError) + + + Foo('test').run(result) + expected = ['startTest', 'addFailure', 'stopTest'] + self.assertEqual(events, expected) + + # "The default implementation does nothing." + def test_setUp(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + # ... and nothing should happen + Foo().setUp() + + # "The default implementation does nothing." + def test_tearDown(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + # ... and nothing should happen + Foo().tearDown() + + # "Return a string identifying the specific test case." + # + # Because of the vague nature of the docs, I'm not going to lock this + # test down too much. Really all that can be asserted is that the id() + # will be a string (either 8-byte or unicode -- again, because the docs + # just say "string") + def test_id(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + self.assertIsInstance(Foo().id(), basestring) + + # "If result is omitted or None, a temporary result object is created + # and used, but is not made available to the caller. As TestCase owns the + # temporary result startTestRun and stopTestRun are called. + + def test_run__uses_defaultTestResult(self): + events = [] + + class Foo(unittest2.TestCase): + def test(self): + events.append('test') + + def defaultTestResult(self): + return LoggingResult(events) + + # Make run() find a result object on its own + Foo('test').run() + + expected = ['startTestRun', 'startTest', 'test', 'addSuccess', + 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + def testShortDescriptionWithoutDocstring(self): + self.assertIsNone(self.shortDescription()) + + def testShortDescriptionWithOneLineDocstring(self): + """Tests shortDescription() for a method with a docstring.""" + self.assertEqual( + self.shortDescription(), + 'Tests shortDescription() for a method with a docstring.') + + def testShortDescriptionWithMultiLineDocstring(self): + """Tests shortDescription() for a method with a longer docstring. + + This method ensures that only the first line of a docstring is + returned used in the short description, no matter how long the + whole thing is. + """ + self.assertEqual( + self.shortDescription(), + 'Tests shortDescription() for a method with a longer ' + 'docstring.') + + def testAddTypeEqualityFunc(self): + class SadSnake(object): + """Dummy class for test_addTypeEqualityFunc.""" + s1, s2 = SadSnake(), SadSnake() + self.assertNotEqual(s1, s2) + def AllSnakesCreatedEqual(a, b, msg=None): + return type(a) is type(b) is SadSnake + self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual) + self.assertEqual(s1, s2) + # No this doesn't clean up and remove the SadSnake equality func + # from this TestCase instance but since its a local nothing else + # will ever notice that. + + def testAssertIs(self): + thing = object() + self.assertIs(thing, thing) + self.assertRaises(self.failureException, self.assertIs, thing, object()) + + def testAssertIsNot(self): + thing = object() + self.assertIsNot(thing, object()) + self.assertRaises(self.failureException, self.assertIsNot, thing, thing) + + def testAssertIsInstance(self): + thing = [] + self.assertIsInstance(thing, list) + self.assertRaises(self.failureException, self.assertIsInstance, + thing, dict) + + def testAssertNotIsInstance(self): + thing = [] + self.assertNotIsInstance(thing, dict) + self.assertRaises(self.failureException, self.assertNotIsInstance, + thing, list) + + def testAssertIn(self): + animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'} + + self.assertIn('a', 'abc') + self.assertIn(2, [1, 2, 3]) + self.assertIn('monkey', animals) + + self.assertNotIn('d', 'abc') + self.assertNotIn(0, [1, 2, 3]) + self.assertNotIn('otter', animals) + + self.assertRaises(self.failureException, self.assertIn, 'x', 'abc') + self.assertRaises(self.failureException, self.assertIn, 4, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertIn, 'elephant', + animals) + + self.assertRaises(self.failureException, self.assertNotIn, 'c', 'abc') + self.assertRaises(self.failureException, self.assertNotIn, 1, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertNotIn, 'cow', + animals) + + def testAssertDictContainsSubset(self): + self.assertDictContainsSubset({}, {}) + self.assertDictContainsSubset({}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1, 'b': 2}) + self.assertDictContainsSubset({'a': 1, 'b': 2}, {'a': 1, 'b': 2}) + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'a': 2}, {'a': 1}, + '.*Mismatched values:.*') + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'c': 1}, {'a': 1}, + '.*Missing:.*') + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'a': 1, 'c': 1}, + {'a': 1}, '.*Missing:.*') + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'a': 1, 'c': 1}, + {'a': 1}, '.*Missing:.*Mismatched values:.*') + + self.assertRaises(self.failureException, + self.assertDictContainsSubset, {1: "one"}, {}) + + def testAssertEqual(self): + equal_pairs = [ + ((), ()), + ({}, {}), + ([], []), + (set(), set()), + (frozenset(), frozenset())] + for a, b in equal_pairs: + # This mess of try excepts is to test the assertEqual behavior + # itself. + try: + self.assertEqual(a, b) + except self.failureException: + self.fail('assertEqual(%r, %r) failed' % (a, b)) + try: + self.assertEqual(a, b, msg='foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with msg= failed' % (a, b)) + try: + self.assertEqual(a, b, 'foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with third parameter failed' % + (a, b)) + + unequal_pairs = [ + ((), []), + ({}, set()), + (set([4,1]), frozenset([4,2])), + (frozenset([4,5]), set([2,3])), + (set([3,4]), set([5,4]))] + for a, b in unequal_pairs: + self.assertRaises(self.failureException, self.assertEqual, a, b) + self.assertRaises(self.failureException, self.assertEqual, a, b, + 'foo') + self.assertRaises(self.failureException, self.assertEqual, a, b, + msg='foo') + + def testEquality(self): + self.assertListEqual([], []) + self.assertTupleEqual((), ()) + self.assertSequenceEqual([], ()) + + a = [0, 'a', []] + b = [] + self.assertRaises(unittest2.TestCase.failureException, + self.assertListEqual, a, b) + self.assertRaises(unittest2.TestCase.failureException, + self.assertListEqual, tuple(a), tuple(b)) + self.assertRaises(unittest2.TestCase.failureException, + self.assertSequenceEqual, a, tuple(b)) + + b.extend(a) + self.assertListEqual(a, b) + self.assertTupleEqual(tuple(a), tuple(b)) + self.assertSequenceEqual(a, tuple(b)) + self.assertSequenceEqual(tuple(a), b) + + self.assertRaises(self.failureException, self.assertListEqual, + a, tuple(b)) + self.assertRaises(self.failureException, self.assertTupleEqual, + tuple(a), b) + self.assertRaises(self.failureException, self.assertListEqual, None, b) + self.assertRaises(self.failureException, self.assertTupleEqual, None, + tuple(b)) + self.assertRaises(self.failureException, self.assertSequenceEqual, + None, tuple(b)) + self.assertRaises(self.failureException, self.assertListEqual, 1, 1) + self.assertRaises(self.failureException, self.assertTupleEqual, 1, 1) + self.assertRaises(self.failureException, self.assertSequenceEqual, + 1, 1) + + self.assertDictEqual({}, {}) + + c = { 'x': 1 } + d = {} + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictEqual, c, d) + + d.update(c) + self.assertDictEqual(c, d) + + d['x'] = 0 + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictEqual, c, d, 'These are unequal') + + self.assertRaises(self.failureException, self.assertDictEqual, None, d) + self.assertRaises(self.failureException, self.assertDictEqual, [], d) + self.assertRaises(self.failureException, self.assertDictEqual, 1, 1) + + def testAssertItemsEqual(self): + self.assertItemsEqual([1, 2, 3], [3, 2, 1]) + self.assertItemsEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) + self.assertRaises(self.failureException, self.assertItemsEqual, + [10], [10, 11]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [10, 11], [10]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [10, 11, 10], [10, 11]) + + # Test that sequences of unhashable objects can be tested for sameness: + self.assertItemsEqual([[1, 2], [3, 4]], [[3, 4], [1, 2]]) + + self.assertItemsEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [[1]], [[2]]) + + # Test unsortable objects + self.assertItemsEqual([2j, None], [None, 2j]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [2j, None], [None, 3j]) + + def testAssertSetEqual(self): + set1 = set() + set2 = set() + self.assertSetEqual(set1, set2) + + self.assertRaises(self.failureException, self.assertSetEqual, None, set2) + self.assertRaises(self.failureException, self.assertSetEqual, [], set2) + self.assertRaises(self.failureException, self.assertSetEqual, set1, None) + self.assertRaises(self.failureException, self.assertSetEqual, set1, []) + + set1 = set(['a']) + set2 = set() + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = set(['a']) + self.assertSetEqual(set1, set2) + + set1 = set(['a']) + set2 = set(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = frozenset(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a', 'b']) + set2 = frozenset(['a', 'b']) + self.assertSetEqual(set1, set2) + + set1 = set() + set2 = "foo" + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + self.assertRaises(self.failureException, self.assertSetEqual, set2, set1) + + # make sure any string formatting is tuple-safe + set1 = set([(0, 1), (2, 3)]) + set2 = set([(4, 5)]) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + def testInequality(self): + # Try ints + self.assertGreater(2, 1) + self.assertGreaterEqual(2, 1) + self.assertGreaterEqual(1, 1) + self.assertLess(1, 2) + self.assertLessEqual(1, 2) + self.assertLessEqual(1, 1) + self.assertRaises(self.failureException, self.assertGreater, 1, 2) + self.assertRaises(self.failureException, self.assertGreater, 1, 1) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1, 2) + self.assertRaises(self.failureException, self.assertLess, 2, 1) + self.assertRaises(self.failureException, self.assertLess, 1, 1) + self.assertRaises(self.failureException, self.assertLessEqual, 2, 1) + + # Try Floats + self.assertGreater(1.1, 1.0) + self.assertGreaterEqual(1.1, 1.0) + self.assertGreaterEqual(1.0, 1.0) + self.assertLess(1.0, 1.1) + self.assertLessEqual(1.0, 1.1) + self.assertLessEqual(1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertLess, 1.1, 1.0) + self.assertRaises(self.failureException, self.assertLess, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertLessEqual, 1.1, 1.0) + + # Try Strings + self.assertGreater('bug', 'ant') + self.assertGreaterEqual('bug', 'ant') + self.assertGreaterEqual('ant', 'ant') + self.assertLess('ant', 'bug') + self.assertLessEqual('ant', 'bug') + self.assertLessEqual('ant', 'ant') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertLess, 'bug', 'ant') + self.assertRaises(self.failureException, self.assertLess, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertLessEqual, 'bug', 'ant') + + # Try Unicode + self.assertGreater(u'bug', u'ant') + self.assertGreaterEqual(u'bug', u'ant') + self.assertGreaterEqual(u'ant', u'ant') + self.assertLess(u'ant', u'bug') + self.assertLessEqual(u'ant', u'bug') + self.assertLessEqual(u'ant', u'ant') + self.assertRaises(self.failureException, self.assertGreater, u'ant', u'bug') + self.assertRaises(self.failureException, self.assertGreater, u'ant', u'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, u'ant', + u'bug') + self.assertRaises(self.failureException, self.assertLess, u'bug', u'ant') + self.assertRaises(self.failureException, self.assertLess, u'ant', u'ant') + self.assertRaises(self.failureException, self.assertLessEqual, u'bug', u'ant') + + # Try Mixed String/Unicode + self.assertGreater('bug', u'ant') + self.assertGreater(u'bug', 'ant') + self.assertGreaterEqual('bug', u'ant') + self.assertGreaterEqual(u'bug', 'ant') + self.assertGreaterEqual('ant', u'ant') + self.assertGreaterEqual(u'ant', 'ant') + self.assertLess('ant', u'bug') + self.assertLess(u'ant', 'bug') + self.assertLessEqual('ant', u'bug') + self.assertLessEqual(u'ant', 'bug') + self.assertLessEqual('ant', u'ant') + self.assertLessEqual(u'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreater, 'ant', u'bug') + self.assertRaises(self.failureException, self.assertGreater, u'ant', 'bug') + self.assertRaises(self.failureException, self.assertGreater, 'ant', u'ant') + self.assertRaises(self.failureException, self.assertGreater, u'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, 'ant', + u'bug') + self.assertRaises(self.failureException, self.assertGreaterEqual, u'ant', + 'bug') + self.assertRaises(self.failureException, self.assertLess, 'bug', u'ant') + self.assertRaises(self.failureException, self.assertLess, u'bug', 'ant') + self.assertRaises(self.failureException, self.assertLess, 'ant', u'ant') + self.assertRaises(self.failureException, self.assertLess, u'ant', 'ant') + self.assertRaises(self.failureException, self.assertLessEqual, 'bug', u'ant') + self.assertRaises(self.failureException, self.assertLessEqual, u'bug', 'ant') + + def testAssertMultiLineEqual(self): + sample_text = """\ +http://www.python.org/doc/2.3/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] +""" + revised_sample_text = """\ +http://www.python.org/doc/2.4.1/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] You may provide your + own implementation that does not subclass from TestCase, of course. +""" + sample_text_error = """\ +- http://www.python.org/doc/2.3/lib/module-unittest.html +? ^ ++ http://www.python.org/doc/2.4.1/lib/module-unittest.html +? ^^^ + test case +- A test case is the smallest unit of testing. [...] ++ A test case is the smallest unit of testing. [...] You may provide your +? +++++++++++++++++++++ ++ own implementation that does not subclass from TestCase, of course. +""" + self.maxDiff = None + for type_changer in (lambda x: x, lambda x: x.decode('utf8')): + try: + self.assertMultiLineEqual(type_changer(sample_text), + type_changer(revised_sample_text)) + except self.failureException, e: + # need to remove the first line of the error message + error = str(e).encode('utf8').split('\n', 1)[1] + + # assertMultiLineEqual is hooked up as the default for + # unicode strings - so we can't use it for this check + self.assertTrue(sample_text_error == error) + + def testAssertSequenceEqualMaxDiff(self): + self.assertEqual(self.maxDiff, 80*8) + seq1 = 'a' + 'x' * 80**2 + seq2 = 'b' + 'x' * 80**2 + diff = '\n'.join(difflib.ndiff(pprint.pformat(seq1).splitlines(), + pprint.pformat(seq2).splitlines())) + # the +1 is the leading \n added by assertSequenceEqual + omitted = unittest2.case.DIFF_OMITTED % (len(diff) + 1,) + + self.maxDiff = len(diff)//2 + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException, e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertTrue(len(msg) < len(diff)) + self.assertIn(omitted, msg) + + self.maxDiff = len(diff) * 2 + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException, e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertTrue(len(msg) > len(diff)) + self.assertNotIn(omitted, msg) + + self.maxDiff = None + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException, e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertTrue(len(msg) > len(diff)) + self.assertNotIn(omitted, msg) + + def testTruncateMessage(self): + self.maxDiff = 1 + message = self._truncateMessage('foo', 'bar') + omitted = unittest2.case.DIFF_OMITTED % len('bar') + self.assertEqual(message, 'foo' + omitted) + + self.maxDiff = None + message = self._truncateMessage('foo', 'bar') + self.assertEqual(message, 'foobar') + + self.maxDiff = 4 + message = self._truncateMessage('foo', 'bar') + self.assertEqual(message, 'foobar') + + def testAssertDictEqualTruncates(self): + test = unittest2.TestCase('assertEqual') + def truncate(msg, diff): + return 'foo' + test._truncateMessage = truncate + try: + test.assertDictEqual({}, {1: 0}) + except self.failureException, e: + self.assertEqual(str(e), 'foo') + else: + self.fail('assertDictEqual did not fail') + + def testAssertMultiLineEqualTruncates(self): + test = unittest2.TestCase('assertEqual') + def truncate(msg, diff): + return 'foo' + test._truncateMessage = truncate + try: + test.assertMultiLineEqual('foo', 'bar') + except self.failureException, e: + self.assertEqual(str(e), 'foo') + else: + self.fail('assertMultiLineEqual did not fail') + + def testAssertIsNone(self): + self.assertIsNone(None) + self.assertRaises(self.failureException, self.assertIsNone, False) + self.assertIsNotNone('DjZoPloGears on Rails') + self.assertRaises(self.failureException, self.assertIsNotNone, None) + + def testAssertRegexpMatches(self): + self.assertRegexpMatches('asdfabasdf', r'ab+') + self.assertRaises(self.failureException, self.assertRegexpMatches, + 'saaas', r'aaaa') + + def testAssertRaisesRegexp(self): + class ExceptionMock(Exception): + pass + + def Stub(): + raise ExceptionMock('We expect') + + self.assertRaisesRegexp(ExceptionMock, re.compile('expect$'), Stub) + self.assertRaisesRegexp(ExceptionMock, 'expect$', Stub) + self.assertRaisesRegexp(ExceptionMock, u'expect$', Stub) + + def testAssertNotRaisesRegexp(self): + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, re.compile('x'), + lambda: None) + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, 'x', + lambda: None) + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, u'x', + lambda: None) + + def testAssertRaisesRegexpMismatch(self): + def Stub(): + raise Exception('Unexpected') + + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, '^Expected$', + Stub) + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, u'^Expected$', + Stub) + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, + re.compile('^Expected$'), Stub) + + + def testSynonymAssertMethodNames(self): + """Test undocumented method name synonyms. + + Please do not use these methods names in your own code. + + This test confirms their continued existence and functionality + in order to avoid breaking existing code. + """ + self.assertNotEquals(3, 5) + self.assertEquals(3, 3) + self.assertAlmostEquals(2.0, 2.0) + self.assertNotAlmostEquals(3.0, 5.0) + self.assert_(True) + + def testDeepcopy(self): + # Issue: 5660 + class TestableTest(unittest2.TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + + # This shouldn't blow up + deepcopy(test) + + +if __name__ == "__main__": + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_discovery.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_discovery.py new file mode 100644 index 0000000..2cd265b --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_discovery.py @@ -0,0 +1,372 @@ +import os +import re +import sys + +import unittest2 + + +class TestDiscovery(unittest2.TestCase): + + # Heavily mocked tests so I can avoid hitting the filesystem + def test_get_name_from_path(self): + loader = unittest2.TestLoader() + + loader._top_level_dir = '/foo' + name = loader._get_name_from_path('/foo/bar/baz.py') + self.assertEqual(name, 'bar.baz') + + if not __debug__: + # asserts are off + return + + self.assertRaises(AssertionError, + loader._get_name_from_path, + '/bar/baz.py') + + def test_find_tests(self): + loader = unittest2.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + path_lists = [['test1.py', 'test2.py', 'not_a_test.py', 'test_dir', + 'test.foo', 'test-not-a-module.py', 'another_dir'], + ['test3.py', 'test4.py', ]] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + def isdir(path): + return path.endswith('dir') + os.path.isdir = isdir + self.addCleanup(restore_isdir) + + def isfile(path): + # another_dir is not a package and so shouldn't be recursed into + return not path.endswith('dir') and not 'another_dir' in path + os.path.isfile = isfile + self.addCleanup(restore_isfile) + + loader._get_module_from_name = lambda path: path + ' module' + loader.loadTestsFromModule = lambda module: module + ' tests' + + top_level = os.path.abspath('/foo') + loader._top_level_dir = top_level + suite = list(loader._find_tests(top_level, 'test*.py')) + + expected = [name + ' module tests' for name in + ('test1', 'test2')] + expected.extend([('test_dir.%s' % name) + ' module tests' for name in + ('test3', 'test4')]) + self.assertEqual(suite, expected) + + def test_find_tests_with_package(self): + loader = unittest2.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + directories = ['a_directory', 'test_directory', 'test_directory2'] + path_lists = [directories, [], [], []] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + os.path.isdir = lambda path: True + self.addCleanup(restore_isdir) + + os.path.isfile = lambda path: os.path.basename(path) not in directories + self.addCleanup(restore_isfile) + + class Module(object): + paths = [] + load_tests_args = [] + + def __init__(self, path): + self.path = path + self.paths.append(path) + if os.path.basename(path) == 'test_directory': + def load_tests(loader, tests, pattern): + self.load_tests_args.append((loader, tests, pattern)) + return 'load_tests' + self.load_tests = load_tests + + def __eq__(self, other): + return self.path == other.path + + # Silence py3k warning + __hash__ = None + + loader._get_module_from_name = lambda name: Module(name) + def loadTestsFromModule(module, use_load_tests): + if use_load_tests: + raise self.failureException('use_load_tests should be False for packages') + return module.path + ' module tests' + loader.loadTestsFromModule = loadTestsFromModule + + loader._top_level_dir = '/foo' + # this time no '.py' on the pattern so that it can match + # a test package + suite = list(loader._find_tests('/foo', 'test*')) + + # We should have loaded tests from the test_directory package by calling load_tests + # and directly from the test_directory2 package + self.assertEqual(suite, + ['load_tests', 'test_directory2' + ' module tests']) + self.assertEqual(Module.paths, ['test_directory', 'test_directory2']) + + # load_tests should have been called once with loader, tests and pattern + self.assertEqual(Module.load_tests_args, + [(loader, 'test_directory' + ' module tests', 'test*')]) + + def test_discover(self): + loader = unittest2.TestLoader() + + original_isfile = os.path.isfile + original_isdir = os.path.isdir + def restore_isfile(): + os.path.isfile = original_isfile + + os.path.isfile = lambda path: False + self.addCleanup(restore_isfile) + + orig_sys_path = sys.path[:] + def restore_path(): + sys.path[:] = orig_sys_path + self.addCleanup(restore_path) + + full_path = os.path.abspath(os.path.normpath('/foo')) + self.assertRaises(ImportError, + loader.discover, + '/foo/bar', top_level_dir='/foo') + + self.assertEqual(loader._top_level_dir, full_path) + self.assertIn(full_path, sys.path) + + os.path.isfile = lambda path: True + os.path.isdir = lambda path: True + + def restore_isdir(): + os.path.isdir = original_isdir + self.addCleanup(restore_isdir) + + _find_tests_args = [] + def _find_tests(start_dir, pattern): + _find_tests_args.append((start_dir, pattern)) + return ['tests'] + loader._find_tests = _find_tests + loader.suiteClass = str + + suite = loader.discover('/foo/bar/baz', 'pattern', '/foo/bar') + + top_level_dir = os.path.abspath(os.path.normpath('/foo/bar')) + start_dir = os.path.abspath(os.path.normpath('/foo/bar/baz')) + self.assertEqual(suite, "['tests']") + self.assertEqual(loader._top_level_dir, top_level_dir) + self.assertEqual(_find_tests_args, [(start_dir, 'pattern')]) + self.assertIn(top_level_dir, sys.path) + + def test_discover_with_modules_that_fail_to_import(self): + loader = unittest2.TestLoader() + + listdir = os.listdir + os.listdir = lambda _: ['test_this_does_not_exist.py'] + isfile = os.path.isfile + os.path.isfile = lambda _: True + orig_sys_path = sys.path[:] + def restore(): + os.path.isfile = isfile + os.listdir = listdir + sys.path[:] = orig_sys_path + self.addCleanup(restore) + + suite = loader.discover('.') + self.assertIn(os.getcwd(), sys.path) + self.assertEqual(suite.countTestCases(), 1) + test = list(list(suite)[0])[0] # extract test from suite + + self.assertRaises(ImportError, + lambda: test.test_this_does_not_exist()) + + def test_command_line_handling_parseArgs(self): + # Haha - take that uninstantiable class + program = object.__new__(unittest2.TestProgram) + + args = [] + def do_discovery(argv): + args.extend(argv) + program._do_discovery = do_discovery + program.parseArgs(['something', 'discover']) + self.assertEqual(args, []) + + program.parseArgs(['something', 'discover', 'foo', 'bar']) + self.assertEqual(args, ['foo', 'bar']) + + def test_command_line_handling_do_discovery_too_many_arguments(self): + class Stop(Exception): + pass + def usageExit(): + raise Stop + + program = object.__new__(unittest2.TestProgram) + program.usageExit = usageExit + + self.assertRaises(Stop, + # too many args + lambda: program._do_discovery(['one', 'two', 'three', 'four'])) + + + def test_command_line_handling_do_discovery_calls_loader(self): + program = object.__new__(unittest2.TestProgram) + + class Loader(object): + args = [] + def discover(self, start_dir, pattern, top_level_dir): + self.args.append((start_dir, pattern, top_level_dir)) + return 'tests' + + program._do_discovery(['-v'], Loader=Loader) + self.assertEqual(program.verbosity, 2) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['--verbose'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery([], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['fish', 'eggs'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['fish', 'eggs', 'ham'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', 'ham')]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['-s', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['-t', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', 'fish')]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['-p', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'fish', None)]) + self.assertFalse(program.failfast) + self.assertFalse(program.catchbreak) + + args = ['-p', 'eggs', '-s', 'fish', '-v', '-f'] + try: + import signal + except ImportError: + signal = None + else: + args.append('-c') + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(args, Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', None)]) + self.assertEqual(program.verbosity, 2) + self.assertTrue(program.failfast) + if signal is not None: + self.assertTrue(program.catchbreak) + + def test_detect_module_clash(self): + class Module(object): + __file__ = 'bar/foo.py' + sys.modules['foo'] = Module + full_path = os.path.abspath('foo') + original_listdir = os.listdir + original_isfile = os.path.isfile + original_isdir = os.path.isdir + + def cleanup(): + os.listdir = original_listdir + os.path.isfile = original_isfile + os.path.isdir = original_isdir + del sys.modules['foo'] + if full_path in sys.path: + sys.path.remove(full_path) + self.addCleanup(cleanup) + + def listdir(_): + return ['foo.py'] + def isfile(_): + return True + def isdir(_): + return True + os.listdir = listdir + os.path.isfile = isfile + os.path.isdir = isdir + + loader = unittest2.TestLoader() + + mod_dir = os.path.abspath('bar') + expected_dir = os.path.abspath('foo') + msg = re.escape(r"'foo' module incorrectly imported from %r. Expected %r. " + "Is this module globally installed?" % (mod_dir, expected_dir)) + self.assertRaisesRegexp( + ImportError, '^%s$' % msg, loader.discover, + start_dir='foo', pattern='foo.py' + ) + self.assertEqual(sys.path[0], full_path) + + + def test_discovery_from_dotted_path(self): + loader = unittest2.TestLoader() + + tests = [self] + from unittest2 import test + expectedPath = os.path.abspath(os.path.dirname(test.__file__)) + + self.wasRun = False + def _find_tests(start_dir, pattern): + self.wasRun = True + self.assertEqual(start_dir, expectedPath) + return tests + loader._find_tests = _find_tests + suite = loader.discover('unittest2.test') + self.assertTrue(self.wasRun) + self.assertEqual(suite._tests, tests) + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_functiontestcase.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_functiontestcase.py new file mode 100644 index 0000000..295aec3 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_functiontestcase.py @@ -0,0 +1,149 @@ +import unittest2 + +from unittest2.test.support import LoggingResult + + +class Test_FunctionTestCase(unittest2.TestCase): + + # "Return the number of tests represented by the this test object. For + # unittest2.TestCase instances, this will always be 1" + def test_countTestCases(self): + test = unittest2.FunctionTestCase(lambda: None) + + self.assertEqual(test.countTestCases(), 1) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if setUp() raises + # an exception. + def test_run_call_order__error_in_setUp(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + raise RuntimeError('raised by setUp') + + def test(): + events.append('test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'addError', 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test raises + # an error (as opposed to a failure). + def test_run_call_order__error_in_test(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + raise RuntimeError('raised by test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown', + 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test signals + # a failure (as opposed to an error). + def test_run_call_order__failure_in_test(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + self.fail('raised by test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown', + 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if tearDown() raises + # an exception. + def test_run_call_order__error_in_tearDown(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + + def tearDown(): + events.append('tearDown') + raise RuntimeError('raised by tearDown') + + expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', + 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "Return a string identifying the specific test case." + # + # Because of the vague nature of the docs, I'm not going to lock this + # test down too much. Really all that can be asserted is that the id() + # will be a string (either 8-byte or unicode -- again, because the docs + # just say "string") + def test_id(self): + test = unittest2.FunctionTestCase(lambda: None) + + self.assertIsInstance(test.id(), basestring) + + # "Returns a one-line description of the test, or None if no description + # has been provided. The default implementation of this method returns + # the first line of the test method's docstring, if available, or None." + def test_shortDescription__no_docstring(self): + test = unittest2.FunctionTestCase(lambda: None) + + self.assertEqual(test.shortDescription(), None) + + # "Returns a one-line description of the test, or None if no description + # has been provided. The default implementation of this method returns + # the first line of the test method's docstring, if available, or None." + def test_shortDescription__singleline_docstring(self): + desc = "this tests foo" + test = unittest2.FunctionTestCase(lambda: None, description=desc) + + self.assertEqual(test.shortDescription(), "this tests foo") + + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_loader.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_loader.py new file mode 100644 index 0000000..60b66d3 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_loader.py @@ -0,0 +1,1289 @@ +import sys +import types + +import unittest2 + +if sys.version_info[:2] == (2,3): + from sets import Set as set + from sets import ImmutableSet as frozenset + +class Test_TestLoader(unittest2.TestCase): + + ### Tests for TestLoader.loadTestsFromTestCase + ################################################################ + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + def test_loadTestsFromTestCase(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest2.TestLoader() + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + # + # Make sure it does the right thing even if no tests were found + def test_loadTestsFromTestCase__no_matches(self): + class Foo(unittest2.TestCase): + def foo_bar(self): pass + + empty_suite = unittest2.TestSuite() + + loader = unittest2.TestLoader() + self.assertEqual(loader.loadTestsFromTestCase(Foo), empty_suite) + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + # + # What happens if loadTestsFromTestCase() is given an object + # that isn't a subclass of TestCase? Specifically, what happens + # if testCaseClass is a subclass of TestSuite? + # + # This is checked for specifically in the code, so we better add a + # test for it. + def test_loadTestsFromTestCase__TestSuite_subclass(self): + class NotATestCase(unittest2.TestSuite): + pass + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromTestCase(NotATestCase) + except TypeError: + pass + else: + self.fail('Should raise TypeError') + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + # + # Make sure loadTestsFromTestCase() picks up the default test method + # name (as specified by TestCase), even though the method name does + # not match the default TestLoader.testMethodPrefix string + def test_loadTestsFromTestCase__default_method_name(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + loader = unittest2.TestLoader() + # This has to be false for the test to succeed + self.assertFalse('runTest'.startswith(loader.testMethodPrefix)) + + suite = loader.loadTestsFromTestCase(Foo) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [Foo('runTest')]) + + ################################################################ + ### /Tests for TestLoader.loadTestsFromTestCase + + ### Tests for TestLoader.loadTestsFromModule + ################################################################ + + # "This method searches `module` for classes derived from TestCase" + def test_loadTestsFromModule__TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = [loader.suiteClass([MyTestCase('test')])] + self.assertEqual(list(suite), expected) + + # "This method searches `module` for classes derived from TestCase" + # + # What happens if no tests are found (no TestCase instances)? + def test_loadTestsFromModule__no_TestCase_instances(self): + m = types.ModuleType('m') + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "This method searches `module` for classes derived from TestCase" + # + # What happens if no tests are found (TestCases instances, but no tests)? + def test_loadTestsFromModule__no_TestCase_tests(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [loader.suiteClass()]) + + # "This method searches `module` for classes derived from TestCase"s + # + # What happens if loadTestsFromModule() is given something other + # than a module? + # + # XXX Currently, it succeeds anyway. This flexibility + # should either be documented or loadTestsFromModule() should + # raise a TypeError + # + # XXX Certain people are using this behaviour. We'll add a test for it + def test_loadTestsFromModule__not_a_module(self): + class MyTestCase(unittest2.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(NotAModule) + + reference = [unittest2.TestSuite([MyTestCase('test')])] + self.assertEqual(list(suite), reference) + + + # Check that loadTestsFromModule honors (or not) a module + # with a load_tests function. + def test_loadTestsFromModule__load_tests(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest2.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, unittest2.TestSuite) + self.assertEquals(load_tests_args, [loader, suite, None]) + + load_tests_args = [] + suite = loader.loadTestsFromModule(m, use_load_tests=False) + self.assertEquals(load_tests_args, []) + + def test_loadTestsFromModule__faulty_load_tests(self): + m = types.ModuleType('m') + + def load_tests(loader, tests, pattern): + raise TypeError('some failure') + m.load_tests = load_tests + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, unittest2.TestSuite) + self.assertEqual(suite.countTestCases(), 1) + test = list(suite)[0] + + self.assertRaisesRegexp(TypeError, "some failure", test.m) + + + ################################################################ + ### /Tests for TestLoader.loadTestsFromModule() + + ### Tests for TestLoader.loadTestsFromName() + ################################################################ + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Is ValueError raised in response to an empty name? + def test_loadTestsFromName__empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('') + except ValueError, e: + self.assertEqual(str(e), "Empty module name") + else: + self.fail("TestLoader.loadTestsFromName failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the name contains invalid characters? + def test_loadTestsFromName__malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise ValueError or ImportError? + try: + loader.loadTestsFromName('abc () //') + except ValueError: + pass + except ImportError: + pass + else: + self.fail("TestLoader.loadTestsFromName failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve ... to a + # module" + # + # What happens when a module by that name can't be found? + def test_loadTestsFromName__unknown_module_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('sdasfasfasdf') + except ImportError, e: + self.assertEqual(str(e), "No module named sdasfasfasdf") + else: + self.fail("TestLoader.loadTestsFromName failed to raise ImportError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the module is found, but the attribute can't? + def test_loadTestsFromName__unknown_attr_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('unittest2.sdasfasfasdf') + except AttributeError, e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when we provide the module, but the attribute can't be + # found? + def test_loadTestsFromName__relative_unknown_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('sdasfasfasdf', unittest2) + except AttributeError, e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromName raise ValueError when passed an empty + # name relative to a provided module? + # + # XXX Should probably raise a ValueError instead of an AttributeError + def test_loadTestsFromName__relative_empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('', unittest2) + except AttributeError: + pass + else: + self.fail("Failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when an impossible name is given, relative to the provided + # `module`? + def test_loadTestsFromName__relative_malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise AttributeError or ValueError? + try: + loader.loadTestsFromName('abc () //', unittest2) + except ValueError: + pass + except AttributeError: + pass + else: + self.fail("TestLoader.loadTestsFromName failed to raise ValueError") + + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromName raise TypeError when the `module` argument + # isn't a module object? + # + # XXX Accepts the not-a-module object, ignorning the object's type + # This should raise an exception or the method name should be changed + # + # XXX Some people are relying on this, so keep it for now + def test_loadTestsFromName__relative_not_a_module(self): + class MyTestCase(unittest2.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('test_2', NotAModule) + + reference = [MyTestCase('test')] + self.assertEqual(list(suite), reference) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does it raise an exception if the name resolves to an invalid + # object? + def test_loadTestsFromName__relative_bad_object(self): + m = types.ModuleType('m') + m.testcase_1 = object() + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromName('testcase_1', m) + except TypeError: + pass + else: + self.fail("Should have raised TypeError") + + # "The specifier name is a ``dotted name'' that may + # resolve either to ... a test case class" + def test_loadTestsFromName__relative_TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('testcase_1', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + def test_loadTestsFromName__relative_TestSuite(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testsuite = unittest2.TestSuite([MyTestCase('test')]) + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('testsuite', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test method within a test case class" + def test_loadTestsFromName__relative_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('testcase_1.test', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does loadTestsFromName() raise the proper exception when trying to + # resolve "a test method within a test case class" that doesn't exist + # for the given name (relative to a provided module)? + def test_loadTestsFromName__relative_invalid_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromName('testcase_1.testfoo', m) + except AttributeError, e: + self.assertEqual(str(e), "type object 'MyTestCase' has no attribute 'testfoo'") + else: + self.fail("Failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a ... TestSuite instance" + def test_loadTestsFromName__callable__TestSuite(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + testcase_2 = unittest2.FunctionTestCase(lambda: None) + def return_TestSuite(): + return unittest2.TestSuite([testcase_1, testcase_2]) + m.return_TestSuite = return_TestSuite + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('return_TestSuite', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1, testcase_2]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + def test_loadTestsFromName__callable__TestCase_instance(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('return_TestCase', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + #***************************************************************** + #Override the suiteClass attribute to ensure that the suiteClass + #attribute is used + def test_loadTestsFromName__callable__TestCase_instance_ProperSuiteClass(self): + class SubTestSuite(unittest2.TestSuite): + pass + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest2.TestLoader() + loader.suiteClass = SubTestSuite + suite = loader.loadTestsFromName('return_TestCase', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test method within a test case class" + #***************************************************************** + #Override the suiteClass attribute to ensure that the suiteClass + #attribute is used + def test_loadTestsFromName__relative_testmethod_ProperSuiteClass(self): + class SubTestSuite(unittest2.TestSuite): + pass + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + loader.suiteClass=SubTestSuite + suite = loader.loadTestsFromName('testcase_1.test', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # What happens if the callable returns something else? + def test_loadTestsFromName__callable__wrong_type(self): + m = types.ModuleType('m') + def return_wrong(): + return 6 + m.return_wrong = return_wrong + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromName('return_wrong', m) + except TypeError: + pass + else: + self.fail("TestLoader.loadTestsFromName failed to raise TypeError") + + # "The specifier can refer to modules and packages which have not been + # imported; they will be imported as a side-effect" + def test_loadTestsFromName__module_not_loaded(self): + # We're going to try to load this module as a side-effect, so it + # better not be loaded before we try. + # + module_name = 'unittest2.test.dummy' + sys.modules.pop(module_name, None) + + loader = unittest2.TestLoader() + try: + suite = loader.loadTestsFromName(module_name) + + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # module should now be loaded, thanks to loadTestsFromName() + self.assertIn(module_name, sys.modules) + finally: + if module_name in sys.modules: + del sys.modules[module_name] + + ################################################################ + ### Tests for TestLoader.loadTestsFromName() + + ### Tests for TestLoader.loadTestsFromNames() + ################################################################ + + # "Similar to loadTestsFromName(), but takes a sequence of names rather + # than a single name." + # + # What happens if that sequence of names is empty? + def test_loadTestsFromNames__empty_name_list(self): + loader = unittest2.TestLoader() + + suite = loader.loadTestsFromNames([]) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "Similar to loadTestsFromName(), but takes a sequence of names rather + # than a single name." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens if that sequence of names is empty? + # + # XXX Should this raise a ValueError or just return an empty TestSuite? + def test_loadTestsFromNames__relative_empty_name_list(self): + loader = unittest2.TestLoader() + + suite = loader.loadTestsFromNames([], unittest2) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Is ValueError raised in response to an empty name? + def test_loadTestsFromNames__empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['']) + except ValueError, e: + self.assertEqual(str(e), "Empty module name") + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when presented with an impossible module name? + def test_loadTestsFromNames__malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise ValueError or ImportError? + try: + loader.loadTestsFromNames(['abc () //']) + except ValueError: + pass + except ImportError: + pass + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when no module can be found for the given name? + def test_loadTestsFromNames__unknown_module_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['sdasfasfasdf']) + except ImportError, e: + self.assertEqual(str(e), "No module named sdasfasfasdf") + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ImportError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the module can be found, but not the attribute? + def test_loadTestsFromNames__unknown_attr_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['unittest2.sdasfasfasdf', 'unittest2']) + except AttributeError, e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromNames failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when given an unknown attribute on a specified `module` + # argument? + def test_loadTestsFromNames__unknown_name_relative_1(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['sdasfasfasdf'], unittest2) + except AttributeError, e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # Do unknown attributes (relative to a provided module) still raise an + # exception even in the presence of valid attribute names? + def test_loadTestsFromNames__unknown_name_relative_2(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['TestCase', 'sdasfasfasdf'], unittest2) + except AttributeError, e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when faced with the empty string? + # + # XXX This currently raises AttributeError, though ValueError is probably + # more appropriate + def test_loadTestsFromNames__relative_empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames([''], unittest2) + except AttributeError: + pass + else: + self.fail("Failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when presented with an impossible attribute name? + def test_loadTestsFromNames__relative_malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise AttributeError or ValueError? + try: + loader.loadTestsFromNames(['abc () //'], unittest2) + except AttributeError: + pass + except ValueError: + pass + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") + + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromNames() make sure the provided `module` is in fact + # a module? + # + # XXX This validation is currently not done. This flexibility should + # either be documented or a TypeError should be raised. + def test_loadTestsFromNames__relative_not_a_module(self): + class MyTestCase(unittest2.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['test_2'], NotAModule) + + reference = [unittest2.TestSuite([MyTestCase('test')])] + self.assertEqual(list(suite), reference) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does it raise an exception if the name resolves to an invalid + # object? + def test_loadTestsFromNames__relative_bad_object(self): + m = types.ModuleType('m') + m.testcase_1 = object() + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromNames(['testcase_1'], m) + except TypeError: + pass + else: + self.fail("Should have raised TypeError") + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test case class" + def test_loadTestsFromNames__relative_TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1'], m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = loader.suiteClass([MyTestCase('test')]) + self.assertEqual(list(suite), [expected]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a TestSuite instance" + def test_loadTestsFromNames__relative_TestSuite(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testsuite = unittest2.TestSuite([MyTestCase('test')]) + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['testsuite'], m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [m.testsuite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to ... a + # test method within a test case class" + def test_loadTestsFromNames__relative_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1.test'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest2.TestSuite([MyTestCase('test')]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to ... a + # test method within a test case class" + # + # Does the method gracefully handle names that initially look like they + # resolve to "a test method within a test case class" but don't? + def test_loadTestsFromNames__relative_invalid_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromNames(['testcase_1.testfoo'], m) + except AttributeError, e: + self.assertEqual(str(e), "type object 'MyTestCase' has no attribute 'testfoo'") + else: + self.fail("Failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a ... TestSuite instance" + def test_loadTestsFromNames__callable__TestSuite(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + testcase_2 = unittest2.FunctionTestCase(lambda: None) + def return_TestSuite(): + return unittest2.TestSuite([testcase_1, testcase_2]) + m.return_TestSuite = return_TestSuite + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['return_TestSuite'], m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = unittest2.TestSuite([testcase_1, testcase_2]) + self.assertEqual(list(suite), [expected]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + def test_loadTestsFromNames__callable__TestCase_instance(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['return_TestCase'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest2.TestSuite([testcase_1]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # Are staticmethods handled correctly? + def test_loadTestsFromNames__callable__call_staticmethod(self): + m = types.ModuleType('m') + class Test1(unittest2.TestCase): + def test(self): + pass + + testcase_1 = Test1('test') + class Foo(unittest2.TestCase): + def foo(): + return testcase_1 + foo = staticmethod(foo) + m.Foo = Foo + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['Foo.foo'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest2.TestSuite([testcase_1]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # What happens when the callable returns something else? + def test_loadTestsFromNames__callable__wrong_type(self): + m = types.ModuleType('m') + def return_wrong(): + return 6 + m.return_wrong = return_wrong + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromNames(['return_wrong'], m) + except TypeError: + pass + else: + self.fail("TestLoader.loadTestsFromNames failed to raise TypeError") + + # "The specifier can refer to modules and packages which have not been + # imported; they will be imported as a side-effect" + def test_loadTestsFromNames__module_not_loaded(self): + # We're going to try to load this module as a side-effect, so it + # better not be loaded before we try. + # + module_name = 'unittest2.test.dummy' + sys.modules.pop(module_name, None) + + loader = unittest2.TestLoader() + try: + suite = loader.loadTestsFromNames([module_name]) + + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [unittest2.TestSuite()]) + + # module should now be loaded, thanks to loadTestsFromName() + self.assertIn(module_name, sys.modules) + finally: + if module_name in sys.modules: + del sys.modules[module_name] + + ################################################################ + ### /Tests for TestLoader.loadTestsFromNames() + + ### Tests for TestLoader.getTestCaseNames() + ################################################################ + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Test.foobar is defined to make sure getTestCaseNames() respects + # loader.testMethodPrefix + def test_getTestCaseNames(self): + class Test(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foobar(self): pass + + loader = unittest2.TestLoader() + + self.assertEqual(loader.getTestCaseNames(Test), ['test_1', 'test_2']) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Does getTestCaseNames() behave appropriately if no tests are found? + def test_getTestCaseNames__no_tests(self): + class Test(unittest2.TestCase): + def foobar(self): pass + + loader = unittest2.TestLoader() + + self.assertEqual(loader.getTestCaseNames(Test), []) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Are not-TestCases handled gracefully? + # + # XXX This should raise a TypeError, not return a list + # + # XXX It's too late in the 2.5 release cycle to fix this, but it should + # probably be revisited for 2.6 + def test_getTestCaseNames__not_a_TestCase(self): + class BadCase(int): + def test_foo(self): + pass + + loader = unittest2.TestLoader() + names = loader.getTestCaseNames(BadCase) + + self.assertEqual(names, ['test_foo']) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Make sure inherited names are handled. + # + # TestP.foobar is defined to make sure getTestCaseNames() respects + # loader.testMethodPrefix + def test_getTestCaseNames__inheritance(self): + class TestP(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foobar(self): pass + + class TestC(TestP): + def test_1(self): pass + def test_3(self): pass + + loader = unittest2.TestLoader() + + names = ['test_1', 'test_2', 'test_3'] + self.assertEqual(loader.getTestCaseNames(TestC), names) + + ################################################################ + ### /Tests for TestLoader.getTestCaseNames() + + ### Tests for TestLoader.testMethodPrefix + ################################################################ + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromTestCase(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests_1 = unittest2.TestSuite([Foo('foo_bar')]) + tests_2 = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromModule(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = [unittest2.TestSuite([Foo('foo_bar')])] + tests_2 = [unittest2.TestSuite([Foo('test_1'), Foo('test_2')])] + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(list(loader.loadTestsFromModule(m)), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(list(loader.loadTestsFromModule(m)), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromName(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = unittest2.TestSuite([Foo('foo_bar')]) + tests_2 = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromName('Foo', m), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromName('Foo', m), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromNames(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = unittest2.TestSuite([unittest2.TestSuite([Foo('foo_bar')])]) + tests_2 = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + tests_2 = unittest2.TestSuite([tests_2]) + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_2) + + # "The default value is 'test'" + def test_testMethodPrefix__default_value(self): + loader = unittest2.TestLoader() + self.assertTrue(loader.testMethodPrefix == 'test') + + ################################################################ + ### /Tests for TestLoader.testMethodPrefix + + ### Tests for TestLoader.sortTestMethodsUsing + ################################################################ + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromTestCase(self): + def reversed_cmp(x, y): + return -cmp(x, y) + + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromModule(self): + def reversed_cmp(x, y): + return -cmp(x, y) + + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] + self.assertEqual(list(loader.loadTestsFromModule(m)), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromName(self): + def reversed_cmp(x, y): + return -cmp(x, y) + + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) + self.assertEqual(loader.loadTestsFromName('Foo', m), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromNames(self): + def reversed_cmp(x, y): + return -cmp(x, y) + + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] + self.assertEqual(list(loader.loadTestsFromNames(['Foo'], m)), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames()" + # + # Does it actually affect getTestCaseNames()? + def test_sortTestMethodsUsing__getTestCaseNames(self): + def reversed_cmp(x, y): + return -cmp(x, y) + + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = reversed_cmp + + test_names = ['test_2', 'test_1'] + self.assertEqual(loader.getTestCaseNames(Foo), test_names) + + # "The default value is the built-in cmp() function" + def test_sortTestMethodsUsing__default_value(self): + loader = unittest2.TestLoader() + self.assertTrue(loader.sortTestMethodsUsing is cmp) + + # "it can be set to None to disable the sort." + # + # XXX How is this different from reassigning cmp? Are the tests returned + # in a random order or something? This behaviour should die + def test_sortTestMethodsUsing__None(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = None + + test_names = ['test_2', 'test_1'] + self.assertEqual(set(loader.getTestCaseNames(Foo)), set(test_names)) + + ################################################################ + ### /Tests for TestLoader.sortTestMethodsUsing + + ### Tests for TestLoader.suiteClass + ################################################################ + + # "Callable object that constructs a test suite from a list of tests." + def test_suiteClass__loadTestsFromTestCase(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests = [Foo('test_1'), Foo('test_2')] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromModule(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [[Foo('test_1'), Foo('test_2')]] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromModule(m), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromName(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [Foo('test_1'), Foo('test_2')] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromName('Foo', m), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromNames(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [[Foo('test_1'), Foo('test_2')]] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests) + + # "The default value is the TestSuite class" + def test_suiteClass__default_value(self): + loader = unittest2.TestLoader() + self.assertTrue(loader.suiteClass is unittest2.TestSuite) + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_new_tests.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_new_tests.py new file mode 100644 index 0000000..cc96bcb --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_new_tests.py @@ -0,0 +1,46 @@ +from cStringIO import StringIO + +import unittest +import unittest2 + +from unittest2.test.support import resultFactory + + +class TestUnittest(unittest2.TestCase): + + def assertIsSubclass(self, actual, klass): + self.assertTrue(issubclass(actual, klass), "Not a subclass.") + + def testInheritance(self): + self.assertIsSubclass(unittest2.TestCase, unittest.TestCase) + self.assertIsSubclass(unittest2.TestResult, unittest.TestResult) + self.assertIsSubclass(unittest2.TestSuite, unittest.TestSuite) + self.assertIsSubclass(unittest2.TextTestRunner, unittest.TextTestRunner) + self.assertIsSubclass(unittest2.TestLoader, unittest.TestLoader) + self.assertIsSubclass(unittest2.TextTestResult, unittest.TestResult) + + def test_new_runner_old_case(self): + runner = unittest2.TextTestRunner(resultclass=resultFactory, + stream=StringIO()) + class Test(unittest.TestCase): + def testOne(self): + pass + suite = unittest2.TestSuite((Test('testOne'),)) + result = runner.run(suite) + self.assertEqual(result.testsRun, 1) + self.assertEqual(len(result.errors), 0) + + def test_old_runner_new_case(self): + runner = unittest.TextTestRunner(stream=StringIO()) + class Test(unittest2.TestCase): + def testOne(self): + self.assertDictEqual({}, {}) + + suite = unittest.TestSuite((Test('testOne'),)) + result = runner.run(suite) + self.assertEqual(result.testsRun, 1) + self.assertEqual(len(result.errors), 0) + + +if __name__ == '__main__': + unittest2.main() \ No newline at end of file diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_program.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_program.py new file mode 100644 index 0000000..56077a6 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_program.py @@ -0,0 +1,239 @@ +from cStringIO import StringIO + +import sys +import unittest2 + +hasInstallHandler = hasattr(unittest2, 'installHandler') + +class Test_TestProgram(unittest2.TestCase): + + # Horrible white box test + def testNoExit(self): + result = object() + test = object() + + class FakeRunner(object): + def run(self, test): + self.test = test + return result + + runner = FakeRunner() + + oldParseArgs = unittest2.TestProgram.parseArgs + def restoreParseArgs(): + unittest2.TestProgram.parseArgs = oldParseArgs + unittest2.TestProgram.parseArgs = lambda *args: None + self.addCleanup(restoreParseArgs) + + def removeTest(): + del unittest2.TestProgram.test + unittest2.TestProgram.test = test + self.addCleanup(removeTest) + + program = unittest2.TestProgram(testRunner=runner, exit=False, verbosity=2) + + self.assertEqual(program.result, result) + self.assertEqual(runner.test, test) + self.assertEqual(program.verbosity, 2) + + class FooBar(unittest2.TestCase): + def testPass(self): + assert True + def testFail(self): + assert False + + class FooBarLoader(unittest2.TestLoader): + """Test loader that returns a suite containing FooBar.""" + def loadTestsFromModule(self, module): + return self.suiteClass( + [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) + + + def test_NonExit(self): + program = unittest2.main(exit=False, + argv=["foobar"], + testRunner=unittest2.TextTestRunner(stream=StringIO()), + testLoader=self.FooBarLoader()) + self.assertTrue(hasattr(program, 'result')) + + + def test_Exit(self): + self.assertRaises( + SystemExit, + unittest2.main, + argv=["foobar"], + testRunner=unittest2.TextTestRunner(stream=StringIO()), + exit=True, + testLoader=self.FooBarLoader()) + + + def test_ExitAsDefault(self): + self.assertRaises( + SystemExit, + unittest2.main, + argv=["foobar"], + testRunner=unittest2.TextTestRunner(stream=StringIO()), + testLoader=self.FooBarLoader()) + + +class InitialisableProgram(unittest2.TestProgram): + exit = False + result = None + verbosity = 1 + defaultTest = None + testRunner = None + testLoader = unittest2.defaultTestLoader + progName = 'test' + test = 'test' + def __init__(self, *args): + pass + +RESULT = object() + +class FakeRunner(object): + initArgs = None + test = None + raiseError = False + + def __init__(self, **kwargs): + FakeRunner.initArgs = kwargs + if FakeRunner.raiseError: + FakeRunner.raiseError = False + raise TypeError + + def run(self, test): + FakeRunner.test = test + return RESULT + +class TestCommandLineArgs(unittest2.TestCase): + + def setUp(self): + self.program = InitialisableProgram() + self.program.createTests = lambda: None + FakeRunner.initArgs = None + FakeRunner.test = None + FakeRunner.raiseError = False + + def testHelpAndUnknown(self): + program = self.program + def usageExit(msg=None): + program.msg = msg + program.exit = True + program.usageExit = usageExit + + for opt in '-h', '-H', '--help': + program.exit = False + program.parseArgs([None, opt]) + self.assertTrue(program.exit) + self.assertIsNone(program.msg) + + program.parseArgs([None, '-$']) + self.assertTrue(program.exit) + self.assertIsNotNone(program.msg) + + def testVerbosity(self): + program = self.program + + for opt in '-q', '--quiet': + program.verbosity = 1 + program.parseArgs([None, opt]) + self.assertEqual(program.verbosity, 0) + + for opt in '-v', '--verbose': + program.verbosity = 1 + program.parseArgs([None, opt]) + self.assertEqual(program.verbosity, 2) + + def testBufferCatchFailfast(self): + program = self.program + for arg, attr in (('buffer', 'buffer'), ('failfast', 'failfast'), + ('catch', 'catchbreak')): + if attr == 'catch' and not hasInstallHandler: + continue + + short_opt = '-%s' % arg[0] + long_opt = '--%s' % arg + for opt in short_opt, long_opt: + setattr(program, attr, None) + + program.parseArgs([None, opt]) + self.assertTrue(getattr(program, attr)) + + for opt in short_opt, long_opt: + not_none = object() + setattr(program, attr, not_none) + + program.parseArgs([None, opt]) + self.assertEqual(getattr(program, attr), not_none) + + def testRunTestsRunnerClass(self): + program = self.program + + program.testRunner = FakeRunner + program.verbosity = 'verbosity' + program.failfast = 'failfast' + program.buffer = 'buffer' + + program.runTests() + + self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity', + 'failfast': 'failfast', + 'buffer': 'buffer'}) + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testRunTestsRunnerInstance(self): + program = self.program + + program.testRunner = FakeRunner() + FakeRunner.initArgs = None + + program.runTests() + + # A new FakeRunner should not have been instantiated + self.assertIsNone(FakeRunner.initArgs) + + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testRunTestsOldRunnerClass(self): + program = self.program + + FakeRunner.raiseError = True + program.testRunner = FakeRunner + program.verbosity = 'verbosity' + program.failfast = 'failfast' + program.buffer = 'buffer' + program.test = 'test' + + program.runTests() + + # If initialising raises a type error it should be retried + # without the new keyword arguments + self.assertEqual(FakeRunner.initArgs, {}) + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testCatchBreakInstallsHandler(self): + module = sys.modules['unittest2.main'] + original = module.installHandler + def restore(): + module.installHandler = original + self.addCleanup(restore) + + self.installed = False + def fakeInstallHandler(): + self.installed = True + module.installHandler = fakeInstallHandler + + program = self.program + program.catchbreak = True + + program.testRunner = FakeRunner + + program.runTests() + self.assertTrue(self.installed) + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_result.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_result.py new file mode 100644 index 0000000..a6dc16c --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_result.py @@ -0,0 +1,416 @@ +import sys +import textwrap +from StringIO import StringIO + +import unittest2 + + +class Test_TestResult(unittest2.TestCase): + # Note: there are not separate tests for TestResult.wasSuccessful(), + # TestResult.errors, TestResult.failures, TestResult.testsRun or + # TestResult.shouldStop because these only have meaning in terms of + # other TestResult methods. + # + # Accordingly, tests for the aforenamed attributes are incorporated + # in with the tests for the defining methods. + ################################################################ + + def test_init(self): + result = unittest2.TestResult() + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 0) + self.assertEqual(result.shouldStop, False) + self.assertIsNone(result._stdout_buffer) + self.assertIsNone(result._stderr_buffer) + + # "This method can be called to signal that the set of tests being + # run should be aborted by setting the TestResult's shouldStop + # attribute to True." + def test_stop(self): + result = unittest2.TestResult() + + result.stop() + + self.assertEqual(result.shouldStop, True) + + # "Called when the test case test is about to be run. The default + # implementation simply increments the instance's testsRun counter." + def test_startTest(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest2.TestResult() + + result.startTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + result.stopTest(test) + + # "Called after the test case test has been executed, regardless of + # the outcome. The default implementation does nothing." + def test_stopTest(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest2.TestResult() + + result.startTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + result.stopTest(test) + + # Same tests as above; make sure nothing has changed + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + # "Called before and after tests are run. The default implementation does nothing." + def test_startTestRun_stopTestRun(self): + result = unittest2.TestResult() + result.startTestRun() + result.stopTestRun() + + # "addSuccess(test)" + # ... + # "Called when the test case test succeeds" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addSuccess(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest2.TestResult() + + result.startTest(test) + result.addSuccess(test) + result.stopTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + # "addFailure(test, err)" + # ... + # "Called when the test case test signals a failure. err is a tuple of + # the form returned by sys.exc_info(): (type, value, traceback)" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addFailure(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + try: + test.fail("foo") + except: + exc_info_tuple = sys.exc_info() + + result = unittest2.TestResult() + + result.startTest(test) + result.addFailure(test, exc_info_tuple) + result.stopTest(test) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.failures[0] + self.assertTrue(test_case is test) + self.assertIsInstance(formatted_exc, str) + + # "addError(test, err)" + # ... + # "Called when the test case test raises an unexpected exception err + # is a tuple of the form returned by sys.exc_info(): + # (type, value, traceback)" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addError(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + try: + raise TypeError() + except: + exc_info_tuple = sys.exc_info() + + result = unittest2.TestResult() + + result.startTest(test) + result.addError(test, exc_info_tuple) + result.stopTest(test) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.errors[0] + self.assertTrue(test_case is test) + self.assertIsInstance(formatted_exc, str) + + def testGetDescriptionWithoutDocstring(self): + result = unittest2.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + 'testGetDescriptionWithoutDocstring (' + __name__ + + '.Test_TestResult)') + + def testGetDescriptionWithOneLineDocstring(self): + """Tests getDescription() for a method with a docstring.""" + result = unittest2.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + ('testGetDescriptionWithOneLineDocstring ' + '(' + __name__ + '.Test_TestResult)\n' + 'Tests getDescription() for a method with a docstring.')) + + def testGetDescriptionWithMultiLineDocstring(self): + """Tests getDescription() for a method with a longer docstring. + The second line of the docstring. + """ + result = unittest2.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + ('testGetDescriptionWithMultiLineDocstring ' + '(' + __name__ + '.Test_TestResult)\n' + 'Tests getDescription() for a method with a longer ' + 'docstring.')) + + def testStackFrameTrimming(self): + class Frame(object): + class tb_frame(object): + f_globals = {} + result = unittest2.TestResult() + self.assertFalse(result._is_relevant_tb_level(Frame)) + + Frame.tb_frame.f_globals['__unittest'] = True + self.assertTrue(result._is_relevant_tb_level(Frame)) + + def testFailFast(self): + result = unittest2.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addError(None, None) + self.assertTrue(result.shouldStop) + + result = unittest2.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addFailure(None, None) + self.assertTrue(result.shouldStop) + + result = unittest2.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addUnexpectedSuccess(None) + self.assertTrue(result.shouldStop) + + def testFailFastSetByRunner(self): + runner = unittest2.TextTestRunner(stream=StringIO(), failfast=True) + self.testRan = False + def test(result): + self.testRan = True + self.assertTrue(result.failfast) + runner.run(test) + self.assertTrue(self.testRan) + + +class TestOutputBuffering(unittest2.TestCase): + + def setUp(self): + self._real_out = sys.stdout + self._real_err = sys.stderr + + def tearDown(self): + sys.stdout = self._real_out + sys.stderr = self._real_err + + def testBufferOutputOff(self): + real_out = self._real_out + real_err = self._real_err + + result = unittest2.TestResult() + self.assertFalse(result.buffer) + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + result.startTest(self) + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + def testBufferOutputStartTestAddSuccess(self): + real_out = self._real_out + real_err = self._real_err + + result = unittest2.TestResult() + self.assertFalse(result.buffer) + + result.buffer = True + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + result.startTest(self) + + self.assertIsNot(real_out, sys.stdout) + self.assertIsNot(real_err, sys.stderr) + self.assertIsInstance(sys.stdout, StringIO) + self.assertIsInstance(sys.stderr, StringIO) + self.assertIsNot(sys.stdout, sys.stderr) + + out_stream = sys.stdout + err_stream = sys.stderr + + result._original_stdout = StringIO() + result._original_stderr = StringIO() + + print 'foo' + print >> sys.stderr, 'bar' + + self.assertEqual(out_stream.getvalue(), 'foo\n') + self.assertEqual(err_stream.getvalue(), 'bar\n') + + self.assertEqual(result._original_stdout.getvalue(), '') + self.assertEqual(result._original_stderr.getvalue(), '') + + result.addSuccess(self) + result.stopTest(self) + + self.assertIs(sys.stdout, result._original_stdout) + self.assertIs(sys.stderr, result._original_stderr) + + self.assertEqual(result._original_stdout.getvalue(), '') + self.assertEqual(result._original_stderr.getvalue(), '') + + self.assertEqual(out_stream.getvalue(), '') + self.assertEqual(err_stream.getvalue(), '') + + + def getStartedResult(self): + result = unittest2.TestResult() + result.buffer = True + result.startTest(self) + return result + + def testBufferOutputAddErrorOrFailure(self): + for message_attr, add_attr, include_error in [ + ('errors', 'addError', True), + ('failures', 'addFailure', False), + ('errors', 'addError', True), + ('failures', 'addFailure', False) + ]: + result = self.getStartedResult() + result._original_stderr = StringIO() + result._original_stdout = StringIO() + + print >> sys.stdout, 'foo' + if include_error: + print >> sys.stderr, 'bar' + + addFunction = getattr(result, add_attr) + addFunction(self, (None, None, None)) + result.stopTest(self) + + result_list = getattr(result, message_attr) + self.assertEqual(len(result_list), 1) + + test, message = result_list[0] + expectedOutMessage = textwrap.dedent(""" + Stdout: + foo + """) + expectedErrMessage = '' + if include_error: + expectedErrMessage = textwrap.dedent(""" + Stderr: + bar + """) + expectedFullMessage = 'None\n%s%s' % (expectedOutMessage, expectedErrMessage) + + self.assertIs(test, self) + self.assertEqual(result._original_stdout.getvalue(), expectedOutMessage) + self.assertEqual(result._original_stderr.getvalue(), expectedErrMessage) + self.assertMultiLineEqual(message, expectedFullMessage) + + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_runner.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_runner.py new file mode 100644 index 0000000..38b39ef --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_runner.py @@ -0,0 +1,129 @@ +import pickle + +from cStringIO import StringIO +from unittest2.test.support import LoggingResult, OldTestResult + +import unittest2 + + +class Test_TextTestRunner(unittest2.TestCase): + """Tests for TextTestRunner.""" + + def test_init(self): + runner = unittest2.TextTestRunner() + self.assertFalse(runner.failfast) + self.assertFalse(runner.buffer) + self.assertEqual(runner.verbosity, 1) + self.assertTrue(runner.descriptions) + self.assertEqual(runner.resultclass, unittest2.TextTestResult) + + + def testBufferAndFailfast(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + result = unittest2.TestResult() + runner = unittest2.TextTestRunner(stream=StringIO(), failfast=True, + buffer=True) + # Use our result object + runner._makeResult = lambda: result + runner.run(Test('testFoo')) + + self.assertTrue(result.failfast) + self.assertTrue(result.buffer) + + def testRunnerRegistersResult(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + originalRegisterResult = unittest2.runner.registerResult + def cleanup(): + unittest2.runner.registerResult = originalRegisterResult + self.addCleanup(cleanup) + + result = unittest2.TestResult() + runner = unittest2.TextTestRunner(stream=StringIO()) + # Use our result object + runner._makeResult = lambda: result + + self.wasRegistered = 0 + def fakeRegisterResult(thisResult): + self.wasRegistered += 1 + self.assertEqual(thisResult, result) + unittest2.runner.registerResult = fakeRegisterResult + + runner.run(unittest2.TestSuite()) + self.assertEqual(self.wasRegistered, 1) + + def test_works_with_result_without_startTestRun_stopTestRun(self): + class OldTextResult(OldTestResult): + def __init__(self, *_): + super(OldTextResult, self).__init__() + separator2 = '' + def printErrors(self): + pass + + runner = unittest2.TextTestRunner(stream=StringIO(), + resultclass=OldTextResult) + runner.run(unittest2.TestSuite()) + + def test_startTestRun_stopTestRun_called(self): + class LoggingTextResult(LoggingResult): + separator2 = '' + def printErrors(self): + pass + + class LoggingRunner(unittest2.TextTestRunner): + def __init__(self, events): + super(LoggingRunner, self).__init__(StringIO()) + self._events = events + + def _makeResult(self): + return LoggingTextResult(self._events) + + events = [] + runner = LoggingRunner(events) + runner.run(unittest2.TestSuite()) + expected = ['startTestRun', 'stopTestRun'] + self.assertEqual(events, expected) + + def test_pickle_unpickle(self): + # Issue #7197: a TextTestRunner should be (un)pickleable. This is + # required by test_multiprocessing under Windows (in verbose mode). + import StringIO + # cStringIO objects are not pickleable, but StringIO objects are. + stream = StringIO.StringIO("foo") + runner = unittest2.TextTestRunner(stream) + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(runner, protocol=protocol) + obj = pickle.loads(s) + # StringIO objects never compare equal, a cheap test instead. + self.assertEqual(obj.stream.getvalue(), stream.getvalue()) + + def test_resultclass(self): + def MockResultClass(*args): + return args + STREAM = object() + DESCRIPTIONS = object() + VERBOSITY = object() + runner = unittest2.TextTestRunner(STREAM, DESCRIPTIONS, VERBOSITY, + resultclass=MockResultClass) + self.assertEqual(runner.resultclass, MockResultClass) + + expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY) + self.assertEqual(runner._makeResult(), expectedresult) + + + def test_oldresult(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + runner = unittest2.TextTestRunner(resultclass=OldTestResult, + stream=StringIO()) + # This will raise an exception if TextTestRunner can't handle old + # test result objects + runner.run(Test('testFoo')) + + +if __name__ == '__main__': + unittest2.main() \ No newline at end of file diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_setups.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_setups.py new file mode 100644 index 0000000..203f72c --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_setups.py @@ -0,0 +1,502 @@ +import sys + +from cStringIO import StringIO + +import unittest2 +from unittest2.test.support import resultFactory + + +class TestSetups(unittest2.TestCase): + + def getRunner(self): + return unittest2.TextTestRunner(resultclass=resultFactory, + stream=StringIO()) + def runTests(self, *cases): + suite = unittest2.TestSuite() + for case in cases: + tests = unittest2.defaultTestLoader.loadTestsFromTestCase(case) + suite.addTests(tests) + + runner = self.getRunner() + + # creating a nested suite exposes some potential bugs + realSuite = unittest2.TestSuite() + realSuite.addTest(suite) + # adding empty suites to the end exposes potential bugs + suite.addTest(unittest2.TestSuite()) + realSuite.addTest(unittest2.TestSuite()) + return runner.run(realSuite) + + def test_setup_class(self): + class Test(unittest2.TestCase): + setUpCalled = 0 + def setUpClass(cls): + Test.setUpCalled += 1 + unittest2.TestCase.setUpClass() + setUpClass = classmethod(setUpClass) + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.setUpCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class(self): + class Test(unittest2.TestCase): + tearDownCalled = 0 + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest2.TestCase.tearDownClass() + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class_two_classes(self): + class Test(unittest2.TestCase): + tearDownCalled = 0 + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest2.TestCase.tearDownClass() + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + tearDownCalled = 0 + def tearDownClass(cls): + Test2.tearDownCalled += 1 + unittest2.TestCase.tearDownClass() + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(Test2.tearDownCalled, 1) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setupclass(self): + class BrokenTest(unittest2.TestCase): + def setUpClass(cls): + raise TypeError('foo') + setUpClass = classmethod(setUpClass) + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(BrokenTest) + + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), + 'setUpClass (%s.BrokenTest)' % __name__) + + def test_error_in_teardown_class(self): + class Test(unittest2.TestCase): + tornDown = 0 + def tearDownClass(cls): + Test.tornDown += 1 + raise TypeError('foo') + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + tornDown = 0 + def tearDownClass(cls): + Test2.tornDown += 1 + raise TypeError('foo') + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 2) + self.assertEqual(Test.tornDown, 1) + self.assertEqual(Test2.tornDown, 1) + + error, _ = result.errors[0] + self.assertEqual(str(error), + 'tearDownClass (%s.Test)' % __name__) + + def test_class_not_torndown_when_setup_fails(self): + class Test(unittest2.TestCase): + tornDown = False + def setUpClass(cls): + raise TypeError + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + Test.tornDown = True + raise TypeError('foo') + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + + self.runTests(Test) + self.assertFalse(Test.tornDown) + + def test_class_not_setup_or_torndown_when_skipped(self): + class Test(unittest2.TestCase): + classSetUp = False + tornDown = False + def setUpClass(cls): + Test.classSetUp = True + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + Test.tornDown = True + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + + Test = unittest2.skip("hop")(Test) + self.runTests(Test) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.tornDown) + + def test_setup_teardown_order_with_pathological_suite(self): + results = [] + + class Module1(object): + def setUpModule(): + results.append('Module1.setUpModule') + setUpModule = staticmethod(setUpModule) + def tearDownModule(): + results.append('Module1.tearDownModule') + tearDownModule = staticmethod(tearDownModule) + + class Module2(object): + def setUpModule(): + results.append('Module2.setUpModule') + setUpModule = staticmethod(setUpModule) + def tearDownModule(): + results.append('Module2.tearDownModule') + tearDownModule = staticmethod(tearDownModule) + + class Test1(unittest2.TestCase): + def setUpClass(cls): + results.append('setup 1') + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + results.append('teardown 1') + tearDownClass = classmethod(tearDownClass) + def testOne(self): + results.append('Test1.testOne') + def testTwo(self): + results.append('Test1.testTwo') + + class Test2(unittest2.TestCase): + def setUpClass(cls): + results.append('setup 2') + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + results.append('teardown 2') + tearDownClass = classmethod(tearDownClass) + def testOne(self): + results.append('Test2.testOne') + def testTwo(self): + results.append('Test2.testTwo') + + class Test3(unittest2.TestCase): + def setUpClass(cls): + results.append('setup 3') + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + results.append('teardown 3') + tearDownClass = classmethod(tearDownClass) + def testOne(self): + results.append('Test3.testOne') + def testTwo(self): + results.append('Test3.testTwo') + + Test1.__module__ = Test2.__module__ = 'Module' + Test3.__module__ = 'Module2' + sys.modules['Module'] = Module1 + sys.modules['Module2'] = Module2 + + first = unittest2.TestSuite((Test1('testOne'),)) + second = unittest2.TestSuite((Test1('testTwo'),)) + third = unittest2.TestSuite((Test2('testOne'),)) + fourth = unittest2.TestSuite((Test2('testTwo'),)) + fifth = unittest2.TestSuite((Test3('testOne'),)) + sixth = unittest2.TestSuite((Test3('testTwo'),)) + suite = unittest2.TestSuite((first, second, third, fourth, fifth, sixth)) + + runner = self.getRunner() + result = runner.run(suite) + self.assertEqual(result.testsRun, 6) + self.assertEqual(len(result.errors), 0) + + self.assertEqual(results, + ['Module1.setUpModule', 'setup 1', + 'Test1.testOne', 'Test1.testTwo', 'teardown 1', + 'setup 2', 'Test2.testOne', 'Test2.testTwo', + 'teardown 2', 'Module1.tearDownModule', + 'Module2.setUpModule', 'setup 3', + 'Test3.testOne', 'Test3.testTwo', + 'teardown 3', 'Module2.tearDownModule']) + + def test_setup_module(self): + class Module(object): + moduleSetup = 0 + def setUpModule(): + Module.moduleSetup += 1 + setUpModule = staticmethod(setUpModule) + + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setup_module(self): + class Module(object): + moduleSetup = 0 + moduleTornDown = 0 + def setUpModule(): + Module.moduleSetup += 1 + raise TypeError('foo') + setUpModule = staticmethod(setUpModule) + def tearDownModule(): + Module.moduleTornDown += 1 + tearDownModule = staticmethod(tearDownModule) + + class Test(unittest2.TestCase): + classSetUp = False + classTornDown = False + def setUpClass(cls): + Test.classSetUp = True + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + Test.classTornDown = True + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(Module.moduleTornDown, 0) + self.assertEqual(result.testsRun, 0) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'setUpModule (Module)') + + def test_testcase_with_missing_module(self): + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules.pop('Module', None) + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 2) + + def test_teardown_module(self): + class Module(object): + moduleTornDown = 0 + def tearDownModule(): + Module.moduleTornDown += 1 + tearDownModule = staticmethod(tearDownModule) + + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_teardown_module(self): + class Module(object): + moduleTornDown = 0 + def tearDownModule(): + Module.moduleTornDown += 1 + raise TypeError('foo') + tearDownModule = staticmethod(tearDownModule) + + class Test(unittest2.TestCase): + classSetUp = False + classTornDown = False + def setUpClass(cls): + Test.classSetUp = True + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + Test.classTornDown = True + tearDownClass = classmethod(tearDownClass) + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 4) + self.assertTrue(Test.classSetUp) + self.assertTrue(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'tearDownModule (Module)') + + def test_skiptest_in_setupclass(self): + class Test(unittest2.TestCase): + def setUpClass(cls): + raise unittest2.SkipTest('foo') + setUpClass = classmethod(setUpClass) + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.skipped), 1) + skipped = result.skipped[0][0] + self.assertEqual(str(skipped), 'setUpClass (%s.Test)' % __name__) + + def test_skiptest_in_setupmodule(self): + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + + class Module(object): + def setUpModule(): + raise unittest2.SkipTest('foo') + setUpModule = staticmethod(setUpModule) + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.skipped), 1) + skipped = result.skipped[0][0] + self.assertEqual(str(skipped), 'setUpModule (Module)') + + def test_suite_debug_executes_setups_and_teardowns(self): + ordering = [] + + class Module(object): + def setUpModule(): + ordering.append('setUpModule') + setUpModule = staticmethod(setUpModule) + def tearDownModule(): + ordering.append('tearDownModule') + tearDownModule = staticmethod(tearDownModule) + + class Test(unittest2.TestCase): + def setUpClass(cls): + ordering.append('setUpClass') + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + ordering.append('tearDownClass') + tearDownClass = classmethod(tearDownClass) + def test_something(self): + ordering.append('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + suite = unittest2.defaultTestLoader.loadTestsFromTestCase(Test) + suite.debug() + expectedOrder = ['setUpModule', 'setUpClass', 'test_something', 'tearDownClass', 'tearDownModule'] + self.assertEqual(ordering, expectedOrder) + + def test_suite_debug_propagates_exceptions(self): + class Module(object): + def setUpModule(): + if phase == 0: + raise Exception('setUpModule') + setUpModule = staticmethod(setUpModule) + def tearDownModule(): + if phase == 1: + raise Exception('tearDownModule') + tearDownModule = staticmethod(tearDownModule) + + class Test(unittest2.TestCase): + def setUpClass(cls): + if phase == 2: + raise Exception('setUpClass') + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + if phase == 3: + raise Exception('tearDownClass') + tearDownClass = classmethod(tearDownClass) + def test_something(self): + if phase == 4: + raise Exception('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + _suite = unittest2.defaultTestLoader.loadTestsFromTestCase(Test) + suite = unittest2.TestSuite() + + # nesting a suite again exposes a bug in the initial implementation + suite.addTest(_suite) + messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something') + for phase, msg in enumerate(messages): + self.assertRaisesRegexp(Exception, msg, suite.debug) diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_skipping.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_skipping.py new file mode 100644 index 0000000..dfe3bef --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_skipping.py @@ -0,0 +1,142 @@ +from unittest2.test.support import LoggingResult + +import unittest2 + + +class Test_TestSkipping(unittest2.TestCase): + + def test_skipping(self): + class Foo(unittest2.TestCase): + def test_skip_me(self): + self.skipTest("skip") + events = [] + result = LoggingResult(events) + test = Foo("test_skip_me") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) + self.assertEqual(result.skipped, [(test, "skip")]) + + # Try letting setUp skip the test now. + class Foo(unittest2.TestCase): + def setUp(self): + self.skipTest("testing") + def test_nothing(self): pass + events = [] + result = LoggingResult(events) + test = Foo("test_nothing") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(result.testsRun, 1) + + def test_skipping_decorators(self): + op_table = ((unittest2.skipUnless, False, True), + (unittest2.skipIf, True, False)) + for deco, do_skip, dont_skip in op_table: + class Foo(unittest2.TestCase): + def test_skip(self): + pass + test_skip = deco(do_skip, "testing")(test_skip) + + def test_dont_skip(self): + pass + test_dont_skip = deco(dont_skip, "testing")(test_dont_skip) + + test_do_skip = Foo("test_skip") + test_dont_skip = Foo("test_dont_skip") + suite = unittest2.TestSuite([test_do_skip, test_dont_skip]) + events = [] + result = LoggingResult(events) + suite.run(result) + self.assertEqual(len(result.skipped), 1) + expected = ['startTest', 'addSkip', 'stopTest', + 'startTest', 'addSuccess', 'stopTest'] + self.assertEqual(events, expected) + self.assertEqual(result.testsRun, 2) + self.assertEqual(result.skipped, [(test_do_skip, "testing")]) + self.assertTrue(result.wasSuccessful()) + + def test_skip_class(self): + class Foo(unittest2.TestCase): + def test_1(self): + record.append(1) + + # was originally a class decorator... + Foo = unittest2.skip("testing")(Foo) + record = [] + result = unittest2.TestResult() + test = Foo("test_1") + suite = unittest2.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + + def test_expected_failure(self): + class Foo(unittest2.TestCase): + def test_die(self): + self.fail("help me!") + test_die = unittest2.expectedFailure(test_die) + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addExpectedFailure', 'stopTest']) + self.assertEqual(result.expectedFailures[0][0], test) + self.assertTrue(result.wasSuccessful()) + + def test_unexpected_success(self): + class Foo(unittest2.TestCase): + def test_die(self): + pass + test_die = unittest2.expectedFailure(test_die) + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addUnexpectedSuccess', 'stopTest']) + self.assertFalse(result.failures) + self.assertEqual(result.unexpectedSuccesses, [test]) + self.assertTrue(result.wasSuccessful()) + + def test_skip_doesnt_run_setup(self): + class Foo(unittest2.TestCase): + wasSetUp = False + wasTornDown = False + def setUp(self): + Foo.wasSetUp = True + def tornDown(self): + Foo.wasTornDown = True + def test_1(self): + pass + test_1 = unittest2.skip('testing')(test_1) + + result = unittest2.TestResult() + test = Foo("test_1") + suite = unittest2.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertFalse(Foo.wasSetUp) + self.assertFalse(Foo.wasTornDown) + + def test_decorated_skip(self): + def decorator(func): + def inner(*a): + return func(*a) + return inner + + class Foo(unittest2.TestCase): + def test_1(self): + pass + test_1 = decorator(unittest2.skip('testing')(test_1)) + + result = unittest2.TestResult() + test = Foo("test_1") + suite = unittest2.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_suite.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_suite.py new file mode 100644 index 0000000..8afa199 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_suite.py @@ -0,0 +1,345 @@ +from unittest2.test.support import EqualityMixin, LoggingResult + +import sys +import unittest2 + +if sys.version_info[:2] == (2,3): + from sets import Set as set + from sets import ImmutableSet as frozenset + +class Test(object): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def test_3(self): pass + def runTest(self): pass + +def _mk_TestSuite(*names): + return unittest2.TestSuite([Test.Foo(n) for n in names]) + + +class Test_TestSuite(unittest2.TestCase, EqualityMixin): + + ### Set up attributes needed by inherited tests + ################################################################ + + # Used by EqualityMixin.test_eq + eq_pairs = [(unittest2.TestSuite(), unittest2.TestSuite()), + (unittest2.TestSuite(), unittest2.TestSuite([])), + (_mk_TestSuite('test_1'), _mk_TestSuite('test_1'))] + + # Used by EqualityMixin.test_ne + ne_pairs = [(unittest2.TestSuite(), _mk_TestSuite('test_1')), + (unittest2.TestSuite([]), _mk_TestSuite('test_1')), + (_mk_TestSuite('test_1', 'test_2'), _mk_TestSuite('test_1', 'test_3')), + (_mk_TestSuite('test_1'), _mk_TestSuite('test_2'))] + + ################################################################ + ### /Set up attributes needed by inherited tests + + ### Tests for TestSuite.__init__ + ################################################################ + + # "class TestSuite([tests])" + # + # The tests iterable should be optional + def test_init__tests_optional(self): + suite = unittest2.TestSuite() + + self.assertEqual(suite.countTestCases(), 0) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # TestSuite should deal with empty tests iterables by allowing the + # creation of an empty suite + def test_init__empty_tests(self): + suite = unittest2.TestSuite([]) + + self.assertEqual(suite.countTestCases(), 0) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # TestSuite should allow any iterable to provide tests + def test_init__tests_from_any_iterable(self): + def tests(): + yield unittest2.FunctionTestCase(lambda: None) + yield unittest2.FunctionTestCase(lambda: None) + + suite_1 = unittest2.TestSuite(tests()) + self.assertEqual(suite_1.countTestCases(), 2) + + suite_2 = unittest2.TestSuite(suite_1) + self.assertEqual(suite_2.countTestCases(), 2) + + suite_3 = unittest2.TestSuite(set(suite_1)) + self.assertEqual(suite_3.countTestCases(), 2) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # Does TestSuite() also allow other TestSuite() instances to be present + # in the tests iterable? + def test_init__TestSuite_instances_in_tests(self): + def tests(): + ftc = unittest2.FunctionTestCase(lambda: None) + yield unittest2.TestSuite([ftc]) + yield unittest2.FunctionTestCase(lambda: None) + + suite = unittest2.TestSuite(tests()) + self.assertEqual(suite.countTestCases(), 2) + + ################################################################ + ### /Tests for TestSuite.__init__ + + # Container types should support the iter protocol + def test_iter(self): + test1 = unittest2.FunctionTestCase(lambda: None) + test2 = unittest2.FunctionTestCase(lambda: None) + suite = unittest2.TestSuite((test1, test2)) + + self.assertEqual(list(suite), [test1, test2]) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Presumably an empty TestSuite returns 0? + def test_countTestCases_zero_simple(self): + suite = unittest2.TestSuite() + + self.assertEqual(suite.countTestCases(), 0) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Presumably an empty TestSuite (even if it contains other empty + # TestSuite instances) returns 0? + def test_countTestCases_zero_nested(self): + class Test1(unittest2.TestCase): + def test(self): + pass + + suite = unittest2.TestSuite([unittest2.TestSuite()]) + + self.assertEqual(suite.countTestCases(), 0) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + def test_countTestCases_simple(self): + test1 = unittest2.FunctionTestCase(lambda: None) + test2 = unittest2.FunctionTestCase(lambda: None) + suite = unittest2.TestSuite((test1, test2)) + + self.assertEqual(suite.countTestCases(), 2) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Make sure this holds for nested TestSuite instances, too + def test_countTestCases_nested(self): + class Test1(unittest2.TestCase): + def test1(self): pass + def test2(self): pass + + test2 = unittest2.FunctionTestCase(lambda: None) + test3 = unittest2.FunctionTestCase(lambda: None) + child = unittest2.TestSuite((Test1('test2'), test2)) + parent = unittest2.TestSuite((test3, child, Test1('test1'))) + + self.assertEqual(parent.countTestCases(), 4) + + # "Run the tests associated with this suite, collecting the result into + # the test result object passed as result." + # + # And if there are no tests? What then? + def test_run__empty_suite(self): + events = [] + result = LoggingResult(events) + + suite = unittest2.TestSuite() + + suite.run(result) + + self.assertEqual(events, []) + + # "Note that unlike TestCase.run(), TestSuite.run() requires the + # "result object to be passed in." + def test_run__requires_result(self): + suite = unittest2.TestSuite() + + try: + suite.run() + except TypeError: + pass + else: + self.fail("Failed to raise TypeError") + + # "Run the tests associated with this suite, collecting the result into + # the test result object passed as result." + def test_run(self): + events = [] + result = LoggingResult(events) + + class LoggingCase(unittest2.TestCase): + def run(self, result): + events.append('run %s' % self._testMethodName) + + def test1(self): pass + def test2(self): pass + + tests = [LoggingCase('test1'), LoggingCase('test2')] + + unittest2.TestSuite(tests).run(result) + + self.assertEqual(events, ['run test1', 'run test2']) + + # "Add a TestCase ... to the suite" + def test_addTest__TestCase(self): + class Foo(unittest2.TestCase): + def test(self): pass + + test = Foo('test') + suite = unittest2.TestSuite() + + suite.addTest(test) + + self.assertEqual(suite.countTestCases(), 1) + self.assertEqual(list(suite), [test]) + + # "Add a ... TestSuite to the suite" + def test_addTest__TestSuite(self): + class Foo(unittest2.TestCase): + def test(self): pass + + suite_2 = unittest2.TestSuite([Foo('test')]) + + suite = unittest2.TestSuite() + suite.addTest(suite_2) + + self.assertEqual(suite.countTestCases(), 1) + self.assertEqual(list(suite), [suite_2]) + + # "Add all the tests from an iterable of TestCase and TestSuite + # instances to this test suite." + # + # "This is equivalent to iterating over tests, calling addTest() for + # each element" + def test_addTests(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + test_1 = Foo('test_1') + test_2 = Foo('test_2') + inner_suite = unittest2.TestSuite([test_2]) + + def gen(): + yield test_1 + yield test_2 + yield inner_suite + + suite_1 = unittest2.TestSuite() + suite_1.addTests(gen()) + + self.assertEqual(list(suite_1), list(gen())) + + # "This is equivalent to iterating over tests, calling addTest() for + # each element" + suite_2 = unittest2.TestSuite() + for t in gen(): + suite_2.addTest(t) + + self.assertEqual(suite_1, suite_2) + + # "Add all the tests from an iterable of TestCase and TestSuite + # instances to this test suite." + # + # What happens if it doesn't get an iterable? + def test_addTest__noniterable(self): + suite = unittest2.TestSuite() + + try: + suite.addTests(5) + except TypeError: + pass + else: + self.fail("Failed to raise TypeError") + + def test_addTest__noncallable(self): + suite = unittest2.TestSuite() + self.assertRaises(TypeError, suite.addTest, 5) + + def test_addTest__casesuiteclass(self): + suite = unittest2.TestSuite() + self.assertRaises(TypeError, suite.addTest, Test_TestSuite) + self.assertRaises(TypeError, suite.addTest, unittest2.TestSuite) + + def test_addTests__string(self): + suite = unittest2.TestSuite() + self.assertRaises(TypeError, suite.addTests, "foo") + + def test_function_in_suite(self): + def f(_): + pass + suite = unittest2.TestSuite() + suite.addTest(f) + + # when the bug is fixed this line will not crash + suite.run(unittest2.TestResult()) + + + def test_basetestsuite(self): + class Test(unittest2.TestCase): + wasSetUp = False + wasTornDown = False + def setUpClass(cls): + cls.wasSetUp = True + setUpClass = classmethod(setUpClass) + def tearDownClass(cls): + cls.wasTornDown = True + tearDownClass = classmethod(tearDownClass) + def testPass(self): + pass + def testFail(self): + fail + class Module(object): + wasSetUp = False + wasTornDown = False + def setUpModule(): + Module.wasSetUp = True + setUpModule = classmethod(setUpModule) + def tearDownModule(): + Module.wasTornDown = True + setUpModule = classmethod(tearDownModule) + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + self.addCleanup(sys.modules.pop, 'Module') + + suite = unittest2.BaseTestSuite() + suite.addTests([Test('testPass'), Test('testFail')]) + self.assertEqual(suite.countTestCases(), 2) + + result = unittest2.TestResult() + suite.run(result) + self.assertFalse(Module.wasSetUp) + self.assertFalse(Module.wasTornDown) + self.assertFalse(Test.wasSetUp) + self.assertFalse(Test.wasTornDown) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 2) + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/test/test_unittest2_with.py b/pyinstaller/PyInstaller/lib/unittest2/test/test_unittest2_with.py new file mode 100644 index 0000000..e46d8f8 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/test/test_unittest2_with.py @@ -0,0 +1,130 @@ + +import sys + +import unittest2 +from unittest2.test.support import OldTestResult, catch_warnings + +import warnings +# needed to enable the deprecation warnings +warnings.simplefilter('default') + +class TestWith(unittest2.TestCase): + """Tests that use the with statement live in this + module so that all other tests can be run with Python 2.4. + """ + + def runContext(self, ctxobj, func, *funcargs, **funckwargs): + bound_to = ctxobj.__enter__() + try: + func(bound_to, *funcargs, **funckwargs) + except Exception, e: + if not ctxobj.__exit__(*sys.exc_info()): + raise + else: + ctxobj.__exit__(None, None, None) + + def testAssertRaisesExcValue(self): + class ExceptionMock(Exception): + pass + + def Stub(foo): + raise ExceptionMock(foo) + v = "particular value" + + ctx = self.assertRaises(ExceptionMock) + self.runContext(ctx, lambda cm: Stub(v)) + e = ctx.exception + self.assertIsInstance(e, ExceptionMock) + self.assertEqual(e.args[0], v) + + def test_assert_dict_unicode_error(self): + def run(cm): + # This causes a UnicodeWarning due to its craziness + one = ''.join([chr(i) for i in range(255)]) + # this used to cause a UnicodeDecodeError constructing the failure msg + ar_cm = self.assertRaises(self.failureException) + innerrun =lambda x: self.assertDictContainsSubset({'foo': one}, {'foo': u'\uFFFD'}) + self.runContext(ar_cm, innerrun) + cm = catch_warnings(record=True) + self.runContext(cm, run) + + def test_formatMessage_unicode_error(self): + def run(cm): + # This causes a UnicodeWarning due to its craziness + one = ''.join([chr(i) for i in range(255)]) + # this used to cause a UnicodeDecodeError constructing msg + self._formatMessage(one, u'\uFFFD') + cm = catch_warnings(record=True) + self.runContext(cm, run) + + def assertOldResultWarning(self, test, failures): + def run(log): + result = OldTestResult() + test.run(result) + self.assertEqual(len(result.failures), failures) + warning, = log + self.assertIs(warning.category, DeprecationWarning) + cm = catch_warnings(record=True) + self.runContext(cm, run) + + def test_old_testresult(self): + class Test(unittest2.TestCase): + def testSkip(self): + self.skipTest('foobar') + def testExpectedFail(self): + raise TypeError + testExpectedFail = unittest2.expectedFailure(testExpectedFail) + def testUnexpectedSuccess(self): + pass + testUnexpectedSuccess = unittest2.expectedFailure(testUnexpectedSuccess) + + for test_name, should_pass in (('testSkip', True), + ('testExpectedFail', True), + ('testUnexpectedSuccess', False)): + test = Test(test_name) + self.assertOldResultWarning(test, int(not should_pass)) + + def test_old_testresult_setup(self): + class Test(unittest2.TestCase): + def setUp(self): + self.skipTest('no reason') + def testFoo(self): + pass + self.assertOldResultWarning(Test('testFoo'), 0) + + def test_old_testresult_class(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + Test = unittest2.skip('no reason')(Test) + self.assertOldResultWarning(Test('testFoo'), 0) + + def testPendingDeprecationMethodNames(self): + """Test fail* methods pending deprecation, they will warn in 3.2. + + Do not use these methods. They will go away in 3.3. + """ + def run(cm): + self.failIfEqual(3, 5) + self.failUnlessEqual(3, 3) + self.failUnlessAlmostEqual(2.0, 2.0) + self.failIfAlmostEqual(3.0, 5.0) + self.failUnless(True) + self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam') + self.failIf(False) + cm = catch_warnings(record=True) + self.runContext(cm,run) + + def testAssertDictContainsSubset_UnicodeVsStrValues(self): + def run(cm): + one = ''.join([chr(i) for i in range(255)]) + two = u'\uFFFD' + # this used to cause a UnicodeDecodeError when the values were compared under python 2.3, under + # python 2.6 it causes a UnicodeWarning so wrapping in catch_warnings context manager + self.assertRaises(self.failureException, self.assertDictContainsSubset, {'foo': one}, {'foo': two}) + cm = catch_warnings(record=True) + self.runContext(cm, run) + + +if __name__ == '__main__': + unittest2.main() diff --git a/pyinstaller/PyInstaller/lib/unittest2/util.py b/pyinstaller/PyInstaller/lib/unittest2/util.py new file mode 100644 index 0000000..c45d008 --- /dev/null +++ b/pyinstaller/PyInstaller/lib/unittest2/util.py @@ -0,0 +1,99 @@ +"""Various utility functions.""" + +__unittest = True + + +_MAX_LENGTH = 80 +def safe_repr(obj, short=False): + try: + result = repr(obj) + except Exception: + result = object.__repr__(obj) + if not short or len(result) < _MAX_LENGTH: + return result + return result[:_MAX_LENGTH] + ' [truncated]...' + +def safe_str(obj): + try: + return str(obj) + except Exception: + return object.__str__(obj) + +def strclass(cls): + return "%s.%s" % (cls.__module__, cls.__name__) + +def sorted_list_difference(expected, actual): + """Finds elements in only one or the other of two, sorted input lists. + + Returns a two-element tuple of lists. The first list contains those + elements in the "expected" list but not in the "actual" list, and the + second contains those elements in the "actual" list but not in the + "expected" list. Duplicate elements in either input list are ignored. + """ + i = j = 0 + missing = [] + unexpected = [] + while True: + try: + e = expected[i] + a = actual[j] + if e < a: + missing.append(e) + i += 1 + while expected[i] == e: + i += 1 + elif e > a: + unexpected.append(a) + j += 1 + while actual[j] == a: + j += 1 + else: + i += 1 + try: + while expected[i] == e: + i += 1 + finally: + j += 1 + while actual[j] == a: + j += 1 + except IndexError: + missing.extend(expected[i:]) + unexpected.extend(actual[j:]) + break + return missing, unexpected + +def unorderable_list_difference(expected, actual, ignore_duplicate=False): + """Same behavior as sorted_list_difference but + for lists of unorderable items (like dicts). + + As it does a linear search per item (remove) it + has O(n*n) performance. + """ + missing = [] + unexpected = [] + while expected: + item = expected.pop() + try: + actual.remove(item) + except ValueError: + missing.append(item) + if ignore_duplicate: + for lst in expected, actual: + try: + while True: + lst.remove(item) + except ValueError: + pass + if ignore_duplicate: + while actual: + item = actual.pop() + unexpected.append(item) + try: + while True: + actual.remove(item) + except ValueError: + pass + return missing, unexpected + + # anything left in actual is unexpected + return missing, actual diff --git a/pyinstaller/PyInstaller/loader/.svn/entries b/pyinstaller/PyInstaller/loader/.svn/entries new file mode 100644 index 0000000..1870881 --- /dev/null +++ b/pyinstaller/PyInstaller/loader/.svn/entries @@ -0,0 +1,56 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/loader +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +iu.py +file + + + +add + +__init__.py +file + + + +add + +archive.py +file + + + +add + +carchive.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/loader/__init__.py b/pyinstaller/PyInstaller/loader/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/pyinstaller/PyInstaller/loader/__init__.py @@ -0,0 +1 @@ +# diff --git a/pyinstaller/PyInstaller/loader/archive.py b/pyinstaller/PyInstaller/loader/archive.py new file mode 100644 index 0000000..6b62d5e --- /dev/null +++ b/pyinstaller/PyInstaller/loader/archive.py @@ -0,0 +1,495 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 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. +# +# 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 + +# subclasses may not need marshal or struct, but since they're +# builtin, importing is safe. +# +# While an Archive is really an abstraction for any "filesystem +# within a file", it is tuned for use with imputil.FuncImporter. +# This assumes it contains python code objects, indexed by the +# the internal name (ie, no '.py'). +# See carchive.py for a more general archive (contains anything) +# that can be understood by a C program. + +_verbose = 0 +_listdir = None +_environ = None + +### **NOTE** This module is used during bootstrap. +### Import *ONLY* builtin modules. + +import marshal +import struct +import imp +import sys + + +def debug(msg): + if 0: + sys.stderr.write(msg + "\n") + sys.stderr.flush() + + +_c_suffixes = filter(lambda x: x[2] == imp.C_EXTENSION, imp.get_suffixes()) + +for nm in ('nt', 'posix'): + if nm in sys.builtin_module_names: + mod = __import__(nm) + _listdir = mod.listdir + _environ = mod.environ + break + +versuffix = '%d%d' % sys.version_info[:2] # :todo: is this still used? + +if "-vi" in sys.argv[1:]: + _verbose = 1 + + +class ArchiveReadError(RuntimeError): + pass + + +class Archive: + """ + A base class for a repository of python code objects. + The extract method is used by imputil.ArchiveImporter + to get code objects by name (fully qualified name), so + an enduser "import a.b" would become + extract('a.__init__') + extract('a.b') + """ + MAGIC = 'PYL\0' + HDRLEN = 12 # default is MAGIC followed by python's magic, int pos of toc + TOCPOS = 8 + TRLLEN = 0 # default - no trailer + TOCTMPLT = {} + os = None + _bincache = None + + def __init__(self, path=None, start=0): + """ + Initialize an Archive. If path is omitted, it will be an empty Archive. + """ + self.toc = None + self.path = path + self.start = start + import imp + self.pymagic = imp.get_magic() + if path is not None: + self.lib = open(self.path, 'rb') + self.checkmagic() + self.loadtoc() + + ####### Sub-methods of __init__ - override as needed ############# + def checkmagic(self): + """ + Overridable. + Check to see if the file object self.lib actually has a file + we understand. + """ + self.lib.seek(self.start) # default - magic is at start of file + + if self.lib.read(len(self.MAGIC)) != self.MAGIC: + raise ArchiveReadError("%s is not a valid %s archive file" + % (self.path, self.__class__.__name__)) + + if self.lib.read(len(self.pymagic)) != self.pymagic: + raise ArchiveReadError("%s has version mismatch to dll" % + (self.path)) + + self.lib.read(4) + + def loadtoc(self): + """ + Overridable. + Default: After magic comes an int (4 byte native) giving the + position of the TOC within self.lib. + Default: The TOC is a marshal-able string. + """ + self.lib.seek(self.start + self.TOCPOS) + (offset,) = struct.unpack('!i', self.lib.read(4)) + self.lib.seek(self.start + offset) + self.toc = marshal.load(self.lib) + + ######## This is what is called by FuncImporter ####### + ## Since an Archive is flat, we ignore parent and modname. + #XXX obsolete - imputil only code + ## def get_code(self, parent, modname, fqname): + ## pass + + ####### Core method - Override as needed ######### + def extract(self, name): + """ + Get the object corresponding to name, or None. + For use with imputil ArchiveImporter, object is a python code object. + 'name' is the name as specified in an 'import name'. + 'import a.b' will become: + extract('a') (return None because 'a' is not a code object) + extract('a.__init__') (return a code object) + extract('a.b') (return a code object) + Default implementation: + self.toc is a dict + self.toc[name] is pos + self.lib has the code object marshal-ed at pos + """ + ispkg, pos = self.toc.get(name, (0, None)) + if pos is None: + return None + self.lib.seek(self.start + pos) + return ispkg, marshal.load(self.lib) + + ######################################################################## + # Informational methods + + def contents(self): + """ + Return a list of the contents + Default implementation assumes self.toc is a dict like object. + Not required by ArchiveImporter. + """ + return self.toc.keys() + + ######################################################################## + # Building + + ####### Top level method - shouldn't need overriding ####### + def build(self, path, lTOC): + """ + Create an archive file of name 'path'. + lTOC is a 'logical TOC' - a list of (name, path, ...) + where name is the internal name, eg 'a' + and path is a file to get the object from, eg './a.pyc'. + """ + self.path = path + self.lib = open(path, 'wb') + #reserve space for the header + if self.HDRLEN: + self.lib.write('\0' * self.HDRLEN) + + #create an empty toc + + if type(self.TOCTMPLT) == type({}): + self.toc = {} + else: # assume callable + self.toc = self.TOCTMPLT() + + for tocentry in lTOC: + self.add(tocentry) # the guts of the archive + + tocpos = self.lib.tell() + self.save_toc(tocpos) + if self.TRLLEN: + self.save_trailer(tocpos) + if self.HDRLEN: + self.update_headers(tocpos) + self.lib.close() + + ####### manages keeping the internal TOC and the guts in sync ####### + def add(self, entry): + """ + Override this to influence the mechanics of the Archive. + Assumes entry is a seq beginning with (nm, pth, ...) where + nm is the key by which we'll be asked for the object. + pth is the name of where we find the object. Overrides of + get_obj_from can make use of further elements in entry. + """ + if self.os is None: + import os + self.os = os + nm = entry[0] + pth = entry[1] + pynm, ext = self.os.path.splitext(self.os.path.basename(pth)) + ispkg = pynm == '__init__' + assert ext in ('.pyc', '.pyo') + self.toc[nm] = (ispkg, self.lib.tell()) + f = open(entry[1], 'rb') + f.seek(8) # skip magic and timestamp + self.lib.write(f.read()) + + def save_toc(self, tocpos): + """ + Default - toc is a dict + Gets marshaled to self.lib + """ + marshal.dump(self.toc, self.lib) + + def save_trailer(self, tocpos): + """ + Default - not used + """ + pass + + def update_headers(self, tocpos): + """ + Default - MAGIC + Python's magic + tocpos + """ + self.lib.seek(self.start) + self.lib.write(self.MAGIC) + self.lib.write(self.pymagic) + self.lib.write(struct.pack('!i', tocpos)) + + +class DummyZlib: + error = RuntimeError + + def decompress(self, data): + raise RuntimeError, "zlib required but cannot be imported" + + def compress(self, data, lvl): + raise RuntimeError, "zlib required but cannot be imported" + + +# Used by PYZOwner +import iu + + +class ZlibArchive(Archive): + """ + ZlibArchive - an archive with compressed entries. Archive is read + from the executable created by PyInstaller. + """ + MAGIC = 'PYZ\0' + TOCPOS = 8 + HDRLEN = Archive.HDRLEN + 5 + TRLLEN = 0 + TOCTMPLT = {} + LEVEL = 9 + + def __init__(self, path=None, offset=None, level=9, crypt=None): + if path is None: + offset = 0 + elif offset is None: + for i in range(len(path) - 1, - 1, - 1): + if path[i] == '?': + try: + offset = int(path[i + 1:]) + except ValueError: + # Just ignore any spurious "?" in the path + # (like in Windows UNC \\?\). + continue + path = path[:i] + break + else: + offset = 0 + + self.LEVEL = level + if crypt is not None: + self.crypted = 1 + self.key = (crypt + "*" * 32)[:32] + else: + self.crypted = 0 + self.key = None + + Archive.__init__(self, path, offset) + + # dynamic import so not imported if not needed + global zlib + if self.LEVEL: + try: + import zlib + except ImportError: + zlib = DummyZlib() + else: + print "WARNING: compression level=0!!!" + zlib = DummyZlib() + + global AES + if self.crypted: + import AES + + def _iv(self, nm): + IV = nm * ((AES.block_size + len(nm) - 1) // len(nm)) + return IV[:AES.block_size] + + def extract(self, name): + (ispkg, pos, lngth) = self.toc.get(name, (0, None, 0)) + if pos is None: + return None + self.lib.seek(self.start + pos) + obj = self.lib.read(lngth) + if self.crypted: + if self.key is None: + raise ImportError('decryption key not found') + obj = AES.new(self.key, AES.MODE_CFB, self._iv(name)).decrypt(obj) + try: + obj = zlib.decompress(obj) + except zlib.error: + if not self.crypted: + raise + raise ImportError('invalid decryption key') + try: + co = marshal.loads(obj) + except EOFError: + raise ImportError("PYZ entry '%s' failed to unmarshal" % name) + return ispkg, co + + def add(self, entry): + if self.os is None: + import os + self.os = os + nm = entry[0] + pth = entry[1] + base, ext = self.os.path.splitext(self.os.path.basename(pth)) + ispkg = base == '__init__' + try: + txt = open(pth[:-1], 'rU').read() + '\n' + except (IOError, OSError): + try: + f = open(pth, 'rb') + f.seek(8) # skip magic and timestamp + bytecode = f.read() + marshal.loads(bytecode).co_filename # to make sure it's valid + obj = zlib.compress(bytecode, self.LEVEL) + except (IOError, ValueError, EOFError, AttributeError): + raise ValueError("bad bytecode in %s and no source" % pth) + else: + txt = txt.replace('\r\n', '\n') + try: + import os + co = compile(txt, self.os.path.join(self.path, nm), 'exec') + except SyntaxError, e: + print "Syntax error in", pth[:-1] + print e.args + raise + obj = zlib.compress(marshal.dumps(co), self.LEVEL) + if self.crypted: + obj = AES.new(self.key, AES.MODE_CFB, self._iv(nm)).encrypt(obj) + self.toc[nm] = (ispkg, self.lib.tell(), len(obj)) + self.lib.write(obj) + + def update_headers(self, tocpos): + """ + add level + """ + Archive.update_headers(self, tocpos) + self.lib.write(struct.pack('!iB', self.LEVEL, self.crypted)) + + def checkmagic(self): + Archive.checkmagic(self) + self.LEVEL, self.crypted = struct.unpack('!iB', self.lib.read(5)) + + +class Keyfile: + def __init__(self, fn=None): + if fn is None: + fn = sys.argv[0] + if fn[-4] == '.': + fn = fn[:-4] + fn += ".key" + + execfile(fn, {"__builtins__": None}, self.__dict__) + if not hasattr(self, "key"): + self.key = None + + +class PYZOwner(iu.Owner): + """ + Load bytecode of Python modules from the executable created by PyInstaller. + + Python bytecode is zipped and appended to the executable. + + NOTE: PYZ format cannot be replaced by zipimport module. + + The problem is that we have no control over zipimport; for instance, + it doesn't work if the zip file is embedded into a PKG appended + to an executable, like we create in one-file. + """ + def __init__(self, path): + try: + # Unzip zip archive bundled with the executable. + self.pyz = ZlibArchive(path) + self.pyz.checkmagic() + except (IOError, ArchiveReadError), e: + raise iu.OwnerError(e) + if self.pyz.crypted: + if not hasattr(sys, "keyfile"): + sys.keyfile = Keyfile() + self.pyz = ZlibArchive(path, crypt=sys.keyfile.key) + iu.Owner.__init__(self, path) + + def getmod(self, nm, newmod=imp.new_module): + rslt = self.pyz.extract(nm) + if rslt is None: + return None + ispkg, bytecode = rslt + mod = newmod(nm) + + # Replace bytecode.co_filename by something more meaningful: + # e.g. /absolute/path/frozen_executable?12345/os/path.pyc + # Paths from developer machine are masked. + try: + # Python packages points to files __init__.pyc. + if ispkg: + mod.__file__ = iu._os_path_join(iu._os_path_join(self.path, + nm.replace('.', iu._os_sep)), '__init__.pyc') + else: + mod.__file__ = iu._os_path_join(self.path, + nm.replace('.', iu._os_sep) + '.pyc') + except AttributeError: + raise ImportError("PYZ entry '%s' (%s) is not a valid code object" + % (nm, repr(bytecode))) + + # Python has modules and packages. A Python package is container + # for several modules or packages. + if ispkg: + # Since PYTHONHOME is set in bootloader, 'sys.prefix' points to the + # correct path where PyInstaller should find bundled dynamic + # libraries. In one-file mode it points to the tmp directory where + # bundled files are extracted at execution time. + localpath = sys.prefix + + # A python packages has to have __path__ attribute. + mod.__path__ = [iu._os_path_dirname(mod.__file__), self.path, localpath, + ] + + debug("PYZOwner setting %s's __path__: %s" % (nm, mod.__path__)) + + importer = iu.PathImportDirector(mod.__path__, + {self.path: PkgInPYZImporter(nm, self), + localpath: ExtInPkgImporter(localpath, nm)}, + [iu.DirOwner]) + mod.__importsub__ = importer.getmod + + mod.__co__ = bytecode + return mod + + +class PkgInPYZImporter: + def __init__(self, name, owner): + self.name = name + self.owner = owner + + def getmod(self, nm): + debug("PkgInPYZImporter.getmod %s -> %s" % (nm, self.name + '.' + nm)) + return self.owner.getmod(self.name + '.' + nm) + + +class ExtInPkgImporter(iu.DirOwner): + def __init__(self, path, prefix): + iu.DirOwner.__init__(self, path) + self.prefix = prefix + + def getmod(self, nm): + return iu.DirOwner.getmod(self, self.prefix + '.' + nm) diff --git a/pyinstaller/PyInstaller/loader/carchive.py b/pyinstaller/PyInstaller/loader/carchive.py new file mode 100644 index 0000000..ae40b56 --- /dev/null +++ b/pyinstaller/PyInstaller/loader/carchive.py @@ -0,0 +1,273 @@ +# Subclass of Archive that can be understood by a C program (see launch.c). +# 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 archive +import struct +import sys +try: + import zlib +except ImportError: + zlib = archive.DummyZlib() + +class CTOC: + """A class encapsulating the table of contents of a CArchive. + + When written to disk, it is easily read from C.""" + ENTRYSTRUCT = '!iiiibc' #(structlen, dpos, dlen, ulen, flag, typcd) followed by name + def __init__(self): + self.data = [] + + def frombinary(self, s): + """Decode the binary string into an in memory list. + + S is a binary string.""" + entrylen = struct.calcsize(self.ENTRYSTRUCT) + p = 0 + while p -1: + typcd = 'M' + self.toc.add(where, dlen, ulen, flag, typcd, nm) + self.lib.write(s) + + def save_toc(self, tocpos): + """Save the table of contents to disk.""" + self.tocpos = tocpos + tocstr = self.toc.tobinary() + self.toclen = len(tocstr) + self.lib.write(tocstr) + + def save_trailer(self, tocpos): + """Save the trailer to disk. + + CArchives can be opened from the end - the trailer points + back to the start. """ + totallen = tocpos + self.toclen + self.TRLLEN + pyvers = sys.version_info[0]*10 + sys.version_info[1] + trl = struct.pack(self.TRLSTRUCT, self.MAGIC, totallen, + tocpos, self.toclen, pyvers) + self.lib.write(trl) + + def openEmbedded(self, name): + """Open a CArchive of name NAME embedded within this CArchive.""" + ndx = self.toc.find(name) + if ndx == -1: + raise KeyError, "Member '%s' not found in %s" % (name, self.path) + (dpos, dlen, ulen, flag, typcd, nm) = self.toc.get(ndx) + if flag: + raise ValueError, "Cannot open compressed archive %s in place" + return CArchive(self.path, self.pkgstart+dpos, dlen) diff --git a/pyinstaller/PyInstaller/loader/iu.py b/pyinstaller/PyInstaller/loader/iu.py new file mode 100644 index 0000000..87a4662 --- /dev/null +++ b/pyinstaller/PyInstaller/loader/iu.py @@ -0,0 +1,666 @@ +# +# Copyright (C) 2005-2011, Giovanni Bajo +# +# Based on previous work under copyright (c) 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. +# +# 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 +# + + +### **NOTE** This module is used during bootstrap. +### Import *ONLY* builtin modules. +### List of built-in modules: sys.builtin_module_names +import sys +import imp +import marshal +import zipimport + + +def debug(msg): + if 0: + sys.stderr.write(msg + "\n") + sys.stderr.flush() + + +#=======================Owners==========================# +# An Owner does imports from a particular piece of turf +# That is, there's an Owner for each thing on sys.path +# There are owners for directories and .pyz files. +# There could be owners for zip files, or even URLs. +# A shadowpath (a dictionary mapping the names in +# sys.path to their owners) is used so that sys.path +# (or a package's __path__) is still a bunch of strings, + + +class OwnerError(IOError): + def __str__(self): + return "" % self.message + + +class Owner: + """ + Base class for loading Python bytecode from different places. + """ + def __init__(self, path): + self.path = path + + def __str__(self): + return self.path + + def getmod(self, nm): + return None + + +class DirOwner(Owner): + """ + Load bytecode of Python modules from file system. + """ + def __init__(self, path): + if path == '': + path = _os_getcwd() + if not pathisdir(path): + raise OwnerError("%s is not a directory" % path) + Owner.__init__(self, path) + + def getmod(self, nm, getsuffixes=imp.get_suffixes, + loadco=marshal.loads, newmod=imp.new_module): + pth = _os_path_join(self.path, nm) + possibles = [(pth, 0, None)] + if pathisdir(pth) and caseOk(pth): + possibles.insert(0, (_os_path_join(pth, '__init__'), 1, pth)) + py = pyc = None + for pth, ispkg, pkgpth in possibles: + for ext, mode, typ in getsuffixes(): + attempt = pth + ext + try: + st = _os_stat(attempt) + except OSError, e: + assert e.errno == 2 # [Errno 2] No such file or directory + else: + # Check case + if not caseOk(attempt): + continue + if typ == imp.C_EXTENSION: + fp = open(attempt, 'rb') + mod = imp.load_module(nm, fp, attempt, (ext, mode, typ)) + if hasattr(mod, "__setattr__"): + mod.__file__ = attempt + else: + # Some modules (eg: Python for .NET) have no __setattr__ + # and dict entry have to be set + mod.__dict__["__file__"] = attempt + return mod + elif typ == imp.PY_SOURCE: + py = (attempt, st) + else: + pyc = (attempt, st) + if py or pyc: + break + if py is None and pyc is None: + return None + while 1: + if pyc is None or py and pyc[1][8] < py[1][8]: + try: + bytecode = compile(open(py[0], 'rU').read() + '\n', py[0], 'exec') + break + except SyntaxError, e: + print "Invalid syntax in %s" % py[0] + print e.args + raise + elif pyc: + stuff = open(pyc[0], 'rb').read() + try: + bytecode = loadco(stuff[8:]) + break + except (ValueError, EOFError): + pyc = None + else: + return None + mod = newmod(nm) + mod.__file__ = bytecode.co_filename + if ispkg: + mod.__path__ = [pkgpth] + subimporter = PathImportDirector(mod.__path__) + mod.__importsub__ = subimporter.getmod + mod.__co__ = bytecode + return mod + + +class ZipOwner(Owner): + """ + Load bytecode of Python modules from .egg files. + """ + def __init__(self, path): + try: + self.__zip = zipimport.zipimporter(path) + except zipimport.ZipImportError, e: + raise OwnerError('%s: %s' % (str(e), path)) + Owner.__init__(self, path) + + def getmod(self, nm, newmod=imp.new_module): + # We cannot simply use zipimport.load_module here + # because it both loads (= create module object) + # and imports (= execute bytecode). Instead, our + # getmod() functions are supposed to only load the modules. + # Note that imp.load_module() does the right thing, instead. + debug('zipimport try: %s within %s' % (nm, self.__zip)) + try: + bytecode = self.__zip.get_code(nm) + mod = newmod(nm) + mod.__file__ = bytecode.co_filename + if self.__zip.is_package(nm): + mod.__path__ = [_os_path_join(self.path, nm)] + subimporter = PathImportDirector(mod.__path__) + mod.__importsub__ = subimporter.getmod + if self.path.endswith(".egg"): + # Fixup some additional special attribute so that + # pkg_resources works correctly. + # TODO: couldn't we fix these attributes always, + # for all zip files? + mod.__file__ = _os_path_join( + _os_path_join(self.path, nm), "__init__.py") + mod.__loader__ = self.__zip + mod.__co__ = bytecode + return mod + except zipimport.ZipImportError: + debug('zipimport not found %s' % nm) + return None + + +# Define order where to look for Python modules first. +# 1. PYZOwner: look in executable created by PyInstaller. +# (_pyi_bootstrap.py will insert it (archive.PYZOwner) in front later.) +# 2. ZipOwner: zip files (.egg files) +# 3. DirOwner: file system +# 4. Owner: module not found +_globalownertypes = [ + ZipOwner, + DirOwner, + Owner, +] + + +#===================Import Directors====================================# +# ImportDirectors live on the metapath +# There's one for builtins, one for frozen modules, and one for sys.path +# Mac would have them for PY_RESOURCE modules etc. +# A generalization of Owner - their concept of "turf" is broader + + +class ImportDirector(Owner): + pass + + +class BuiltinImportDirector(ImportDirector): + def __init__(self): + self.path = 'Builtins' + + def getmod(self, nm, isbuiltin=imp.is_builtin): + # Return initialized built-in module object or None + # if there is no built-in module with that name. + return imp.init_builtin(nm) + + +class PathImportDirector(ImportDirector): + def __init__(self, pathlist=None, importers=None, ownertypes=None): + self.path = pathlist + if ownertypes == None: + self.ownertypes = _globalownertypes + else: + self.ownertypes = ownertypes + if importers: + self.shadowpath = importers + else: + self.shadowpath = {} + self.building = {} + + def __str__(self): + return str(self.path or sys.path) + + def getmod(self, nm): + mod = None + for thing in (self.path or sys.path): + if isinstance(thing, basestring): + owner = self.shadowpath.get(thing, -1) + if owner == -1: + owner = self.shadowpath[thing] = self.__makeOwner(thing) + if owner: + mod = owner.getmod(nm) + else: + mod = thing.getmod(nm) + if mod: + break + return mod + + def __makeOwner(self, path): + if path in self.building: + return None + self.building[path] = 1 + owner = None + for klass in self.ownertypes: + try: + # this may cause an import, which may cause recursion + # hence the protection + owner = klass(path) + except OwnerError: + pass + else: + break + del self.building[path] + return owner + + +def getDescr(fnm): + ext = getpathext(fnm) + for (suffix, mode, typ) in imp.get_suffixes(): + if suffix == ext: + return (suffix, mode, typ) + +#=================ImportManager============================# +# The one-and-only ImportManager +# ie, the builtin import + +UNTRIED = -1 + + +class ImportManagerException(Exception): + def __init__(self, args): + self.args = args + + def __repr__(self): + return "<%s: %s>" % (self.__name__, self.args) + + +class ImportManager: + # really the equivalent of builtin import + def __init__(self): + self.metapath = [ + BuiltinImportDirector(), + PathImportDirector() + ] + self.threaded = 0 + self.rlock = None + self.locker = None + self.setThreaded() + + def setThreaded(self): + thread = sys.modules.get('thread', None) + if thread and not self.threaded: + #debug("iu setting threaded") + self.threaded = 1 + self.rlock = thread.allocate_lock() + self._get_ident = thread.get_ident + + def install(self): + import __builtin__ + __builtin__.__import__ = self.importHook + __builtin__.reload = self.reloadHook + + def importHook(self, name, globals=None, locals=None, fromlist=None, level=-1): + __globals_name = None + if globals: + __globals_name = globals.get('__name__') + # first see if we could be importing a relative name + debug("importHook(%s, %s, locals, %s, %s)" % (name, __globals_name, fromlist, level)) + _sys_modules_get = sys.modules.get + _self_doimport = self.doimport + threaded = self.threaded + + # break the name being imported up so we get: + # a.b.c -> [a, b, c] + nmparts = namesplit(name) + + if not globals: + contexts = [None] + if level > 0: + raise RuntimeError("Relative import requires 'globals'") + elif level == 0: + # absolute import, do not try relative + contexts = [None] + else: # level != 0 + importernm = globals.get('__name__', '') + ispkg = hasattr(_sys_modules_get(importernm), '__path__') + debug('importernm %s' % importernm) + if level < 0: + # behaviour up to Python 2.4 (and default in Python 2.5) + # add the package to searched contexts + contexts = [None] + else: + # relative import, do not try absolute + if not importernm: + raise RuntimeError("Relative import requires package") + # level=1 => current package + # level=2 => previous package => drop 1 level + if level > 1: + importernm = importernm.split('.')[:-level + 1] + importernm = '.'.join(importernm) + contexts = [None] + if importernm: + if ispkg: + # If you use the "from __init__ import" syntax, the package + # name will have a __init__ in it. We want to strip it. + if importernm[-len(".__init__"):] == ".__init__": + importernm = importernm[:-len(".__init__")] + contexts.insert(0, importernm) + else: + pkgnm = packagename(importernm) + if pkgnm: + contexts.insert(0, pkgnm) + + # so contexts is [pkgnm, None], [pkgnm] or just [None] + for context in contexts: + ctx = context + i = 0 + for i, nm in enumerate(nmparts): + debug(" importHook trying %s in %s" % (nm, ctx)) + if ctx: + fqname = ctx + '.' + nm + else: + fqname = nm + if threaded: + self._acquire() + try: + mod = _sys_modules_get(fqname, UNTRIED) + if mod is UNTRIED: + debug('trying %s %s %s' % (nm, ctx, fqname)) + mod = _self_doimport(nm, ctx, fqname) + finally: + if threaded: + self._release() + if mod: + ctx = fqname + else: + break + else: + # no break, point i beyond end + i = i + 1 + if i: + break + + if i < len(nmparts): + if ctx and hasattr(sys.modules[ctx], nmparts[i]): + debug("importHook done with %s %s %s (case 1)" % (name, __globals_name, fromlist)) + return sys.modules[nmparts[0]] + # Some executables may fail if 'fqname' is not in sys.modules. + try: + del sys.modules[fqname] + except KeyError: + pass + raise ImportError("No module named %s" % fqname) + if not fromlist: + debug("importHook done with %s %s %s (case 2)" % (name, __globals_name, fromlist)) + if context: + return sys.modules[context + '.' + nmparts[0]] + return sys.modules[nmparts[0]] + bottommod = sys.modules[ctx] + if hasattr(bottommod, '__path__'): + fromlist = list(fromlist) + i = 0 + while i < len(fromlist): + nm = fromlist[i] + if nm == '*': + fromlist[i:i + 1] = list(getattr(bottommod, '__all__', [])) + if i >= len(fromlist): + break + nm = fromlist[i] + i = i + 1 + if not hasattr(bottommod, nm): + if threaded: + self._acquire() + try: + mod = self.doimport(nm, ctx, ctx + '.' + nm) + finally: + if threaded: + self._release() + debug("importHook done with %s %s %s (case 3)" % (name, __globals_name, fromlist)) + return bottommod + + def doimport(self, nm, parentnm, fqname, reload=0): + # Not that nm is NEVER a dotted name at this point + debug("doimport(%s, %s, %s)" % (nm, parentnm, fqname)) + if parentnm: + parent = sys.modules[parentnm] + if hasattr(parent, '__path__'): + importfunc = getattr(parent, '__importsub__', None) + if not importfunc: + subimporter = PathImportDirector(parent.__path__) + importfunc = parent.__importsub__ = subimporter.getmod + debug("using parent's importfunc: %s" % importfunc) + mod = importfunc(nm) + if mod and not reload: + setattr(parent, nm, mod) + else: + debug("..parent not a package") + return None + else: + parent = None + # now we're dealing with an absolute import + for director in self.metapath: + mod = director.getmod(nm) + if mod: + break + if mod: + if hasattr(mod, "__setattr__"): + mod.__name__ = fqname + else: + # Some modules (eg: Python for .NET) have no __setattr__ + # and dict entry have to be set + mod.__dict__["__name__"] = fqname + if reload: + sys.modules[fqname].__dict__.update(mod.__dict__) + else: + sys.modules[fqname] = mod + if hasattr(mod, '__co__'): + co = mod.__co__ + del mod.__co__ + try: + if reload: + exec co in sys.modules[fqname].__dict__ + else: + exec co in mod.__dict__ + except: + # In Python 2.4 and above, sys.modules is left clean + # after a broken import. We need to do the same to + # achieve perfect compatibility (see ticket #32). + if sys.version_info >= (2, 4): + # FIXME: how can we recover from a broken reload()? + # Should we save the mod dict and restore it in case + # of failure? + if not reload: + # Some modules (eg: dbhash.py) cleanup + # sys.modules themselves. We should then + # be lenient and avoid errors. + sys.modules.pop(fqname, None) + if hasattr(parent, nm): + delattr(parent, nm) + raise + if fqname == 'thread' and not self.threaded: + #debug("thread detected!") + self.setThreaded() + else: + sys.modules[fqname] = None + debug("..found %s when looking for %s" % (mod, fqname)) + return mod + + def reloadHook(self, mod): + fqnm = mod.__name__ + nm = namesplit(fqnm)[-1] + parentnm = packagename(fqnm) + newmod = self.doimport(nm, parentnm, fqnm, reload=1) + #mod.__dict__.update(newmod.__dict__) + return newmod + + def _acquire(self): + if self.rlock.locked(): + if self.locker == self._get_ident(): + self.lockcount = self.lockcount + 1 + #debug("_acquire incrementing lockcount to %s" % self.lockcount) + return + self.rlock.acquire() + self.locker = self._get_ident() + self.lockcount = 0 + #debug("_acquire first time!") + + def _release(self): + if self.lockcount: + self.lockcount = self.lockcount - 1 + #debug("_release decrementing lockcount to %s" % self.lockcount) + else: + self.locker = None + self.rlock.release() + #debug("_release releasing lock!") + +#========= some helper functions =============================# + + +def packagename(s): + """ + For package name like 'module.submodule.subsubmodule' returns + 'module.submodule'. If name does not contain any dots '.', + empty string '' is returned. + """ + i = s.rfind('.') + if i >= 0: + return s[:i] + else: + return '' + + +def namesplit(s): + """ + Split package name at the position of dot '.'. + + Examples: + 'module.submodule' => ['module', 'submodule'] + 'module' => ['module'] + '' => [] + """ + rslt = [] + # Ensure that for empty string '' an empty list is returned. + if s: + rslt = s.split('.') + return rslt + + +def getpathext(fnm): + i = fnm.rfind('.') + if i >= 0: + return fnm[i:] + else: + return '' + + +def pathisdir(pathname): + "Local replacement for os.path.isdir()." + try: + s = _os_stat(pathname) + except OSError: + return None + return (s[0] & 0170000) == 0040000 + + +_os_stat = _os_path_join = _os_getcwd = _os_path_dirname = None +_os_environ = _os_listdir = _os_path_basename = None +_os_sep = None + + +def _os_bootstrap(): + """ + Set up 'os' module replacement functions for use during import bootstrap. + """ + + global _os_stat, _os_getcwd, _os_environ, _os_listdir + global _os_path_join, _os_path_dirname, _os_path_basename + global _os_sep + + names = sys.builtin_module_names + + join = dirname = environ = listdir = basename = None + mindirlen = 0 + # Only 'posix' and 'nt' os specific modules are supported. + # 'dos', 'os2' and 'mac' (MacOS 9) are not supported. + if 'posix' in names: + from posix import stat, getcwd, environ, listdir + sep = _os_sep = '/' + mindirlen = 1 + elif 'nt' in names: + from nt import stat, getcwd, environ, listdir + sep = _os_sep = '\\' + mindirlen = 3 + else: + raise ImportError('no os specific module found') + + if join is None: + def join(a, b, sep=sep): + if a == '': + return b + lastchar = a[-1:] + if lastchar == '/' or lastchar == sep: + return a + b + return a + sep + b + + if dirname is None: + def dirname(a, sep=sep, mindirlen=mindirlen): + for i in range(len(a) - 1, -1, -1): + c = a[i] + if c == '/' or c == sep: + if i < mindirlen: + return a[:i + 1] + return a[:i] + return '' + + if basename is None: + def basename(p): + i = p.rfind(sep) + if i == -1: + return p + else: + return p[i + len(sep):] + + def _listdir(dir, cache=None): + # The cache is not used. It was found to cause problems + # with programs that dynamically add python modules to be + # reimported by that same program (i.e., plugins), because + # the cache is only built once at the beginning, and never + # updated. So, we must really list the directory again. + return listdir(dir) + + _os_stat = stat + _os_getcwd = getcwd + _os_path_join = join + _os_path_dirname = dirname + _os_environ = environ + _os_listdir = _listdir + _os_path_basename = basename + + +_os_bootstrap() + + +if 'PYTHONCASEOK' not in _os_environ: + def caseOk(filename): + files = _os_listdir(_os_path_dirname(filename)) + return _os_path_basename(filename) in files +else: + def caseOk(filename): + return True diff --git a/pyinstaller/PyInstaller/log.py b/pyinstaller/PyInstaller/log.py new file mode 100644 index 0000000..59890c3 --- /dev/null +++ b/pyinstaller/PyInstaller/log.py @@ -0,0 +1,59 @@ +#! -*- mode: python; coding: utf-8 -*- +""" +Logging module for PyInstaller +""" +# +# Copyright 2011 by Hartmut Goebel +# +# 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 3 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, see . +# + +__all__ = ['getLogger', 'INFO', 'WARN', 'DEBUG', 'ERROR', 'FATAL'] + +import logging +from logging import getLogger, INFO, WARN, DEBUG, ERROR, FATAL + +FORMAT = '%(relativeCreated)d %(levelname)s: %(message)s' + +try: + logging.basicConfig(format=FORMAT, level=logging.INFO) +except TypeError: + # In Python 2.3 basicConfig does not accept arguments + # :todo: remove when dropping Python 2.3 compatibility + logging.basicConfig() + root = logging.getLogger() + assert len(root.handlers) == 1 + root.handlers[0].setFormatter(logging.Formatter(FORMAT)) + root.setLevel(logging.INFO) + +logger = getLogger('PyInstaller') + + +def __add_options(parser): + levels = ('DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL') + parser.add_option('--log-level', + choices=levels, + default='INFO', + dest='loglevel', + help=('Log level ' + '(default: %%default, choose one of %s)' + % ', '.join(levels)) + ) + +def __process_options(parser, opts): + try: + level = getattr(logging, opts.loglevel.upper()) + except AttributeError: + parser.error('Unknown log level `%s`' % opts.loglevel) + logger.setLevel(level) diff --git a/pyinstaller/PyInstaller/makespec.py b/pyinstaller/PyInstaller/makespec.py new file mode 100644 index 0000000..0f88645 --- /dev/null +++ b/pyinstaller/PyInstaller/makespec.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python +# +# Automatically build spec files containing a description of the project +# +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 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, os + +from PyInstaller import HOMEPATH +from PyInstaller import is_win, is_cygwin, is_darwin + + +onefiletmplt = """# -*- mode: python -*- +a = Analysis(%(scripts)s, + pathex=%(pathex)s, + hiddenimports=%(hiddenimports)r, + hookspath=%(hookspath)r) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=os.path.join(%(distdir)s, '%(exename)s'), + debug=%(debug)s, + strip=%(strip)s, + upx=%(upx)s, + console=%(console)s %(exe_options)s) +""" + +onedirtmplt = """# -*- mode: python -*- +a = Analysis(%(scripts)s, + pathex=%(pathex)s, + hiddenimports=%(hiddenimports)r, + hookspath=%(hookspath)r) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join(%(builddir)s, '%(exename)s'), + debug=%(debug)s, + strip=%(strip)s, + upx=%(upx)s, + console=%(console)s %(exe_options)s) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=%(strip)s, + upx=%(upx)s, + name=os.path.join(%(distdir)s, '%(name)s')) +""" + +comsrvrtmplt = """# -*- mode: python -*- +a = Analysis(%(scripts)s, + pathex=%(pathex)s, + hiddenimports=%(hiddenimports)r, + hookspath=%(hookspath)r) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join(%(builddir)s, '%(exename)s'), + debug=%(debug)s, + strip=%(strip)s, + upx=%(upx)s, + console=%(console)s %(exe_options)s) +dll = DLL(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join(%(builddir)s, '%(dllname)s'), + debug=%(debug)s) +coll = COLLECT(exe, dll, + a.binaries, + a.zipfiles, + a.datas, + strip=%(strip)s, + upx=%(upx)s, + name=os.path.join(%(distdir)s, '%(name)s')) +""" + +bundleexetmplt = """app = BUNDLE(exe, + name=os.path.join(%(distdir)s, '%(exename)s.app')) +""" + +bundletmplt = """app = BUNDLE(coll, + name=os.path.join(%(distdir)s, '%(name)s.app')) +""" + + +def quote_win_filepath( path ): + # quote all \ with another \ after using normpath to clean up the path + return os.path.normpath(path).replace('\\', '\\\\') + +# Support for trying to avoid hard-coded paths in the .spec files. +# Eg, all files rooted in the Installer directory tree will be +# written using "HOMEPATH", thus allowing this spec file to +# be used with any Installer installation. +# Same thing could be done for other paths too. +path_conversions = ( + (HOMEPATH, "HOMEPATH"), + ) + +def make_variable_path(filename, conversions = path_conversions): + for (from_path, to_name) in conversions: + assert os.path.abspath(from_path)==from_path, ( + "path '%s' should already be absolute" % from_path) + if filename[:len(from_path)] == from_path: + rest = filename[len(from_path):] + if rest[0] in "\\/": + rest = rest[1:] + return to_name, rest + return None, filename + +# An object used in place of a "path string" which knows how to repr() +# itself using variable names instead of hard-coded paths. +class Path: + def __init__(self, *parts): + self.path = apply(os.path.join, parts) + self.variable_prefix = self.filename_suffix = None + def __repr__(self): + if self.filename_suffix is None: + self.variable_prefix, self.filename_suffix = make_variable_path(self.path) + if self.variable_prefix is None: + return repr(self.path) + return "os.path.join(" + self.variable_prefix + "," + repr(self.filename_suffix) + ")" + +def __add_options(parser): + """ + Add the `Makespec` options to a option-parser instance or a + option group. + """ + g = parser.add_option_group('What to generate') + g.add_option("-F", "--onefile", dest="onefile", + action="store_true", default=False, + help="create a single file deployment") + g.add_option("-D", "--onedir", dest="onefile", + action="store_false", + help="create a single directory deployment (default)") + g.add_option("-o", "--out", + dest="workdir", metavar="DIR", + help="generate the spec file in the specified directory " + "(default: current directory)") + g.add_option("-n", "--name", + help="name to assign to the project " + "(default: first script's basename)") + + g = parser.add_option_group('What to bundle, where to search') + g.add_option("-p", "--paths", default=[], dest="pathex", + metavar="DIR", action="append", + help="set base path for import (like using PYTHONPATH). " + "Multiple directories are allowed, separating them " + "with %s, or using this option multiple times" + % repr(os.pathsep)) + g.add_option('--hidden-import', + action='append', + metavar="MODULENAME", dest='hiddenimports', + help='import hidden in the script(s). This option can ' + 'be used multiple times.') + g.add_option("--additional-hooks-dir", action="append", dest="hookspath", + help="additional path to search for hooks " + "(may be given several times)") + + g = parser.add_option_group('How to generate') + g.add_option("-d", "--debug", action="store_true", default=False, + help=("use the debug (verbose) build of the executable for " + "packaging. This will make the packaged executable be " + "more verbose when run.")) + g.add_option("-s", "--strip", action="store_true", + help="strip the exe and shared libs " + "(don't try this on Windows)") + g.add_option("--noupx", action="store_true", default=False, + help="do not use UPX even if available (works differently " + "between Windows and *nix)") + #p.add_option("-Y", "--crypt", metavar="FILE", + # help="encrypt pyc/pyo files") + + g = parser.add_option_group('Windows and Mac OS X specific options') + g.add_option("-c", "--console", "--nowindowed", dest="console", + action="store_true", default=True, + help="use a console subsystem executable (default)") + g.add_option("-w", "--windowed", "--noconsole", dest="console", + action="store_false", + help="use a windowed subsystem executable, which on Windows " + "does not open the console when the program is launched." + 'On Mac OS X it allows running gui applications and also' + 'creates an .app bundle.' + 'Mandatory for gui applications on Mac OS X') + g.add_option("-i", "--icon", dest="icon_file", + metavar="FILE.ICO or FILE.EXE,ID or FILE.ICNS", + help="If FILE is an .ico file, add the icon to the final " + "executable. Otherwise, the syntax 'file.exe,id' to " + "extract the icon with the specified id " + "from file.exe and add it to the final executable. " + "If FILE is an .icns file, add the icon to the final " + ".app bundle on Mac OS X (for Mac not yet implemented)") + + g = parser.add_option_group('Windows specific options') + g.add_option("--version-file", + dest="version_file", metavar="FILE", + help="add a version resource from FILE to the exe") + g.add_option("-m", "--manifest", metavar="FILE or XML", + help="add manifest FILE or XML to the exe") + g.add_option("-r", "--resource", default=[], dest="resources", + metavar="FILE[,TYPE[,NAME[,LANGUAGE]]]", action="append", + help="add/update resource of the given type, name and language " + "from FILE to the final executable. FILE can be a " + "data file or an exe/dll. For data files, atleast " + "TYPE and NAME need to be specified, LANGUAGE defaults " + "to 0 or may be specified as wildcard * to update all " + "resources of the given TYPE and NAME. For exe/dll " + "files, all resources from FILE will be added/updated " + "to the final executable if TYPE, NAME and LANGUAGE " + "are omitted or specified as wildcard *." + "Multiple resources are allowed, using this option " + "multiple times.") + + +def main(scripts, name=None, onefile=0, + console=True, debug=False, strip=0, noupx=0, comserver=0, + workdir=None, pathex=[], version_file=None, + icon_file=None, manifest=None, resources=[], crypt=None, + hiddenimports=None, hookspath=None, **kwargs): + + if not name: + name = os.path.splitext(os.path.basename(scripts[0]))[0] + + distdir = "dist" + builddir = os.path.join('build', 'pyi.' + sys.platform, name) + + pathex = pathex[:] + if workdir is None: + workdir = os.getcwd() + pathex.append(workdir) + else: + pathex.append(os.getcwd()) + if workdir == HOMEPATH: + workdir = os.path.join(HOMEPATH, name) + if not os.path.exists(workdir): + os.makedirs(workdir) + exe_options = '' + if version_file: + exe_options = "%s, version='%s'" % (exe_options, quote_win_filepath(version_file)) + if icon_file: + exe_options = "%s, icon='%s'" % (exe_options, quote_win_filepath(icon_file)) + if manifest: + if "<" in manifest: + # Assume XML string + exe_options = "%s, manifest='%s'" % (exe_options, manifest.replace("'", "\\'")) + else: + # Assume filename + exe_options = "%s, manifest='%s'" % (exe_options, quote_win_filepath(manifest)) + if resources: + resources = map(quote_win_filepath, resources) + exe_options = "%s, resources=%s" % (exe_options, repr(resources)) + + hiddenimports = hiddenimports or [] + scripts = map(Path, scripts) + + d = {'scripts':scripts, + 'pathex' :pathex, + 'hiddenimports': hiddenimports, + 'hookspath': hookspath, + #'exename': '', + 'name': name, + 'distdir': repr(distdir), + 'builddir': repr(builddir), + 'debug': debug, + 'strip': strip, + 'upx' : not noupx, + 'crypt' : repr(crypt), + 'crypted': crypt is not None, + 'console': console or debug, + 'exe_options': exe_options} + + if is_win or is_cygwin: + d['exename'] = name+'.exe' + d['dllname'] = name+'.dll' + else: + d['exename'] = name + + # only Windows and Mac OS X distinguish windowed and console apps + if not is_win and not is_darwin: + d['console'] = True + + specfnm = os.path.join(workdir, name+'.spec') + specfile = open(specfnm, 'w') + if onefile: + specfile.write(onefiletmplt % d) + if not console: + specfile.write(bundleexetmplt % d) + elif comserver: + specfile.write(comsrvrtmplt % d) + if not console: + specfile.write(bundletmplt % d) + else: + specfile.write(onedirtmplt % d) + if not console: + specfile.write(bundletmplt % d) + specfile.close() + return specfnm + diff --git a/pyinstaller/PyInstaller/utils/.svn/entries b/pyinstaller/PyInstaller/utils/.svn/entries new file mode 100644 index 0000000..69c3e4d --- /dev/null +++ b/pyinstaller/PyInstaller/utils/.svn/entries @@ -0,0 +1,84 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/PyInstaller/utils +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +icon.py +file + + + +add + +misc.py +file + + + +add + +winutils.py +file + + + +add + +__init__.py +file + + + +add + +versioninfo.py +file + + + +add + +git.py +file + + + +add + +winmanifest.py +file + + + +add + +winresource.py +file + + + +add + diff --git a/pyinstaller/PyInstaller/utils/__init__.py b/pyinstaller/PyInstaller/utils/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/pyinstaller/PyInstaller/utils/__init__.py @@ -0,0 +1 @@ +# diff --git a/pyinstaller/PyInstaller/utils/git.py b/pyinstaller/PyInstaller/utils/git.py new file mode 100644 index 0000000..9e049c8 --- /dev/null +++ b/pyinstaller/PyInstaller/utils/git.py @@ -0,0 +1,32 @@ +# This module contains various helper functions for git DVCS +# +# Copyright (C) 2011, hartmut Goebel +# +# 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 + +from PyInstaller import compat + +def get_repo_revision(): + try: + rev = compat.exec_command('git', 'rev-parse', '--short', 'HEAD').strip() + if rev: + return rev + except: + pass + return '' + + +if __name__ == '__main__': + print get_repo_revision() diff --git a/pyinstaller/PyInstaller/utils/icon.py b/pyinstaller/PyInstaller/utils/icon.py new file mode 100644 index 0000000..a2383e6 --- /dev/null +++ b/pyinstaller/PyInstaller/utils/icon.py @@ -0,0 +1,201 @@ +#! /usr/bin/env python +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 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 + +# This code is courtesy of Thomas Heller, who +# has kindly donated it to this project. + +RT_ICON = 3 +RT_GROUP_ICON = 14 +LOAD_LIBRARY_AS_DATAFILE = 2 + +import struct +import types +try: + StringTypes = types.StringTypes +except AttributeError: + StringTypes = [ type("") ] + +import PyInstaller.log as logging +logger = logging.getLogger('PyInstaller.icon') + +class Structure: + def __init__(self): + size = self._sizeInBytes = struct.calcsize(self._format_) + self._fields_ = list(struct.unpack(self._format_, '\000' * size)) + indexes = self._indexes_ = {} + for i, nm in enumerate(self._names_): + indexes[nm] = i + + def dump(self): + logger.info("DUMP of %s", self) + for name in self._names_: + if not name.startswith('_'): + logger.info("%20s = %s", name, getattr(self, name)) + logger.info("") + + def __getattr__(self, name): + if name in self._names_: + index = self._indexes_[name] + return self._fields_[index] + try: + return self.__dict__[name] + except KeyError: + raise AttributeError, name + + def __setattr__(self, name, value): + if name in self._names_: + index = self._indexes_[name] + self._fields_[index] = value + else: + self.__dict__[name] = value + + def tostring(self): + return apply(struct.pack, [self._format_,] + self._fields_) + + def fromfile(self, file): + data = file.read(self._sizeInBytes) + self._fields_ = list(struct.unpack(self._format_, data)) + +class ICONDIRHEADER(Structure): + _names_ = "idReserved", "idType", "idCount" + _format_ = "hhh" + +class ICONDIRENTRY(Structure): + _names_ = ("bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", + "wBitCount", "dwBytesInRes", "dwImageOffset") + _format_ = "bbbbhhii" + +class GRPICONDIR(Structure): + _names_ = "idReserved", "idType", "idCount" + _format_ = "hhh" + +class GRPICONDIRENTRY(Structure): + _names_ = ("bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", + "wBitCount", "dwBytesInRes", "nID") + _format_ = "bbbbhhih" + +class IconFile: + def __init__(self, path): + self.path = path + file = open(path, "rb") + self.entries = [] + self.images = [] + header = self.header = ICONDIRHEADER() + header.fromfile(file) + for i in range(header.idCount): + entry = ICONDIRENTRY() + entry.fromfile(file) + self.entries.append(entry) + for e in self.entries: + file.seek(e.dwImageOffset, 0) + self.images.append(file.read(e.dwBytesInRes)) + + def grp_icon_dir(self): + return self.header.tostring() + + def grp_icondir_entries(self, id=1): + data = "" + for entry in self.entries: + e = GRPICONDIRENTRY() + for n in e._names_[:-1]: + setattr(e, n, getattr(entry, n)) + e.nID = id + id = id + 1 + data = data + e.tostring() + return data + + +def CopyIcons_FromIco(dstpath, srcpath, id=1): + import win32api #, win32con + icons = map(IconFile, srcpath) + logger.info("Updating icons from %s to %s", srcpath, dstpath) + + hdst = win32api.BeginUpdateResource(dstpath, 0) + + iconid = 1 + for i, f in enumerate(icons): + data = f.grp_icon_dir() + data = data + f.grp_icondir_entries(iconid) + win32api.UpdateResource(hdst, RT_GROUP_ICON, i, data) + logger.info("Writing RT_GROUP_ICON %d resource with %d bytes", i, len(data)) + for data in f.images: + win32api.UpdateResource(hdst, RT_ICON, iconid, data) + logger.info("Writing RT_ICON %d resource with %d bytes", iconid, len(data)) + iconid = iconid + 1 + + win32api.EndUpdateResource(hdst, 0) + +def CopyIcons(dstpath, srcpath): + import os.path + + if type(srcpath) in StringTypes: + srcpath = [ srcpath ] + + def splitter(s): + try: + srcpath, index = s.split(',') + return srcpath.strip(), int(index) + except ValueError: + return s, None + + srcpath = map(splitter, srcpath) + logger.info("SRCPATH %s", srcpath) + + if len(srcpath) > 1: + # At the moment, we support multiple icons only from .ico files + srcs = [] + for s in srcpath: + e = os.path.splitext(s[0])[1] + if e.lower() != '.ico': + raise ValueError, "multiple icons supported only from .ico files" + if s[1] is not None: + raise ValueError, "index not allowed for .ico files" + srcs.append(s[0]) + return CopyIcons_FromIco(dstpath, srcs) + + srcpath,index = srcpath[0] + srcext = os.path.splitext(srcpath)[1] + if srcext.lower() == '.ico': + return CopyIcons_FromIco(dstpath, [srcpath]) + if index is not None: + logger.info("Updating icons from %s, %d to %s", srcpath, index, dstpath) + else: + logger.info("Updating icons from %s to %s", srcpath, dstpath) + import win32api #, win32con + hdst = win32api.BeginUpdateResource(dstpath, 0) + hsrc = win32api.LoadLibraryEx(srcpath, 0, LOAD_LIBRARY_AS_DATAFILE) + if index is None: + grpname = win32api.EnumResourceNames(hsrc, RT_GROUP_ICON)[0] + elif index >= 0: + grpname = win32api.EnumResourceNames(hsrc, RT_GROUP_ICON)[index] + else: + grpname = -index + data = win32api.LoadResource(hsrc, RT_GROUP_ICON, grpname) + win32api.UpdateResource(hdst, RT_GROUP_ICON, grpname, data) + for iconname in win32api.EnumResourceNames(hsrc, RT_ICON): + data = win32api.LoadResource(hsrc, RT_ICON, iconname) + win32api.UpdateResource(hdst, RT_ICON, iconname, data) + win32api.FreeLibrary(hsrc) + win32api.EndUpdateResource(hdst, 0) + +if __name__ == "__main__": + import sys + + dstpath = sys.argv[1] + srcpath = sys.argv[2:] + CopyIcons(dstpath, srcpath) diff --git a/pyinstaller/PyInstaller/utils/misc.py b/pyinstaller/PyInstaller/utils/misc.py new file mode 100644 index 0000000..23a5ff5 --- /dev/null +++ b/pyinstaller/PyInstaller/utils/misc.py @@ -0,0 +1,114 @@ +# +# Copyright (C) 2005-2011, Giovanni Bajo +# Based on previous work under copyright (c) 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 + +""" +This module is for the miscellaneous routines which do not fit somewhere else. +""" + +import glob +import os + +from PyInstaller import log as logging +from PyInstaller.compat import is_win + +logger = logging.getLogger(__name__) + + +def dlls_in_subdirs(directory): + """Returns *.dll, *.so, *.dylib in given directories and subdirectories.""" + files = [] + for root, dirs, files in os.walk(directory): + files.extend(dlls_in_dir(root)) + + +def dlls_in_dir(directory): + """Returns *.dll, *.so, *.dylib in given directory.""" + files = [] + files.extend(glob.glob(os.path.join(directory, '*.so'))) + files.extend(glob.glob(os.path.join(directory, '*.dll'))) + files.extend(glob.glob(os.path.join(directory, '*.dylib'))) + return files + + +def find_executable(executable, path=None): + """ + Try to find 'executable' in the directories listed in 'path' (a + string listing directories separated by 'os.pathsep'; defaults to + os.environ['PATH']). + + Returns the complete filename or None if not found. + + Code from http://snippets.dzone.com/posts/show/6313 + """ + if path is None: + path = os.environ['PATH'] + paths = path.split(os.pathsep) + extlist = [''] + + if is_win: + (base, ext) = os.path.splitext(executable) + # Executable files on windows have an arbitrary extension, but + # .exe is automatically appended if not present in the name. + if not ext: + executable = executable + ".exe" + pathext = os.environ['PATHEXT'].lower().split(os.pathsep) + (base, ext) = os.path.splitext(executable) + if ext.lower() not in pathext: + extlist = pathext + + for ext in extlist: + execname = executable + ext + if os.path.isfile(execname): + return execname + else: + for p in paths: + f = os.path.join(p, execname) + if os.path.isfile(f): + return f + else: + return None + + +def get_unicode_modules(): + """ + Try importing codecs and encodings to include unicode support + in created binary. + """ + modules = [] + try: + import codecs + modules = ['codecs'] + import encodings + # `encodings` imports `codecs`, so only the first is required. + modules = ['encodings'] + except ImportError: + pass + return modules + + +def get_code_object(filename): + """ + Convert source code from Python source file to code object. + """ + try: + source_code_string = open(filename, 'rU').read() + '\n' + code_object = compile(source_code_string, filename, 'exec') + return code_object + except SyntaxError, e: + logger.exception(e) + raise SystemExit(10) diff --git a/pyinstaller/PyInstaller/utils/versioninfo.py b/pyinstaller/PyInstaller/utils/versioninfo.py new file mode 100644 index 0000000..576484c --- /dev/null +++ b/pyinstaller/PyInstaller/utils/versioninfo.py @@ -0,0 +1,536 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 win32api +import struct +import pywintypes + +TEST=0 + +LOAD_LIBRARY_AS_DATAFILE = 2 +RT_VERSION = 16 + +def getRaw(o): + return str(buffer(o)) + + +def decode(pathnm): + h = win32api.LoadLibraryEx(pathnm, 0, LOAD_LIBRARY_AS_DATAFILE) + nm = win32api.EnumResourceNames(h, RT_VERSION)[0] + data = win32api.LoadResource(h, RT_VERSION, nm) + vs = VSVersionInfo() + j = vs.fromRaw(data) + if TEST: + print vs + if data[:j] != vs.toRaw(): + print "AAAAAGGHHHH" + glbls = { + 'VSVersionInfo': VSVersionInfo, + 'FixedFileInfo': FixedFileInfo, + 'StringFileInfo': StringFileInfo, + 'StringTable': StringTable, + 'StringStruct': StringStruct, + 'VarFileInfo': VarFileInfo, + 'VarStruct': VarStruct, + } + vs2 = eval(repr(vs), glbls) + if vs.toRaw() != vs2.toRaw(): + print + print 'reconstruction not the same!' + print vs2 + win32api.FreeLibrary(h) + return vs + + +class VSVersionInfo: + """ + WORD wLength; // length of the VS_VERSION_INFO structure + WORD wValueLength; // length of the Value member + WORD wType; // 1 means text, 0 means binary + WCHAR szKey[]; // Contains the Unicode string "VS_VERSION_INFO". + WORD Padding1[]; + VS_FIXEDFILEINFO Value; + WORD Padding2[]; + WORD Children[]; // zero or more StringFileInfo or VarFileInfo + // structures (or both) that are children of the + // current version structure. + """ + + def __init__(self, ffi=None, kids=None): + self.ffi = ffi + self.kids = kids or [] + + def fromRaw(self, data): + i, (sublen, vallen, wType, nm) = parseCommon(data) + #vallen is length of the ffi, typ is 0, nm is 'VS_VERSION_INFO' + i = ((i + 3) / 4) * 4 + # now a VS_FIXEDFILEINFO + self.ffi = FixedFileInfo() + j = self.ffi.fromRaw(data, i) + #print ffi + if TEST and data[i:j] != self.ffi.toRaw(): + print "raw:", `data[i:j]` + print "ffi:", `self.ffi.toRaw()` + i = j + while i < sublen: + j = i + i, (csublen, cvallen, ctyp, nm) = parseCommon(data, i) + if str(nm).strip() == "StringFileInfo": + sfi = StringFileInfo() + k = sfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen) + if TEST and data[j:k] != sfi.toRaw(): + rd = data[j:k] + sd = sfi.toRaw() + for x in range(0, len(rd), 16): + rds = rd[x:x+16] + sds = sd[x:x+16] + if rds != sds: + print "rd[%s:%s+16]: %r" % (x, x, rds) + print "sd[%s:%s+16]: %r" % (x, x, sds) + print + print ("raw: len %d, wLength %d" + % (len(rd), struct.unpack('h', rd[:2])[0])) + print ("sfi: len %d, wLength %d" + % (len(sd), struct.unpack('h', sd[:2])[0])) + self.kids.append(sfi) + i = k + else: + vfi = VarFileInfo() + k = vfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen) + self.kids.append(vfi) + if TEST and data[j:k] != vfi.toRaw(): + print "raw:", `data[j:k]` + print "vfi:", `vfi.toRaw()` + i = k + i = j + csublen + i = ((i + 3) / 4) * 4 + return i + + def toRaw(self): + nm = pywintypes.Unicode('VS_VERSION_INFO') + rawffi = self.ffi.toRaw() + vallen = len(rawffi) + typ = 0 + sublen = 6 + 2*len(nm) + 2 + pad = '' + if sublen % 4: + pad = '\000\000' + sublen = sublen + len(pad) + vallen + pad2 = '' + if sublen % 4: + pad2 = '\000\000' + tmp = "".join([kid.toRaw() for kid in self.kids ]) + sublen = sublen + len(pad2) + len(tmp) + return (struct.pack('hhh', sublen, vallen, typ) + + getRaw(nm) + '\000\000' + pad + rawffi + pad2 + tmp) + + def __repr__(self, indent=''): + indent = indent + ' ' + tmp = [kid.__repr__(indent+' ') + for kid in self.kids] + tmp = ', \n'.join(tmp) + return ("VSVersionInfo(\n%sffi=%s,\n%skids=[\n%s\n%s]\n)" + % (indent, self.ffi.__repr__(indent), indent, + tmp, indent)) + + +def parseCommon(data, start=0): + i = start + 6 + (wLength, wValueLength, wType) = struct.unpack('3h', data[start:i]) + #print "wLength, wValueLength, wType, i:", wLength, wValueLength, wType, i + i, szKey = parseUString(data, i, i+wLength) + #i = ((i + 3) / 4) * 4 + #print `data[start+6:start+wLength]` + return i, (wLength, wValueLength, wType, szKey) + +def parseUString(data, start, limit): + i = start + while i < limit: + if data[i:i+2] == '\000\000': + break + i += 2 + szKey = pywintypes.UnicodeFromRaw(data[start:i]) + i += 2 + #print "szKey:", repr(szKey), "(consumed", i-start, "bytes - to", i, ")" + return i, szKey + + +class FixedFileInfo: + """ + DWORD dwSignature; //Contains the value 0xFEEFO4BD + DWORD dwStrucVersion; // binary version number of this structure. + // The high-order word of this member contains + // the major version number, and the low-order + // word contains the minor version number. + DWORD dwFileVersionMS; // most significant 32 bits of the file's binary + // version number + DWORD dwFileVersionLS; // + DWORD dwProductVersionMS; // most significant 32 bits of the binary version + // number of the product with which this file was + // distributed + DWORD dwProductVersionLS; // + DWORD dwFileFlagsMask; // bitmask that specifies the valid bits in + // dwFileFlags. A bit is valid only if it was + // defined when the file was created. + DWORD dwFileFlags; // VS_FF_DEBUG, VS_FF_PATCHED etc. + DWORD dwFileOS; // VOS_NT, VOS_WINDOWS32 etc. + DWORD dwFileType; // VFT_APP etc. + DWORD dwFileSubtype; // 0 unless VFT_DRV or VFT_FONT or VFT_VXD + DWORD dwFileDateMS; + DWORD dwFileDateLS; + """ + def __init__(self, filevers=(0, 0, 0, 0), prodvers=(0, 0, 0, 0), + mask=0x3f, flags=0x0, OS=0x40004, fileType=0x1, + subtype=0x0, date=(0, 0)): + self.sig = 0xfeef04bdL + self.strucVersion = 0x10000 + self.fileVersionMS = (filevers[0] << 16) | (filevers[1] & 0xffff) + self.fileVersionLS = (filevers[2] << 16) | (filevers[3] & 0xffff) + self.productVersionMS = (prodvers[0] << 16) | (prodvers[1] & 0xffff) + self.productVersionLS = (prodvers[2] << 16) | (prodvers[3] & 0xffff) + self.fileFlagsMask = mask + self.fileFlags = flags + self.fileOS = OS + self.fileType = fileType + self.fileSubtype = subtype + self.fileDateMS = date[0] + self.fileDateLS = date[1] + + def fromRaw(self, data, i): + (self.sig, + self.strucVersion, + self.fileVersionMS, + self.fileVersionLS, + self.productVersionMS, + self.productVersionLS, + self.fileFlagsMask, + self.fileFlags, + self.fileOS, + self.fileType, + self.fileSubtype, + self.fileDateMS, + self.fileDateLS) = struct.unpack('13l', data[i:i+52]) + return i+52 + + def toRaw(self): + return struct.pack('L12l', self.sig, + self.strucVersion, + self.fileVersionMS, + self.fileVersionLS, + self.productVersionMS, + self.productVersionLS, + self.fileFlagsMask, + self.fileFlags, + self.fileOS, + self.fileType, + self.fileSubtype, + self.fileDateMS, + self.fileDateLS) + + def __repr__(self, indent=''): + fv = (self.fileVersionMS >> 16, self.fileVersionMS & 0xffff, + self.fileVersionLS >> 16, self.fileVersionLS & 0xFFFF) + pv = (self.productVersionMS >> 16, self.productVersionMS & 0xffff, + self.productVersionLS >> 16, self.productVersionLS & 0xFFFF) + fd = (self.fileDateMS, self.fileDateLS) + tmp = ["FixedFileInfo(", + "filevers=%s," % fv, + "prodvers=%s," % pv, + "mask=%s," % hex(self.fileFlagsMask), + "flags=%s," % hex(self.fileFlags), + "OS=%s," % hex(self.fileOS), + "fileType=%s," % hex(self.fileType), + "subtype=%s," % hex(self.fileSubtype), + "date=%s" % fd, + ")" + ] + return ('\n'+indent+' ').join(tmp) + + +##StringFileInfo { +##}; + +class StringFileInfo: + """ + WORD wLength; // length of the version resource + WORD wValueLength; // length of the Value member in the current + // VS_VERSION_INFO structure + WORD wType; // 1 means text, 0 means binary + WCHAR szKey[]; // Contains the Unicode string 'StringFileInfo'. + WORD Padding[]; + StringTable Children[]; // list of zero or more String structures + """ + def __init__(self, kids=None): + self.name = "StringFileInfo" + self.kids = kids or [] + + def fromRaw(self, sublen, vallen, name, data, i, limit): + self.name = name + while i < limit: + st = StringTable() + j = st.fromRaw(data, i, limit) + if TEST and data[i:j] != st.toRaw(): + rd = data[i:j] + sd = st.toRaw() + for x in range(0, len(rd), 16): + rds = rd[x:x+16] + sds = sd[x:x+16] + if rds != sds: + print "rd[%s:%s+16]: %r" % (x, x, rds) + print "sd[%s:%s+16]: %r" % (x, x, sds) + print + print ("raw: len %d, wLength %d" + % (len(rd), struct.unpack('h', rd[:2])[0])) + print (" st: len %d, wLength %d" + % (len(sd), struct.unpack('h', sd[:2])[0])) + self.kids.append(st) + i = j + return i + + def toRaw(self): + if type(self.name) is STRINGTYPE: + self.name = pywintypes.Unicode(self.name) + vallen = 0 + typ = 1 + sublen = 6 + 2*len(self.name) + 2 + pad = '' + if sublen % 4: + pad = '\000\000' + tmp = ''.join([kid.toRaw() for kid in self.kids]) + sublen = sublen + len(pad) + len(tmp) + if tmp[-2:] == '\000\000': + sublen = sublen - 2 + return (struct.pack('hhh', sublen, vallen, typ) + + getRaw(self.name) + '\000\000' + pad + tmp) + + def __repr__(self, indent=''): + newindent = indent + ' ' + tmp = [kid.__repr__(newindent) + for kid in self.kids] + tmp = ', \n'.join(tmp) + return ("%sStringFileInfo(\n%s[\n%s\n%s])" + % (indent, newindent, tmp, newindent)) + + +class StringTable: + """ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[]; + String Children[]; // list of zero or more String structures. + """ + def __init__(self, name=None, kids=None): + self.name = name or '' + self.kids = kids or [] + + def fromRaw(self, data, i, limit): + #print "Parsing StringTable" + i, (cpsublen, cpwValueLength, cpwType, self.name) = parseCodePage(data, i, limit) # should be code page junk + #i = ((i + 3) / 4) * 4 + while i < limit: + ss = StringStruct() + j = ss.fromRaw(data, i, limit) + if TEST and data[i:j] != ss.toRaw(): + print "raw:", `data[i:j]` + print " ss:", `ss.toRaw()` + i = j + self.kids.append(ss) + i = ((i + 3) / 4) * 4 + return i + + def toRaw(self): + if type(self.name) is STRINGTYPE: + self.name = pywintypes.Unicode(self.name) + vallen = 0 + typ = 1 + sublen = 6 + 2*len(self.name) + 2 + tmp = [] + for kid in self.kids: + raw = kid.toRaw() + if len(raw) % 4: + raw = raw + '\000\000' + tmp.append(raw) + tmp = ''.join(tmp) + sublen += len(tmp) + if tmp[-2:] == '\000\000': + sublen -= 2 + return (struct.pack('hhh', sublen, vallen, typ) + + getRaw(self.name) + '\000\000' + tmp) + + def __repr__(self, indent=''): + newindent = indent + ' ' + tmp = map(repr, self.kids) + tmp = (',\n%s' % newindent).join(tmp) + return ("%sStringTable(\n%s%r,\n%s[%s])" + % (indent, newindent, self.name, newindent, tmp)) + + +class StringStruct: + """ + WORD wLength; + WORD wValueLength; + WORD wType; + WCHAR szKey[]; + WORD Padding[]; + String Value[]; + """ + def __init__(self, name=None, val=None): + self.name = name or '' + self.val = val or '' + + def fromRaw(self, data, i, limit): + i, (sublen, vallen, typ, self.name) = parseCommon(data, i) + limit = i + sublen + i = ((i + 3) / 4) * 4 + i, self.val = parseUString(data, i, limit) + return i + + def toRaw(self): + if type(self.name) is STRINGTYPE: + self.name = pywintypes.Unicode(self.name) + if type(self.val) is STRINGTYPE: + self.val = pywintypes.Unicode(self.val) + vallen = len(self.val) + 1 + typ = 1 + sublen = 6 + 2*len(self.name) + 2 + pad = '' + if sublen % 4: + pad = '\000\000' + sublen = sublen + len(pad) + 2*vallen + return (struct.pack('hhh', sublen, vallen, typ) + + getRaw(self.name) + '\000\000' + pad + + getRaw(self.val) + '\000\000') + + def __repr__(self, indent=''): + return "StringStruct(%r, %r)" % (self.name, self.val) + + +def parseCodePage(data, i, limit): + #print "Parsing CodePage" + i, (sublen, wValueLength, wType, nm) = parseCommon(data, i) + #i = ((i + 3) / 4) * 4 + return i, (sublen, wValueLength, wType, nm) + + +class VarFileInfo: + """ + WORD wLength; // length of the version resource + WORD wValueLength; // length of the Value member in the current + // VS_VERSION_INFO structure + WORD wType; // 1 means text, 0 means binary + WCHAR szKey[]; // Contains the Unicode string 'VarFileInfo'. + WORD Padding[]; + Var Children[]; // list of zero or more Var structures + """ + def __init__(self, kids=None): + self.kids = kids or [] + + def fromRaw(self, sublen, vallen, name, data, i, limit): + self.sublen = sublen + self.vallen = vallen + self.name = name + i = ((i + 3) / 4) * 4 + while i < limit: + vs = VarStruct() + j = vs.fromRaw(data, i, limit) + self.kids.append(vs) + if TEST and data[i:j] != vs.toRaw(): + print "raw:", `data[i:j]` + print "cmp:", `vs.toRaw()` + i = j + return i + + def toRaw(self): + self.vallen = 0 + self.wType = 1 + self.name = pywintypes.Unicode('VarFileInfo') + sublen = 6 + 2*len(self.name) + 2 + pad = '' + if sublen % 4: + pad = '\000\000' + tmp = ''.join([kid.toRaw() for kid in self.kids]) + self.sublen = sublen + len(pad) + len(tmp) + return (struct.pack('hhh', self.sublen, self.vallen, self.wType) + + getRaw(self.name) + '\000\000' + pad + tmp) + + def __repr__(self, indent=''): + tmp = map(repr, self.kids) + return "%sVarFileInfo([%s])" % (indent, ', '.join(tmp)) + + +STRINGTYPE = type('') + +class VarStruct: + """ + WORD wLength; // length of the version resource + WORD wValueLength; // length of the Value member in the current + // VS_VERSION_INFO structure + WORD wType; // 1 means text, 0 means binary + WCHAR szKey[]; // Contains the Unicode string 'Translation' + // or a user-defined key string value + WORD Padding[]; // + WORD Value[]; // list of one or more values that are language + // and code-page identifiers + """ + def __init__(self, name=None, kids=None): + self.name = name or '' + self.kids = kids or [] + + def fromRaw(self, data, i, limit): + i, (self.sublen, self.wValueLength, self.wType, self.name) = parseCommon(data, i) + i = ((i + 3) / 4) * 4 + for j in range(self.wValueLength/2): + kid = struct.unpack('h', data[i:i+2])[0] + self.kids.append(kid) + i += 2 + return i + + def toRaw(self): + self.wValueLength = len(self.kids) * 2 + self.wType = 0 + if type(self.name) is STRINGTYPE: + self.name = pywintypes.Unicode(self.name) + sublen = 6 + 2*len(self.name) + 2 + pad = '' + if sublen % 4: + pad = '\000\000' + self.sublen = sublen + len(pad) + self.wValueLength + tmp = ''.join([struct.pack('h', kid) for kid in self.kids]) + return (struct.pack('hhh', self.sublen, self.wValueLength, self.wType) + + getRaw(self.name) + '\000\000' + pad + tmp) + + def __repr__(self, indent=''): + return "VarStruct(%r, %r)" % (self.name, self.kids) + + +def SetVersion(exenm, versionfile): + txt = open(versionfile, 'rU').read() + vs = eval(txt) + hdst = win32api.BeginUpdateResource(exenm, 0) + win32api.UpdateResource(hdst, RT_VERSION, 1, vs.toRaw()) + win32api.EndUpdateResource (hdst, 0) + + +if __name__ == '__main__': + import sys + TEST = 1 + if len(sys.argv) < 2: + decode('c:/Program Files/Netscape/Communicator/Program/netscape.exe') + else: + print "Examining", sys.argv[1] + decode(sys.argv[1]) diff --git a/pyinstaller/PyInstaller/utils/winmanifest.py b/pyinstaller/PyInstaller/utils/winmanifest.py new file mode 100644 index 0000000..3dd905b --- /dev/null +++ b/pyinstaller/PyInstaller/utils/winmanifest.py @@ -0,0 +1,1039 @@ +#!/usr/bin/env python +# +# Copyright (C) 2009, Florian Hoech +# +# 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, 5th Floor, Boston, MA 02110-1301, USA + +# DEV NOTES +# +# Currently not implemented in the Manifest class: +# * Validation (only very basic sanity checks are currently in place) +# * comClass, typelib, comInterfaceProxyStub and windowClass child elements of +# the file element +# * comInterfaceExternalProxyStub and windowClass child elements of the +# assembly element +# * Application Configuration File and Multilanguage User Interface (MUI) +# support when searching for assembly files +# +# Isolated Applications and Side-by-side Assemblies: +# http://msdn.microsoft.com/en-us/library/dd408052%28VS.85%29.aspx +# +# Changelog: +# 2009-12-17 fix: small glitch in toxml / toprettyxml methods (xml declaration +# wasn't replaced when a different encodig than UTF-8 was used) +# chg: catch xml.parsers.expat.ExpatError and re-raise as +# ManifestXMLParseError +# chg: support initialize option in parse method also +# +# 2009-12-13 fix: fixed os import +# fix: skip invalid / empty dependent assemblies +# +# 2009-08-21 fix: Corrected assembly searching sequence for localized +# assemblies +# fix: Allow assemblies with no dependent files +# +# 2009-07-31 chg: Find private assemblies even if unversioned +# add: Manifest.same_id method to check if two manifests have the +# same assemblyIdentity +# +# 2009-07-30 fix: Potential failure in File.calc_hash method if hash +# algorythm not supported +# add: Publisher configuration (policy) support when searching for +# assembly files +# fix: Private assemblies are now actually found if present (and no +# shared assembly exists) +# add: Python 2.3 compatibility (oldest version supported by +# pyinstaller) +# +# 2009-07-28 chg: Code cleanup, removed a bit of redundancy +# add: silent mode (set silent attribute on module) +# chg: Do not print messages in silent mode +# +# 2009-06-18 chg: Use glob instead of regular expression in Manifest.find_files +# +# 2009-05-04 fix: Don't fail if manifest has empty description +# fix: Manifests created by the toxml, toprettyxml, writexml or +# writeprettyxml methods are now correctly recognized by +# Windows, which expects the XML declaration to be ordered +# version-encoding-standalone (standalone being optional) +# add: 'encoding' keyword argument in toxml, toprettyxml, writexml +# and writeprettyxml methods +# chg: UpdateManifestResourcesFromXML and +# UpdateManifestResourcesFromXMLFile: set resource name +# depending on file type ie. exe or dll +# fix: typo in __main__: UpdateManifestResourcesFromDataFile +# should have been UpdateManifestResourcesFromXMLFile +# +# 2009-03-21 First version + +""" +winmanifest.py + +Create, parse and write MS Windows Manifest files. +Find files which are part of an assembly, by searching shared and +private assemblies. +Update or add manifest resources in Win32 PE files. + +Commandline usage: +winmanifest.py +Updates or adds manifest as resource in Win32 PE file . + +""" + +import os +from glob import glob +import re +import sys +import xml +from xml.dom import Node, minidom +from xml.dom.minidom import Document, Element + +from PyInstaller import compat +from PyInstaller.compat import hashlib, architecture +from PyInstaller import log as logging +logger = logging.getLogger('PyInstaller.build.winmanifest') + +try: + from PyInstaller.utils import winresource +except ImportError, detail: + winresource = None + logger.warn(detail) + logger.warn("Cannot check for assembly dependencies - resource access ") + logger.warn("unavailable. To enable resource access, please install ") + logger.warn("http://sourceforge.net/projects/pywin32/") + + +LANGUAGE_NEUTRAL_NT5 = "x-ww" +LANGUAGE_NEUTRAL_NT6 = "none" +RT_MANIFEST = 24 + +Document.aChild = Document.appendChild +Document.cE = Document.createElement +Document.cT = Document.createTextNode +Document.getEByTN = Document.getElementsByTagName +Element.aChild = Element.appendChild +Element.getA = Element.getAttribute +Element.getEByTN = Element.getElementsByTagName +Element.remA = Element.removeAttribute +Element.setA = Element.setAttribute + + +def getChildElementsByTagName(self, tagName): + """ Return child elements of type tagName if found, else [] """ + result = [] + for child in self.childNodes: + if isinstance(child, Element): + if child.tagName == tagName: + result.append(child) + return result + + +def getFirstChildElementByTagName(self, tagName): + """ Return the first element of type tagName if found, else None """ + for child in self.childNodes: + if isinstance(child, Element): + if child.tagName == tagName: + return child + return None + + +Document.getCEByTN = getChildElementsByTagName +Document.getFCEByTN = getFirstChildElementByTagName +Element.getCEByTN = getChildElementsByTagName +Element.getFCEByTN = getFirstChildElementByTagName + + +class _Dummy: + pass + + +if winresource: + _File = winresource.File +else: + _File = _Dummy + + +class File(_File): + + """ A file referenced by an assembly inside a manifest. """ + + def __init__(self, filename="", hashalg=None, hash=None, comClasses=None, + typelibs=None, comInterfaceProxyStubs=None, + windowClasses=None): + if winresource: + winresource.File.__init__(self, filename) + else: + self.filename = filename + self.name = os.path.basename(filename) + if hashalg: + self.hashalg = hashalg.upper() + else: + self.hashalg = None + if (os.path.isfile(filename) and hashalg and hashlib and + hasattr(hashlib, hashalg.lower())): + self.calc_hash() + else: + self.hash = hash + self.comClasses = comClasses or [] # TO-DO: implement + self.typelibs = typelibs or [] # TO-DO: implement + self.comInterfaceProxyStubs = comInterfaceProxyStubs or [] # TO-DO: implement + self.windowClasses = windowClasses or [] # TO-DO: implement + + def calc_hash(self, hashalg=None): + """ + Calculate the hash of the file. + + Will be called automatically from the constructor if the file exists + and hashalg is given (and supported), but may also be called manually + e.g. to update the hash if the file has changed. + + """ + fd = open(self.filename, "rb") + buf = fd.read() + fd.close() + if hashalg: + self.hashalg = hashalg.upper() + self.hash = getattr(hashlib, self.hashalg.lower())(buf).hexdigest() + + def find(self, searchpath): + logger.info("Searching for file %s", self.name) + fn = os.path.join(searchpath, self.name) + if os.path.isfile(fn): + logger.info("Found file %s", fn) + return fn + else: + logger.warn("No such file %s", fn) + return None + + +class InvalidManifestError(Exception): + pass + + +class ManifestXMLParseError(InvalidManifestError): + pass + + +class Manifest(object): + + # Manifests: + # http://msdn.microsoft.com/en-us/library/aa375365%28VS.85%29.aspx + + """ + Manifest constructor. + + To build a basic manifest for your application: + mf = Manifest(type='win32', name='YourAppName', language='*', + processorArchitecture='x86', version=[1, 0, 0, 0]) + + To write the XML to a manifest file: + mf.writexml("YourAppName.exe.manifest") + or + mf.writeprettyxml("YourAppName.exe.manifest") + + """ + + def __init__(self, manifestVersion=None, noInheritable=False, + noInherit=False, type_=None, name=None, language=None, + processorArchitecture=None, version=None, + publicKeyToken=None, description=None, + requestedExecutionLevel=None, uiAccess=None, + dependentAssemblies=None, files=None, + comInterfaceExternalProxyStubs=None): + self.filename = None + self.optional = None + self.manifestType = "assembly" + self.manifestVersion = manifestVersion or [1, 0] + self.noInheritable = noInheritable + self.noInherit = noInherit + self.type = type_ + self.name = name + self.language = language + self.processorArchitecture = processorArchitecture + self.version = version + self.publicKeyToken = publicKeyToken + # publicKeyToken: + # A 16-character hexadecimal string that represents the last 8 bytes + # of the SHA-1 hash of the public key under which the assembly is + # signed. The public key used to sign the catalog must be 2048 bits + # or greater. Required for all shared side-by-side assemblies. + # http://msdn.microsoft.com/en-us/library/aa375692(VS.85).aspx + self.applyPublisherPolicy = None + self.description = None + self.requestedExecutionLevel = requestedExecutionLevel + self.uiAccess = uiAccess + self.dependentAssemblies = dependentAssemblies or [] + self.bindingRedirects = [] + self.files = files or [] + self.comInterfaceExternalProxyStubs = comInterfaceExternalProxyStubs or [] # TO-DO: implement + + def add_dependent_assembly(self, manifestVersion=None, noInheritable=False, + noInherit=False, type_=None, name=None, language=None, + processorArchitecture=None, version=None, + publicKeyToken=None, description=None, + requestedExecutionLevel=None, uiAccess=None, + dependentAssemblies=None, files=None, + comInterfaceExternalProxyStubs=None): + """ + Shortcut for self.dependentAssemblies.append(Manifest(*args, **kwargs)) + """ + self.dependentAssemblies.append(Manifest(manifestVersion, + noInheritable, noInherit, type_, name, + language, processorArchitecture, + version, publicKeyToken, description, + requestedExecutionLevel, uiAccess, + dependentAssemblies, files, + comInterfaceExternalProxyStubs)) + if self.filename: + # Enable search for private assembly by assigning bogus filename + # (only the directory has to be correct) + self.dependentAssemblies[-1].filename = ":".join((self.filename, + name)) + + def add_file(self, name="", hashalg="", hash="", comClasses=None, + typelibs=None, comInterfaceProxyStubs=None, + windowClasses=None): + """ Shortcut for manifest.files.append """ + self.files.append(File(name, hashalg, hash, comClasses, + typelibs, comInterfaceProxyStubs, windowClasses)) + + def find_files(self, ignore_policies=True): + """ Search shared and private assemblies and return a list of files. + + If any files are not found, return an empty list. + + IMPORTANT NOTE: For the purpose of getting the dependent assembly + files of an executable, the publisher configuration (aka policy) + should be ignored (which is the default). Setting ignore_policies=False + is only useful to find out which files are actually loaded at + runtime. + + """ + + # Shared Assemblies: + # http://msdn.microsoft.com/en-us/library/aa375996%28VS.85%29.aspx + # + # Private Assemblies: + # http://msdn.microsoft.com/en-us/library/aa375674%28VS.85%29.aspx + # + # Assembly Searching Sequence: + # http://msdn.microsoft.com/en-us/library/aa374224%28VS.85%29.aspx + # + # NOTE: + # Multilanguage User Interface (MUI) support not yet implemented + + files = [] + + languages = [] + if self.language not in (None, "", "*", "neutral"): + languages.append(self.getlanguage()) + if "-" in self.language: + # language-culture syntax, e.g. en-us + # Add only the language part + languages.append(self.language.split("-")[0]) + if self.language not in ("en-us", "en"): + languages.append("en-us") + if self.language != "en": + languages.append("en") + languages.append(self.getlanguage("*")) + + winsxs = os.path.join(compat.getenv("SystemRoot"), "WinSxS") + if not os.path.isdir(winsxs): + logger.warn("No such dir %s", winsxs) + manifests = os.path.join(winsxs, "Manifests") + if not os.path.isdir(manifests): + logger.warn("No such dir %s", manifests) + if not ignore_policies and self.version: + if sys.getwindowsversion() < (6, ): + # Windows XP + pcfiles = os.path.join(winsxs, "Policies") + if not os.path.isdir(pcfiles): + logger.warn("No such dir %s", pcfiles) + else: + # Vista or later + pcfiles = manifests + + for language in languages: + version = self.version + + # Search for publisher configuration + if not ignore_policies and version: + # Publisher Configuration (aka policy) + # A publisher configuration file globally redirects + # applications and assemblies having a dependence on one + # version of a side-by-side assembly to use another version of + # the same assembly. This enables applications and assemblies + # to use the updated assembly without having to rebuild all of + # the affected applications. + # http://msdn.microsoft.com/en-us/library/aa375680%28VS.85%29.aspx + # + # Under Windows XP and 2003, policies are stored as + # .policy files inside + # %SystemRoot%\WinSxS\Policies\ + # Under Vista and later, policies are stored as + # .manifest files inside %SystemRoot%\winsxs\Manifests + redirected = False + if os.path.isdir(pcfiles): + logger.info("Searching for publisher configuration %s ...", + self.getpolicyid(True, language=language)) + if sys.getwindowsversion() < (6, ): + # Windows XP + policies = os.path.join(pcfiles, + self.getpolicyid(True, + language=language) + + ".policy") + else: + # Vista or later + policies = os.path.join(pcfiles, + self.getpolicyid(True, + language=language) + + ".manifest") + for manifestpth in glob(policies): + if not os.path.isfile(manifestpth): + logger.warn("Not a file %s", manifestpth) + continue + logger.info("Found %s", manifestpth) + try: + policy = ManifestFromXMLFile(manifestpth) + except Exception, exc: + logger.error("Could not parse file %s", manifestpth) + logger.exception(exc) + else: + logger.info("Checking publisher policy for " + "binding redirects") + for assembly in policy.dependentAssemblies: + if (not assembly.same_id(self, True) or + assembly.optional): + continue + for redirect in assembly.bindingRedirects: + if logger.isEnabledFor(logging.INFO): + old = "-".join([".".join([str(i) + for i in + part]) + for part in + redirect[0]]) + new = ".".join([str(i) + for i in + redirect[1]]) + logger.info("Found redirect for " + "version(s) %s -> %n", + old, new) + if (version >= redirect[0][0] and + version <= redirect[0][-1] and + version != redirect[1]): + logger.info("Applying redirect " + "%s -> %s", + ".".join([str(i) + for i in + version]), + new) + version = redirect[1] + redirected = True + if not redirected: + logger.info("Publisher configuration not used") + + # Search for assemblies according to assembly searching sequence + paths = [] + if os.path.isdir(manifests): + # Add winsxs search paths + paths.extend(glob(os.path.join(manifests, + self.getid(language=language, + version=version) + + "_*.manifest"))) + if self.filename: + # Add private assembly search paths + dirnm = os.path.dirname(self.filename) + if language in (LANGUAGE_NEUTRAL_NT5, + LANGUAGE_NEUTRAL_NT6): + for ext in (".dll", ".manifest"): + paths.extend(glob(os.path.join(dirnm, self.name + ext))) + paths.extend(glob(os.path.join(dirnm, self.name, + self.name + ext))) + else: + for ext in (".dll", ".manifest"): + paths.extend(glob(os.path.join(dirnm, language, + self.name + ext))) + for ext in (".dll", ".manifest"): + paths.extend(glob(os.path.join(dirnm, language, + self.name, + self.name + ext))) + logger.info("Searching for assembly %s ...", + self.getid(language=language, version=version)) + for manifestpth in paths: + if not os.path.isfile(manifestpth): + logger.warn("Not a file %s", manifestpth) + continue + assemblynm = os.path.basename( + os.path.splitext(manifestpth)[0]) + try: + if manifestpth.endswith(".dll"): + logger.info("Found manifest in %s", manifestpth) + manifest = ManifestFromResFile(manifestpth, [1]) + else: + logger.info("Found manifest %s", manifestpth) + manifest = ManifestFromXMLFile(manifestpth) + except Exception, exc: + logger.error("Could not parse manifest %s", manifestpth) + logger.exception(exc) + else: + if manifestpth.startswith(winsxs): + assemblydir = os.path.join(winsxs, assemblynm) + if not os.path.isdir(assemblydir): + logger.warn("No such dir %s", assemblydir) + logger.warn("Assembly incomplete") + return [] + else: + assemblydir = os.path.dirname(manifestpth) + files.append(manifestpth) + for file_ in self.files or manifest.files: + fn = file_.find(assemblydir) + if fn: + files.append(fn) + else: + # If any of our files does not exist, + # the assembly is incomplete + logger.warn("Assembly incomplete") + return [] + return files + + logger.warn("Assembly not found") + return [] + + def getid(self, language=None, version=None): + """ + Return an identification string which uniquely names a manifest. + + This string is a combination of the manifest's processorArchitecture, + name, publicKeyToken, version and language. + + Arguments: + version (tuple or list of integers) - If version is given, use it + instead of the manifest's + version. + + """ + if not self.name: + logger.warn("Assembly metadata incomplete") + return "" + id = [] + if self.processorArchitecture: + id.append(self.processorArchitecture) + id.append(self.name) + if self.publicKeyToken: + id.append(self.publicKeyToken) + if version or self.version: + id.append(".".join([str(i) for i in version or self.version])) + if not language: + language = self.getlanguage() + if language: + id.append(language) + return "_".join(id) + + def getlanguage(self, language=None, windowsversion=None): + """ + Get and return the manifest's language as string. + + Can be either language-culture e.g. 'en-us' or a string indicating + language neutrality, e.g. 'x-ww' on Windows XP or 'none' on Vista + and later. + + """ + if not language: + language = self.language + if language in (None, "", "*", "neutral"): + return (LANGUAGE_NEUTRAL_NT5, + LANGUAGE_NEUTRAL_NT6)[(windowsversion or + sys.getwindowsversion()) >= (6, )] + return language + + def getpolicyid(self, fuzzy=True, language=None, windowsversion=None): + """ + Return an identification string which can be used to find a policy. + + This string is a combination of the manifest's processorArchitecture, + major and minor version, name, publicKeyToken and language. + + Arguments: + fuzzy (boolean) - If False, insert the full version in + the id string. Default is True (omit). + windowsversion - If not specified (or None), default to + (tuple or list of integers) sys.getwindowsversion(). + + """ + if not self.name: + logger.warn("Assembly metadata incomplete") + return "" + id = [] + if self.processorArchitecture: + id.append(self.processorArchitecture) + name = [] + name.append("policy") + if self.version: + name.append(str(self.version[0])) + name.append(str(self.version[1])) + name.append(self.name) + id.append(".".join(name)) + if self.publicKeyToken: + id.append(self.publicKeyToken) + if self.version and (windowsversion or sys.getwindowsversion()) >= (6, ): + # Vista and later + if fuzzy: + id.append("*") + else: + id.append(".".join([str(i) for i in self.version])) + if not language: + language = self.getlanguage(windowsversion=windowsversion) + if language: + id.append(language) + id.append("*") + id = "_".join(id) + if self.version and (windowsversion or sys.getwindowsversion()) < (6, ): + # Windows XP + if fuzzy: + id = os.path.join(id, "*") + else: + id = os.path.join(id, ".".join([str(i) for i in self.version])) + return id + + def load_dom(self, domtree, initialize=True): + """ + Load manifest from DOM tree. + + If initialize is True (default), reset existing attributes first. + + """ + if domtree.nodeType == Node.DOCUMENT_NODE: + rootElement = domtree.documentElement + elif domtree.nodeType == Node.ELEMENT_NODE: + rootElement = domtree + else: + raise InvalidManifestError("Invalid root element node type " + + str(rootElement.nodeType) + + " - has to be one of (DOCUMENT_NODE, " + "ELEMENT_NODE)") + allowed_names = ("assembly", "assemblyBinding", "configuration", + "dependentAssembly") + if rootElement.tagName not in allowed_names: + raise InvalidManifestError( + "Invalid root element <%s> - has to be one of <%s>" % + (rootElement.tagName, ">, <".join(allowed_names))) + # logger.info("loading manifest metadata from element <%s>", rootElement.tagName) + if rootElement.tagName == "configuration": + for windows in rootElement.getCEByTN("windows"): + for assemblyBinding in windows.getCEByTN("assemblyBinding"): + self.load_dom(assemblyBinding, initialize) + else: + if initialize: + self.__init__() + self.manifestType = rootElement.tagName + self.manifestVersion = [int(i) + for i in + (rootElement.getA("manifestVersion") or + "1.0").split(".")] + self.noInheritable = bool(rootElement.getFCEByTN("noInheritable")) + self.noInherit = bool(rootElement.getFCEByTN("noInherit")) + for assemblyIdentity in rootElement.getCEByTN("assemblyIdentity"): + self.type = assemblyIdentity.getA("type") or None + self.name = assemblyIdentity.getA("name") or None + self.language = assemblyIdentity.getA("language") or None + self.processorArchitecture = assemblyIdentity.getA( + "processorArchitecture") or None + version = assemblyIdentity.getA("version") + if version: + self.version = [int(i) for i in version.split(".")] + self.publicKeyToken = assemblyIdentity.getA("publicKeyToken") or None + for publisherPolicy in rootElement.getCEByTN("publisherPolicy"): + self.applyPublisherPolicy = (publisherPolicy.getA("apply") or + "").lower() == "yes" + for description in rootElement.getCEByTN("description"): + if description.firstChild: + self.description = description.firstChild.wholeText + for trustInfo in rootElement.getCEByTN("trustInfo"): + for security in trustInfo.getCEByTN("security"): + for reqPriv in security.getCEByTN("requestedPrivileges"): + for reqExeLev in reqPriv.getCEByTN("requestedExecutionLevel"): + self.requestedExecutionLevel = reqExeLev.getA("level") + self.uiAccess = (reqExeLev.getA("uiAccess") or + "").lower() == "true" + if rootElement.tagName == "assemblyBinding": + dependencies = [rootElement] + else: + dependencies = rootElement.getCEByTN("dependency") + for dependency in dependencies: + for dependentAssembly in dependency.getCEByTN( + "dependentAssembly"): + manifest = ManifestFromDOM(dependentAssembly) + if not manifest.name: + # invalid, skip + continue + manifest.optional = (dependency.getA("optional") or + "").lower() == "yes" + self.dependentAssemblies.append(manifest) + if self.filename: + # Enable search for private assembly by assigning bogus + # filename (only the directory has to be correct) + self.dependentAssemblies[-1].filename = ":".join( + (self.filename, manifest.name)) + for bindingRedirect in rootElement.getCEByTN("bindingRedirect"): + oldVersion = [[int(i) for i in part.split(".")] + for part in + bindingRedirect.getA("oldVersion").split("-")] + newVersion = [int(i) + for i in + bindingRedirect.getA("newVersion").split(".")] + self.bindingRedirects.append((oldVersion, newVersion)) + for file_ in rootElement.getCEByTN("file"): + self.add_file(name=file_.getA("name"), + hashalg=file_.getA("hashalg"), + hash=file_.getA("hash")) + + def parse(self, filename_or_file, initialize=True): + """ Load manifest from file or file object """ + if isinstance(filename_or_file, (str, unicode)): + filename = filename_or_file + else: + filename = filename_or_file.name + try: + domtree = minidom.parse(filename_or_file) + except xml.parsers.expat.ExpatError, e: + args = [e.args[0]] + if isinstance(filename, unicode): + filename = filename.encode(sys.getdefaultencoding(), "replace") + args.insert(0, '\n File "%s"\n ' % filename) + raise ManifestXMLParseError(" ".join([str(arg) for arg in args])) + if initialize: + self.__init__() + self.filename = filename + self.load_dom(domtree, False) + + def parse_string(self, xmlstr, initialize=True): + """ Load manifest from XML string """ + try: + domtree = minidom.parseString(xmlstr) + except xml.parsers.expat.ExpatError, e: + raise ManifestXMLParseError(e) + self.load_dom(domtree, initialize) + + def same_id(self, manifest, skip_version_check=False): + """ + Return a bool indicating if another manifest has the same identitiy. + + This is done by comparing language, name, processorArchitecture, + publicKeyToken, type and version. + + """ + if skip_version_check: + version_check = True + else: + version_check = self.version == manifest.version + return (self.language == manifest.language and + self.name == manifest.name and + self.processorArchitecture == manifest.processorArchitecture and + self.publicKeyToken == manifest.publicKeyToken and + self.type == manifest.type and + version_check) + + def todom(self): + """ Return the manifest as DOM tree """ + doc = Document() + docE = doc.cE(self.manifestType) + if self.manifestType == "assemblyBinding": + cfg = doc.cE("configuration") + win = doc.cE("windows") + win.aChild(docE) + cfg.aChild(win) + doc.aChild(cfg) + else: + doc.aChild(docE) + if self.manifestType != "dependentAssembly": + docE.setA("xmlns", "urn:schemas-microsoft-com:asm.v1") + if self.manifestType != "assemblyBinding": + docE.setA("manifestVersion", + ".".join([str(i) for i in self.manifestVersion])) + if self.noInheritable: + docE.aChild(doc.cE("noInheritable")) + if self.noInherit: + docE.aChild(doc.cE("noInherit")) + aId = doc.cE("assemblyIdentity") + if self.type: + aId.setAttribute("type", self.type) + if self.name: + aId.setAttribute("name", self.name) + if self.language: + aId.setAttribute("language", self.language) + if self.processorArchitecture: + aId.setAttribute("processorArchitecture", + self.processorArchitecture) + if self.version: + aId.setAttribute("version", + ".".join([str(i) for i in self.version])) + if self.publicKeyToken: + aId.setAttribute("publicKeyToken", self.publicKeyToken) + if aId.hasAttributes(): + docE.aChild(aId) + else: + aId.unlink() + if self.applyPublisherPolicy != None: + ppE = doc.cE("publisherPolicy") + if self.applyPublisherPolicy: + ppE.setA("apply", "yes") + else: + ppE.setA("apply", "no") + docE.aChild(ppE) + if self.description: + descE = doc.cE("description") + descE.aChild(doc.cT(self.description)) + docE.aChild(descE) + if self.requestedExecutionLevel in ("asInvoker", "highestAvailable", + "requireAdministrator"): + tE = doc.cE("trustInfo") + tE.setA("xmlns", "urn:schemas-microsoft-com:asm.v3") + sE = doc.cE("security") + rpE = doc.cE("requestedPrivileges") + relE = doc.cE("requestedExecutionLevel") + relE.setA("level", self.requestedExecutionLevel) + if self.uiAccess: + relE.setA("uiAccess", "true") + else: + relE.setA("uiAccess", "false") + rpE.aChild(relE) + sE.aChild(rpE) + tE.aChild(sE) + docE.aChild(tE) + if self.dependentAssemblies: + for assembly in self.dependentAssemblies: + if self.manifestType != "assemblyBinding": + dE = doc.cE("dependency") + if assembly.optional: + dE.setAttribute("optional", "yes") + daE = doc.cE("dependentAssembly") + adom = assembly.todom() + for child in adom.documentElement.childNodes: + daE.aChild(child.cloneNode(False)) + adom.unlink() + if self.manifestType != "assemblyBinding": + dE.aChild(daE) + docE.aChild(dE) + else: + docE.aChild(daE) + if self.bindingRedirects: + for bindingRedirect in self.bindingRedirects: + brE = doc.cE("bindingRedirect") + brE.setAttribute("oldVersion", + "-".join([".".join([str(i) + for i in + part]) + for part in + bindingRedirect[0]])) + brE.setAttribute("newVersion", + ".".join([str(i) for i in bindingRedirect[1]])) + docE.aChild(brE) + if self.files: + for file_ in self.files: + fE = doc.cE("file") + for attr in ("name", "hashalg", "hash"): + val = getattr(file_, attr) + if val: + fE.setA(attr, val) + docE.aChild(fE) + return doc + + def toprettyxml(self, indent=" ", newl=os.linesep, encoding="UTF-8"): + """ Return the manifest as pretty-printed XML """ + domtree = self.todom() + # WARNING: The XML declaration has to follow the order + # version-encoding-standalone (standalone being optional), otherwise + # if it is embedded in an exe the exe will fail to launch! + # ('application configuration incorrect') + if sys.version_info >= (2,3): + xmlstr = domtree.toprettyxml(indent, newl, encoding) + else: + xmlstr = domtree.toprettyxml(indent, newl) + xmlstr = xmlstr.strip(os.linesep).replace( + '' % encoding, + '' % + encoding) + domtree.unlink() + return xmlstr + + def toxml(self, encoding="UTF-8"): + """ Return the manifest as XML """ + domtree = self.todom() + # WARNING: The XML declaration has to follow the order + # version-encoding-standalone (standalone being optional), otherwise + # if it is embedded in an exe the exe will fail to launch! + # ('application configuration incorrect') + xmlstr = domtree.toxml(encoding).replace( + '' % encoding, + '' % encoding) + domtree.unlink() + return xmlstr + + def update_resources(self, dstpath, names=None, languages=None): + """ Update or add manifest resource in dll/exe file dstpath """ + UpdateManifestResourcesFromXML(dstpath, self.toprettyxml(), names, + languages) + + def writeprettyxml(self, filename_or_file=None, indent=" ", newl=os.linesep, + encoding="UTF-8"): + """ Write the manifest as XML to a file or file object """ + if not filename_or_file: + filename_or_file = self.filename + if isinstance(filename_or_file, (str, unicode)): + filename_or_file = open(filename_or_file, "wb") + xmlstr = self.toprettyxml(indent, newl, encoding) + filename_or_file.write(xmlstr) + filename_or_file.close() + + def writexml(self, filename_or_file=None, indent=" ", newl=os.linesep, + encoding="UTF-8"): + """ Write the manifest as XML to a file or file object """ + if not filename_or_file: + filename_or_file = self.filename + if isinstance(filename_or_file, (str, unicode)): + filename_or_file = open(filename_or_file, "wb") + xmlstr = self.toxml(indent, newl, encoding) + filename_or_file.write(xmlstr) + filename_or_file.close() + + +def ManifestFromResFile(filename, names=None, languages=None): + """ Create and return manifest instance from resource in dll/exe file """ + res = GetManifestResources(filename, names, languages) + pth = [] + if res and res[RT_MANIFEST]: + while isinstance(res, dict) and res.keys(): + key = res.keys()[0] + pth.append(str(key)) + res = res[key] + if isinstance(res, dict): + raise InvalidManifestError("No matching manifest resource found in '%s'" % + filename) + manifest = Manifest() + manifest.filename = ":".join([filename] + pth) + manifest.parse_string(res, False) + return manifest + + +def ManifestFromDOM(domtree): + """ Create and return manifest instance from DOM tree """ + manifest = Manifest() + manifest.load_dom(domtree) + return manifest + + +def ManifestFromXML(xmlstr): + """ Create and return manifest instance from XML """ + manifest = Manifest() + manifest.parse_string(xmlstr) + return manifest + + +def ManifestFromXMLFile(filename_or_file): + """ Create and return manifest instance from file """ + manifest = Manifest() + manifest.parse(filename_or_file) + return manifest + + +def GetManifestResources(filename, names=None, languages=None): + """ Get manifest resources from file """ + return winresource.GetResources(filename, [RT_MANIFEST], names, languages) + + +def UpdateManifestResourcesFromXML(dstpath, xmlstr, names=None, + languages=None): + """ Update or add manifest XML as resource in dstpath """ + logger.info("Updating manifest in %s", dstpath) + if dstpath.lower().endswith(".exe"): + name = 1 + else: + name = 2 + winresource.UpdateResources(dstpath, xmlstr, RT_MANIFEST, names or [name], + languages or [0, "*"]) + + +def UpdateManifestResourcesFromXMLFile(dstpath, srcpath, names=None, + languages=None): + """ Update or add manifest XML from srcpath as resource in dstpath """ + logger.info("Updating manifest from %s in %s", srcpath, dstpath) + if dstpath.lower().endswith(".exe"): + name = 1 + else: + name = 2 + winresource.UpdateResourcesFromDataFile(dstpath, srcpath, RT_MANIFEST, + names or [name], + languages or [0, "*"]) + + +def create_manifest(filename, manifest, console): + """ + Create assembly manifest. + """ + if not manifest: + manifest = ManifestFromXMLFile(filename) + # /path/NAME.exe.manifest - split extension twice to get NAME. + name = os.path.basename(filename) + manifest.name = os.path.splitext(os.path.splitext(name)[0])[0] + elif isinstance(manifest, basestring) and "<" in manifest: + # Assume XML string + manifest = ManifestFromXML(manifest) + elif not isinstance(manifest, Manifest): + # Assume filename + manifest = ManifestFromXMLFile(manifest) + dep_names = set([dep.name for dep in manifest.dependentAssemblies]) + if manifest.filename != filename: + # Update dependent assemblies + depmanifest = ManifestFromXMLFile(filename) + for assembly in depmanifest.dependentAssemblies: + if not assembly.name in dep_names: + manifest.dependentAssemblies.append(assembly) + dep_names.add(assembly.name) + if (not console and + not "Microsoft.Windows.Common-Controls" in dep_names): + # Add Microsoft.Windows.Common-Controls to dependent assemblies + manifest.dependentAssemblies.append( + Manifest(type_="win32", + name="Microsoft.Windows.Common-Controls", + language="*", + processorArchitecture=processor_architecture(), + version=(6, 0, 0, 0), + publicKeyToken="6595b64144ccf1df") + ) + manifest.writeprettyxml(filename) + return manifest + + +def processor_architecture(): + """ + Detect processor architecture for assembly manifest. + + According to: + http://msdn.microsoft.com/en-us/library/windows/desktop/aa374219(v=vs.85).aspx + item processorArchitecture in assembly manifest is + + 'x86' - 32bit Windows + 'amd64' - 64bit Windows + """ + if architecture() == '32bit': + return 'x86' + else: + return 'amd64' + + +if __name__ == "__main__": + dstpath = sys.argv[1] + srcpath = sys.argv[2] + UpdateManifestResourcesFromXMLFile(dstpath, srcpath) diff --git a/pyinstaller/PyInstaller/utils/winresource.py b/pyinstaller/PyInstaller/utils/winresource.py new file mode 100644 index 0000000..ee25fb9 --- /dev/null +++ b/pyinstaller/PyInstaller/utils/winresource.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python +# +# Copyright (C) 2009, Florian Hoech +# +# 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, 5th Floor, Boston, MA 02110-1301, USA + +""" +winresource.py + +Read and write resources from/to Win32 PE files. + +Commandline usage: +winresource.py +Updates or adds resources from file in file . + +2009-03 Florian Hoech + +""" + +import os.path +import pywintypes +import win32api + +import PyInstaller.log as logging +logger = logging.getLogger('PyInstaller.build.winresource') + +from PyInstaller.compat import set + +LOAD_LIBRARY_AS_DATAFILE = 2 +ERROR_BAD_EXE_FORMAT = 193 +ERROR_RESOURCE_DATA_NOT_FOUND = 1812 +ERROR_RESOURCE_TYPE_NOT_FOUND = 1813 +ERROR_RESOURCE_NAME_NOT_FOUND = 1814 +ERROR_RESOURCE_LANG_NOT_FOUND = 1815 + + +class File(object): + + """ Win32 PE file class. """ + + def __init__(self, filename): + self.filename = filename + + def get_resources(self, types=None, names=None, languages=None): + """ + Get resources. + + types = a list of resource types to search for (None = all) + names = a list of resource names to search for (None = all) + languages = a list of resource languages to search for (None = all) + Return a dict of the form {type_: {name: {language: data}}} which + might also be empty if no matching resources were found. + + """ + return GetResources(self.filename, types, names, languages) + + def update_resources(self, data, type_, names=None, languages=None): + """ + Update or add resource data. + + type_ = resource type to update + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + UpdateResources(self.filename, data, type_, names, languages) + + def update_resources_from_datafile(self, srcpath, type_, names=None, + languages=None): + """ + Update or add resource data from file srcpath. + + type_ = resource type to update + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + UpdateResourcesFromDataFile(self.filename, srcpath, type_, names, + languages) + + def update_resources_from_dict(self, res, types=None, names=None, + languages=None): + """ + Update or add resources from resource dict. + + types = a list of resource types to update (None = all) + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + UpdateResourcesFromDict(self.filename, res, types, names, + languages) + + def update_resources_from_resfile(self, srcpath, types=None, names=None, + languages=None): + """ + Update or add resources from dll/exe file srcpath. + + types = a list of resource types to update (None = all) + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + UpdateResourcesFromResFile(self.filename, srcpath, types, names, + languages) + + +def _GetResources(hsrc, types=None, names=None, languages=None): + """ + Get resources from hsrc. + + types = a list of resource types to search for (None = all) + names = a list of resource names to search for (None = all) + languages = a list of resource languages to search for (None = all) + Return a dict of the form {type_: {name: {language: data}}} which + might also be empty if no matching resources were found. + + """ + if types: types = set(types) + if names: names = set(names) + if languages: languages = set(languages) + res = {} + try: + # logger.debug("Enumerating resource types") + enum_types = win32api.EnumResourceTypes(hsrc) + if types and not "*" in types: + enum_types = filter(lambda type_: + type_ in types, + enum_types) + for type_ in enum_types: + # logger.debug("Enumerating resources of type %s", type_) + enum_names = win32api.EnumResourceNames(hsrc, type_) + if names and not "*" in names: + enum_names = filter(lambda name: + name in names, + enum_names) + for name in enum_names: + # logger.debug("Enumerating resources of type %s name %s", type_, name) + enum_languages = win32api.EnumResourceLanguages(hsrc, + type_, + name) + if languages and not "*" in languages: + enum_languages = filter(lambda language: + language in languages, + enum_languages) + for language in enum_languages: + data = win32api.LoadResource(hsrc, type_, name, language) + if not type_ in res: + res[type_] = {} + if not name in res[type_]: + res[type_][name] = {} + res[type_][name][language] = data + except pywintypes.error, exception: + if exception.args[0] in (ERROR_RESOURCE_DATA_NOT_FOUND, + ERROR_RESOURCE_TYPE_NOT_FOUND, + ERROR_RESOURCE_NAME_NOT_FOUND, + ERROR_RESOURCE_LANG_NOT_FOUND): + # logger.info('%s: %s', exception.args[1:3]) + pass + else: + raise exception + return res + + +def GetResources(filename, types=None, names=None, languages=None): + """ + Get resources from dll/exe file. + + types = a list of resource types to search for (None = all) + names = a list of resource names to search for (None = all) + languages = a list of resource languages to search for (None = all) + Return a dict of the form {type_: {name: {language: data}}} which + might also be empty if no matching resources were found. + + """ + hsrc = win32api.LoadLibraryEx(filename, 0, LOAD_LIBRARY_AS_DATAFILE) + res = _GetResources(hsrc, types, names, languages) + win32api.FreeLibrary(hsrc) + return res + + +def UpdateResources(dstpath, data, type_, names=None, languages=None): + """ + Update or add resource data in dll/exe file dstpath. + + type_ = resource type to update + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + # look for existing resources + res = GetResources(dstpath, [type_], names, languages) + # add type_, names and languages not already present in existing resources + if not type_ in res and type_ != "*": + res[type_] = {} + if names: + for name in names: + if not name in res[type_] and name != "*": + res[type_][name] = [] + if languages: + for language in languages: + if not language in res[type_][name] and language != "*": + res[type_][name].append(language) + # add resource to destination, overwriting existing resources + hdst = win32api.BeginUpdateResource(dstpath, 0) + for type_ in res: + for name in res[type_]: + for language in res[type_][name]: + logger.info("Updating resource type %s name %s language %s", + type_, name, language) + win32api.UpdateResource(hdst, type_, name, data, language) + win32api.EndUpdateResource(hdst, 0) + + +def UpdateResourcesFromDataFile(dstpath, srcpath, type_, names=None, + languages=None): + """ + Update or add resource data from file srcpath in dll/exe file dstpath. + + type_ = resource type to update + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + src = open(srcpath, "rb") + data = src.read() + src.close() + UpdateResources(dstpath, data, type_, names, languages) + + +def UpdateResourcesFromDict(dstpath, res, types=None, names=None, + languages=None): + """ + Update or add resources from resource dict in dll/exe file dstpath. + + types = a list of resource types to update (None = all) + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + if types: types = set(types) + if names: names = set(names) + if langauges: languages = set(languages) + for type_ in res: + if not types or type_ in types: + for name in res[type_]: + if not names or name in names: + for language in res[type_][name]: + if not languages or language in languages: + UpdateResources(dstpath, + res[type_][name][language], + [type_], [name], [language]) + + +def UpdateResourcesFromResFile(dstpath, srcpath, types=None, names=None, + languages=None): + """ + Update or add resources from dll/exe file srcpath in dll/exe file dstpath. + + types = a list of resource types to update (None = all) + names = a list of resource names to update (None = all) + languages = a list of resource languages to update (None = all) + + """ + res = GetResources(srcpath, types, names, languages) + UpdateResourcesFromDict(dstpath, res) diff --git a/pyinstaller/PyInstaller/utils/winutils.py b/pyinstaller/PyInstaller/utils/winutils.py new file mode 100644 index 0000000..467beb2 --- /dev/null +++ b/pyinstaller/PyInstaller/utils/winutils.py @@ -0,0 +1,65 @@ +# +# Copyright (C) 2005-2011, Giovanni Bajo +# Based on previous work under copyright (c) 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 + + +# Utils for Windows platform. + + +__all__ = ['get_windows_dir'] + +import os + +from PyInstaller import compat + +import PyInstaller.log as logging +logger = logging.getLogger(__name__) + + +def get_windows_dir(): + """ + Return the Windows directory e.g. C:\\Windows. + """ + try: + import win32api + except ImportError: + windir = compat.getenv('SystemRoot', compat.getenv('WINDIR')) + else: + windir = win32api.GetWindowsDirectory() + if not windir: + raise SystemExit("Error: Can not determine your Windows directory") + return windir + + +def get_system_path(): + """ + Return the path that Windows will search for dlls. + """ + _bpath = [] + try: + import win32api + sys_dir = win32api.GetSystemDirectory() + except ImportError: + sys_dir = os.path.normpath(os.path.join(get_windows_dir(), 'system32')) + # Ensure C:\Windows\system32 and C:\Windows directories are + # always present in PATH variable. + # C:\Windows\system32 is valid even for 64bit Windows. Access do DLLs are + # transparently redirected to C:\Windows\syswow64 for 64bit applactions. + # http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx + _bpath = [sys_dir, get_windows_dir()] + _bpath.extend(compat.getenv('PATH', '').split(os.pathsep)) + return _bpath diff --git a/pyinstaller/README.rst b/pyinstaller/README.rst new file mode 100644 index 0000000..617553f --- /dev/null +++ b/pyinstaller/README.rst @@ -0,0 +1,69 @@ +PyInstaller +=========== +Official website: http://www.pyinstaller.org + +Requirements +------------ +- Python: + * 2.3 - 2.7 (Python 3 is not supported) + +- Windows (32bit/64bit): + * Windows XP or newer. + * pywin32_ when using Python 2.6+ + +- Linux (32bit/64bit) + * ldd: console application to print the shared libraries required + by each program or shared library. + * objdump: Console application to display information from + object files. + +- Mac OS X (32/64bit): + * Mac OS X 10.4 (Tiger) or newer (Leopard, Snow Leopard, Lion). + +- Solaris (experimental) + * ldd + * objdump + +- AIX (experimental) + * AIX 6.1 or newer. + Python executables created using PyInstaller on AIX 6.1 should + work on AIX 5.2/5.3. PyInstaller will not work with statically + linked Python libraries which has been encountered in Python 2.2 + installations on AIX 5.x. + * ldd + * objdump + + +Usage +----- + +:: + + python pyinstaller.py /path/to/yourscript.py + +For more details, see the `doc/Manual.html`_. + +Installation in brief +--------------------- + +1. Unpack the archive on you path of choice. +2. For Windows (32/64bit), Linux (32/64bit) and Mac OS X (32/64bit) + precompiled boot-loaders are available. So the installation is + complete. + + For other platforms, users should first try to build the + boot-loader:: + + cd source + python ./waf configure build install + + +Major changes in this release +----------------------------- +See `doc/CHANGES.txt`_. + + +.. _pywin32: http://sourceforge.net/projects/pywin32/ +.. _`doc/Manual.html`: http://www.pyinstaller.org/export/develop/project/doc/Manual.html?format=raw +.. _`doc/CHANGES.txt`: http://www.pyinstaller.org/export/develop/project/doc/CHANGES.txt?format=raw + diff --git a/pyinstaller/buildtests/.svn/entries b/pyinstaller/buildtests/.svn/entries new file mode 100644 index 0000000..70c1d07 --- /dev/null +++ b/pyinstaller/buildtests/.svn/entries @@ -0,0 +1,84 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +basic +dir + + + +add + +multipackage +dir + + + +add + +eggs4testing +dir + + + +add + +import +dir + + + +add + +setupenv_windows.py +file + + + +add + +libraries +dir + + + +add + +interactive +dir + + + +add + +runtests.py +file + + + +add + diff --git a/pyinstaller/buildtests/basic/.svn/entries b/pyinstaller/buildtests/basic/.svn/entries new file mode 100644 index 0000000..01bd2c2 --- /dev/null +++ b/pyinstaller/buildtests/basic/.svn/entries @@ -0,0 +1,476 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/basic +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test_python_home.py +file + + + +add + +test_nestedlaunch0.py +file + + + +add + +test_absolute_ld_library_path.py +file + + + +add + +ctypeslib.py +file + + + +add + +test_6.spec +file + + + +add + +test_8.py +file + + + +add + +test_chdir_meipass.py +file + + + +add + +pkg1 +dir + + + +add + +test_chdir_meipass.spec +file + + + +add + +pkg2 +dir + + + +add + +test_f_option.py +file + + + +add + +test_python_makefile.py +file + + + +add + +hooks1 +dir + + + +add + +test_python_makefile.spec +file + + + +add + +test_python_makefile_onefile.spec +file + + + +add + +test_email.py +file + + + +add + +test_module_attributes.py +file + + + +add + +test_getfilesystemencoding.spec +file + + + +add + +test_time.py +file + + + +add + +test_time.spec +file + + + +add + +test_encoders.py +file + + + +add + +test_encoders.spec +file + + + +add + +test_threading.spec +file + + + +add + +test_ctypes.py +file + + + +add + +test_celementtree.py +file + + + +add + +test_helloworld.spec +file + + + +add + +test_celementtree.spec +file + + + +add + +test_filename.py +file + + + +add + +test_filename.spec +file + + + +add + +test_13.py +file + + + +add + +test_pkg_structures.py +file + + + +add + +test_pkg_structures.spec +file + + + +add + +test_threading2.spec +file + + + +add + +test_onefile_multiprocess.py +file + + + +add + +test_nestedlaunch0.spec +file + + + +add + +data7.py +file + + + +add + +test_6.py +file + + + +add + +test_email_oldstyle.py +file + + + +add + +test_8.spec +file + + + +add + +test_get_meipass2_value.py +file + + + +add + +test_python_makefile_onefile.py +file + + + +add + +test_f_option.spec +file + + + +add + +test_get_meipass2_value.spec +file + + + +add + +test_12.py +file + + + +add + +test_12.spec +file + + + +add + +test_getfilesystemencoding.py +file + + + +add + +test_absolute_python_path.py +file + + + +add + +test_module_attributes.spec +file + + + +add + +test_pkg_structures-version.txt +file + + + +add + +test_nestedlaunch1.py +file + + + +add + +test_nestedlaunch1.spec +file + + + +add + +data6.py +file + + + +add + +test_site.py +file + + + +add + +test_5.py +file + + + +add + +test_threading.py +file + + + +add + +test_site.spec +file + + + +add + +test_pkg_structures.ico +file + + + +add + + + + + +has-props +has-prop-mods + +test_5.spec +file + + + +add + +test_ctypes.spec +file + + + +add + +test_helloworld.py +file + + + +add + +ctypes +dir + + + +add + +test_13.spec +file + + + +add + +test_threading2.py +file + + + +add + diff --git a/pyinstaller/buildtests/basic/.svn/props/test_pkg_structures.ico.svn-work b/pyinstaller/buildtests/basic/.svn/props/test_pkg_structures.ico.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/pyinstaller/buildtests/basic/.svn/props/test_pkg_structures.ico.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/pyinstaller/buildtests/basic/ctypes/.svn/entries b/pyinstaller/buildtests/basic/ctypes/.svn/entries new file mode 100644 index 0000000..3557126 --- /dev/null +++ b/pyinstaller/buildtests/basic/ctypes/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/basic/ctypes +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +testctypes-win.c +file + + + +add + +testctypes.c +file + + + +add + diff --git a/pyinstaller/buildtests/basic/ctypes/testctypes-win.c b/pyinstaller/buildtests/basic/ctypes/testctypes-win.c new file mode 100644 index 0000000..bb57f7d --- /dev/null +++ b/pyinstaller/buildtests/basic/ctypes/testctypes-win.c @@ -0,0 +1,4 @@ +int __declspec(dllexport) dummy(int arg) +{ + return arg; +} diff --git a/pyinstaller/buildtests/basic/ctypes/testctypes.c b/pyinstaller/buildtests/basic/ctypes/testctypes.c new file mode 100644 index 0000000..3bf8b84 --- /dev/null +++ b/pyinstaller/buildtests/basic/ctypes/testctypes.c @@ -0,0 +1,4 @@ +int dummy(int arg) +{ + return arg; +} diff --git a/pyinstaller/buildtests/basic/ctypeslib.py b/pyinstaller/buildtests/basic/ctypeslib.py new file mode 100644 index 0000000..b560fe1 --- /dev/null +++ b/pyinstaller/buildtests/basic/ctypeslib.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +import sys +from ctypes import * + +# Current working directory is set to dist directory for tests. + +def dummy(arg): + if sys.platform == "win32": + tct = CDLL("..\\..\\ctypes\\testctypes-win.dll") + elif sys.platform.startswith("darwin"): + tct = CDLL("../../ctypes/testctypes.dylib") + else: + tct = CDLL("../../ctypes/testctypes.so") + return tct.dummy(arg) diff --git a/pyinstaller/buildtests/basic/data6.py b/pyinstaller/buildtests/basic/data6.py new file mode 100644 index 0000000..407de30 --- /dev/null +++ b/pyinstaller/buildtests/basic/data6.py @@ -0,0 +1 @@ +x = 2 diff --git a/pyinstaller/buildtests/basic/data7.py b/pyinstaller/buildtests/basic/data7.py new file mode 100644 index 0000000..970a87a --- /dev/null +++ b/pyinstaller/buildtests/basic/data7.py @@ -0,0 +1,21 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +#nasty +import time +time.sleep(5) +x = 5 diff --git a/pyinstaller/buildtests/basic/hooks1/.svn/entries b/pyinstaller/buildtests/basic/hooks1/.svn/entries new file mode 100644 index 0000000..97f5c7f --- /dev/null +++ b/pyinstaller/buildtests/basic/hooks1/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/basic/hooks1 +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +hook-pkg1.py +file + + + +add + diff --git a/pyinstaller/buildtests/basic/hooks1/hook-pkg1.py b/pyinstaller/buildtests/basic/hooks1/hook-pkg1.py new file mode 100644 index 0000000..6091dbd --- /dev/null +++ b/pyinstaller/buildtests/basic/hooks1/hook-pkg1.py @@ -0,0 +1,11 @@ +attrs = [('notamodule','')] +def hook(mod): + import os, sys, marshal + other = os.path.join(mod.__path__[0], '../pkg2/__init__.pyc') + if os.path.exists(other): + co = marshal.loads(open(other,'rb').read()[8:]) + else: + co = compile(open(other[:-1],'rU').read()+'\n', other, 'exec') + mod.__init__(mod.__name__, other, co) + mod.__path__.append(os.path.join(mod.__path__[0], 'extra')) + return mod diff --git a/pyinstaller/buildtests/basic/pkg1/.svn/entries b/pyinstaller/buildtests/basic/pkg1/.svn/entries new file mode 100644 index 0000000..a349783 --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg1/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/basic/pkg1 +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + +a.py +file + + + +add + diff --git a/pyinstaller/buildtests/basic/pkg1/__init__.py b/pyinstaller/buildtests/basic/pkg1/__init__.py new file mode 100644 index 0000000..55814c6 --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg1/__init__.py @@ -0,0 +1,7 @@ +""" pkg1 replaces itself with pkg2""" + +__all__ = ["a", "b"] +import pkg2 +import sys +sys.modules[__name__] = pkg2 +from pkg2 import * diff --git a/pyinstaller/buildtests/basic/pkg1/a.py b/pyinstaller/buildtests/basic/pkg1/a.py new file mode 100644 index 0000000..c8cc68e --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg1/a.py @@ -0,0 +1,4 @@ +""" pkg1.a.py is never imported """ + +print " %s" % __doc__ +print " %s %s" % (__name__, __file__) diff --git a/pyinstaller/buildtests/basic/pkg2/.svn/entries b/pyinstaller/buildtests/basic/pkg2/.svn/entries new file mode 100644 index 0000000..59af5fa --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg2/.svn/entries @@ -0,0 +1,56 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/basic/pkg2 +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +a +dir + + + +add + +extra +dir + + + +add + +__init__.py +file + + + +add + +a.py +file + + + +add + diff --git a/pyinstaller/buildtests/basic/pkg2/__init__.py b/pyinstaller/buildtests/basic/pkg2/__init__.py new file mode 100644 index 0000000..fff01ca --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg2/__init__.py @@ -0,0 +1,9 @@ +""" pkg2 does various namespace tricks, __path__ append """ + +def notamodule(): + return "notamodule from pkg2.__init__" + +import os +__path__.append(os.path.join( + os.path.dirname(__file__), 'extra')) +__all__ = ["a", "b", "notamodule"] diff --git a/pyinstaller/buildtests/basic/pkg2/a.py b/pyinstaller/buildtests/basic/pkg2/a.py new file mode 100644 index 0000000..5a94731 --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg2/a.py @@ -0,0 +1,5 @@ +""" pkg2.a defines overridden and a_func """ + +def a_func(): + return "a_func from pkg2.a" +print "pkg2.a imported" diff --git a/pyinstaller/buildtests/basic/pkg2/a/.svn/entries b/pyinstaller/buildtests/basic/pkg2/a/.svn/entries new file mode 100644 index 0000000..03c70ea --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg2/a/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/basic/pkg2/a +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +readme.txt +file + + + +add + diff --git a/pyinstaller/buildtests/basic/pkg2/a/readme.txt b/pyinstaller/buildtests/basic/pkg2/a/readme.txt new file mode 100644 index 0000000..f36a5e0 --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg2/a/readme.txt @@ -0,0 +1 @@ +There is no __init__.py in this directory, so pkg2.a refers to ../a.py, not this. diff --git a/pyinstaller/buildtests/basic/pkg2/extra/.svn/entries b/pyinstaller/buildtests/basic/pkg2/extra/.svn/entries new file mode 100644 index 0000000..70f0a91 --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg2/extra/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/basic/pkg2/extra +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +b.py +file + + + +add + diff --git a/pyinstaller/buildtests/basic/pkg2/extra/b.py b/pyinstaller/buildtests/basic/pkg2/extra/b.py new file mode 100644 index 0000000..02331a1 --- /dev/null +++ b/pyinstaller/buildtests/basic/pkg2/extra/b.py @@ -0,0 +1,4 @@ +""" b.py lives in extra, but shows as pkg2.b (and pkg1.b)""" + +def b_func(): + return "b_func from pkg2.b (pkg2/extra/b.py)" diff --git a/pyinstaller/buildtests/basic/test_12.py b/pyinstaller/buildtests/basic/test_12.py new file mode 100644 index 0000000..28f1617 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_12.py @@ -0,0 +1,19 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +print "test12 - xml.com" +from xml.dom import pulldom +print "test12 - done" diff --git a/pyinstaller/buildtests/basic/test_12.spec b/pyinstaller/buildtests/basic/test_12.spec new file mode 100644 index 0000000..8446c56 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_12.spec @@ -0,0 +1,16 @@ +# -*- mode: python -*- + +__testname__ = 'test_12' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT( exe, + a.binaries, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_13.py b/pyinstaller/buildtests/basic/test_13.py new file mode 100644 index 0000000..212a078 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_13.py @@ -0,0 +1,25 @@ +# Copyright (C) 2007, Matteo Bertini +# Based on previous work under copyright (c) 2001, 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 + +print "test13 - Used to fail if _xmlplus is installed" + +import sys +if sys.version_info[:2] >= (2, 5): + import _elementtree + print "test13 DONE" +else: + print "Python 2.5 test13 skipped" diff --git a/pyinstaller/buildtests/basic/test_13.spec b/pyinstaller/buildtests/basic/test_13.spec new file mode 100644 index 0000000..df50372 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_13.spec @@ -0,0 +1,21 @@ +# -*- mode: python -*- + +__testname__ = 'test_13' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) + diff --git a/pyinstaller/buildtests/basic/test_5.py b/pyinstaller/buildtests/basic/test_5.py new file mode 100644 index 0000000..f0cf7e1 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_5.py @@ -0,0 +1,20 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +print "test5 - W ignore" + +import re +print "test5 - done" diff --git a/pyinstaller/buildtests/basic/test_5.spec b/pyinstaller/buildtests/basic/test_5.spec new file mode 100644 index 0000000..1743d8f --- /dev/null +++ b/pyinstaller/buildtests/basic/test_5.spec @@ -0,0 +1,19 @@ +# -*- mode: python -*- + +__testname__ = 'test_5' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + [('W ignore', '', 'OPTION')], + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True) +coll = COLLECT( exe, + a.binaries, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_6.py b/pyinstaller/buildtests/basic/test_6.py new file mode 100644 index 0000000..0edcc0e --- /dev/null +++ b/pyinstaller/buildtests/basic/test_6.py @@ -0,0 +1,36 @@ +# Copyright (C) 2012, Hartmut Goebel +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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, os +import data6 + +print "data6.x is", data6.x + +txt = """\ +x = %d +""" % (data6.x + 1) + +if hasattr(sys, 'frozen'): + data6_filename = os.path.join(sys._MEIPASS, 'data6.py') +else: + data6_filename = data6.__file__ + +open(data6_filename, 'w').write(txt) + +reload(data6) +print "data6.x is now", data6.x diff --git a/pyinstaller/buildtests/basic/test_6.spec b/pyinstaller/buildtests/basic/test_6.spec new file mode 100644 index 0000000..a019c1a --- /dev/null +++ b/pyinstaller/buildtests/basic/test_6.spec @@ -0,0 +1,16 @@ +# -*- mode: python -*- + +__testname__ = 'test_6' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT( exe, + a.binaries, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_8.py b/pyinstaller/buildtests/basic/test_8.py new file mode 100644 index 0000000..83a9b15 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_8.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +try: + import codecs +except ImportError: + print "This test works only with Python versions that support Unicode" + sys.exit(0) + +a = "foo bar" +au = codecs.getdecoder("utf-8")(a)[0] +b = codecs.getencoder("utf-8")(au)[0] +print "codecs working:", a == b +assert a == b +sys.exit(0) diff --git a/pyinstaller/buildtests/basic/test_8.spec b/pyinstaller/buildtests/basic/test_8.spec new file mode 100644 index 0000000..9f5c3a9 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_8.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_8' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__+'.exe'), + debug=0, + strip=0, + upx=0, + console=1 ) +coll = COLLECT( exe, + a.binaries, + strip=0, + upx=0, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_absolute_ld_library_path.py b/pyinstaller/buildtests/basic/test_absolute_ld_library_path.py new file mode 100644 index 0000000..db125d8 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_absolute_ld_library_path.py @@ -0,0 +1,36 @@ +# LD_LIBRARY_PATH set by bootloader should not contain ./ +# +# This test assumes the LD_LIBRARY_PATH is not set before running the test. +# If you experience that this test fails, try to unset the variable and +# rerun the test. +# +# This is how it is done in bash: +# +# $ cd buildtests +# $ unset LD_LIBRARY_PATH +# $ ./runtests.py basic/test_absolute_ld_library_path.py + +import os +import sys + +# Bootloader should override set LD_LIBRARY_PATH. + +# For Linux, Solaris, AIX only + +libpath = os.path.normpath(os.path.abspath(os.path.dirname(sys.executable))) +libpath += '/' + +# The name of the environment variable used to define the path where the +# OS should search for dynamic libraries. +if sys.platform.startswith('aix'): + libpath_var_name = 'LIBPATH' +else: + libpath_var_name = 'LD_LIBRARY_PATH' + +print('LD_LIBRARY_PATH expected: ' + libpath) + +libpath_from_env = os.environ.get(libpath_var_name) +print('LD_LIBRARY_PATH current: ' + libpath_from_env) + +if not libpath == libpath_from_env: + raise SystemExit("Expected LD_LIBRARY_PATH doesn't match.") diff --git a/pyinstaller/buildtests/basic/test_absolute_python_path.py b/pyinstaller/buildtests/basic/test_absolute_python_path.py new file mode 100644 index 0000000..b9aef6d --- /dev/null +++ b/pyinstaller/buildtests/basic/test_absolute_python_path.py @@ -0,0 +1,25 @@ +# sys.path should contain absolute paths. +# With relative paths frozen application will +# fail to import modules when currect working +# directory is changed. + +import os +import sys +import tempfile + +print(sys.path) +print('CWD: ' + os.getcwdu()) + +# Change working directory. +os.chdir(tempfile.gettempdir()) +print('Changing working directory...') +print('CWD: ' + os.getcwdu()) + +# Try import a module. It should fail +try: + for pth in sys.path: + if not os.path.isabs(pth): + SystemExit('ERROR: sys.path not absolute') + import datetime +except: + SystemExit('ERROR: sys.path not absolute') diff --git a/pyinstaller/buildtests/basic/test_celementtree.py b/pyinstaller/buildtests/basic/test_celementtree.py new file mode 100644 index 0000000..7f7a38f --- /dev/null +++ b/pyinstaller/buildtests/basic/test_celementtree.py @@ -0,0 +1,9 @@ +""" +import xml.etree.ElementTree +from copy import copy, deepcopy +import _elementtree +import xml.etree.cElementTree +print dir(xml.etree.cElementTree) +""" +from xml.etree.cElementTree import ElementTree +print "OK" diff --git a/pyinstaller/buildtests/basic/test_celementtree.spec b/pyinstaller/buildtests/basic/test_celementtree.spec new file mode 100644 index 0000000..0227c06 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_celementtree.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_celementtree' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=True, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/basic/test_chdir_meipass.py b/pyinstaller/buildtests/basic/test_chdir_meipass.py new file mode 100644 index 0000000..6a21c8d --- /dev/null +++ b/pyinstaller/buildtests/basic/test_chdir_meipass.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +import os +import sys + +os.chdir(sys._MEIPASS) + +import data6 + +print os.getcwd() diff --git a/pyinstaller/buildtests/basic/test_chdir_meipass.spec b/pyinstaller/buildtests/basic/test_chdir_meipass.spec new file mode 100644 index 0000000..56b02f5 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_chdir_meipass.spec @@ -0,0 +1,18 @@ +# -*- mode: python -*- + +__testname__ = 'test_chdir_meipass' + +a = Analysis([__testname__ + '.py'], pathex=['.']) + +pyz = PYZ(a.pure) +exe = EXE( pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=os.path.join('dist', __testname__, __testname__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + diff --git a/pyinstaller/buildtests/basic/test_ctypes.py b/pyinstaller/buildtests/basic/test_ctypes.py new file mode 100644 index 0000000..0d2be9d --- /dev/null +++ b/pyinstaller/buildtests/basic/test_ctypes.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +# Test resolving dynamic libraries loaded in Python code at runtime +# by Python module 'ctypes' + +import ctypeslib +assert ctypeslib.dummy(42) == 42 diff --git a/pyinstaller/buildtests/basic/test_ctypes.spec b/pyinstaller/buildtests/basic/test_ctypes.spec new file mode 100644 index 0000000..0fe6a25 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_ctypes.spec @@ -0,0 +1,83 @@ +# -*- mode: python -*- +import sys +import os + + +is_win = sys.platform.startswith('win') +is_darwin = sys.platform == 'darwin' # Mac OS X + + +def mac_gcc_architecture(): + """ + Returns the -arch parameter for gcc on Mac OS X. + """ + # Darwin's platform.architecture() is buggy and always + # returns "64bit" event for the 32bit version of Python's + # universal binary. So we roll out our own (that works + # on Darwin). + if sys.maxint > 2L ** 32: + # 64bit + return 'x86_64' + else: + # 32bit + return 'i386' + + +CTYPES_DIR = 'ctypes' +TEST_LIB = os.path.join(CTYPES_DIR, 'testctypes') + + +if is_win: + TEST_LIB += '.dll' +elif is_darwin: + TEST_LIB += '.dylib' +else: + TEST_LIB += '.so' + + +# If the required dylib does not reside in the current directory, the Analysis +# class machinery, based on ctypes.util.find_library, will not find it. This +# was done on purpose for this test, to show how to give Analysis class +# a clue. +if is_win: + os.environ['PATH'] = os.path.abspath(CTYPES_DIR) + ';' + os.environ['PATH'] +else: + os.environ['LD_LIBRARY_PATH'] = CTYPES_DIR + os.environ['DYLD_LIBRARY_PATH'] = CTYPES_DIR + os.environ['LIBPATH'] = CTYPES_DIR + + +os.chdir(CTYPES_DIR) + + +if is_win: + ret = os.system('cl /LD testctypes-win.c') + if ret != 0: + os.system('gcc -shared testctypes-win.c -o testctypes.dll') +elif is_darwin: + # On Mac OS X we need to detect architecture - 32 bit or 64 bit. + cmd = ('gcc -arch ' + mac_gcc_architecture() + ' -Wall -dynamiclib ' + 'testctypes.c -o testctypes.dylib -headerpad_max_install_names') + os.system(cmd) + id_dylib = os.path.abspath('testctypes.dylib') + os.system('install_name_tool -id %s testctypes.dylib' % (id_dylib,)) +else: + os.system('gcc -fPIC -shared testctypes.c -o testctypes.so') + + +os.chdir("..") + + +__testname__ = 'test_ctypes' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + name=os.path.join('dist', __testname__, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1) diff --git a/pyinstaller/buildtests/basic/test_email.py b/pyinstaller/buildtests/basic/test_email.py new file mode 100644 index 0000000..0b6b387 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_email.py @@ -0,0 +1,23 @@ +# +# 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 + + +# Test import of new-style email module names. +# This should work on Python 2.5+ +from email import utils +from email.header import Header +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.nonmultipart import MIMENonMultipart diff --git a/pyinstaller/buildtests/basic/test_email_oldstyle.py b/pyinstaller/buildtests/basic/test_email_oldstyle.py new file mode 100644 index 0000000..4f00536 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_email_oldstyle.py @@ -0,0 +1,20 @@ +# +# 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 + + +# Since Python 2.5 email modules were renamed. +# Test that old-style email naming still works +# in Python 2.5+ +from email.MIMEMultipart import MIMEMultipart diff --git a/pyinstaller/buildtests/basic/test_encoders.py b/pyinstaller/buildtests/basic/test_encoders.py new file mode 100644 index 0000000..ea32bf2 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_encoders.py @@ -0,0 +1 @@ +assert "foo".decode("ascii") == u"foo" diff --git a/pyinstaller/buildtests/basic/test_encoders.spec b/pyinstaller/buildtests/basic/test_encoders.spec new file mode 100644 index 0000000..4432cb2 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_encoders.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_encoders' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_f_option.py b/pyinstaller/buildtests/basic/test_f_option.py new file mode 100644 index 0000000..1839ca6 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_f_option.py @@ -0,0 +1,23 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +print "test_f_option - test 'f' option (just show os.environ)" +import os, sys +if sys.platform[:3] == 'win': + print " sorry, no use / need for the 'f' option on Windows" +else: + print " LD_LIBRARY_PATH %s" % os.environ.get('LD_LIBRARY_PATH', '') +print "test_f_option complete" diff --git a/pyinstaller/buildtests/basic/test_f_option.spec b/pyinstaller/buildtests/basic/test_f_option.spec new file mode 100644 index 0000000..1384068 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_f_option.spec @@ -0,0 +1,17 @@ +# -*- mode: python -*- + +__testname__ = 'test_f_option' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + [('f','','OPTION')], + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT( exe, + a.binaries, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_filename.py b/pyinstaller/buildtests/basic/test_filename.py new file mode 100644 index 0000000..2200e4f --- /dev/null +++ b/pyinstaller/buildtests/basic/test_filename.py @@ -0,0 +1,3 @@ +if __file__ != "test_filename.py": + raise ValueError(__file__) + diff --git a/pyinstaller/buildtests/basic/test_filename.spec b/pyinstaller/buildtests/basic/test_filename.spec new file mode 100644 index 0000000..417ca09 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_filename.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_filename' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_get_meipass2_value.py b/pyinstaller/buildtests/basic/test_get_meipass2_value.py new file mode 100644 index 0000000..1df08bd --- /dev/null +++ b/pyinstaller/buildtests/basic/test_get_meipass2_value.py @@ -0,0 +1,45 @@ +# Bootloader unsets _MEIPASS2 for child processes so that if the program +# invokes another PyInstaller one-file program as subprocess, this +# subprocess will not fooled into thinking that it is already unpacked. +# +# This test checks if it is really unset. + +import os +import sys + + +def _get_meipass_value(): + if sys.platform.startswith('win'): + command = 'echo %_MEIPASS2%' + else: + command = 'echo $_MEIPASS2' + + try: + import subprocess + proc = subprocess.Popen(command, shell=True, + stdout=subprocess.PIPE) + proc.wait() + stdout, stderr = proc.communicate() + meipass = stdout.strip() + except ImportError: + # Python 2.3 does not have subprocess module. + pipe = os.popen(command) + meipass = pipe.read().strip() + pipe.close() + return meipass + + +meipass = _get_meipass_value() + + +# Win32 fix. +if meipass.startswith(r'%'): + meipass = '' + + +print meipass +print '_MEIPASS2 value:', sys._MEIPASS + + +if meipass: + raise SystemExit('Error: _MEIPASS2 env variable available in subprocess.') diff --git a/pyinstaller/buildtests/basic/test_get_meipass2_value.spec b/pyinstaller/buildtests/basic/test_get_meipass2_value.spec new file mode 100644 index 0000000..7e919d6 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_get_meipass2_value.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_get_meipass2_value' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +a = Analysis([__testname__ + '.py'], + pathex=[], + hookspath=None) +pyz = PYZ(a.pure) +exe = EXE( pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=os.path.join('dist', __testname__ + '.exe'), + debug=True, + strip=None, + upx=True, + console=True ) diff --git a/pyinstaller/buildtests/basic/test_getfilesystemencoding.py b/pyinstaller/buildtests/basic/test_getfilesystemencoding.py new file mode 100644 index 0000000..b37319d --- /dev/null +++ b/pyinstaller/buildtests/basic/test_getfilesystemencoding.py @@ -0,0 +1,44 @@ +# Copyright (C) 2007, Matteo Bertini +# Based on previous work under copyright (c) 2001, 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 + + +frozen_encoding = str(sys.getfilesystemencoding()) + + +# For various OS is encoding different. +# On Windows it should be still mbcs. +if sys.platform.startswith('win'): + encoding = 'mbcs' +# On Mac OS X the value should be still the same. +elif sys.platform.startswith('darwin'): + encoding = 'utf-8' +# On Linux and other unixes it should be None. +# Please note that on Linux the value differs from the value +# in interactive shell. +else: + encoding = 'None' + + +print('Encoding expected: ' + encoding) +print('Encoding current: ' + frozen_encoding) + + +if not frozen_encoding == encoding: + raise SystemExit('Frozen encoding is not the same as unfrozen.') diff --git a/pyinstaller/buildtests/basic/test_getfilesystemencoding.spec b/pyinstaller/buildtests/basic/test_getfilesystemencoding.spec new file mode 100644 index 0000000..64991b6 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_getfilesystemencoding.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_getfilesystemencoding' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) +coll = COLLECT(exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_helloworld.py b/pyinstaller/buildtests/basic/test_helloworld.py new file mode 100644 index 0000000..a5ac4f9 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_helloworld.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python + +print 'Hello Python!' diff --git a/pyinstaller/buildtests/basic/test_helloworld.spec b/pyinstaller/buildtests/basic/test_helloworld.spec new file mode 100644 index 0000000..235886f --- /dev/null +++ b/pyinstaller/buildtests/basic/test_helloworld.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_helloworld' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT( exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_module_attributes.py b/pyinstaller/buildtests/basic/test_module_attributes.py new file mode 100644 index 0000000..85d3393 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_module_attributes.py @@ -0,0 +1,78 @@ +# Copyright (C) 2007, Matteo Bertini +# Based on previous work under copyright (c) 2001, 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 + + +# Compare attributes of ElementTree (cElementTree) module from frozen executable +# with ElementTree (cElementTree) module from standard python. + + +import copy +import os +import subprocess +import sys +import xml.etree.ElementTree as ET +import xml.etree.cElementTree as cET + + +if hasattr(sys, 'frozen'): + # In frozen mode current working dir is the path with final executable. + _pyexe_file = os.path.join('..', '..', 'python_exe.build') +else: + _pyexe_file = 'python_exe.build' + +_lines = open(_pyexe_file).readlines() +_pyexe = _lines[0].strip() +_env_path = _lines[2].strip() + + +def exec_python(pycode): + """ + Wrap running python script in a subprocess. + + Return stdout of the invoked command. + """ + # Environment variable 'PATH' has to be defined on Windows. + # Otherwise dynamic library pythonXY.dll cannot be found by + # Python executable. + env = copy.deepcopy(os.environ) + env['PATH'] = _env_path + out = subprocess.Popen([_pyexe, '-c', pycode], env=env, + stdout=subprocess.PIPE, shell=False).stdout.read() + + return out.strip() + + +def compare(test_name, expect, frozen): + print(test_name) + print(' Attributes expected: ' + expect) + print(' Attributes current: ' + frozen) + print('') + # Compare attributes of frozen module with unfronzen module. + if not frozen == expect: + raise SystemExit('Frozen module has no same attribuses as unfrozen.') + + +# Pure Python module. +_expect = exec_python('import xml.etree.ElementTree as ET; print dir(ET)') +_frozen = str(dir(ET)) +compare('ElementTree', _expect, _frozen) + + +# C-extension Python module. +_expect = exec_python('import xml.etree.cElementTree as cET; print dir(cET)') +_frozen = str(dir(cET)) +compare('cElementTree', _expect, _frozen) diff --git a/pyinstaller/buildtests/basic/test_module_attributes.spec b/pyinstaller/buildtests/basic/test_module_attributes.spec new file mode 100644 index 0000000..1edb467 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_module_attributes.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_module_attributes' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_nestedlaunch0.py b/pyinstaller/buildtests/basic/test_nestedlaunch0.py new file mode 100644 index 0000000..1ac5bd3 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_nestedlaunch0.py @@ -0,0 +1,5 @@ +import cmath + +if __name__ == "__main__": + print dir() + diff --git a/pyinstaller/buildtests/basic/test_nestedlaunch0.spec b/pyinstaller/buildtests/basic/test_nestedlaunch0.spec new file mode 100644 index 0000000..a48d6c6 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_nestedlaunch0.spec @@ -0,0 +1,16 @@ +# -*- mode: python -*- + +__testname__ = 'test_nestedlaunch0' + +a = Analysis([__testname__ + '.py'], + pathex=['']) +pyz = PYZ(a.pure) +exe = EXE( pyz, + a.scripts, + a.binaries, + a.zipfiles, + name=os.path.join('dist', __testname__, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) diff --git a/pyinstaller/buildtests/basic/test_nestedlaunch1.py b/pyinstaller/buildtests/basic/test_nestedlaunch1.py new file mode 100644 index 0000000..c02cbbc --- /dev/null +++ b/pyinstaller/buildtests/basic/test_nestedlaunch1.py @@ -0,0 +1,13 @@ +import os +import sys + +if __name__ == "__main__": + fn = os.path.join(os.path.dirname(sys.executable), + "..", "test_nestedlaunch0", "test_nestedlaunch0.exe") + try: + import subprocess + except ImportError: + if os.system(fn) != 0: + raise RuntimeError("os.system failed: %s" % fn) + else: + subprocess.check_call([fn]) diff --git a/pyinstaller/buildtests/basic/test_nestedlaunch1.spec b/pyinstaller/buildtests/basic/test_nestedlaunch1.spec new file mode 100644 index 0000000..4859203 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_nestedlaunch1.spec @@ -0,0 +1,16 @@ +# -*- mode: python -*- + +__testname__ = 'test_nestedlaunch1' + +a = Analysis([__testname__ + '.py'], + pathex=['']) +pyz = PYZ(a.pure) +exe = EXE( pyz, + a.scripts, + a.binaries, + a.zipfiles, + name=os.path.join('dist', __testname__, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) diff --git a/pyinstaller/buildtests/basic/test_onefile_multiprocess.py b/pyinstaller/buildtests/basic/test_onefile_multiprocess.py new file mode 100644 index 0000000..c63a35a --- /dev/null +++ b/pyinstaller/buildtests/basic/test_onefile_multiprocess.py @@ -0,0 +1,60 @@ +# Bootloader unsets _MEIPASS2 for child processes to allow running +# PyInstaller binaries inside pyinstaller binaries. +# This is ok for mac or unix with fork() system call. +# But on Windows we need to overcome missing fork() fuction. +# +# See http://www.pyinstaller.org/wiki/Recipe/Multiprocessing +import multiprocessing.forking +import os +import sys + + +class _Popen(multiprocessing.forking.Popen): + def __init__(self, *args, **kw): + if hasattr(sys, 'frozen'): + # We have to get original _MEIPASS2 value from PYTHONPATH + # to get --onefile and --onedir mode working. + # Last character is stripped in C-loader. + os.putenv('_MEIPASS2', sys._MEIPASS) + try: + super(_Popen, self).__init__(*args, **kw) + finally: + if hasattr(sys, 'frozen'): + # On some platforms (e.g. AIX) 'os.unsetenv()' is not + # available. In those cases we cannot delete the variable + # but only set it to the empty string. The bootloader + # can handle this case. + if hasattr(os, 'unsetenv'): + os.unsetenv('_MEIPASS2') + else: + os.putenv('_MEIPASS2', '') + + +class Process(multiprocessing.Process): + _Popen = _Popen + + +class SendeventProcess(Process): + def __init__(self, resultQueue): + self.resultQueue = resultQueue + + multiprocessing.Process.__init__(self) + self.start() + + def run(self): + print 'SendeventProcess' + self.resultQueue.put((1, 2)) + print 'SendeventProcess' + + +if __name__ == '__main__': + multiprocessing.freeze_support() + print 'main' + resultQueue = multiprocessing.Queue() + SendeventProcess(resultQueue) + print 'main' + if hasattr(sys, 'frozen'): + # We need to wait for all child processes otherwise + # --onefile mode won't work. + while multiprocessing.active_children(): + multiprocessing.active_children()[0].join() diff --git a/pyinstaller/buildtests/basic/test_pkg_structures-version.txt b/pyinstaller/buildtests/basic/test_pkg_structures-version.txt new file mode 100644 index 0000000..5658798 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_pkg_structures-version.txt @@ -0,0 +1,38 @@ +VSVersionInfo( + ffi=FixedFileInfo( + filevers=(96, 12, 19, 1), + prodvers=(4, 1, 2, 1), + mask=0x3f, + flags=0x0, + OS=0x40004, + fileType=0x1, + subtype=0x0, + date=(0, 0) + ), + kids=[ + StringFileInfo( + [ + StringTable( + '040904b0', + [ + StringStruct('Comments', 'Designed and coded by Elmer Fudd'), + StringStruct('CompanyName', 'Fuddy Duddies, Inc. 8 Flossie Dr. Arlington, VA 00001'), + StringStruct('FileDescription', 'Silly stuff'), + StringStruct('FileVersion', '96, 12, 19, 1'), + StringStruct('InternalName', 'SILLINESS'), + StringStruct('LegalCopyright', 'Copyright 2001 Fuddy Duddies, Inc.'), + StringStruct('LegalTrademarks', 'SILLINESS is a registered trademark of Fuddy Duddies, Inc.'), + StringStruct('OriginalFilename', 'silliness.exe'), + StringStruct('ProductName', 'SILLINESS'), + StringStruct('ProductVersion', '2, 0, 3, 0') + ] + ) + ] + ), + VarFileInfo( + [ + VarStruct('Translation', [1033, 1200]) + ] + ) + ] +) \ No newline at end of file diff --git a/pyinstaller/buildtests/basic/test_pkg_structures.ico b/pyinstaller/buildtests/basic/test_pkg_structures.ico new file mode 100644 index 0000000..f68cecd Binary files /dev/null and b/pyinstaller/buildtests/basic/test_pkg_structures.ico differ diff --git a/pyinstaller/buildtests/basic/test_pkg_structures.py b/pyinstaller/buildtests/basic/test_pkg_structures.py new file mode 100644 index 0000000..90d6239 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_pkg_structures.py @@ -0,0 +1,45 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + + +# Tests - hooks, strange pkg structures, version, icon. + + +e1 = 'a_func from pkg2.a' +e2 = 'b_func from pkg2.b (pkg2/extra/b.py)' +e3 = 'notamodule from pkg2.__init__' + + +from pkg1 import * + + +t1 = a.a_func() +if t1 != e1: + print "expected:", e1 + print " got:", t1 + + +t2 = b.b_func() +if t2 != e2: + print "expected:", e2 + print " got:", t2 + + +t3 = notamodule() +if t3 != e3: + print "expected:", e3 + print " got:", t3 diff --git a/pyinstaller/buildtests/basic/test_pkg_structures.spec b/pyinstaller/buildtests/basic/test_pkg_structures.spec new file mode 100644 index 0000000..35a734e --- /dev/null +++ b/pyinstaller/buildtests/basic/test_pkg_structures.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_pkg_structures' + +a = Analysis([__testname__ + '.py'], + hookspath=['hooks1']) + +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + icon=__testname__+'.ico', + version=__testname__+'-version.txt', + debug=0, + console=1) + +coll = COLLECT( exe, + a.binaries, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_python_home.py b/pyinstaller/buildtests/basic/test_python_home.py new file mode 100644 index 0000000..5b61e7d --- /dev/null +++ b/pyinstaller/buildtests/basic/test_python_home.py @@ -0,0 +1,31 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# PYTHONHOME (sys.prefix) has to be same as sys._MEIPASS. + +import sys + + +print('sys._MEIPASS: ' + sys._MEIPASS) +print('sys.prefix: ' + sys.prefix) +print('sys.exec_prefix: ' + sys.exec_prefix) + +if not sys.prefix == sys._MEIPASS: + raise SystemExit('sys.prefix is not set to path as in sys._MEIPASS.') +if not sys.exec_prefix == sys._MEIPASS: + raise SystemExit('sys.exec_prefix is not set to path as in sys._MEIPASS.') diff --git a/pyinstaller/buildtests/basic/test_python_makefile.py b/pyinstaller/buildtests/basic/test_python_makefile.py new file mode 100644 index 0000000..b0ffc86 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_python_makefile.py @@ -0,0 +1,22 @@ +# distutils module requires Makefile and pyconfig.h files from Python +# installation. + +import os +import sys +from distutils import sysconfig + + +config_h = sysconfig.get_config_h_filename() +print('pyconfig.h: ' + config_h) +files = [config_h] + +# On Windows Makefile does not exist. +if not sys.platform.startswith('win'): + makefile = sysconfig.get_makefile_filename() + print('Makefile: ' + makefile) + files.append(makefile) + + +for f in files: + if not os.path.exists(f): + raise SystemExit('File does not exist: %s' % f) diff --git a/pyinstaller/buildtests/basic/test_python_makefile.spec b/pyinstaller/buildtests/basic/test_python_makefile.spec new file mode 100644 index 0000000..25c1b15 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_python_makefile.spec @@ -0,0 +1,24 @@ +# -*- mode: python -*- + +__testname__ = 'test_python_makefile' + +a = Analysis([__testname__ + '.py'], + pathex=['.'], + hiddenimports=[], + hookspath=None) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=True, + strip=None, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=None, + upx=True, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_python_makefile_onefile.py b/pyinstaller/buildtests/basic/test_python_makefile_onefile.py new file mode 100644 index 0000000..8fd650b --- /dev/null +++ b/pyinstaller/buildtests/basic/test_python_makefile_onefile.py @@ -0,0 +1,24 @@ +# distutils module requires Makefile and pyconfig.h files from Python +# installation. + +import os +import sys +import time +from distutils import sysconfig + + +config_h = sysconfig.get_config_h_filename() +print('pyconfig.h: ' + config_h) +files = [config_h] + +# On Windows Makefile does not exist. +if not sys.platform.startswith('win'): + makefile = sysconfig.get_makefile_filename() + print('Makefile: ' + makefile) + files.append(makefile) + +time.sleep(30) + +for f in files: + if not os.path.exists(f): + raise SystemExit('File does not exist: %s' % f) diff --git a/pyinstaller/buildtests/basic/test_python_makefile_onefile.spec b/pyinstaller/buildtests/basic/test_python_makefile_onefile.spec new file mode 100644 index 0000000..629f412 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_python_makefile_onefile.spec @@ -0,0 +1,19 @@ +# -*- mode: python -*- + +__testname__ = 'test_python_makefile_onefile' + +a = Analysis([__testname__ + '.py'], + pathex=['.'], + hiddenimports=[], + hookspath=None) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name=os.path.join('dist', __testname__), + debug=True, + strip=None, + upx=True, + console=True ) diff --git a/pyinstaller/buildtests/basic/test_site.py b/pyinstaller/buildtests/basic/test_site.py new file mode 100644 index 0000000..4e222d4 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_site.py @@ -0,0 +1,32 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# Test inclusion of fake 'site' module. + +import site + + +# Default values in fake 'site' module should be False, None or empty list. + +if not site.ENABLE_USER_SITE == False: + raise SystemExit('ENABLE_USER_SITE not False.') +if not site.PREFIXES == []: + raise SystemExit('PREFIXES not empty list.') + +if site.USER_SITE is not None and site.USER_BASE is not None: + raise SystemExit('USER_SITE or USER_BASE not None.') diff --git a/pyinstaller/buildtests/basic/test_site.spec b/pyinstaller/buildtests/basic/test_site.spec new file mode 100644 index 0000000..2d10b1a --- /dev/null +++ b/pyinstaller/buildtests/basic/test_site.spec @@ -0,0 +1,21 @@ +# -*- mode: python -*- +a = Analysis(['test_site.py'], + pathex=['/home/martin/Work/pyinstaller/gitrepo/buildtests/import'], + hiddenimports=['encodings'], + hookspath=None) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build/pyi.linux2/test_site', 'test_site'), + debug=True, + strip=None, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=None, + upx=True, + name=os.path.join('dist', 'test_site')) diff --git a/pyinstaller/buildtests/basic/test_threading.py b/pyinstaller/buildtests/basic/test_threading.py new file mode 100644 index 0000000..c8f555c --- /dev/null +++ b/pyinstaller/buildtests/basic/test_threading.py @@ -0,0 +1,31 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 threading + +def doit(nm): + print nm, 'started' + import data7 + print nm, data7.x + +t1 = threading.Thread(target=doit, args=('t1',)) +t2 = threading.Thread(target=doit, args=('t2',)) +t1.start() +t2.start() +doit('main') +t1.join() +t2.join() diff --git a/pyinstaller/buildtests/basic/test_threading.spec b/pyinstaller/buildtests/basic/test_threading.spec new file mode 100644 index 0000000..d8b2cb3 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_threading.spec @@ -0,0 +1,16 @@ +# -*- mode: python -*- + +__testname__ = 'test_threading' + +a = Analysis([__testname__+'.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT( exe, + a.binaries, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/basic/test_threading2.py b/pyinstaller/buildtests/basic/test_threading2.py new file mode 100644 index 0000000..f9a85f3 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_threading2.py @@ -0,0 +1,72 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# Test bootloader behaviour for threading code. +# Default behaviour of Python interpreter is to wait for all threads +# before exiting main process. +# Bootloader should behave also this way. + + +import os +import sys +import threading + + +_OUT_EXPECTED = ['ONE', 'TWO', 'THREE'] + + +# Code for the subprocess. +if 'PYI_THREAD_TEST_CASE' in os.environ: + class TestThreadClass(threading.Thread): + def __init__(self): + threading.Thread.__init__(self) + + def run(self): + print 'ONE' + print 'TWO' + print 'THREE' + # Main process should not exit before the thread stops. + # This is the behaviour of Python interpreter. + TestThreadClass().start() + + +# Execute itself in a subprocess. +else: + # Differenciate subprocess code. + itself = sys.argv[0] + # Run subprocess. + try: + import subprocess + proc = subprocess.Popen([itself], stdout=subprocess.PIPE, + env={'PYI_THREAD_TEST_CASE': 'any_string'}, + stderr=subprocess.PIPE, shell=False) + # Waits for subprocess to complete. + out, err = proc.communicate() + except ImportError: + # Python 2.3 does not have subprocess module. + command = 'PYI_THREAD_TEST_CASE=any_string ' + itself + pipe = os.popen(command) + out = pipe.read() + pipe.close() + + # Make output from subprocess visible. + print out + + # Check output. + if out.splitlines() != _OUT_EXPECTED: + raise SystemExit('Subprocess did not print ONE, TWO, THREE in correct order.') diff --git a/pyinstaller/buildtests/basic/test_threading2.spec b/pyinstaller/buildtests/basic/test_threading2.spec new file mode 100644 index 0000000..0d3f093 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_threading2.spec @@ -0,0 +1,24 @@ +# -*- mode: python -*- + +__testname__ = 'test_threading2' + +a = Analysis([__testname__+'.py'], + pathex=[], + hiddenimports=[], + hookspath=None) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=None, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=None, + upx=True, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/basic/test_time.py b/pyinstaller/buildtests/basic/test_time.py new file mode 100644 index 0000000..7f29eb1 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_time.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +import time +print time.strptime(time.ctime()) diff --git a/pyinstaller/buildtests/basic/test_time.spec b/pyinstaller/buildtests/basic/test_time.spec new file mode 100644 index 0000000..f7c0512 --- /dev/null +++ b/pyinstaller/buildtests/basic/test_time.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_time' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT( exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/eggs4testing/.gitignore b/pyinstaller/buildtests/eggs4testing/.gitignore new file mode 100644 index 0000000..24c9b52 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/.gitignore @@ -0,0 +1,4 @@ +/venv +/*.egg-info +/dist +/build diff --git a/pyinstaller/buildtests/eggs4testing/.svn/entries b/pyinstaller/buildtests/eggs4testing/.svn/entries new file mode 100644 index 0000000..4a8a132 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/.svn/entries @@ -0,0 +1,77 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/eggs4testing +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +setup-zipped.py +file + + + +add + +unzipped_egg +dir + + + +add + +setup-unzipped.py +file + + + +add + +.gitignore +file + + + +add + +make.sh +file + + + +add + +README.txt +file + + + +add + +zipped_egg +dir + + + +add + diff --git a/pyinstaller/buildtests/eggs4testing/README.txt b/pyinstaller/buildtests/eggs4testing/README.txt new file mode 100644 index 0000000..96cc978 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/README.txt @@ -0,0 +1,7 @@ +This is a package for testing eggs in `PyInstaller`_ + +:Author: Hartmut Goebel +:Copyright: 2012 by Hartmut Goebel +:Licence: GNU Public Licence v3 (GPLv3) + +_PyInstaller: www.pyinstaller.org diff --git a/pyinstaller/buildtests/eggs4testing/make.sh b/pyinstaller/buildtests/eggs4testing/make.sh new file mode 100644 index 0000000..8946a8e --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/make.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +export PYTHONDONTWRITEBYTECODE=1 + +rm -rf build/ dist/ *.egg-info + +python setup-zipped.py bdist_egg +# nedd to clean up build-dir, otherwise stuff from `zipped_egg` +# goes into `unzipped_egg*.egg` +rm -rf build/ +python setup-unzipped.py bdist_egg +rm -rf build/ + +# setup a virtualenv for testing +virtualenv venv --distribute +. venv/bin/activate +easy_install --zip-ok dist/zipped_egg*.egg +easy_install --always-unzip dist/unzipped_egg*.egg +cp ../import/test_eggs*.py venv + +# see if the unpackaged test-case still works +cd venv +python test_eggs1.py +python test_eggs2.py +cd .. + +cd venv +rm -rfv ../../import/zipped.egg ../../import/unzipped.egg +mv -v lib/python2.7/site-packages/zipped_egg-*.egg ../../import/zipped.egg +mv -v lib/python2.7/site-packages/unzipped_egg-*.egg ../../import/unzipped.egg +cd .. + +deactivate + +rm -rf build/ dist/ venv *.egg-info diff --git a/pyinstaller/buildtests/eggs4testing/setup-unzipped.py b/pyinstaller/buildtests/eggs4testing/setup-unzipped.py new file mode 100644 index 0000000..60b646a --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/setup-unzipped.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +from setuptools import setup + +setup(name='unzipped_egg', + version='0.1', + description='A unzipped egg for testing PyInstaller', + packages=['unzipped_egg'], + package_data={'unzipped_egg': ['data/datafile.txt']}, + zip_safe = False, + ) diff --git a/pyinstaller/buildtests/eggs4testing/setup-zipped.py b/pyinstaller/buildtests/eggs4testing/setup-zipped.py new file mode 100644 index 0000000..3ccb013 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/setup-zipped.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +from setuptools import setup + +setup(name='zipped_egg', + version='0.1', + description='A zipped egg for testing PyInstaller', + packages=['zipped_egg'], + package_data={'zipped_egg': ['data/datafile.txt']}, + ) diff --git a/pyinstaller/buildtests/eggs4testing/unzipped_egg/.svn/entries b/pyinstaller/buildtests/eggs4testing/unzipped_egg/.svn/entries new file mode 100644 index 0000000..5063bfa --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/unzipped_egg/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/eggs4testing/unzipped_egg +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + +data +dir + + + +add + diff --git a/pyinstaller/buildtests/eggs4testing/unzipped_egg/__init__.py b/pyinstaller/buildtests/eggs4testing/unzipped_egg/__init__.py new file mode 100644 index 0000000..e6fd881 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/unzipped_egg/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +import pkg_resources +data = pkg_resources.resource_string(__name__, 'data/datafile.txt').rstrip() diff --git a/pyinstaller/buildtests/eggs4testing/unzipped_egg/data/.svn/entries b/pyinstaller/buildtests/eggs4testing/unzipped_egg/data/.svn/entries new file mode 100644 index 0000000..990c668 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/unzipped_egg/data/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/eggs4testing/unzipped_egg/data +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +datafile.txt +file + + + +add + diff --git a/pyinstaller/buildtests/eggs4testing/unzipped_egg/data/datafile.txt b/pyinstaller/buildtests/eggs4testing/unzipped_egg/data/datafile.txt new file mode 100644 index 0000000..8c759f0 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/unzipped_egg/data/datafile.txt @@ -0,0 +1 @@ +This is data file for `unzipped`. diff --git a/pyinstaller/buildtests/eggs4testing/zipped_egg/.svn/entries b/pyinstaller/buildtests/eggs4testing/zipped_egg/.svn/entries new file mode 100644 index 0000000..c84feb8 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/zipped_egg/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/eggs4testing/zipped_egg +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + +data +dir + + + +add + diff --git a/pyinstaller/buildtests/eggs4testing/zipped_egg/__init__.py b/pyinstaller/buildtests/eggs4testing/zipped_egg/__init__.py new file mode 100644 index 0000000..e6fd881 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/zipped_egg/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +import pkg_resources +data = pkg_resources.resource_string(__name__, 'data/datafile.txt').rstrip() diff --git a/pyinstaller/buildtests/eggs4testing/zipped_egg/data/.svn/entries b/pyinstaller/buildtests/eggs4testing/zipped_egg/data/.svn/entries new file mode 100644 index 0000000..149042f --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/zipped_egg/data/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/eggs4testing/zipped_egg/data +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +datafile.txt +file + + + +add + diff --git a/pyinstaller/buildtests/eggs4testing/zipped_egg/data/datafile.txt b/pyinstaller/buildtests/eggs4testing/zipped_egg/data/datafile.txt new file mode 100644 index 0000000..93e7f98 --- /dev/null +++ b/pyinstaller/buildtests/eggs4testing/zipped_egg/data/datafile.txt @@ -0,0 +1 @@ +This is data file for `zipped`. diff --git a/pyinstaller/buildtests/import/.svn/entries b/pyinstaller/buildtests/import/.svn/entries new file mode 100644 index 0000000..a767d51 --- /dev/null +++ b/pyinstaller/buildtests/import/.svn/entries @@ -0,0 +1,280 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test_relative_import3.spec +file + + + +add + +test_onefile_zipimport.py +file + + + +add + +test_relative_import.py +file + + + +add + +test_eggs1.py +file + + + +add + +test_relative_import.spec +file + + + +add + +test_ctypes_cdll_c.py +file + + + +add + +test_eggs1.spec +file + + + +add + +test_ctypes_cdll_c.spec +file + + + +add + +test_app_with_plugins.py +file + + + +add + +error_during_import2.py +file + + + +add + +test_app_with_plugins.spec +file + + + +add + +test_c_extension.py +file + + + +add + +test_onefile_c_extension.py +file + + + +add + +test_hiddenimport.toc +file + + + +add + +test_hiddenimport.py +file + + + +add + +relimp3a +dir + + + +add + +test_relative_import2.py +file + + + +add + +relimp3b +dir + + + +add + +test_hiddenimport.spec +file + + + +add + +relimp3c +dir + + + +add + +test_relative_import2.spec +file + + + +add + +test_ctypes_cdll_c2.py +file + + + +add + +test_error_during_import.py +file + + + +add + +test_ctypes_cdll_c2.spec +file + + + +add + +test_error_during_import.spec +file + + + +add + +test_relative_import.toc +file + + + +add + +relimp +dir + + + +add + +static_plugin.py +file + + + +add + +zipped.egg +file + + + +add + + + + + +has-props +has-prop-mods + +test_eggs2.py +file + + + +add + +test_eggs2.spec +file + + + +add + +unzipped.egg +dir + + + +add + +test_onefile_zipimport2.py +file + + + +add + +relimp2 +dir + + + +add + +test_relative_import3.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/.svn/props/zipped.egg.svn-work b/pyinstaller/buildtests/import/.svn/props/zipped.egg.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/pyinstaller/buildtests/import/.svn/props/zipped.egg.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/pyinstaller/buildtests/import/error_during_import2.py b/pyinstaller/buildtests/import/error_during_import2.py new file mode 100644 index 0000000..32bea96 --- /dev/null +++ b/pyinstaller/buildtests/import/error_during_import2.py @@ -0,0 +1,2 @@ +import os +os.environ["qwiejioqwjeioqwjeioqwje"] diff --git a/pyinstaller/buildtests/import/relimp/.svn/entries b/pyinstaller/buildtests/import/relimp/.svn/entries new file mode 100644 index 0000000..fd754c7 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/.svn/entries @@ -0,0 +1,77 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +relimp1.py +file + + + +add + +B +dir + + + +add + +relimp2.py +file + + + +add + +F +dir + + + +add + +__init__.py +file + + + +add + +relimp +dir + + + +add + +E.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp/B/.svn/entries b/pyinstaller/buildtests/import/relimp/B/.svn/entries new file mode 100644 index 0000000..be6ef7d --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/B/.svn/entries @@ -0,0 +1,49 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp/B +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + +C.py +file + + + +add + +D.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp/B/C.py b/pyinstaller/buildtests/import/relimp/B/C.py new file mode 100644 index 0000000..c5fece9 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/B/C.py @@ -0,0 +1,12 @@ +name = 'relimp.B.C' + +from . import D # Imports relimp.B.D +from .D import X # Imports relimp.B.D.X +from .. import E # Imports relimp.E +from ..F import G # Imports relimp.F.G +from ..F import H # Imports relimp.F.H + +assert D.name == 'relimp.B.D' +assert E.name == 'relimp.E' +assert G.name == 'relimp.F.G' +assert H.name == 'relimp.F.H' diff --git a/pyinstaller/buildtests/import/relimp/B/D.py b/pyinstaller/buildtests/import/relimp/B/D.py new file mode 100644 index 0000000..d302f0d --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/B/D.py @@ -0,0 +1,4 @@ +name = 'relimp.B.D' + +class X: + name = 'relimp.B.D.X' diff --git a/pyinstaller/buildtests/import/relimp/B/__init__.py b/pyinstaller/buildtests/import/relimp/B/__init__.py new file mode 100644 index 0000000..3d0e579 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/B/__init__.py @@ -0,0 +1 @@ +name = 'relimp.B' diff --git a/pyinstaller/buildtests/import/relimp/E.py b/pyinstaller/buildtests/import/relimp/E.py new file mode 100644 index 0000000..6d5d189 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/E.py @@ -0,0 +1 @@ +name = 'relimp.E' diff --git a/pyinstaller/buildtests/import/relimp/F/.svn/entries b/pyinstaller/buildtests/import/relimp/F/.svn/entries new file mode 100644 index 0000000..8765b11 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/F/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp/F +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + +G.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp/F/G.py b/pyinstaller/buildtests/import/relimp/F/G.py new file mode 100644 index 0000000..5a78e01 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/F/G.py @@ -0,0 +1 @@ +name = 'relimp.F.G' diff --git a/pyinstaller/buildtests/import/relimp/F/__init__.py b/pyinstaller/buildtests/import/relimp/F/__init__.py new file mode 100644 index 0000000..5fde96d --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/F/__init__.py @@ -0,0 +1,4 @@ +name = 'relimp.F' + +class H: + name = 'relimp.F.H' diff --git a/pyinstaller/buildtests/import/relimp/__init__.py b/pyinstaller/buildtests/import/relimp/__init__.py new file mode 100644 index 0000000..6da83e6 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/__init__.py @@ -0,0 +1,2 @@ + +name = 'relimp' diff --git a/pyinstaller/buildtests/import/relimp/relimp/.svn/entries b/pyinstaller/buildtests/import/relimp/relimp/.svn/entries new file mode 100644 index 0000000..48b2b02 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/relimp/.svn/entries @@ -0,0 +1,49 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp/relimp +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +relimp2.py +file + + + +add + +relimp3.py +file + + + +add + +__init__.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp/relimp/__init__.py b/pyinstaller/buildtests/import/relimp/relimp/__init__.py new file mode 100644 index 0000000..c302efb --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/relimp/__init__.py @@ -0,0 +1 @@ +name = 'relimp.relimp' diff --git a/pyinstaller/buildtests/import/relimp/relimp/relimp2.py b/pyinstaller/buildtests/import/relimp/relimp/relimp2.py new file mode 100644 index 0000000..fedc784 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/relimp/relimp2.py @@ -0,0 +1,22 @@ + +from __future__ import absolute_import + +name = 'relimp.relimp.relimp2' + +from . import relimp3 +assert relimp3.name == 'relimp.relimp.relimp3' + +from .. import relimp +assert relimp.name == 'relimp.relimp' + +import relimp +assert relimp.name == 'relimp' + +import relimp.relimp2 +assert relimp.relimp2.name == 'relimp.relimp2' + +# While this seams to work when running Python, it is wrong: +# .relimp should be a sibling of this package +#from .relimp import relimp2 +#assert relimp2.name == 'relimp.relimp2' + diff --git a/pyinstaller/buildtests/import/relimp/relimp/relimp3.py b/pyinstaller/buildtests/import/relimp/relimp/relimp3.py new file mode 100644 index 0000000..2d0d494 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/relimp/relimp3.py @@ -0,0 +1 @@ +name = 'relimp.relimp.relimp3' diff --git a/pyinstaller/buildtests/import/relimp/relimp1.py b/pyinstaller/buildtests/import/relimp/relimp1.py new file mode 100644 index 0000000..36f606e --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/relimp1.py @@ -0,0 +1,16 @@ + +from __future__ import absolute_import + +name = 'relimp.relimp1' + +from . import relimp2 as upper +from . relimp import relimp2 as lower + +assert upper.name == 'relimp.relimp2' +assert lower.name == 'relimp.relimp.relimp2' + +if upper.__name__ == lower.__name__: + raise SystemExit("Imported the same module") + +if upper.__file__ == lower.__file__: + raise SystemExit("Imported the same file") diff --git a/pyinstaller/buildtests/import/relimp/relimp2.py b/pyinstaller/buildtests/import/relimp/relimp2.py new file mode 100644 index 0000000..8952ce7 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp/relimp2.py @@ -0,0 +1 @@ +name = 'relimp.relimp2' diff --git a/pyinstaller/buildtests/import/relimp2/.svn/entries b/pyinstaller/buildtests/import/relimp2/.svn/entries new file mode 100644 index 0000000..c590ffb --- /dev/null +++ b/pyinstaller/buildtests/import/relimp2/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp2 +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +bar +dir + + + +add + +__init__.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp2/__init__.py b/pyinstaller/buildtests/import/relimp2/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp2/__init__.py @@ -0,0 +1 @@ +# diff --git a/pyinstaller/buildtests/import/relimp2/bar/.svn/entries b/pyinstaller/buildtests/import/relimp2/bar/.svn/entries new file mode 100644 index 0000000..e31dcd7 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp2/bar/.svn/entries @@ -0,0 +1,49 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp2/bar +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +baz.py +file + + + +add + +__init__.py +file + + + +add + +bar2 +dir + + + +add + diff --git a/pyinstaller/buildtests/import/relimp2/bar/__init__.py b/pyinstaller/buildtests/import/relimp2/bar/__init__.py new file mode 100644 index 0000000..e943251 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp2/bar/__init__.py @@ -0,0 +1 @@ +from .baz import * diff --git a/pyinstaller/buildtests/import/relimp2/bar/bar2/.svn/entries b/pyinstaller/buildtests/import/relimp2/bar/bar2/.svn/entries new file mode 100644 index 0000000..b770159 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp2/bar/bar2/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp2/bar/bar2 +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp2/bar/bar2/__init__.py b/pyinstaller/buildtests/import/relimp2/bar/bar2/__init__.py new file mode 100644 index 0000000..28744f6 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp2/bar/bar2/__init__.py @@ -0,0 +1 @@ +from ..baz import * diff --git a/pyinstaller/buildtests/import/relimp2/bar/baz.py b/pyinstaller/buildtests/import/relimp2/bar/baz.py new file mode 100644 index 0000000..e0185c9 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp2/bar/baz.py @@ -0,0 +1,2 @@ +def say_hello_please(): + print "Hello World!" diff --git a/pyinstaller/buildtests/import/relimp3a/.svn/entries b/pyinstaller/buildtests/import/relimp3a/.svn/entries new file mode 100644 index 0000000..8a0852b --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/.svn/entries @@ -0,0 +1,56 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp3a +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +aa +dir + + + +add + +relimp3b.py +file + + + +add + +relimp3c.py +file + + + +add + +__init__.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp3a/__init__.py b/pyinstaller/buildtests/import/relimp3a/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/__init__.py @@ -0,0 +1 @@ +# diff --git a/pyinstaller/buildtests/import/relimp3a/aa/.svn/entries b/pyinstaller/buildtests/import/relimp3a/aa/.svn/entries new file mode 100644 index 0000000..2ffd29b --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/aa/.svn/entries @@ -0,0 +1,49 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp3a/aa +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +relimp3c.py +file + + + +add + +__init__.py +file + + + +add + +a1.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp3a/aa/__init__.py b/pyinstaller/buildtests/import/relimp3a/aa/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/aa/__init__.py @@ -0,0 +1 @@ +# diff --git a/pyinstaller/buildtests/import/relimp3a/aa/a1.py b/pyinstaller/buildtests/import/relimp3a/aa/a1.py new file mode 100644 index 0000000..cac7d99 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/aa/a1.py @@ -0,0 +1,6 @@ +from .relimp3b import b1 +from .relimp3c import c1 + +def getString(): + return b1.string + c1.string + diff --git a/pyinstaller/buildtests/import/relimp3a/aa/relimp3c.py b/pyinstaller/buildtests/import/relimp3a/aa/relimp3c.py new file mode 100644 index 0000000..710c942 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/aa/relimp3c.py @@ -0,0 +1,2 @@ +class c1: + string = "... and this" diff --git a/pyinstaller/buildtests/import/relimp3a/relimp3b.py b/pyinstaller/buildtests/import/relimp3a/relimp3b.py new file mode 100644 index 0000000..68263d9 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/relimp3b.py @@ -0,0 +1 @@ +raise ValueError diff --git a/pyinstaller/buildtests/import/relimp3a/relimp3c.py b/pyinstaller/buildtests/import/relimp3a/relimp3c.py new file mode 100644 index 0000000..68263d9 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3a/relimp3c.py @@ -0,0 +1 @@ +raise ValueError diff --git a/pyinstaller/buildtests/import/relimp3b/.svn/entries b/pyinstaller/buildtests/import/relimp3b/.svn/entries new file mode 100644 index 0000000..9aafcde --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3b/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp3b +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + +b1.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp3b/__init__.py b/pyinstaller/buildtests/import/relimp3b/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3b/__init__.py @@ -0,0 +1 @@ +# diff --git a/pyinstaller/buildtests/import/relimp3b/b1.py b/pyinstaller/buildtests/import/relimp3b/b1.py new file mode 100644 index 0000000..78ff89b --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3b/b1.py @@ -0,0 +1 @@ +string = "I hope you see this!" diff --git a/pyinstaller/buildtests/import/relimp3c/.svn/entries b/pyinstaller/buildtests/import/relimp3c/.svn/entries new file mode 100644 index 0000000..312809a --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3c/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/relimp3c +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + diff --git a/pyinstaller/buildtests/import/relimp3c/__init__.py b/pyinstaller/buildtests/import/relimp3c/__init__.py new file mode 100644 index 0000000..a9615e1 --- /dev/null +++ b/pyinstaller/buildtests/import/relimp3c/__init__.py @@ -0,0 +1 @@ +raise RuntimeError diff --git a/pyinstaller/buildtests/import/static_plugin.py b/pyinstaller/buildtests/import/static_plugin.py new file mode 100644 index 0000000..66237d3 --- /dev/null +++ b/pyinstaller/buildtests/import/static_plugin.py @@ -0,0 +1,23 @@ +# +# Copyright (C) 2012, Daniel Hyams +# +# 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 + + +# This is a static plugin module that goes +# with the test_app_with_plugins sample. + + +print('Static plugin imported.') diff --git a/pyinstaller/buildtests/import/test_app_with_plugins.py b/pyinstaller/buildtests/import/test_app_with_plugins.py new file mode 100644 index 0000000..0406e31 --- /dev/null +++ b/pyinstaller/buildtests/import/test_app_with_plugins.py @@ -0,0 +1,75 @@ +# +# Copyright (C) 2012, Daniel Hyams +# +# 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 + + +# This little sample application generates a plugin on the fly, +# and then tries to import it. + + +import os +import sys + + +# We first import a static plugin; the application might have +# certain plugins that it always loads. +try: + print('Attempting to import static_plugin...') + mdl = __import__('static_plugin') +except ImportError: + raise SystemExit('Failed to import the static plugin.') + + +plugin_contents = """ +print('DYNAMIC PLUGIN IMPORTED.') +print('This is some user-generated plugin that does not exist until') +print(' the application starts and other modules in the directory') +print(' are imported (like the static_plugin).') +""" + + +# Create the dynamic plugin in the same directory as the executable. +if hasattr(sys, 'frozen'): + program_dir = os.path.abspath(sys.prefix) +else: + program_dir = os.path.dirname(os.path.abspath(__file__)) +plugin_filename = os.path.join(program_dir, 'dynamic_plugin.py') +fp = open(plugin_filename, 'w') +fp.write(plugin_contents) +fp.close() + + +# Try import dynamic plugin. +is_error = False +try: + print('Attempting to import dynamic_plugin...') + mdl = __import__('dynamic_plugin') +except ImportError: + is_error = True + + +# Clean up. Remove files dynamic_plugin.py[c] +for f in (plugin_filename, plugin_filename + 'c'): + try: + os.remove(plugin_filename) + except OSError: + pass + + +# Statement 'try except finally' is available since Python 2.5+. +if is_error: + # Raise exeption. + raise SystemExit('Failed to import the dynamic plugin.') diff --git a/pyinstaller/buildtests/import/test_app_with_plugins.spec b/pyinstaller/buildtests/import/test_app_with_plugins.spec new file mode 100644 index 0000000..953a7b3 --- /dev/null +++ b/pyinstaller/buildtests/import/test_app_with_plugins.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_app_with_plugins' + + + +a = Analysis([__testname__+'.py'], pathex=[]) + +TOC_custom = [('static_plugin.py','static_plugin.py','DATA')] + +pyz = PYZ(a.pure) + +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=1, + console=1) +coll = COLLECT( exe, + a.binaries, + TOC_custom, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/import/test_c_extension.py b/pyinstaller/buildtests/import/test_c_extension.py new file mode 100644 index 0000000..bc3ff36 --- /dev/null +++ b/pyinstaller/buildtests/import/test_c_extension.py @@ -0,0 +1,39 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# In dist directory are Python C-extension file names like module.submodule.so +# E.g. ./simplejson/_speedups.so -> ./simplejson._speedups.so + + +import os +import sys + + +from simplejson import _speedups + + +modpath = os.path.join(sys.prefix, 'simplejson._speedups') +frozen_modpath = os.path.splitext(_speedups.__file__)[0] + + +print('Module path expected: ' + modpath) +print('Module path current: ' + frozen_modpath) + + +if not frozen_modpath == modpath: + raise SystemExit('Python C-extension file name is not correct.') diff --git a/pyinstaller/buildtests/import/test_ctypes_cdll_c.py b/pyinstaller/buildtests/import/test_ctypes_cdll_c.py new file mode 100644 index 0000000..77de5cc --- /dev/null +++ b/pyinstaller/buildtests/import/test_ctypes_cdll_c.py @@ -0,0 +1,6 @@ +import ctypes, ctypes.util + +# Make sure we are able to load the MSVCRXX.DLL we are currently bound +# to through ctypes. +lib = ctypes.CDLL(ctypes.util.find_library('c')) +print lib diff --git a/pyinstaller/buildtests/import/test_ctypes_cdll_c.spec b/pyinstaller/buildtests/import/test_ctypes_cdll_c.spec new file mode 100644 index 0000000..48bc5b9 --- /dev/null +++ b/pyinstaller/buildtests/import/test_ctypes_cdll_c.spec @@ -0,0 +1,21 @@ +# -*- mode: python -*- + +__testname__ = 'test_ctypes_cdll_c' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=True, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/import/test_ctypes_cdll_c2.py b/pyinstaller/buildtests/import/test_ctypes_cdll_c2.py new file mode 100644 index 0000000..582c45a --- /dev/null +++ b/pyinstaller/buildtests/import/test_ctypes_cdll_c2.py @@ -0,0 +1,7 @@ +# same test of test-ctypes-cdll-c.py, but built in one-file mode +import ctypes, ctypes.util + +# Make sure we are able to load the MSVCRXX.DLL we are currently bound +# to through ctypes. +lib = ctypes.CDLL(ctypes.util.find_library('c')) +print lib diff --git a/pyinstaller/buildtests/import/test_ctypes_cdll_c2.spec b/pyinstaller/buildtests/import/test_ctypes_cdll_c2.spec new file mode 100644 index 0000000..a78ada7 --- /dev/null +++ b/pyinstaller/buildtests/import/test_ctypes_cdll_c2.spec @@ -0,0 +1,15 @@ +# -*- mode: python -*- + +__testname__ = 'test_ctypes_cdll_c2' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.binaries, + a.zipfiles, + a.scripts, + name=os.path.join('dist', __testname__ + '.exe'), + debug=True, + strip=False, + upx=False, + console=1 ) diff --git a/pyinstaller/buildtests/import/test_eggs1.py b/pyinstaller/buildtests/import/test_eggs1.py new file mode 100644 index 0000000..ca8ac21 --- /dev/null +++ b/pyinstaller/buildtests/import/test_eggs1.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), 'unzipped.egg')) + +import pkg_resources + +t = pkg_resources.resource_string('unzipped_egg', 'data/datafile.txt') +assert t.rstrip() == 'This is data file for `unzipped`.' + +import unzipped_egg +assert unzipped_egg.data == 'This is data file for `unzipped`.' + +print 'Okay.' diff --git a/pyinstaller/buildtests/import/test_eggs1.spec b/pyinstaller/buildtests/import/test_eggs1.spec new file mode 100644 index 0000000..e7ee3e8 --- /dev/null +++ b/pyinstaller/buildtests/import/test_eggs1.spec @@ -0,0 +1,19 @@ +# -*- mode: python -*- + +__testname__ = 'test_eggs1' + +a = Analysis([__testname__ + '.py'], + pathex=['unzipped.egg', 'zipped.egg']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=None, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/import/test_eggs2.py b/pyinstaller/buildtests/import/test_eggs2.py new file mode 100644 index 0000000..9cca1af --- /dev/null +++ b/pyinstaller/buildtests/import/test_eggs2.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), 'zipped.egg')) + +import pkg_resources + +t = pkg_resources.resource_string('zipped_egg', 'data/datafile.txt') +assert t.rstrip() == 'This is data file for `zipped`.' + +import zipped_egg +assert zipped_egg.data == 'This is data file for `zipped`.' + +print 'Okay.' diff --git a/pyinstaller/buildtests/import/test_eggs2.spec b/pyinstaller/buildtests/import/test_eggs2.spec new file mode 100644 index 0000000..4fd7b49 --- /dev/null +++ b/pyinstaller/buildtests/import/test_eggs2.spec @@ -0,0 +1,19 @@ +# -*- mode: python -*- + +__testname__ = 'test_eggs2' + +a = Analysis([__testname__ + '.py'], + pathex=['unzipped.egg', 'zipped.egg']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=None, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/import/test_error_during_import.py b/pyinstaller/buildtests/import/test_error_during_import.py new file mode 100644 index 0000000..e31e076 --- /dev/null +++ b/pyinstaller/buildtests/import/test_error_during_import.py @@ -0,0 +1,8 @@ +# See ticket #27: historically, PyInstaller was catching all errors during imports... +try: + import error_during_import2 +except KeyError: + print "OK" +else: + raise RuntimeError("failure!") + diff --git a/pyinstaller/buildtests/import/test_error_during_import.spec b/pyinstaller/buildtests/import/test_error_during_import.spec new file mode 100644 index 0000000..bf8931f --- /dev/null +++ b/pyinstaller/buildtests/import/test_error_during_import.spec @@ -0,0 +1,15 @@ +# -*- mode: python -*- + +__testname__ = 'test_error_during_import' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + name = os.path.join('dist', __testname__, __testname__ +'.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) diff --git a/pyinstaller/buildtests/import/test_hiddenimport.py b/pyinstaller/buildtests/import/test_hiddenimport.py new file mode 100644 index 0000000..d589af2 --- /dev/null +++ b/pyinstaller/buildtests/import/test_hiddenimport.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python + +# simply do nothing here, not even print out a line +pass diff --git a/pyinstaller/buildtests/import/test_hiddenimport.spec b/pyinstaller/buildtests/import/test_hiddenimport.spec new file mode 100644 index 0000000..2755f21 --- /dev/null +++ b/pyinstaller/buildtests/import/test_hiddenimport.spec @@ -0,0 +1,19 @@ +# -*- mode: python -*- + +__testname__ = 'test_hiddenimport' + +a = Analysis([__testname__ + '.py'], + hiddenimports=['anydbm']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=None, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/import/test_hiddenimport.toc b/pyinstaller/buildtests/import/test_hiddenimport.toc new file mode 100644 index 0000000..61aae45 --- /dev/null +++ b/pyinstaller/buildtests/import/test_hiddenimport.toc @@ -0,0 +1,3 @@ +[ + 'anydbm', +] diff --git a/pyinstaller/buildtests/import/test_onefile_c_extension.py b/pyinstaller/buildtests/import/test_onefile_c_extension.py new file mode 100644 index 0000000..bc3ff36 --- /dev/null +++ b/pyinstaller/buildtests/import/test_onefile_c_extension.py @@ -0,0 +1,39 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# In dist directory are Python C-extension file names like module.submodule.so +# E.g. ./simplejson/_speedups.so -> ./simplejson._speedups.so + + +import os +import sys + + +from simplejson import _speedups + + +modpath = os.path.join(sys.prefix, 'simplejson._speedups') +frozen_modpath = os.path.splitext(_speedups.__file__)[0] + + +print('Module path expected: ' + modpath) +print('Module path current: ' + frozen_modpath) + + +if not frozen_modpath == modpath: + raise SystemExit('Python C-extension file name is not correct.') diff --git a/pyinstaller/buildtests/import/test_onefile_zipimport.py b/pyinstaller/buildtests/import/test_onefile_zipimport.py new file mode 100644 index 0000000..16122d6 --- /dev/null +++ b/pyinstaller/buildtests/import/test_onefile_zipimport.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- mode: python -*- +# +# 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 . +# +""" +test for zipimport - minimalistic, just import pgk_resource +""" + +import os +import sys + +print __name__, 'is running' +print 'sys.path:', sys.path +print 'dir contents .exe:', os.listdir(os.path.dirname(sys.executable)) +print '-----------' +print 'dir contents sys._MEIPASS:', os.listdir(sys._MEIPASS) + +print '-----------' +print 'now importing pkg_resources' +import pkg_resources +print "dir(pkg_resources)", dir(pkg_resources) diff --git a/pyinstaller/buildtests/import/test_onefile_zipimport2.py b/pyinstaller/buildtests/import/test_onefile_zipimport2.py new file mode 100644 index 0000000..17b0be5 --- /dev/null +++ b/pyinstaller/buildtests/import/test_onefile_zipimport2.py @@ -0,0 +1,44 @@ +# -*- mode: python -*- +#!/usr/bin/env python +# +# 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 . +# +""" +test for zipimport - use a more complex import +""" + +import os +import sys + +print __name__, 'is running' +print 'sys.path:', sys.path +print 'dir contents .exe:', os.listdir(os.path.dirname(sys.executable)) +if hasattr(sys, 'frozen') and sys.frozen: + print '-----------' + print 'dir contents sys._MEIPASS:', os.listdir(sys._MEIPASS) + +print '-----------' +print 'now importing pkg_resources' +import pkg_resources + +print '-----------' +print 'now importing setuptools.dist' +import setuptools.dist +print '-----------' +print 'now importing setuptools.command' diff --git a/pyinstaller/buildtests/import/test_relative_import.py b/pyinstaller/buildtests/import/test_relative_import.py new file mode 100644 index 0000000..045b8f1 --- /dev/null +++ b/pyinstaller/buildtests/import/test_relative_import.py @@ -0,0 +1,8 @@ + +import relimp.B.C +from relimp.F import H +import relimp.relimp1 + +assert relimp.relimp1.name == 'relimp.relimp1' +assert relimp.B.C.name == 'relimp.B.C' +assert relimp.F.H.name == 'relimp.F.H' diff --git a/pyinstaller/buildtests/import/test_relative_import.spec b/pyinstaller/buildtests/import/test_relative_import.spec new file mode 100644 index 0000000..68a1876 --- /dev/null +++ b/pyinstaller/buildtests/import/test_relative_import.spec @@ -0,0 +1,15 @@ +# -*- mode: python -*- + +__testname__ = 'test_relative_import' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + name = os.path.join('dist', __testname__, __testname__ +'.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) diff --git a/pyinstaller/buildtests/import/test_relative_import.toc b/pyinstaller/buildtests/import/test_relative_import.toc new file mode 100644 index 0000000..30de4ac --- /dev/null +++ b/pyinstaller/buildtests/import/test_relative_import.toc @@ -0,0 +1,14 @@ +[ +'relimp', +'relimp.B', +'relimp.B.C', +'relimp.B.D', +'relimp.E', +'relimp.F', +'relimp.F.G', +'relimp.relimp', +'relimp.relimp.relimp2', +'relimp.relimp.relimp3', +'relimp.relimp1', +'relimp.relimp2', +] diff --git a/pyinstaller/buildtests/import/test_relative_import2.py b/pyinstaller/buildtests/import/test_relative_import2.py new file mode 100644 index 0000000..eebb9ca --- /dev/null +++ b/pyinstaller/buildtests/import/test_relative_import2.py @@ -0,0 +1,5 @@ +import relimp2.bar +import relimp2.bar.bar2 + +relimp2.bar.say_hello_please() +relimp2.bar.bar2.say_hello_please() diff --git a/pyinstaller/buildtests/import/test_relative_import2.spec b/pyinstaller/buildtests/import/test_relative_import2.spec new file mode 100644 index 0000000..a5d81ee --- /dev/null +++ b/pyinstaller/buildtests/import/test_relative_import2.spec @@ -0,0 +1,15 @@ +# -*- mode: python -*- + +__testname__ = 'test_relative_import2' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + name = os.path.join('dist', __testname__, __testname__ +'.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) diff --git a/pyinstaller/buildtests/import/test_relative_import3.py b/pyinstaller/buildtests/import/test_relative_import3.py new file mode 100644 index 0000000..a81039b --- /dev/null +++ b/pyinstaller/buildtests/import/test_relative_import3.py @@ -0,0 +1,4 @@ +from relimp3a.aa import a1 + +if __name__ == '__main__': + print a1.getString() diff --git a/pyinstaller/buildtests/import/test_relative_import3.spec b/pyinstaller/buildtests/import/test_relative_import3.spec new file mode 100644 index 0000000..c07c55f --- /dev/null +++ b/pyinstaller/buildtests/import/test_relative_import3.spec @@ -0,0 +1,15 @@ +# -*- mode: python -*- + +__testname__ = 'test_relative_import3' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + name = os.path.join('dist', __testname__, __testname__ +'.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) diff --git a/pyinstaller/buildtests/import/unzipped.egg/.svn/entries b/pyinstaller/buildtests/import/unzipped.egg/.svn/entries new file mode 100644 index 0000000..e6bfa1a --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/unzipped.egg +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +unzipped_egg +dir + + + +add + +EGG-INFO +dir + + + +add + diff --git a/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/.svn/entries b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/.svn/entries new file mode 100644 index 0000000..b391612 --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/.svn/entries @@ -0,0 +1,63 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +SOURCES.txt +file + + + +add + +top_level.txt +file + + + +add + +PKG-INFO +file + + + +add + +dependency_links.txt +file + + + +add + +not-zip-safe +file + + + +add + diff --git a/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/PKG-INFO b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/PKG-INFO new file mode 100644 index 0000000..b867d7a --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: unzipped-egg +Version: 0.1 +Summary: A unzipped egg for testing PyInstaller +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/SOURCES.txt b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/SOURCES.txt new file mode 100644 index 0000000..ab45164 --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/SOURCES.txt @@ -0,0 +1,8 @@ +README.txt +setup-unzipped.py +unzipped_egg/__init__.py +unzipped_egg.egg-info/PKG-INFO +unzipped_egg.egg-info/SOURCES.txt +unzipped_egg.egg-info/dependency_links.txt +unzipped_egg.egg-info/not-zip-safe +unzipped_egg.egg-info/top_level.txt \ No newline at end of file diff --git a/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/dependency_links.txt b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/not-zip-safe b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/not-zip-safe @@ -0,0 +1 @@ + diff --git a/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/top_level.txt b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/top_level.txt new file mode 100644 index 0000000..6612c2f --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/EGG-INFO/top_level.txt @@ -0,0 +1 @@ +unzipped_egg diff --git a/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/.svn/entries b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/.svn/entries new file mode 100644 index 0000000..74f8a0c --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +__init__.py +file + + + +add + +data +dir + + + +add + diff --git a/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/__init__.py b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/__init__.py new file mode 100644 index 0000000..e6fd881 --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# +# This file is part of the package for testing eggs in `PyInstaller`. +# +# Author: Hartmut Goebel +# Copyright: 2012 by Hartmut Goebel +# Licence: GNU Public Licence v3 (GPLv3) +# + +import pkg_resources +data = pkg_resources.resource_string(__name__, 'data/datafile.txt').rstrip() diff --git a/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/data/.svn/entries b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/data/.svn/entries new file mode 100644 index 0000000..d29a751 --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/data/.svn/entries @@ -0,0 +1,35 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/data +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +datafile.txt +file + + + +add + diff --git a/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/data/datafile.txt b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/data/datafile.txt new file mode 100644 index 0000000..8c759f0 --- /dev/null +++ b/pyinstaller/buildtests/import/unzipped.egg/unzipped_egg/data/datafile.txt @@ -0,0 +1 @@ +This is data file for `unzipped`. diff --git a/pyinstaller/buildtests/import/zipped.egg b/pyinstaller/buildtests/import/zipped.egg new file mode 100644 index 0000000..b4ff616 Binary files /dev/null and b/pyinstaller/buildtests/import/zipped.egg differ diff --git a/pyinstaller/buildtests/interactive/.svn/entries b/pyinstaller/buildtests/interactive/.svn/entries new file mode 100644 index 0000000..cf15831 --- /dev/null +++ b/pyinstaller/buildtests/interactive/.svn/entries @@ -0,0 +1,126 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/interactive +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test_buffering.spec +file + + + +add + +test_matplotlib.spec +file + + + +add + +test_wx.py +file + + + +add + +test_tkinter.py +file + + + +add + +test_wx.spec +file + + + +add + +test_pyqt4.py +file + + + +add + +test_pygame.py +file + + + +add + +test_tkinter.spec +file + + + +add + +test_pyqt4.spec +file + + + +add + +test_pygame.spec +file + + + +add + +test_tix.py +file + + + +add + +test_tix.spec +file + + + +add + +test_buffering.py +file + + + +add + +test_matplotlib.py +file + + + +add + diff --git a/pyinstaller/buildtests/interactive/test_buffering.py b/pyinstaller/buildtests/interactive/test_buffering.py new file mode 100644 index 0000000..739cd60 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_buffering.py @@ -0,0 +1,30 @@ +# Copyright (C) 2005, Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 +print "test_buffering - unbufferred" +print "type: 123456" +print "should see: 12345" +print "type: " +print "if unbuffered should see: 6" +print "if NOT unbuffered, should see nothing" +print "Q to quit" +import sys +while 1: + data = sys.stdin.read(5) + sys.stdout.write(data) + if 'Q' in data: + break +print "test_buffering - done" diff --git a/pyinstaller/buildtests/interactive/test_buffering.spec b/pyinstaller/buildtests/interactive/test_buffering.spec new file mode 100644 index 0000000..b7433dc --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_buffering.spec @@ -0,0 +1,17 @@ +# -*- mode: python -*- + +__testname__ = 'test_buffering' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + [('u', '', 'OPTION')], + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=0, + console=1) +coll = COLLECT( exe, + a.binaries, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/interactive/test_matplotlib.py b/pyinstaller/buildtests/interactive/test_matplotlib.py new file mode 100644 index 0000000..403b90f --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_matplotlib.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import numpy +from matplotlib import mlab +from matplotlib import pyplot + + +def main(): + # Part of the example at + # http://matplotlib.sourceforge.net/plot_directive/mpl_examples/pylab_examples/contour_demo.py + delta = 0.025 + x = numpy.arange(-3.0, 3.0, delta) + y = numpy.arange(-2.0, 2.0, delta) + X, Y = numpy.meshgrid(x, y) + Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0) + Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1) + Z = 10.0 * (Z2 - Z1) + pyplot.figure() + CS = pyplot.contour(X, Y, Z) + pyplot.show() + + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/interactive/test_matplotlib.spec b/pyinstaller/buildtests/interactive/test_matplotlib.spec new file mode 100644 index 0000000..9657a74 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_matplotlib.spec @@ -0,0 +1,34 @@ +# -*- mode: python -*- + +__testname__ = 'test_matplotlib' + +if sys.platform == 'win32' and sys.version_info[:2] >= (2, 6): + manifest = ''' + + + + + +''' +else: + manifest = None + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True, + manifest=manifest ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/interactive/test_pygame.py b/pyinstaller/buildtests/interactive/test_pygame.py new file mode 100644 index 0000000..bbbc6f4 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_pygame.py @@ -0,0 +1,235 @@ +# -*- coding: utf-8 -*- +################################################################################ +#! /usr/bin/python +################################################################################ +# Crosstown Traffic +# Hendrik Heuer +# GNU General Public License +################################################################################ + +# This is a copy of +# http://jonasbsb.jo.funpic.de/hendrix/pygame-example.py + +try: + import sys + import random + import math + import os + import pygame + import time + from pygame.locals import * + +except ImportError, err: + print "Error, couldn't load module. %s" % (err) + sys.exit(2) + +if not pygame.mixer: print 'Warning, sound disabled' + +### Klassendefinitionen + +class Screen: + def __init__(self, resolution=(640, 480), cmdline=""): + self.color = (0,0,0) + self.resolution = resolution + if "--fullscreen" in cmdline: + self.window = \ + pygame.display.set_mode(self.resolution, pygame.FULLSCREEN) + else: + self.window = pygame.display.set_mode(self.resolution) + # pygame.display.set_mode() verändert die Größe des Fensters + # Über das zweite Argument, pygame.FULLSCREEN, kann das Fenster + # in den Vollbildmodus versetzt werden + + pygame.display.set_caption('A Simple Yet Insightful Pygame Example') + # Verändert die Beschriftung des Fensters + + pygame.mouse.set_visible(0) + # Verhindert, dass die Maus gezeigt wird + + self.screen = pygame.display.get_surface() + # Generiert ein Surface des Fensters + # Siehe: Allgemeines über Surfaces + + self.screen.fill(self.color) + # Füllt das Surface self.screen mit der übergebenen Farbe + # Siehe: Allgemeines über Farben + + self.screen_rect = self.screen.get_rect() + # Rectangle des Fensters + # Siehe: Allgemeines über Rectangles + + def size(self): + return self.screen_rect + + def fill(self): + self.screen.fill(self.color) + # Füllt das Surface self.screen mit der übergebenen Farbe + # Siehe: Allgemeines über Farben + + def quit(): + sys.exit(0) + +class Sprite(pygame.sprite.Sprite): + def __init__(self, screen): + pygame.sprite.Sprite.__init__(self) + # Die Klasse Sprite wird von der pygame-Basisklasse + # pygame.sprite.Sprite abgeleitet + + self.screen= screen + + self.width = 10 + self.height = 10 + # Legt die Höhe und Breite der Objekte fest + + self.x = random.randint(0, screen.resolution[0] + self.width) + self.y = random.randint(0, screen.resolution[1] + self.height) + # Generiert zufällig eine x- und eine y-Koordinate als Startpunkt + + self.direction = random.choice((1,-1)) + self.angle = random.choice((0.45, 2.69)) * self.direction + self.speed = random.randint(5,8) + # Wählt zufällig Werte für die Richtung und Geschwindigkeit aus + + self.image = pygame.Surface([self.width, self.height]) + # Generiert ein Surface des Objektes mit der definierten Größe + # Siehe: Allgemeines über Surfaces + + self.rect = self.image.get_rect() + # Siehe: Allgemeines über Rectangles + + self.rect = self.rect.move(self.x,self.y) + # self.rect.move(x-Wert, y-Wert) berechnet einen + # neuen Punkt und ordnet ihn dem Rectangle des + # Objektes zu + # + # Das Koordinatensystem beginnt am oberen, linken Rand + # des Bildschirms mit (0,0) + + self.area = pygame.display.get_surface().get_rect() + # Rectangle des Fensters + # Siehe: Allgemeines über Rectangles + + def position(self): + return self.rect + + def changeColor(self): + newColor = [] + for i in range(3): + newColor.append(random.randint(0,255)) + self.color = newColor + # Generiert einen zufälligen Farbwert + + self.image.fill(self.color) + # Füllt das Surface des Objektes + # Siehe: Allgemeines über Farben + + def update(self): + dx = self.speed*math.cos(self.angle) + dy = self.speed*math.sin(self.angle) + # Mathematische Grundlage der Bewegung + # siehe: http://de.wikipedia.org/wiki/Sinus + + newpos = self.rect.move(dx,dy) + # berechnet eine neue Position + + if not self.area.contains(newpos): + # Kollisionsberechnung + tl = not self.area.collidepoint(newpos.topleft) + tr = not self.area.collidepoint(newpos.topright) + bl = not self.area.collidepoint(newpos.bottomleft) + br = not self.area.collidepoint(newpos.bottomright) + # Kollisionen mit den Eckpunkten des Fensters werden + # berechnet und als boolescher Wert gespeichert + # (0 keine Kollision, 1 Kollision) + + if tr and tl or (br and bl): + # Falls das Objekt mit dem oberen oder unteren + # Bildschirmrand kollidiert, + self.angle = -self.angle + self.changeColor() + # wird der Winkel (und damit die Richtung) umgekehrt + # und die Farbe verändert + + if tl and bl or (tr and br): + # Falls das Objekt mit dem linken oder rechten + # Bildschirmrand kollidiert, + self.angle = math.pi - self.angle + self.changeColor() + # Wird der Winkel (und damit die Richtung) umgekehrt + # und die Farbe verändert + + self.rect = newpos + # Ordnet dem Rectangle des Objekts die neue Position zu + # Die Veränderung der Position wird erst hier gültig! + + +### Funktionsdefinitionen + +def end(): + sys.exit(0) + +def game(events, screen, sprites): + for event in events: + # Wertet die Event-Warteschleife aus + if event.type == QUIT: + # Beendet das Programm, wenn z.B. das Fenster geschlossen wurde + end() + return + elif event.type == KEYDOWN and event.key == K_ESCAPE: + # Beendet das Programm, wenn die Taste Escape gedrückt wurde + end() + return + elif event.type == KEYDOWN and event.key == K_f: + # Schaltet in den Vollbildmodus, wenn die Taste F gedrückt wurde + pygame.display.toggle_fullscreen() + return + + screen.fill() + # Füllt den Bildschirm + + sprites.update() + # Bewegung und Kollisionserkennung der Sprite-Gruppe + # Die update-Funktion der Instanzen wird automatisch + # für alle 123 Rechtecke aufgerufen + + sprites.draw(screen.screen) + # Zeichnet die Sprite-Instanzen auf den Bildschirm + + pygame.display.update() + # Aktualisiert den Bildschirm + + +def main(): + pygame.init() + + pygame.key.set_repeat(1, 1) + # Legt fest, wie oft Tastendrücke automatisch wiederholt werden + # Das erste Argument gibt an ab wann, das zweite in welchen + # Intervallen der Tastendruck wiederholt wird + + clock = pygame.time.Clock() + # Erstellt einen Zeitnehmer + + screen = Screen(cmdline=sys.argv) + # Erstellt eine Instanz der Klasse Screen() + + movingSprites = [] + for i in range(123): + movingSprites.append(Sprite(screen)) + # Die for-Schleife erstellt 123 Instanzen der Klasse Sprite + # und fügt sie der Liste movingSprites hinzu + + sprites = pygame.sprite.RenderPlain((movingSprites)) + # Fasst die erstellen Sprite-Instanzen zu einer Gruppe zusammen + # um das Zeichnen der Sprites zu erleichtern + + while True: + clock.tick(30) + # Verhindert, dass das Spiel zu schnell läuft + + game(pygame.event.get(), screen, sprites) + # Ruft die Funktion game auf und übergibt ihr + # die Event-Warteschleife, die Zeichenfläche und die Objekte + end() + +main() diff --git a/pyinstaller/buildtests/interactive/test_pygame.spec b/pyinstaller/buildtests/interactive/test_pygame.spec new file mode 100644 index 0000000..b18e205 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_pygame.spec @@ -0,0 +1,28 @@ +# -*- mode: python -*- + +__testname__ = 'test_pygame' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) + +import sys +if sys.platform.startswith("darwin"): + app = BUNDLE(coll, + name=os.path.join('dist', __testname__ + '.app'), + version='0.0.1') diff --git a/pyinstaller/buildtests/interactive/test_pyqt4.py b/pyinstaller/buildtests/interactive/test_pyqt4.py new file mode 100644 index 0000000..1c6eab6 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_pyqt4.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys + +from PyQt4 import Qt +from PyQt4 import QtCore +from PyQt4 import QtGui + + +def main(): + app = Qt.QApplication(sys.argv) + read_formats = ', '.join([unicode(format).lower() \ + for format in QtGui.QImageReader.supportedImageFormats()]) + print("Qt4 plugin paths: " + unicode(list(app.libraryPaths()))) + print("Qt4 image read support: " + read_formats) + print('Qt4 Libraries path: ' + unicode(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.LibrariesPath))) + label = Qt.QLabel("Hello World from PyQt4", None) + label.setWindowTitle("Hello World from PyQt4") + label.resize(300, 300) + label.show() + app.exec_() + + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/interactive/test_pyqt4.spec b/pyinstaller/buildtests/interactive/test_pyqt4.spec new file mode 100644 index 0000000..934e592 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_pyqt4.spec @@ -0,0 +1,28 @@ +# -*- mode: python -*- + +__testname__ = 'test_pyqt4' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) + +import sys +if sys.platform.startswith("darwin"): + app = BUNDLE(coll, + name=os.path.join('dist', __testname__ + '.app'), + version='0.0.1') diff --git a/pyinstaller/buildtests/interactive/test_tix.py b/pyinstaller/buildtests/interactive/test_tix.py new file mode 100644 index 0000000..1ac1b78 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_tix.py @@ -0,0 +1,12 @@ + +import Tix as tix + +root = tix.Tk() +root.title("Test for TiX") + +tix.Label(text="Press to exit").pack() +tix.DirList(root).pack() +tix.Button(root, text="Close", command=root.destroy).pack() +root.bind("", lambda x: root.destroy()) + +tix.mainloop() diff --git a/pyinstaller/buildtests/interactive/test_tix.spec b/pyinstaller/buildtests/interactive/test_tix.spec new file mode 100644 index 0000000..6caa033 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_tix.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_tix' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/interactive/test_tkinter.py b/pyinstaller/buildtests/interactive/test_tkinter.py new file mode 100644 index 0000000..f5ec4b1 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_tkinter.py @@ -0,0 +1,10 @@ +from Tkinter import * + +root = Tk() +root.title("Test for Tkinter") +root.bind("", lambda x: root.destroy()) + +Label(text="Press to exit").pack() +Button(root, text="Close", command=root.destroy).pack() + +root.mainloop() diff --git a/pyinstaller/buildtests/interactive/test_tkinter.spec b/pyinstaller/buildtests/interactive/test_tkinter.spec new file mode 100644 index 0000000..5c21ab8 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_tkinter.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_tkinter' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=1 ) +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/interactive/test_wx.py b/pyinstaller/buildtests/interactive/test_wx.py new file mode 100644 index 0000000..ac12d80 --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_wx.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import wx + + +def main(): + app = wx.App(0) + frame = wx.Frame(None, title="Hello World from wxPython") + panel = wx.Panel(frame) + label = wx.StaticText(panel, -1, "Hello World from wxPython") + frame.Fit() + frame.Show() + app.MainLoop() + + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/interactive/test_wx.spec b/pyinstaller/buildtests/interactive/test_wx.spec new file mode 100644 index 0000000..b8f03ad --- /dev/null +++ b/pyinstaller/buildtests/interactive/test_wx.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_wx' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/libraries/.svn/entries b/pyinstaller/buildtests/libraries/.svn/entries new file mode 100644 index 0000000..4bbad45 --- /dev/null +++ b/pyinstaller/buildtests/libraries/.svn/entries @@ -0,0 +1,210 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/libraries +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test_pycrypto.py +file + + + +add + +test_usb.py +file + + + +add + +test_pycrypto.spec +file + + + +add + +test_wx_pubsub_kwargs.py +file + + + +add + +test_wx.py +file + + + +add + +test_wx.spec +file + + + +add + +test_xml.py +file + + + +add + +test_sqlalchemy.py +file + + + +add + +test_Image2.py +file + + + +add + +test_PIL2.py +file + + + +add + +test_Image2.spec +file + + + +add + +test_PIL2.spec +file + + + +add + +test_numpy.py +file + + + +add + +test_numpy.spec +file + + + +add + +test_pyttsx.py +file + + + +add + +test_wx_pubsub.py +file + + + +add + +test_pyodbc.py +file + + + +add + +test_enchant.py +file + + + +add + +test_onefile_tkinter.py +file + + + +add + +test_Image.py +file + + + +add + +test_PIL.py +file + + + +add + +test_Image.spec +file + + + +add + +test_PIL.spec +file + + + +add + +tinysample.tiff +file + + + +add + + + + + +has-props +has-prop-mods + +test_wx_pubsub_arg1.py +file + + + +add + diff --git a/pyinstaller/buildtests/libraries/.svn/props/tinysample.tiff.svn-work b/pyinstaller/buildtests/libraries/.svn/props/tinysample.tiff.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/pyinstaller/buildtests/libraries/.svn/props/tinysample.tiff.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/pyinstaller/buildtests/libraries/test_Image.py b/pyinstaller/buildtests/libraries/test_Image.py new file mode 100644 index 0000000..8f4400c --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_Image.py @@ -0,0 +1,36 @@ +# +# Copyright (C) 2011, Daniel Hyams +# +# 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 Image + + +# Disable "leaking" the installed version. +Image.__file__ = '/' + + +if hasattr(sys, 'frozen'): + basedir = sys._MEIPASS +else: + basedir = os.path.dirname(__file__) + + +im = Image.open(os.path.join(basedir, "tinysample.tiff")) +im.save(os.path.join(basedir, "tinysample.png")) diff --git a/pyinstaller/buildtests/libraries/test_Image.spec b/pyinstaller/buildtests/libraries/test_Image.spec new file mode 100644 index 0000000..d167f67 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_Image.spec @@ -0,0 +1,27 @@ +# -*- mode: python -*- + +__testname__ = 'test_Image' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) + +TOC_custom = [('tinysample.tiff','tinysample.tiff','DATA')] + +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=True, + strip=False, + upx=True, + console=True ) + +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + TOC_custom, + strip=False, + upx=True, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/libraries/test_Image2.py b/pyinstaller/buildtests/libraries/test_Image2.py new file mode 100644 index 0000000..b0ec755 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_Image2.py @@ -0,0 +1,26 @@ +# +# Copyright (C) 2007, Giovanni Bajo +# +# 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 + + +# Verify packaging of PIL.Image. Specifically, the hidden import of FixTk +# importing tkinter is causing some problems. + + +from Image import fromstring + + +print fromstring diff --git a/pyinstaller/buildtests/libraries/test_Image2.spec b/pyinstaller/buildtests/libraries/test_Image2.spec new file mode 100644 index 0000000..de737f4 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_Image2.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_Image2' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT( exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/libraries/test_PIL.py b/pyinstaller/buildtests/libraries/test_PIL.py new file mode 100644 index 0000000..f06d11c --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_PIL.py @@ -0,0 +1,36 @@ +# +# Copyright (C) 2011, Daniel Hyams +# +# 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 PIL.Image + + +# Disable "leaking" the installed version. +PIL.Image.__file__ = '/' + + +if hasattr(sys, 'frozen'): + basedir = sys._MEIPASS +else: + basedir = os.path.dirname(__file__) + + +im = PIL.Image.open(os.path.join(basedir, "tinysample.tiff")) +im.save(os.path.join(basedir, "tinysample.png")) diff --git a/pyinstaller/buildtests/libraries/test_PIL.spec b/pyinstaller/buildtests/libraries/test_PIL.spec new file mode 100644 index 0000000..105c5dd --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_PIL.spec @@ -0,0 +1,27 @@ +# -*- mode: python -*- + +__testname__ = 'test_PIL' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) + +TOC_custom = [('tinysample.tiff','tinysample.tiff','DATA')] + +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=True, + strip=False, + upx=True, + console=True ) + +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + TOC_custom, + strip=False, + upx=True, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/libraries/test_PIL2.py b/pyinstaller/buildtests/libraries/test_PIL2.py new file mode 100644 index 0000000..7819546 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_PIL2.py @@ -0,0 +1,26 @@ +# +# Copyright (C) 2007, Giovanni Bajo +# +# 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 + + +# Verify packaging of PIL.Image. Specifically, the hidden import of FixTk +# importing tkinter is causing some problems. + + +from PIL.Image import fromstring + + +print fromstring diff --git a/pyinstaller/buildtests/libraries/test_PIL2.spec b/pyinstaller/buildtests/libraries/test_PIL2.spec new file mode 100644 index 0000000..0e4648c --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_PIL2.spec @@ -0,0 +1,20 @@ +# -*- mode: python -*- + +__testname__ = 'test_PIL2' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT( exe, + a.binaries, + strip=False, + upx=False, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/libraries/test_enchant.py b/pyinstaller/buildtests/libraries/test_enchant.py new file mode 100644 index 0000000..79ec15e --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_enchant.py @@ -0,0 +1,53 @@ +# +# Copyright (C) 2011, Martin Zibricky +# +# 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 + + +# Enchant hook test. + + +import sys +import enchant + + +backends = [x.name for x in enchant.Broker().describe()] +langs = enchant.list_languages() +dicts = [x[0] for x in enchant.list_dicts()] + + +# At least one backend should be available +if len(backends) < 1: + print('E: No dictionary backend available') + exit(1) + +if len(dicts) < 1: + print('W: No dictionary available') + +print(80 * '-') +print('PYTHONPATH: %s' % sys.path) +print(80 * '-') +print('Backends: ' + ', '.join(backends)) +print('Languages: %s' % ', '.join(langs)) +print('Dictionaries: %s' % dicts) +print(80 * '-') + +# Try spell checking if English is availale +l = 'en_US' +if l in langs: + d = enchant.Dict(l) + print('d.check("hallo") %s' % d.check('hallo')) + print('d.check("halllo") %s' % d.check('halllo')) + print('d.suggest("halllo") %s' % d.suggest('halllo')) diff --git a/pyinstaller/buildtests/libraries/test_numpy.py b/pyinstaller/buildtests/libraries/test_numpy.py new file mode 100644 index 0000000..538ddd2 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_numpy.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from numpy.core.numeric import dot + + +def main(): + print "dot(3, 4):", dot(3, 4) + + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/libraries/test_numpy.spec b/pyinstaller/buildtests/libraries/test_numpy.spec new file mode 100644 index 0000000..9892ee0 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_numpy.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_numpy' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/libraries/test_onefile_tkinter.py b/pyinstaller/buildtests/libraries/test_onefile_tkinter.py new file mode 100644 index 0000000..0f31102 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_onefile_tkinter.py @@ -0,0 +1,52 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# Ensure environment variables TCL_LIBRARY and TK_LIBRARY are set properly. +# and data files are bundled. + + +import glob +import os +import sys + +from Tkinter import * + + +def compare(test_name, expect, frozen): + expect = os.path.normpath(expect) + print(test_name) + print(' Expected: ' + expect) + print(' Current: ' + frozen) + print('') + # Path must match. + if not frozen == expect: + raise SystemExit('Data directory is not set properly.') + # Directory must exist. + if not os.path.exists(frozen): + raise SystemExit('Data directory does not exist.') + # Directory must contain some .tcl files and not to be empty. + if not len(glob.glob(frozen + '/*.tcl')) > 0: + raise SystemExit('Data directory does not contain .tcl files.') + + +tcl_dir = os.environ['TCL_LIBRARY'] +tk_dir = os.environ['TK_LIBRARY'] + + +compare('Tcl', os.path.join(sys.prefix, '_MEI', 'tcl'), tcl_dir) +compare('Tk', os.path.join(sys.prefix, '_MEI', 'tk'), tk_dir) diff --git a/pyinstaller/buildtests/libraries/test_pycrypto.py b/pyinstaller/buildtests/libraries/test_pycrypto.py new file mode 100644 index 0000000..baec0d6 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_pycrypto.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import binascii + +from Crypto.Cipher import AES + +BLOCK_SIZE = 16 + + +def main(): + print "AES null encryption, block size", BLOCK_SIZE + # Just for testing functionality after all + print "HEX", binascii.hexlify(AES.new("\0" * + BLOCK_SIZE).encrypt("\0" * + BLOCK_SIZE)) + + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/libraries/test_pycrypto.spec b/pyinstaller/buildtests/libraries/test_pycrypto.spec new file mode 100644 index 0000000..d9f739f --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_pycrypto.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_pycrypto' + +a = Analysis([__testname__ + '.py']) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=False, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=False, + name=os.path.join('dist', __testname__)) diff --git a/pyinstaller/buildtests/libraries/test_pyodbc.py b/pyinstaller/buildtests/libraries/test_pyodbc.py new file mode 100644 index 0000000..162053c --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_pyodbc.py @@ -0,0 +1,25 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# pyodbc is a binary Python module. On Windows when installed with easy_install +# it is installed as zipped Python egg. This binary module is extracted +# to PYTHON_EGG_CACHE directory. PyInstaller should find the binary there and +# include it with frozen executable. + + +import pyodbc diff --git a/pyinstaller/buildtests/libraries/test_pyttsx.py b/pyinstaller/buildtests/libraries/test_pyttsx.py new file mode 100644 index 0000000..3144931 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_pyttsx.py @@ -0,0 +1,28 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# Basic code example from pyttsx tutorial. +# http://packages.python.org/pyttsx/engine.html#examples + + +import pyttsx + +engine = pyttsx.init() +engine.say('Sally sells seashells by the seashore.') +engine.say('The quick brown fox jumped over the lazy dog.') +engine.runAndWait() diff --git a/pyinstaller/buildtests/libraries/test_sqlalchemy.py b/pyinstaller/buildtests/libraries/test_sqlalchemy.py new file mode 100644 index 0000000..a9a43a2 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_sqlalchemy.py @@ -0,0 +1,10 @@ +# sqlalchemy hook test + +# The hook behaviour is to include with sqlalchemy all installed database +# backends. +import sqlalchemy + + +# import mysql and postgreql bindings +__import__('MySQLdb') +__import__('psycopg2') diff --git a/pyinstaller/buildtests/libraries/test_usb.py b/pyinstaller/buildtests/libraries/test_usb.py new file mode 100644 index 0000000..2b3b3c1 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_usb.py @@ -0,0 +1,7 @@ +import usb.core + +# Detect usb devices. +devices = usb.core.find(find_all = True) + +if not devices: + raise SystemExit('No USB device found.') diff --git a/pyinstaller/buildtests/libraries/test_wx.py b/pyinstaller/buildtests/libraries/test_wx.py new file mode 100644 index 0000000..5ab357b --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_wx.py @@ -0,0 +1,3 @@ +import wx +app = wx.App(0) +frame = wx.Frame(None, title="Hello World from wxPython", size=(320, 240)) diff --git a/pyinstaller/buildtests/libraries/test_wx.spec b/pyinstaller/buildtests/libraries/test_wx.spec new file mode 100644 index 0000000..4b4f877 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_wx.spec @@ -0,0 +1,22 @@ +# -*- mode: python -*- + +__testname__ = 'test_wx' + +a = Analysis([__testname__ + '.py'], + pathex=[]) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__ + '.exe'), + debug=True, + strip=False, + upx=True, + console=True ) +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name=os.path.join('dist', __testname__),) diff --git a/pyinstaller/buildtests/libraries/test_wx_pubsub.py b/pyinstaller/buildtests/libraries/test_wx_pubsub.py new file mode 100644 index 0000000..3d49812 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_wx_pubsub.py @@ -0,0 +1,29 @@ +# +# Copyright (C) 2012, Daniel Hyams +# +# 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 + + +from wx.lib.pubsub import pub as Publisher + + +def on_message(number): + print 'In the handler' + if not number == 762: + raise SystemExit('wx_pubsub failed.') + + +Publisher.subscribe(on_message, 'topic.subtopic') +Publisher.sendMessage('topic.subtopic', number=762) diff --git a/pyinstaller/buildtests/libraries/test_wx_pubsub_arg1.py b/pyinstaller/buildtests/libraries/test_wx_pubsub_arg1.py new file mode 100644 index 0000000..5f70d87 --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_wx_pubsub_arg1.py @@ -0,0 +1,32 @@ +# +# Copyright (C) 2012, Daniel Hyams +# +# 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 + + +from wx.lib.pubsub import setuparg1 +from wx.lib.pubsub import pub as Publisher + + +def on_message(message): + print ("In the handler") + # Data is delivered encapsulated in message and + # not directly as function argument. + if not message.data == 762: + raise SystemExit('wx_pubsub_arg1 failed.') + + +Publisher.subscribe(on_message, 'topic.subtopic') +Publisher.sendMessage('topic.subtopic', 762) diff --git a/pyinstaller/buildtests/libraries/test_wx_pubsub_kwargs.py b/pyinstaller/buildtests/libraries/test_wx_pubsub_kwargs.py new file mode 100644 index 0000000..3c9945f --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_wx_pubsub_kwargs.py @@ -0,0 +1,30 @@ +# +# Copyright (C) 2012, Daniel Hyams +# +# 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 + + +from wx.lib.pubsub import setupkwargs +from wx.lib.pubsub import pub as Publisher + + +def on_message(number): + print 'In the handler' + if not number == 762: + raise SystemExit('wx_pubsub_kwargs failed.') + + +Publisher.subscribe(on_message, 'topic.subtopic') +Publisher.sendMessage('topic.subtopic', number=762) diff --git a/pyinstaller/buildtests/libraries/test_xml.py b/pyinstaller/buildtests/libraries/test_xml.py new file mode 100644 index 0000000..5607f4b --- /dev/null +++ b/pyinstaller/buildtests/libraries/test_xml.py @@ -0,0 +1,2 @@ +# xml hook test +import xml diff --git a/pyinstaller/buildtests/libraries/tinysample.tiff b/pyinstaller/buildtests/libraries/tinysample.tiff new file mode 100644 index 0000000..a7bd937 Binary files /dev/null and b/pyinstaller/buildtests/libraries/tinysample.tiff differ diff --git a/pyinstaller/buildtests/multipackage/.svn/entries b/pyinstaller/buildtests/multipackage/.svn/entries new file mode 100644 index 0000000..6be357b --- /dev/null +++ b/pyinstaller/buildtests/multipackage/.svn/entries @@ -0,0 +1,217 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/buildtests/multipackage +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +test_multipackage1.spec +file + + + +add + +multipackage4_B.toc +file + + + +add + +multipackage1_B.py +file + + + +add + +test_multipackage3.py +file + + + +add + +multipackage5_B.toc +file + + + +add + +multipackage2_B.py +file + + + +add + +test_multipackage2.spec +file + + + +add + +test_multipackage4.py +file + + + +add + +multipackage5_C.toc +file + + + +add + +multipackage3_B.py +file + + + +add + +test_multipackage3.spec +file + + + +add + +test_multipackage5.py +file + + + +add + +multipackage4_B.py +file + + + +add + +test_multipackage4.spec +file + + + +add + +multipackage5_B.py +file + + + +add + +test_multipackage5.spec +file + + + +add + +multipackage5_C.py +file + + + +add + +test_multipackage1.toc +file + + + +add + +test_multipackage2.toc +file + + + +add + +multipackage1_B.toc +file + + + +add + +test_multipackage3.toc +file + + + +add + +test_multipackage1.py +file + + + +add + +multipackage2_B.toc +file + + + +add + +test_multipackage4.toc +file + + + +add + +multipackage3_B.toc +file + + + +add + +test_multipackage2.py +file + + + +add + +test_multipackage5.toc +file + + + +add + diff --git a/pyinstaller/buildtests/multipackage/multipackage1_B.py b/pyinstaller/buildtests/multipackage/multipackage1_B.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage1_B.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/multipackage1_B.toc b/pyinstaller/buildtests/multipackage/multipackage1_B.toc new file mode 100644 index 0000000..390a997 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage1_B.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'multipackage1_B', + '.*ssl.*', + '(?i).*python.*', +] diff --git a/pyinstaller/buildtests/multipackage/multipackage2_B.py b/pyinstaller/buildtests/multipackage/multipackage2_B.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage2_B.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/multipackage2_B.toc b/pyinstaller/buildtests/multipackage/multipackage2_B.toc new file mode 100644 index 0000000..951ad12 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage2_B.toc @@ -0,0 +1,120 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'multipackage2_B', +] diff --git a/pyinstaller/buildtests/multipackage/multipackage3_B.py b/pyinstaller/buildtests/multipackage/multipackage3_B.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage3_B.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/multipackage3_B.toc b/pyinstaller/buildtests/multipackage/multipackage3_B.toc new file mode 100644 index 0000000..fd91299 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage3_B.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'multipackage3_B', + '.*ssl.*', + '(?i).*python.*', +] diff --git a/pyinstaller/buildtests/multipackage/multipackage4_B.py b/pyinstaller/buildtests/multipackage/multipackage4_B.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage4_B.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/multipackage4_B.toc b/pyinstaller/buildtests/multipackage/multipackage4_B.toc new file mode 100644 index 0000000..1f49aad --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage4_B.toc @@ -0,0 +1,120 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'multipackage4_B', +] diff --git a/pyinstaller/buildtests/multipackage/multipackage5_B.py b/pyinstaller/buildtests/multipackage/multipackage5_B.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage5_B.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/multipackage5_B.toc b/pyinstaller/buildtests/multipackage/multipackage5_B.toc new file mode 100644 index 0000000..24ef46c --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage5_B.toc @@ -0,0 +1,114 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'multipackage5_B', +] diff --git a/pyinstaller/buildtests/multipackage/multipackage5_C.py b/pyinstaller/buildtests/multipackage/multipackage5_C.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage5_C.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/multipackage5_C.toc b/pyinstaller/buildtests/multipackage/multipackage5_C.toc new file mode 100644 index 0000000..d6121b6 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/multipackage5_C.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'multipackage5_C', + '.*multipackage5_B.multipackage5_B\.exe:.*ssl.*', + '(?i).*multipackage5_B.multipackage5_B\.exe:.*python.*', +] diff --git a/pyinstaller/buildtests/multipackage/test_multipackage1.py b/pyinstaller/buildtests/multipackage/test_multipackage1.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage1.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/test_multipackage1.spec b/pyinstaller/buildtests/multipackage/test_multipackage1.spec new file mode 100644 index 0000000..4159d89 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage1.spec @@ -0,0 +1,42 @@ +# -*- mode: python -*- + +''' +MULTIPROCESS FEATURE: file A (onefile pack) depends on file B (onefile pack). +''' + +__testname__ = 'test_multipackage1' +__testdep__ = 'multipackage1_B' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +b = Analysis([__testdep__ + '.py'], + pathex=['.']) + +MERGE((b, __testdep__, __testdep__ + '.exe'), (a, __testname__, __testname__ + '.exe')) + +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + a.dependencies, + name=os.path.join('dist', __testname__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +pyzB = PYZ(b.pure) +exeB = EXE(pyzB, + b.scripts, + b.binaries, + b.zipfiles, + b.datas, + b.dependencies, + name=os.path.join('dist', __testdep__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + diff --git a/pyinstaller/buildtests/multipackage/test_multipackage1.toc b/pyinstaller/buildtests/multipackage/test_multipackage1.toc new file mode 100644 index 0000000..c81872d --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage1.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'test_multipackage1', + '.*multipackage1_B\.exe:.*ssl.*', + '(?i).*multipackage1_B\.exe:.*python.*', +] diff --git a/pyinstaller/buildtests/multipackage/test_multipackage2.py b/pyinstaller/buildtests/multipackage/test_multipackage2.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage2.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/test_multipackage2.spec b/pyinstaller/buildtests/multipackage/test_multipackage2.spec new file mode 100644 index 0000000..8ce6f2d --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage2.spec @@ -0,0 +1,50 @@ +# -*- mode: python -*- + +''' +MULTIPROCESS FEATURE: file A (onefile pack) depends on file B (onedir pack) +''' + +__testname__ = 'test_multipackage2' +__testdep__ = 'multipackage2_B' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +b = Analysis([__testdep__ + '.py'], + pathex=['.']) + +MERGE((b, __testdep__, os.path.join(__testdep__, __testdep__ + '.exe')), + (a, __testname__, __testname__ + '.exe')) + +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + a.dependencies, + name=os.path.join('dist', __testname__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +pyzB = PYZ(b.pure) +exeB = EXE(pyzB, + b.scripts, + b.dependencies, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testdep__, + __testdep__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +coll = COLLECT( exeB, + b.binaries, + b.zipfiles, + b.datas, + strip=False, + upx=True, + name=os.path.join('dist', __testdep__)) + diff --git a/pyinstaller/buildtests/multipackage/test_multipackage2.toc b/pyinstaller/buildtests/multipackage/test_multipackage2.toc new file mode 100644 index 0000000..54166ee --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage2.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'test_multipackage2', + '.*multipackage2_B.multipackage2_B\.exe:.*ssl.*', + '(?i).*multipackage2_B.multipackage2_B\.exe:.*python.*', +] diff --git a/pyinstaller/buildtests/multipackage/test_multipackage3.py b/pyinstaller/buildtests/multipackage/test_multipackage3.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage3.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/test_multipackage3.spec b/pyinstaller/buildtests/multipackage/test_multipackage3.spec new file mode 100644 index 0000000..32a1c40 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage3.spec @@ -0,0 +1,50 @@ +# -*- mode: python -*- + +''' +TESTING MULTIPROCESS FEATURE: file A (onedir pack) depends on file B (onefile pack). +''' + +__testname__ = 'test_multipackage3' +__testdep__ = 'multipackage3_B' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +b = Analysis([__testdep__ + '.py'], + pathex=['.']) + +MERGE((b, __testdep__, os.path.join(__testdep__ + '.exe')), + (a, __testname__, os.path.join(__testname__, __testname__ + '.exe'))) + +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.dependencies, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name=os.path.join('dist', __testname__ )) + +pyzB = PYZ(b.pure) +exeB = EXE(pyzB, + b.scripts, + b.binaries, + b.zipfiles, + b.datas, + b.dependencies, + name=os.path.join('dist', __testdep__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + diff --git a/pyinstaller/buildtests/multipackage/test_multipackage3.toc b/pyinstaller/buildtests/multipackage/test_multipackage3.toc new file mode 100644 index 0000000..f931aaf --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage3.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'test_multipackage3', + '.*multipackage3_B\.exe:.*ssl.*', + '(?i).*multipackage3_B\.exe:.*python.*', +] diff --git a/pyinstaller/buildtests/multipackage/test_multipackage4.py b/pyinstaller/buildtests/multipackage/test_multipackage4.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage4.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/test_multipackage4.spec b/pyinstaller/buildtests/multipackage/test_multipackage4.spec new file mode 100644 index 0000000..b7d6714 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage4.spec @@ -0,0 +1,57 @@ +# -*- mode: python -*- + +''' +TESTING MULTIPROCESS FEATURE: file A (onedir pack) depends on file B (onedir pack). +''' + +__testname__ = 'test_multipackage4' +__testdep__ = 'multipackage4_B' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +b = Analysis([__testdep__ + '.py'], + pathex=['.']) + +MERGE((b, __testdep__, os.path.join(__testdep__, __testdep__ + '.exe')), + (a, __testname__, os.path.join(__testname__, __testname__ + '.exe'))) + +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.dependencies, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name=os.path.join('dist', __testname__)) + +pyzB = PYZ(b.pure) +exeB = EXE(pyzB, + b.scripts, + b.dependencies, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testdep__, + __testdep__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +coll = COLLECT( exeB, + b.binaries, + b.zipfiles, + b.datas, + strip=False, + upx=True, + name=os.path.join('dist', __testdep__)) + diff --git a/pyinstaller/buildtests/multipackage/test_multipackage4.toc b/pyinstaller/buildtests/multipackage/test_multipackage4.toc new file mode 100644 index 0000000..7a2c523 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage4.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'test_multipackage4', + '.*multipackage4_B.multipackage4_B\.exe:.*ssl.*', + '(?i).*multipackage4_B.multipackage4_B\.exe:.*python.*', +] diff --git a/pyinstaller/buildtests/multipackage/test_multipackage5.py b/pyinstaller/buildtests/multipackage/test_multipackage5.py new file mode 100644 index 0000000..3c4a904 --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage5.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import httplib +import gzip + +def main(): + print "Hello World!" + +if __name__ == "__main__": + main() diff --git a/pyinstaller/buildtests/multipackage/test_multipackage5.spec b/pyinstaller/buildtests/multipackage/test_multipackage5.spec new file mode 100644 index 0000000..2280ecf --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage5.spec @@ -0,0 +1,74 @@ +# -*- mode: python -*- + +''' +TESTING MULTIPROCESS FEATURE: file A (onedir pack) depends on file B (onedir pack) and file C (onefile pack) +''' + +__testname__ = 'test_multipackage5' +__testdep__ = 'multipackage5_B' +__testdep2__ = 'multipackage5_C' + +a = Analysis([__testname__ + '.py'], + pathex=['.']) +b = Analysis([__testdep__ + '.py'], + pathex=['.']) +c = Analysis([__testdep2__ + '.py'], + pathex=['.']) + + +MERGE((b, __testdep__, os.path.join(__testdep__, __testdep__ + '.exe')), + (c, __testdep2__, os.path.join(__testdep2__ + '.exe')), + (a, __testname__, os.path.join(__testname__, __testname__ + '.exe'))) + +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.dependencies, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testname__, + __testname__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +coll = COLLECT( exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name=os.path.join('dist', __testname__)) + +pyzB = PYZ(b.pure) +exeB = EXE(pyzB, + b.scripts, + b.dependencies, + exclude_binaries=1, + name=os.path.join('build', 'pyi.'+sys.platform, __testdep__, + __testdep__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) + +coll = COLLECT( exeB, + b.binaries, + b.zipfiles, + b.datas, + strip=False, + upx=True, + name=os.path.join('dist', __testdep__)) + +pyzC = PYZ(c.pure) +exeC = EXE(pyzC, + c.scripts, + c.binaries, + c.zipfiles, + c.datas, + c.dependencies, + name=os.path.join('dist', __testdep2__ + '.exe'), + debug=False, + strip=False, + upx=True, + console=1 ) diff --git a/pyinstaller/buildtests/multipackage/test_multipackage5.toc b/pyinstaller/buildtests/multipackage/test_multipackage5.toc new file mode 100644 index 0000000..9c4161a --- /dev/null +++ b/pyinstaller/buildtests/multipackage/test_multipackage5.toc @@ -0,0 +1,122 @@ +[ + 'random', + 'encodings\.iso8859_15', + 'encodings\.iso8859_14', + 'encodings\.cp1140', + 'tempfile', + 'base64', + 'encodings\.cp775', + 'encodings\.utf_7', + 'string', + 'encodings\.utf_8', + 'encodings\.cp037', + 'encodings\.base64_codec', + 'encodings\.cp864', + 'httplib', + 'encodings\.iso8859_8', + 'encodings\.iso8859_3', + 'encodings\.iso8859_2', + 'encodings\.iso8859_1', + 'encodings\.iso8859_7', + 'encodings\.iso8859_6', + 'encodings\.iso8859_5', + 'encodings\.iso8859_4', + 'encodings\.cp857', + 'encodings\.cp856', + 'encodings\.cp855', + 'encodings\.cp852', + 'encodings\.cp850', + 'locale', + 'encodings', + 'calendar', + 'encodings\.mac_iceland', + 'os2emxpath', + 'encodings\.palmos', + 'rfc822', + 'encodings\.iso8859_9', + 're', + 'encodings\.koi8_r', + 'encodings\.koi8_u', + 'encodings\.mac_turkish', + 'encodings\.undefined', + 'encodings\.cp862', + 'UserDict', + 'encodings\.cp1026', + 'encodings\.utf_16_le', + 'codecs', + 'quopri', + 'encodings\.uu_codec', + 'socket', + 'StringIO', + 'traceback', + 'encodings\.zlib_codec', + 'os', + 'encodings\.cp875', + 'encodings\.cp874', + 'dummy_thread', + 'encodings\.unicode_escape', + 'encodings\.punycode', + 'encodings\.mac_roman', + 'posixpath', + 'encodings\.cp437', + 'encodings\.cp1258', + 'sre_constants', + 'encodings\.cp737', + 'encodings\.utf_16_be', + 'encodings\.latin_1', + 'encodings\.charmap', + 'encodings\.cp1006', + 'encodings\.cp424', + 'copy', + 'encodings\.iso8859_13', + 'encodings\.iso8859_10', + 'uu', + 'encodings\.cp500', + 'stringprep', + 'encodings\.unicode_internal', + 'encodings\.aliases', + 'mimetools', + 'copy_reg', + 'sre_compile', + 'encodings\.utf_16', + 'encodings\.cp1257', + 'encodings\.cp1256', + 'encodings\.cp1255', + 'encodings\.cp1254', + 'encodings\.cp1253', + 'encodings\.cp1252', + 'encodings\.cp1251', + 'encodings\.cp1250', + 'encodings\.string_escape', + 'encodings\.mac_cyrillic', + 'encodings\.quopri_codec', + 'encodings\.rot_13', + 'encodings\.hex_codec', + 'encodings\.mac_latin2', + 'encodings\.cp869', + 'encodings\.cp866', + 'getopt', + 'encodings\.cp865', + 'encodings\.cp863', + 'encodings\.cp860', + 'encodings\.cp861', + 'stat', + 'warnings', + 'encodings\.ascii', + 'repr', + 'encodings\.idna', + 'types', + 'encodings\.raw_unicode_escape', + 'encodings\.mbcs', + 'urlparse', + 'linecache', + '_strptime', + 'gzip', + 'encodings\.mac_greek', + 'iu', + 'archive', + '_pyi_bootstrap', + 'test_multipackage5', + '.*multipackage5_B.multipackage5_B\.exe:.*ssl.*', + '(?i).*multipackage5_B.multipackage5_B\.exe:.*python.*', +] diff --git a/pyinstaller/buildtests/runtests.py b/pyinstaller/buildtests/runtests.py new file mode 100644 index 0000000..2561e9d --- /dev/null +++ b/pyinstaller/buildtests/runtests.py @@ -0,0 +1,655 @@ +#!/usr/bin/env python +# +# Copyright (C) 2011-2012 Martin Zibricky +# Copyright (C) 2011-2012 Hartmut Goebel +# Copyright (C) 2005-2011 Giovanni Bajo +# Based on previous work under copyright (c) 2001, 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 + +# This program will execute any file with name test*.py. If your test +# need an aditional dependency name it test*.py to be ignored +# by this program but be recognizable by any one as a dependency of that +# particular test. + +import glob +import optparse +import os +import re +import shutil +import sys + +try: + import PyInstaller +except ImportError: + # if importing PyInstaller fails, try to load from parent + # directory to support running without installation + import imp + if not hasattr(os, "getuid") or os.getuid() != 0: + imp.load_module('PyInstaller', *imp.find_module('PyInstaller', + [os.path.dirname(os.path.dirname(os.path.abspath(__file__)))])) + + +from PyInstaller import HOMEPATH +from PyInstaller import is_py23, is_py24, is_py25, is_py26, is_win, is_darwin +from PyInstaller import compat +from PyInstaller.lib import unittest2 as unittest +from PyInstaller.lib import junitxml +from PyInstaller.utils import misc + + +VERBOSE = False +REPORT = False +# Directory with this script (runtests.py). +BASEDIR = os.path.dirname(os.path.abspath(__file__)) + + +class MiscDependencies(object): + """ + Place holder for special requirements of some tests. + + e.g. basic/test_ctypes needs C compiler. + + Every method returns None when successful or a string containing + error message to be displayed on console. + """ + def c_compiler(self): + """ + Check availability of C compiler. + """ + compiler = None + msg = 'Cannot find GCC, MinGW or Visual Studio in PATH.' + if is_win: + # Try MSVC. + compiler = misc.find_executable('cl') + if compiler is None: + # Try GCC. + compiler = misc.find_executable('gcc') + if compiler is None: + return msg + return None # C compiler was found. + + +class SkipChecker(object): + """ + Check conditions if a test case should be skipped. + """ + def __init__(self): + depend = MiscDependencies() + # Required Python or OS version for some tests. + self.MIN_VERSION_OR_OS = { + 'basic/test_time': is_py23, + 'basic/test_celementtree': is_py25, + 'basic/test_email': is_py25, + # On Mac DYLD_LIBRARY_PATH is not used. + 'basic/test_absolute_ld_library_path': not is_win and not is_darwin, + 'import/test_c_extension': is_py25, + 'import/test_onefile_c_extension': is_py25, + 'import/test_relative_import': is_py25, + 'import/test_relative_import2': is_py26, + 'import/test_relative_import3': is_py25, + 'libraries/test_enchant': is_win, + } + # Required Python modules for some tests. + self.MODULES = { + 'basic/test_ctypes': ['ctypes'], + 'basic/test_module_attributes': ['xml.etree.cElementTree'], + 'basic/test_nestedlaunch1': ['ctypes'], + 'basic/test_onefile_multiprocess': ['multiprocessing'], + 'libraries/test_enchant': ['enchant'], + 'libraries/test_Image': ['PIL'], + 'libraries/test_Image2': ['PIL'], + 'libraries/test_numpy': ['numpy'], + 'libraries/test_onefile_tkinter': ['Tkinter'], + 'libraries/test_PIL': ['PIL'], + 'libraries/test_PIL2': ['PIL'], + 'libraries/test_pycrypto': ['Crypto'], + 'libraries/test_pyodbc': ['pyodbc'], + 'libraries/test_pyttsx': ['pyttsx'], + 'libraries/test_sqlalchemy': ['sqlalchemy', 'MySQLdb', 'psycopg2'], + 'libraries/test_usb': ['ctypes', 'usb'], + 'libraries/test_wx': ['wx'], + 'libraries/test_wx_pubsub': ['wx'], + 'libraries/test_wx_pubsub_arg1': ['wx'], + 'libraries/test_wx_pubsub_kwargs': ['wx'], + 'import/test_c_extension': ['simplejson'], + 'import/test_ctypes_cdll_c': ['ctypes'], + 'import/test_ctypes_cdll_c2': ['ctypes'], + 'import/test_eggs2': ['pkg_resources'], + 'import/test_onefile_c_extension': ['simplejson'], + 'import/test_onefile_zipimport': ['pkg_resources'], + 'import/test_onefile_zipimport2': ['pkg_resources', 'setuptools'], + 'interactive/test_pygame': ['pygame'], + } + # Other dependecies of some tests. + self.DEPENDENCIES = { + 'basic/test_ctypes': [depend.c_compiler()], + # Support for unzipped eggs is not yet implemented. + # http://www.pyinstaller.org/ticket/541 + 'import/test_eggs1': ['Unzipped eggs not yet implemented.'], + } + + def _check_python_and_os(self, test_name): + """ + Return True if test name is not in the list or Python or OS + version is not met. + """ + if (test_name in self.MIN_VERSION_OR_OS and + not self.MIN_VERSION_OR_OS[test_name]): + return False + return True + + def _check_modules(self, test_name): + """ + Return name of missing required module, if any. None means + no module is missing. + """ + if test_name in self.MODULES: + for mod_name in self.MODULES[test_name]: + # STDOUT and STDERR are discarded (devnull) to hide + # import exceptions. + trash = open(compat.devnull) + retcode = compat.exec_python_rc('-c', "import %s" % mod_name, + stdout=trash, stderr=trash) + trash.close() + if retcode != 0: + return mod_name + return None + + def _check_dependencies(self, test_name): + """ + Return error message when a requirement is not met, None otherwise. + """ + if test_name in self.DEPENDENCIES: + for dep in self.DEPENDENCIES[test_name]: + if dep is not None: + return dep + return None + + def check(self, test_name): + """ + Check test requirements if they are any specified. + + Return tupple (True/False, 'Reason for skipping.'). + True if all requirements are met. Then test case may + be executed. + """ + if not self._check_python_and_os(test_name): + return (False, 'Required another Python version or OS.') + + required_module = self._check_modules(test_name) + if required_module is not None: + return (False, "Module %s is missing." % required_module) + + dependency = self._check_dependencies(test_name) + if dependency is not None: + return (False, dependency) + + return (True, 'Requirements met.') + + +NO_SPEC_FILE = [ + 'basic/test_absolute_ld_library_path', + 'basic/test_absolute_python_path', + 'basic/test_email', + 'basic/test_email_oldstyle', + 'basic/test_onefile_multiprocess', + 'basic/test_python_home', + 'import/test_c_extension', + 'import/test_onefile_c_extension', + 'import/test_onefile_zipimport', + 'import/test_onefile_zipimport2', + 'libraries/test_enchant', + 'libraries/test_onefile_tkinter', + 'libraries/test_sqlalchemy', + 'libraries/test_pyodbc', + 'libraries/test_pyttsx', + 'libraries/test_usb', + 'libraries/test_wx_pubsub', + 'libraries/test_wx_pubsub_kwargs', + 'libraries/test_wx_pubsub_arg1' +] + + +class BuildTestRunner(object): + + def __init__(self, test_name, verbose=False, report=False): + # Use path separator '/' even on windows for test_name name. + self.test_name = test_name.replace('\\', '/') + self.verbose = verbose + self.test_dir, self.test_file = os.path.split(self.test_name) + # For junit xml report some behavior is changed. + # Especially redirecting sys.stdout. + self.report = report + + def _msg(self, text): + """ + Important text. Print it to console only in verbose mode. + """ + if self.verbose: + # This allows to redirect stdout to junit xml report. + sys.stdout.write('\n' + 10 * '#' + ' ' + text + ' ' + 10 * '#' + '\n\n') + sys.stdout.flush() + + def _plain_msg(self, text): + """ + Print text to console only in verbose mode. + """ + if self.verbose: + sys.stdout.write(text + '\n') + sys.stdout.flush() + + def _find_exepath(self, test, parent_dir='dist'): + of_prog = os.path.join(parent_dir, test) # one-file deploy filename + od_prog = os.path.join(parent_dir, test, test) # one-dir deploy filename + + prog = None + if os.path.isfile(of_prog): + prog = of_prog + elif os.path.isfile(of_prog + ".exe"): + prog = of_prog + ".exe" + elif os.path.isdir(of_prog): + if os.path.isfile(od_prog): + prog = od_prog + elif os.path.isfile(od_prog + ".exe"): + prog = od_prog + ".exe" + return prog + + def _run_created_exe(self, test, testdir=None): + """ + Run executable created by PyInstaller. + """ + self._msg('EXECUTING TEST ' + self.test_name) + # Run the test in a clean environment to make sure they're + # really self-contained + path = compat.getenv('PATH') + compat.unsetenv('PATH') + prog = self._find_exepath(test, 'dist') + if prog is None: + self._plain_msg('ERROR: no file generated by PyInstaller found!') + compat.setenv("PATH", path) + return 1 + else: + self._plain_msg("RUNNING: " + prog) + old_wd = os.getcwd() + os.chdir(os.path.dirname(prog)) + prog = os.path.join(os.curdir, os.path.basename(prog)) + retcode, out, err = compat.exec_command_all(prog) + os.chdir(old_wd) + self._msg('STDOUT %s' % self.test_name) + self._plain_msg(out) + self._msg('STDERR %s' % self.test_name) + self._plain_msg(err) + compat.setenv("PATH", path) + return retcode + + def test_exists(self): + """ + Return True if test file exists. + """ + return os.path.exists(os.path.join(BASEDIR, self.test_name + '.py')) + + def test_building(self): + """ + Run building of test script. + + Return True if build succeded False otherwise. + """ + OPTS = ['--debug'] + + if self.verbose: + OPTS.extend(['--debug', '--log-level=INFO']) + else: + OPTS.append('--log-level=ERROR') + + # Build executable in onefile mode. + if self.test_file.startswith('test_onefile'): + OPTS.append('--onefile') + else: + OPTS.append('--onedir') + + self._msg("BUILDING TEST " + self.test_name) + + # Use pyinstaller.py for building test_name. + testfile_spec = self.test_file + '.spec' + if not os.path.exists(self.test_file + '.spec'): + # .spec file does not exist and it has to be generated + # for main script. + testfile_spec = self.test_file + '.py' + + pyinst_script = os.path.join(HOMEPATH, 'pyinstaller.py') + + # In report mode is stdout and sys.stderr redirected. + if self.report: + # Write output from subprocess to stdout/err. + retcode, out, err = compat.exec_python_all(pyinst_script, + testfile_spec, *OPTS) + sys.stdout.write(out) + sys.stdout.write(err) + else: + retcode = compat.exec_python_rc(pyinst_script, + testfile_spec, *OPTS) + + return retcode == 0 + + def test_exe(self): + """ + Test running of all created executables. + """ + files = glob.glob(os.path.join('dist', self.test_file + '*')) + retcode = 0 + for exe in files: + exe = os.path.splitext(exe)[0] + retcode_tmp = self._run_created_exe(exe[5:], self.test_dir) + retcode = retcode or retcode_tmp + return retcode == 0 + + def test_logs(self): + """ + Compare log files (now used only by multipackage test_name). + + Return True if .toc files match or when .toc patters + are not defined. + """ + logsfn = glob.glob(self.test_file + '.toc') + # Other main scritps do not start with 'test_'. + logsfn += glob.glob(self.test_file.split('_', 1)[1] + '_?.toc') + for logfn in logsfn: + self._msg("EXECUTING MATCHING " + logfn) + tmpname = os.path.splitext(logfn)[0] + prog = self._find_exepath(tmpname) + if prog is None: + prog = self._find_exepath(tmpname, + os.path.join('dist', self.test_file)) + fname_list = compat.exec_python( + os.path.join(HOMEPATH, 'utils', 'ArchiveViewer.py'), + '-b', '-r', prog) + # Fix line-endings so eval() does not fail. + fname_list = fname_list.replace('\r\n', '\n').replace('\n\r', '\n') + fname_list = eval(fname_list) + pattern_list = eval(open(logfn, 'rU').read()) + # Alphabetical order of patterns. + pattern_list.sort() + count = 0 + for pattern in pattern_list: + found = False + for fname in fname_list: + if re.match(pattern, fname): + count += 1 + found = True + self._plain_msg('MATCH: %s --> %s' % (pattern, fname)) + break + if not found: + self._plain_msg('MISSING: %s' % pattern) + + # Not all modules matched. + # Stop comparing other .toc files and fail the test. + if count < len(pattern_list): + return False + + return True + + +class GenericTestCase(unittest.TestCase): + def __init__(self, test_dir, func_name): + """ + test_dir Directory containing testing python scripts. + func_name Name of test function to create. + """ + self.test_name = test_dir + '/' + func_name + + # Create new test fuction. This has to be done before super(). + setattr(self, func_name, self._generic_test_function) + super(GenericTestCase, self).__init__(func_name) + + # For tests current working directory has to be changed temporaly. + self.curr_workdir = os.getcwdu() + + def setUp(self): + testdir = os.path.dirname(self.test_name) + os.chdir(os.path.join(BASEDIR, testdir)) # go to testdir + # For some 'basic' tests we need create file with path to python + # executable and if it is running in debug mode. + build_python = open(os.path.join(BASEDIR, 'basic', 'python_exe.build'), + 'w') + build_python.write(sys.executable + "\n") + build_python.write('debug=%s' % __debug__ + '\n') + # On Windows we need to preserve systme PATH for subprocesses in tests. + build_python.write(os.environ.get('PATH') + '\n') + build_python.close() + + def tearDown(self): + os.chdir(self.curr_workdir) # go back from testdir + + def _generic_test_function(self): + # Skip test case if test requirement are not met. + s = SkipChecker() + req_met, msg = s.check(self.test_name) + if not req_met: + raise unittest.SkipTest(msg) + # Create a build and test it. + b = BuildTestRunner(self.test_name, verbose=VERBOSE, report=REPORT) + self.assertTrue(b.test_exists(), + msg='Test %s not found.' % self.test_name) + self.assertTrue(b.test_building(), + msg='Build of %s failed.' % self.test_name) + self.assertTrue(b.test_exe(), + msg='Running exe of %s failed.' % self.test_name) + self.assertTrue(b.test_logs(), + msg='Matching .toc of %s failed.' % self.test_name) + + +class BasicTestCase(GenericTestCase): + test_dir = 'basic' + + def __init__(self, func_name): + super(BasicTestCase, self).__init__(self.test_dir, func_name) + + +class ImportTestCase(GenericTestCase): + test_dir = 'import' + + def __init__(self, func_name): + super(ImportTestCase, self).__init__(self.test_dir, func_name) + + +class LibrariesTestCase(GenericTestCase): + test_dir = 'libraries' + + def __init__(self, func_name): + super(LibrariesTestCase, self).__init__(self.test_dir, func_name) + + +class MultipackageTestCase(GenericTestCase): + test_dir = 'multipackage' + + def __init__(self, func_name): + super(MultipackageTestCase, self).__init__(self.test_dir, func_name) + + +class InteractiveTestCase(GenericTestCase): + """ + Interactive tests require user interaction mostly GUI. + + Interactive tests have to be run directly by user. + They can't be run by any continuous integration system. + """ + test_dir = 'interactive' + + def __init__(self, func_name): + super(InteractiveTestCase, self).__init__(self.test_dir, func_name) + + +class TestCaseGenerator(object): + """ + Generate test cases. + """ + def _detect_tests(self, directory): + files = glob.glob(os.path.join(directory, 'test_*.py')) + # Test name is a file name without extension. + tests = [os.path.splitext(os.path.basename(x))[0] for x in files] + tests.sort() + return tests + + def create_suite(self, test_types): + """ + Create test suite and add test cases to it. + + test_types Test classes to create test cases from. + + Return test suite with tests. + """ + suite = unittest.TestSuite() + + for _type in test_types: + tests = self._detect_tests(_type.test_dir) + # Create test cases for a specific type. + for test_name in tests: + suite.addTest(_type(test_name)) + + return suite + + +def clean(): + """ + Remove temporary files created while running tests. + """ + # Files/globs to clean up. + patterns = """python_exe.build + logdict*.log + disttest* + buildtest* + warn*.txt + *.py[co] + */*.py[co] + */*/*.py[co] + build/ + dist/ + */*.dll + */*.lib + */*.obj + */*.exp + */*.so + */*.dylib + """.split() + + # Remove temporary files in all subdirectories. + for directory in os.listdir(BASEDIR): + if not os.path.isdir(directory): + continue + for pattern in patterns: + file_list = glob.glob(os.path.join(directory, pattern)) + for pth in file_list: + try: + if os.path.isdir(pth): + shutil.rmtree(pth) + else: + os.remove(pth) + except OSError, e: + print e + + # Delete *.spec files for tests without spec file. + for pth in NO_SPEC_FILE: + pth = os.path.join(BASEDIR, pth + '.spec') + if os.path.exists(pth): + os.remove(pth) + + +def run_tests(test_suite, xml_file): + """ + Run test suite and save output to junit xml file if requested. + """ + if xml_file: + print 'Writting test results to: %s' % xml_file + fp = open('report.xml', 'w') + result = junitxml.JUnitXmlResult(fp) + # Text from stdout/stderr should be added to failed test cases. + result.buffer = True + result.startTestRun() + test_suite.run(result) + result.stopTestRun() + fp.close() + else: + unittest.TextTestRunner(verbosity=2).run(test_suite) + + +def main(): + try: + parser = optparse.OptionParser(usage='%prog [options] [TEST-NAME ...]', + epilog='TEST-NAME can be the name of the .py-file, ' + 'the .spec-file or only the basename.') + except TypeError: + parser = optparse.OptionParser(usage='%prog [options] [TEST-NAME ...]') + + parser.add_option('-c', '--clean', action='store_true', + help='Clean up generated files') + parser.add_option('-i', '--interactive-tests', action='store_true', + help='Run interactive tests (default: run normal tests)') + parser.add_option('-v', '--verbose', + action='store_true', + default=False, + help='Verbose mode (default: %default)') + parser.add_option('--junitxml', action='store', default=None, + metavar='FILE', help='Create junit-xml style test report file') + + opts, args = parser.parse_args() + + global VERBOSE, REPORT + VERBOSE = opts.verbose + REPORT = opts.junitxml is not None + + # Do only cleanup. + if opts.clean: + clean() + raise SystemExit() # Exit code is 0 in this case. + + # Run only specified tests. + if args: + if opts.interactive_tests: + parser.error('Must not specify -i/--interactive-tests when passing test names.') + suite = unittest.TestSuite() + for arg in args: + test_list = glob.glob(arg) + if not test_list: + test_list = [arg] + else: + test_list = [x for x in test_list if os.path.splitext(x)[1] == ".py"] + for t in test_list: + test_dir = os.path.dirname(t) + test_script = os.path.basename(os.path.splitext(t)[0]) + suite.addTest(GenericTestCase(test_dir, test_script)) + print 'Running test: %s' % (test_dir + '/' + test_script) + + # Run all tests or all interactive tests. + else: + if opts.interactive_tests: + print 'Running interactive tests...' + test_classes = [InteractiveTestCase] + else: + print 'Running normal tests (-i for interactive tests)...' + test_classes = [BasicTestCase, ImportTestCase, + LibrariesTestCase, MultipackageTestCase] + + # Create test suite. + generator = TestCaseGenerator() + suite = generator.create_suite(test_classes) + + # Run created test suite. + clean() + run_tests(suite, opts.junitxml) + + +if __name__ == '__main__': + main() diff --git a/pyinstaller/buildtests/setupenv_windows.py b/pyinstaller/buildtests/setupenv_windows.py new file mode 100644 index 0000000..17a8c8d --- /dev/null +++ b/pyinstaller/buildtests/setupenv_windows.py @@ -0,0 +1,133 @@ +# +# Copyright (C) 2012, Martin Zibricky +# +# 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 + + +# Install necessary 3rd party Python modules to run all tests. +# This script is supposed to be used in a continuous integration system: +# https://jenkins.shiningpanda.com/pyinstaller/ +# Python there is mostly 64bit. Only Python 2.4 is 32bit on Windows 7. + + +import glob +import optparse +import os +import platform +import sys + +# easy_install command used in a Python script. +from setuptools.command import easy_install + + +try: + import PyInstaller +except ImportError: + # if importing PyInstaller fails, try to load from parent + # directory to support running without installation + import imp + if not hasattr(os, "getuid") or os.getuid() != 0: + imp.load_module('PyInstaller', *imp.find_module('PyInstaller', + [os.path.dirname(os.path.dirname(os.path.abspath(__file__)))])) + + +from PyInstaller.compat import is_py24, is_py25, is_py26 + + +PYVER = '.'.join([str(x) for x in sys.version_info[0:2]]) + + +def py_arch(): + """ + .exe installers of Python modules contain architecture name in filename. + """ + mapping = {'32bit': 'win32', '64bit': 'win-amd64'} + arch = platform.architecture()[0] + return mapping[arch] + + +_PACKAGES = { + # 'modulename': 'pypi_name_or_url_or_path' + 'MySQLdb': ['MySQL-python-*%s-py%s.exe' % (py_arch(), PYVER)], + 'numpy': ['numpy-unoptimized-*%s-py%s.exe' % (py_arch(), PYVER)], + 'PIL': ['PIL-*%s-py%s.exe' % (py_arch(), PYVER)], + 'psycopg2': ['psycopg2-*%s-py%s.exe' % (py_arch(), PYVER)], + 'pycrypto': ['pycrypto'], + 'pyodbc': ['pyodbc'], + 'simplejson': ['simplejson'], + 'sqlalchemy': ['SQLAlchemy-*%s-py%s.exe' % (py_arch(), PYVER)], + 'wx': ['wxPython-common-*%s-py%s.exe' % (py_arch(), PYVER), + 'wxPython-2*%s-py%s.exe' % (py_arch(), PYVER)], + 'win32api': ['http://downloads.sourceforge.net/project/pywin32/pywin32/Build%%20217/pywin32-217.%s-py%s.exe' % + (py_arch(), PYVER)], +} + +_PY_VERSION = { + 'MySQLdb': is_py26, + 'numpy': is_py26, + 'PIL': is_py26, + 'psycopg2': is_py26, + 'simplejson': is_py25, + 'sqlalchemy': is_py24, + # Installers are available only for Python 2.6/2.7. + 'wx': is_py26, +} + + +def main(): + parser = optparse.OptionParser() + parser.add_option('-d', '--download-dir', + help='Directory with maually downloaded python modules.' + ) + opts, _ = parser.parse_args() + + # Install packages. + for k, v in _PACKAGES.items(): + # Test python version for module. + if k in _PY_VERSION: + # Python version too old, skip module. + if PYVER < _PY_VERSION[k]: + continue + try: + __import__(k) + print 'Already installed... %s' % k + # Module is not available - install it. + except ImportError: + # If not url or module name then look for installers in download area. + if not v[0].startswith('http') and v[0].endswith('exe'): + files = [] + # Try all file patterns. + for pattern in v: + pattern = os.path.join(opts.download_dir, pattern) + files += glob.glob(pattern) + # No file with that pattern was not found - skip it. + if not files: + print 'Skipping module... %s' % k + continue + # Full path to installers in download directory. + v = files + print 'Installing module... %s' % k + # Some modules might require several .exe files to install. + for f in v: + print ' %s' % f + # Use --no-deps ... installing module dependencies might fail + # because easy_install tries to install the same module from + # PYPI from source code and if fails because of C code that + # that needs to be compiled. + easy_install.main(['--no-deps', '--always-unzip', f]) + + +if __name__ == '__main__': + main() diff --git a/pyinstaller/doc/.svn/entries b/pyinstaller/doc/.svn/entries new file mode 100644 index 0000000..8ac581b --- /dev/null +++ b/pyinstaller/doc/.svn/entries @@ -0,0 +1,112 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/doc +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +Manual.html +file + + + +add + +source +dir + + + +add + +pyi-build.html +file + + + +add + +LICENSE.GPL +file + + + +add + +images +dir + + + +add + +Manual.pdf +file + + + +add + +pyi-makeCOMServer.html +file + + + +add + +CHANGES.txt +file + + + +add + +pyinstaller.html +file + + + +add + +pyi-makespec.html +file + + + +add + +KNOWNBUGS.txt +file + + + +add + +stylesheets +dir + + + +add + diff --git a/pyinstaller/doc/CHANGES.txt b/pyinstaller/doc/CHANGES.txt new file mode 100644 index 0000000..49a1775 --- /dev/null +++ b/pyinstaller/doc/CHANGES.txt @@ -0,0 +1,262 @@ +(+ user visible changes, * internal stuff) + +Changes since PyInstaller 1.5.1 +------------------------------- + + Minimum suported Python version is 2.3. + + (OSX) Add support for Mac OS X 64-bit + + (OSX) Add support Mac OS X 10.7 (Lion) and 10.8 (Mountain Lion). + + (OSX) With argument --windowed PyInstaller creates application bundle (.app) + automatically. + + Add experimental support for AIX (thanks to Martin Gamwell Dawids). + + Add experimental support for Solaris (thanks to Hywel Richards). + + Add Multipackage function to create a collection of packages to avoid + library duplication. See documentation for more details. + + New symplified command line interface. Configure.py/Makespec.py/Build.py + replaced by pyinstaller.py. See documentation for more details. + + Removed cross-building/bundling feature which was never really finished. + + Added option --log-level to all scripts to adjust level of output + (thanks to Hartmut Goebel). + + rthooks.dat moved to support/rthooks.dat + + Packaged executable now returns the same return-code as the + unpackaged script (thanks to Brandyn White). + + Add import hook for PyUSB (thanks to Chien-An "Zero" Cho). + + Add import hook for wx.lib.pubsub (thanks to Daniel Hyams). + + Add import hook for pyttsx. + + Improve import hook for Tkinter. + + Improve import hook for PyQt4. + + Improve import hook for win32com. + + Improve support for running PyInstaller in virtualenv. + + Add cli options --additional-hooks-dir and --hidden-import. + + Remove cli options -X, -K, -C, --upx, --tk, --configfile, --skip-configure. + + UPX is used by default if available in the PATH variable. + * Remove compatibility code for old platforms (dos, os2, MacOS 9). + * Use Python logging system for message output (thanks to Hartmut + Goebel). + * Environment variable MEIPASS2 is accessible as sys._MEIPASS. + * Bootloader now overrides PYTHONHOME and PYTHONPATH. + PYTHONHOME and PYTHONPATH is set to the value of MEIPASS2 variable. + * Bootloader uses absolute paths. + * (OSX) Drop dependency on otool from Xcode on Mac OSX. + * (OSX) Fix missing qt_menu.nib in dist directory when using PyQt4. + * (OSX) Bootloader does not use DYLD_LIBRARY_PATH on Mac OS X anymore. + @loader_path is used instead. + * (OSX) Add support to detect *.dylib dependencies on Mac OS X containing + @executable_path, @loader_path and @rpath. + * (OSX) Use macholib to detect dependencies on dynamic libraries. + * Improve test suite. + * Improve source code structure. + * Replace os.system() calls by suprocess module. + * Bundle fake 'site' module with frozen applications to prevent loading + any user's Python modules from host OS. + * Include runtime hooks (rthooks) in code analysis. + * Source code hosting moved to github: + https://github.com/pyinstaller/pyinstaller + * Hosting for running tests daily: + https://jenkins.shiningpanda-ci.com/pyinstaller/ + +Changes since PyInstaller 1.5 +----------------------------- + + New default PyInstaller icon for generated executables on Windows. + + Add support for Python built with --enable-shared on Mac OSX. + + Add requirements section to documentation. + * Documentation is now generated by rst2html and rst2pdf. + * Fix wrong path separators for bootloader-file on Windows + * Add workaround for incorrect platform.system() on some Python Windows + installation where this function returns 'Microsoft' instead 'Windows'. + * Fix --windowed option for Mac OSX where a console executable was + created every time even with this option. + * Mention dependency on otool, ldd and objdump in documentation. + * Fix typo preventing detection of DLL libraries loaded by ctypes module. + +Changes since PyInstaller 1.4 +----------------------------- + + Full support for Python 2.7. + + Full support for Python 2.6 on Windows. No manual redistribution + of DLLs, CRT, manifest, etc. is required: PyInstaller is able to + bundle all required dependencies (thanks to Florian Hoech). + + Added support for Windows 64-bit (thanks to Martin Zibricky). + + Added binary bootloaders for Linux (32-bit and 64-bit, using LSB), + and Darwin (32-bit). This means that PyInstaller users on this + platform don't need to compile the bootloader themselves anymore + (thanks to Martin Zibricky and Lorenzo Mancini). + * Rewritten the build system for the bootloader using waf (thanks + to Martin Zibricky) + * Correctly detect Python unified binary under Mac OSX, and bail out + if the unsupported 64-bit version is used (thanks to Nathan Weston). + * Fix TkInter support under Mac OSX (thanks to Lorenzo Mancini). + * Improve bundle creation under Mac OSX and correctly support also + one-dir builds within bundles (thanks to Lorenzo Mancini). + * Fix spurious KeyError when using dbhash + * Fix import of nested packages made from Pyrex-generated files. + * PyInstaller is now able to follow dependencies of binary extensions + (.pyd/.so) compressed within .egg-files. + + Add import hook for PyTables. + + Add missing import hook for QtWebKit. + + Add import hook for pywinauto. + + Add import hook for reportlab (thanks Nevar). + * Improve matplotlib import hook (for Mac OSX). + * Improve Django import hooks. + * Improve compatibility across multiple Linux distributions by + being more careful on which libraries are included/excluded in + the package. + * Improve compatibility with older Python versions (Python 2.2+). + * Fix double-bouncing-icon bug on Mac OSX. Now windowed applications + correctly start on Mac OSX showing a single bouncing icon. + * Fix weird "missing symbol" errors under Mac OSX (thanks to Isaac + Wagner). + +Changes since PyInstaller 1.3 +----------------------------- + + Fully support up to Python 2.6 on Linux/Mac and Python 2.5 + on Windows. + + Preliminar Mac OSX support: both one-file and one-dir is supported; + for non-console applications, a bundle can be created. Thanks + to many people that worked on this across several months (Daniele + Zannotti, Matteo Bertini, Lorenzo Mancini). + + Improved Linux support: generated executables are fatter but now + should now run on many different Linux distributions (thanks to David + Mugnai). + * Add support for specifying data files in import hooks. PyInstaller + can now automatically bundle all data files or plugins required + for a certain 3rd-party package. + + Add intelligent support for ctypes: PyInstaller is now able to + track all places in the source code where ctypes is used and + automatically bundle dynamic libraries accessed through ctypes. + (Thanks to Lorenzo Mancini for submitting this). This is very + useful when using ctypes with custom-made dynamic libraries. + + Executables built with PyInstaller under Windows can now be digitally + signed. + + Add support for absolute imports in Python 2.5+ (thanks to Arve + Knudsen). + + Add support for relative imports in Python 2.5+. + + Add support for cross-compilation: PyInstaller is now able to + build Windows executables when running under Linux. See documentation + for more details. + + Add support for .egg files: PyInstaller is now able to look for + dependencies within .egg files, bundle them and make them available + at runtime with all the standard features (entry-points, etc.). + + Add partial support for .egg directories: PyInstaller will treat them + as normal packages and thus it will not bundle metadata. + + Under Linux/Mac, it is now possible to build an executable even when + a system packages does not have .pyc or .pyo files available and the + system-directory can be written only by root. PyInstaller will in + fact generate the required .pyc/.pyo files on-the-fly within a + build-temporary directory. + + Add automatic import hooks for many third-party packages, including: + + PyQt4 (thanks to Pascal Veret), with complete plugin support. + + pyodbc (thanks to Don Dwiggins) + + cElementTree (both native version and Python 2.5 version) + + lxml + + SQLAlchemy (thanks to Greg Copeland) + + email in Python 2.5 (though it does not support the old-style + Python 2.4 syntax with Python 2.5) + + gadfly + + PyQWt5 + + mako + + Improved PyGTK (thanks to Marco Bonifazi and foxx). + + paste (thanks to Jamie Kirkpatrick) + + matplotlib + + Add fix for the very annoying "MSVCRT71 could not be extracted" bug, + which was caused by the DLL being packaged twice (thanks to Idris + Aykun). + * Removed C++-style comments from the bootloader for compatibility + with the AIX compiler. + + Fix support for .py files with DOS line endings under Linux (fixes + PyOpenGL). + + Fix support for PIL when imported without top-level package ("import + Image"). + + Fix PyXML import hook under NT (thanks to Lorenzo Mancini) + + Fixed problem with PyInstaller picking up the wrong copy of optparse. + * Improve correctness of the binary cache of UPX'd/strip'd files. This + fixes problems when switching between multiple versions of the + same third-party library (like e.g. wxPython allows to do). + + Fix a stupid bug with modules importing optparse (under Linux) (thanks + to Louai Al-Khanji). + + Under Python 2.4+, if an exception is raised while importing a module + inside a package, the module is now removed from the parent's + namespace (to match the behaviour of Python itself). + * Fix random race-condition at startup of one-file packages, that was + causing this exception to be generated: "PYZ entry 'encodings' (0j) + is not a valid code object". + + Fix problem when having unicode strings among path elements. + + Fix random exception ("bad file descriptor") with "prints" in non-console + mode (actually a pythonw "bug" that's fixed in Python 3.0). + * Sometimes the temporary directory did not get removed upon program + exit, when running on Linux. + * Fixed random segfaults at startup on 64-bit platforms (like x86-64). + +PyInstaller 1.3 +--------------- + + Fix bug with user-provided icons disappearing from built executables + when these were compressed with UPX. + + Fix problems with packaging of applications using PIL (that was broken + because of a bug in Python's import machinery, in recent Python + versions). Also add a workaround including Tcl/Tk with PIL unless + ImageTk is imported. + + (Windows) When used under Windows XP, packaged programs now have + the correct look & feel and follow user's themes (thanks to the manifest + file being linked within the generated executable). This is especially + useful for applications using wxPython. + + Fix a buffer overrun in the bootloader (which could lead to a crash) + when the built executable is run from within a deep directory (more than + 70-80 characters in the pathname). + * Bootstrap modules are now compressed in the executable (so that they + are not visible in plaintext by just looking at it with a hex editor). + * Fixed a regression introduced in 1.1: under Linux, the bootloader does + not depend on libpythonX.X.so anymore. + + +PyInstaller 1.2 +--------------- + + Fix a crash when invoking UPX with certain kinds of builds. + + Fix icon support by re-adding a resource section in the bootloader + executable. + + +PyInstaller 1.1 +--------------- + + + (Windows) Make single-file packages not depend on MSVCRT71.DLL anymore, + even under Python 2.4. You can eventually ship your programs really as + single-file executables, even when using the newest Python version! + + Fix problem with incorrect python path detection. Now using helpers from + distutils. + + Fix problem with rare encodings introduced in newer Python versions: now all + the encodings are automatically found and included, so this problem should + be gone forever. + + Fix building of COM servers (was broken in 1.0 because of the new build + system). + + Mimic Python 2.4 behaviour with broken imports: sys.modules is cleaned up + afterwise. This allows to package SQLObject applications under Windows + with Python 2.4 and above. + + Add import hook for the following packages: + + GTK + + PyOpenGL (tested 2.0.1.09) + + dsnpython (tested 1.3.4) + + KInterasDB (courtesy of Eugene Prigorodov) + + Fix packaging of code using "time.strptime" under Python 2.3+. + + (Linux) Ignore linux-gate.so while calculating dependencies (fix provided + by Vikram Aggarwal). + + (Windows) With Python 2.4, setup UPX properly so to be able to compress + binaries generated with Visual Studio .NET 2003 (such as most of the + extensions). UPX 1.92+ is needed for this. + + +PyInstaller 1.0 (with respect to McMillan's Python Installer 5b5): +--------------- + + + Add support for Python 2.3 (fix packaging of codecs). + + Add support for Python 2.4 (under Windows, needed to recompiled the + bootloader with a different compiler version). + + Fix support for Python 1.5.2, should be fully functional now (required + to rewrite some parts of the string module for the bootloader). + + Fix a rare bug in extracting the dependencies of a DLL (bug in PE header + parser). + + Fix packaging of PyQt programs (needed an import hook for a hidden import). + + Fix imports calculation for modules using the "from __init__ import" syntax. + + Fix a packaging bug when a module was being import both through binary + dependency and direct import. + + * Restyle documentation (now using docutils and reStructuredText). + * New Windows build system for automatic compilations of bootloader in all + the required flavours (using Scons) diff --git a/pyinstaller/doc/KNOWNBUGS.txt b/pyinstaller/doc/KNOWNBUGS.txt new file mode 100644 index 0000000..7ac0efe --- /dev/null +++ b/pyinstaller/doc/KNOWNBUGS.txt @@ -0,0 +1,5 @@ +========== +KNOWN BUGS +========== + +Consult http://www.pyinstaller.org/report/1 for the current list of Known Bugs diff --git a/pyinstaller/doc/LICENSE.GPL b/pyinstaller/doc/LICENSE.GPL new file mode 100644 index 0000000..a66eb90 --- /dev/null +++ b/pyinstaller/doc/LICENSE.GPL @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/pyinstaller/doc/Manual.html b/pyinstaller/doc/Manual.html new file mode 100644 index 0000000..e29f444 --- /dev/null +++ b/pyinstaller/doc/Manual.html @@ -0,0 +1,2007 @@ + + + + + + +PyInstaller Manual + + + + + +
+

PyInstaller Manual

+ +++ + + + + + + + + + + + + + + + +
Version:PyInstaller 2.0
Homepage:http://www.pyinstaller.org
Author:Giovanni Bajo & William Caban (based on Gordon McMillan's manual)
Contact:rasky@develer.com
Revision:$Rev$
Source URL:$HeadURL$
Copyright:This document has been placed in the public domain.
+
+

Contents

+ +
+
+

Requirements

+
+
Windows
+
    +
  • Windows XP or newer.
  • +
  • PyWin32 +Python extensions for Windows is only necessary for users of Python 2.6+.
  • +
+
+
Linux
+
    +
  • ldd +- Console application to print the shared libraries required by each program +or shared library.
  • +
  • objdump +- Console application to display information from object files.
  • +
+
+
Mac OS X
+
    +
  • Mac OS X 10.4 (Tiger) or newer (Leopard, Snow Leopard, Lion, Mountain Lion).
  • +
+
+
Solaris
+
    +
  • ldd
  • +
  • objdump
  • +
+
+
AIX
+
    +
  • AIX 6.1 or newer. +Python executables created using PyInstaller on AIX 6.1 should work +on AIX 5.2/5.3. PyInstaller will not work with statically linked Python +libraries which has been encountered in Python 2.2 installations on AIX 5.x.
  • +
  • ldd
  • +
  • objdump
  • +
+
+
+
+
+

Installing PyInstaller

+
    +
  1. Unpack the archive on you path of choice. For the purpose of this +documentation we will assume /your/path/to/pyinstaller/.

    +

    You will be using a couple of scripts in the /your/path/to/pyinstaller/ +directory, and these will find everything they need from their own +location. For convenience, keep the paths to these scripts short +(don't install in a deeply nested subdirectory).

    +

    Please note: Installer is not a Python package, so it +doesn't need to go in site-packages, or have a .pth file.

    +
  2. +
  3. For Windows (32/64bit), Linux (32/64bit) and Mac OS X (32/64bit) +precompiled boot-loaders are available. So the installation is +complete now.

    +

    For other platforms (Solaris, AIX, etc.), users should first try to build the +boot-loader:

    +
    +

    cd source +python ./waf configure build install

    +
    +
  4. +
+
+
+

Getting Started

+

For the purpose of this documentation we will assume PyInstaller as +installed into /your/path/to/pyinstaller/.

+
+

Build your project

+

For building a Windows COM server, please see section Windows COM +Server Support below.

+

In the /your/path/to/pyinstaller/ directory, run:

+
+python pyinstaller.py [opts] yourprogram.py
+
+

This will create a sub-directory your-program in the /your/path/to/pyinstaller/ +directory. The generated files will be placed within the sub-directory +your-program/dist; that's where the files you are interested in +will be placed. A spec file called your-program.spec will be +created in the sub-directory your-program, too. Additionally a +subtracts your-program/build is created where intermediate build +files are kept.

+

If your current working directory is not /your/path/to/pyinstaller/, the +directories dist and build and the spec file will be +created in the current working directory. Say: the intermediate +directory your-program will be skipped.

+

If you have already created a spec file for your project then in +the /your/path/to/pyinstaller/ directory run:

+
+python pyinstaller.py [opts] your-program.spec
+
+

If your current working directory is not /your/path/to/pyinstaller/, this works +analogously.

+

If everything is working and you are happy with the default settings, +this will be all you have to do. If not, see Allowed OPTIONS, When +things go wrong and be sure to read the introduction to Spec +Files.

+
+
+

Allowed Options

+

By default, pyinstaller.py creates a distribution directory containing the main +executable and the dynamic libraries. The option --onefile (specifies that you want +PyInstaller to build a single file with everything inside.

+

The syntax to use pyinstaller.py is the following:

+
+python pyinstaller.py [opts] <scriptname> [ <scriptname> ...] | <specfile>
+
+

Allowed OPTIONS are:

+ +++ + + + + + + + + + + + + + + + + + + + +
+-h, --helpshow this help message and exit
+-v, --versionshow program version
+--upx-dir=UPX_DIR
 Directory containing UPX.
+-a, --asciido NOT include unicode encodings (default: included if +available)
+--buildpath=BUILDPATH
 Buildpath (default: +SPECPATH/build/pyi.TARGET_PLATFORM/SPECNAME)
+-y, --noconfirm
 Remove output directory (default: +SPECPATH/dist/SPECNAME) without confirmation
+--log-level=LOGLEVEL
 Log level (default: INFO, choose one of DEBUG, INFO, +WARN, ERROR, CRITICAL)
+

What to generate:

+ +++ + + + + + + + + + + + +
+-F, --onefilecreate a single file deployment
+-D, --onedircreate a single directory deployment (default)
+-o DIR, --out=DIR
 create the spec file in directory. If not specified, and the current +directory is Installer's root directory, an output subdirectory will be +created. Otherwise the current directory is used.
+-n NAME, --name=NAME
 optional name to assign to the project (from which the spec file name is +generated). If omitted, the basename of the (first) script is used.
+

What to bundle, where to search:

+ +++ + + + + + + + + + + +
+-p DIR, --paths=DIR
 set base path for import (like using PYTHONPATH). Multiple directories are +allowed, separating them with the path separator (';' under Windows, ':' +under Linux), or using this option multiple times.
+--hidden-import=MODULENAME
 import hidden in the script(s). This option can be +used multiple times.
+--additional-hooks-dir=HOOKSPATH
 Additional path to search for hooks. This will go into the extend +the hookspath, see Analysis below. This option may be given +several times.
+

How to generate:

+ +++ + + + + + + + +
+-d, --debuguse the debug (verbose) build of the executable
+-s, --stripthe executable and all shared libraries will be run through strip. Note +that cygwin's strip tends to render normal Win32 dlls unusable.
+--noupxdo not use UPX even if available (works differently +between Windows and *nix)
+

Windows and Mac OS X specific options:

+ +++ + + + + + + + +
+-c, --console, --nowindowed
 use a console subsystem executable (default)
+-w, --windowed, --noconsole
 use a windowed subsystem executable, which on Windows +does not open the console when the program is launched. +On Mac OS X it allows running gui applications and also +creates also .app bundle. +This option is mandatory when freezing an gui application on Mac OS X. +Otherwise the application will not start..
+
+
-i FILE.ICO, -i FILE.EXE,ID, -i FILE.ICNS, --icon=FILE.ICO, --icon=FILE.EXE,ID, --icon=FILE.ICNS
+
If FILE is an .ico file, add the icon to the final +executable. Otherwise, the syntax 'file.exe,id' to +extract the icon with the specified id from file.exe +and add it to the final executable. If FILE is an +.icns file, add the icon to the final .app bundle on +Mac OS X (for Mac not yet implemented)
+
+

Windows specific options:

+ +++ + + + + + + + +
+--version-file=FILE
 add a version resource from FILE to the exe
+-m FILE, -m XML, --manifest=FILE, --manifest=XML
 add manifest FILE or XML to the exe
+
+
-r FILE[,TYPE[,NAME[,LANGUAGE]]], --resource=FILE[,TYPE[,NAME[,LANGUAGE]]]
+
add/update resource of the given type, name and +language from FILE to the final executable. FILE can +be a data file or an exe/dll. For data files, atleast +TYPE and NAME need to be specified, LANGUAGE defaults +to 0 or may be specified as wildcard * to update all +resources of the given TYPE and NAME. For exe/dll +files, all resources from FILE will be added/updated +to the final executable if TYPE, NAME and LANGUAGE are +omitted or specified as wildcard *.Multiple resources +are allowed, using this option multiple times.
+
+

For building with optimization on (like Python -O), see section +Building Optimized.

+
+
+

A spec file for your project

+

The spec file is the description of what you want PyInstaller to do with +your program. By deafult, pyinstaller.py generates a spec file automatically. +For simple projects, the generated spec file will be probably sufficient.

+

For more complex projects, it should be regarded as a template. The spec file is +actually Python code, and modifying it should be ease. See Spec Files for +details.

+

In the root directory of PyInstaller, there is a simple wizard to create simple +spec files that cover all basic usages:

+
+python utils/Makespec.py [--onefile] yourprogram.py
+
+

Elaborating on Makespec.py, this is the supported command line:

+
+python utils/Makespec.py [opts] <scriptname> [<scriptname> ...]
+
+

Script Makespec.py shares some options with pyinstaller.py. For allowed options see:

+
+python utils/Makespec.py --help
+
+
+
+

Windows COM Server support

+

For Windows COM support execute:

+
+python MakeComServer.py [OPTION] script...
+
+

This will generate a new script drivescript.py and a spec file for the script.

+

These options are allowed:

+ +++ + + + + + + + + + +
+--debugUse the verbose version of the executable.
+--verboseRegister the COM server(s) with the quiet flag off.
+--asciido not include encodings (this is passed through to Makespec).
+--out <dir>Generate the driver script and spec file in dir.
+

Now Build your project on the generated spec file.

+

If you have the win32dbg package installed, you can use it with the generated +COM server. In the driver script, set debug=1 in the registration line.

+

Warnings: the inprocess COM server support will not work when the client +process already has Python loaded. It would be rather tricky to +non-obtrusively hook into an already running Python, but the show-stopper is +that the Python/C API won't let us find out which interpreter instance I should +hook into. (If this is important to you, you might experiment with using +apartment threading, which seems the best possibility to get this to work). To +use a "frozen" COM server from a Python process, you'll have to load it as an +exe:

+
+o = win32com.client.Dispatch(progid,
+                 clsctx=pythoncom.CLSCTX_LOCAL_SERVER)
+
+

MakeCOMServer also assumes that your top level code (registration etc.) is +"normal". If it's not, you will have to edit the generated script.

+
+
+

Building Optimized

+

There are two facets to running optimized: gathering .pyo's, and setting the +Py_OptimizeFlag. Installer will gather .pyo's if it is run optimized:

+
+python -O pyinstaller.py ...
+
+

The Py_OptimizeFlag will be set if you use a ('O','','OPTION') in one of +the TOCs building the EXE:

+
+exe = EXE(pyz,
+          a.scripts + [('O','','OPTION')],
+          ...
+
+

See Spec Files for details.

+
+
+

A Note on using UPX

+

On both Windows and Linux, UPX can give truly startling compression - the days +of fitting something useful on a diskette are not gone forever! Installer has +been tested with many UPX versions without problems. Just get it and install it +on your PATH.

+

For Windows, there is a problem of compatibility between UPX and executables +generated by Microsoft Visual Studio .NET 2003 (or the equivalent free +toolkit available for download). This is especially worrisome for users of +Python 2.4+, where most extensions (and Python itself) are compiled with that +compiler. This issue has been fixed in later beta versions of UPX, so you +will need at least UPX 1.92 beta. pyinstaller.py will check this for you +and complain if you have an older version of UPX and you are using Python 2.4.

+ +

For Linux, a bit more discussion is in order. First, UPX is only useful on +executables, not shared libs. Installer accounts for that, but to get the full +benefit, you might rebuild Python with more things statically linked.

+

More importantly, when run finds that its sys.argv[0] does not contain a path, +it will use /proc/pid/exe to find itself (if it can). This happens, for +example, when executed by Apache. If it has been upx-ed, this symbolic link +points to the tempfile created by the upx stub and PyInstaller will fail (please +see the UPX docs for more information). So for now, at least, you can't use upx +for CGI's executed by Apache. Otherwise, you can ignore the warnings in the UPX +docs, since what PyInstaller opens is the executable Installer created, not the +temporary upx-created executable.

+
+
+

Accessing Data Files

+

If your application needs to access data files, e.g configuration +files or icons images, you need some minor changes to you application +and you need to collect the file into distribution directory tree (in +--onedir mode) resp. into the executable (in --onefile mode).

+
+

Adopt your application

+

Instead of:

+
+basedir = os.path.dirname(__file__)
+
+

use:

+
+if getattr(sys, 'frozen', None):
+     basedir = sys._MEIPASS
+else:
+     basedir = os.path.dirname(__file__)
+
+

sys._MEIPASS points in --onedir mode to the directory containing +the created executable and in --onefile mode to the temporary directory +where binaries get extracted.

+
+
+

Collect your data files

+

Collecting the data-files is easy: pass a list of your data files (in +TOC format) to the COLLECT. The name in the (name, path, +'DATA') tuple can be a relative path name.

+

Then, at runtime, you can use code like this to find the file:

+
+os.path.join(basedir, relativename)
+
+

In a --onedir distribution, the files will listed in the +COLLECT will show up in the distribution directory tree, so you +can simply pack them into your isntaller or distribution archive.

+

In a --onefile distribution, data files are bundled within the +executable and then at runtime extracted into the work directory. This +is done by the C code which is also able to reconstruct directory +trees.

+
+
+
+

How one-file mode works

+ +

A --onefile works by packing all the shared libs / dlls into the archive +attached to the bootloader executable (or next to the executable in a non-elf +configuration). When first started, it finds that it needs to extract these +files before it can run "for real". That's because locating and loading a +shared lib or linked-in dll is a system level action, not user-level. With +PyInstaller 2.0 it always uses a temporary directory (_MEIXXXXX, +where XXXXX is a random number to avoid conflicts) in the +user's temp directory. It then executes itself again, setting things up so +the system will be able to load the shared libs / dlls. When execution is +complete, it recursively removes the entire directory it created.

+

The temporary directory is exported to the program's environment as +sys._MEIPASS. This can be used in case you manually modified +the spec file to tell PyInstaller to add additional files (eg: data files) +within the executable (see also Accessing Data Files).

+

This has a number of implications:

+
    +
  • You can run multiple copies - they won't collide.
  • +
  • Running multiple copies will be rather expensive to the system (nothing is +shared).
  • +
  • On Windows, using Task Manager to kill the parent process will leave the +directory behind.
  • +
  • On *nix, a kill -9 (or crash) will leave the directory behind.
  • +
  • Otherwise, on both platforms, the directory will be recursively deleted.
  • +
  • So any files you might create in sys._MEIPASS will be deleted.
  • +
  • The executable can be in a protected or read-only directory.
  • +
+

Notes for *nix users: Take notice that if the executable does a setuid root, +a determined hacker could possibly (given enough tries) introduce a malicious +lookalike of one of the shared libraries during the hole between when the +library is extracted into the temporary directory and when it gets loaded +by the execvp'd process. So maybe you shouldn't do setuid root programs +using --onefile. In fact, we do not recomend the use of --onefile +on setuid programs.

+
+
+

.egg files and setuptools

+

setuptools is a distutils extensions which provide many benefits, including +the ability to distribute the extension as eggs. Together with the +nifty easy_install (a tool which automatically locates, downloads and +installs Python extensions), eggs are becoming more and more +widespread as a way for distributing Python extensions.

+

eggs can be either files or directories. An egg directory is basically +a standard Python package, with some additional metadata that can be used for +advanced setuptools features like entry-points. An egg file is simply a +ZIP file, and it works as a package as well because Python 2.3+ is able to +transparently import modules stored within ZIP files.

+

PyInstaller supports eggs at a good level. In fact:

+
    +
  • It is able to follow dependencies within eggs (both files and directories). +So if your program imports a package shipped in egg format, and this package +requires additional libraries, PyInstaller will correctly include everything +within the generated executable.
  • +
  • egg-files are fully supported. To let everything works (entry-points, +pkg_resource library, etc.), PyInstaller either copy the egg-files +into the distribution directory (in one-dir mode) or packs them as-is within +the generated executable and unpack them at startup into the temporary directory +(see How one-file mode works).
  • +
  • egg-directories are partially supported. In fact, PyInstaller at build +time treat them as regular package. This means that all advanced features requiring +egg metadatas will not work.
  • +
+

Improved support for eggs is planned for a future release of PyInstaller.

+
+
+

Multipackage function

+

Some applications are made of several different binaries, that might rely on the same +third-party libraries and/or share lots of code. When packaging such applications, it +would be a pity to treat each application binary separately and repackage all its +dependencies, potentially duplicating lots of code and libraries.

+

With Pyinstaller, you can use the multipackage feature to create multiple binaries that +might share libraries among themselves: each dependency is packaged only once in one of +the binaries, while the others simply have an "external reference" to it, that tells them +to go finding that dependency in the binary contains it.

+

The easiest way to access this function is to simply pass multiple script files to +pyinstaller.py (or utils/Makespec.py`). It will generate a spec file that contains +a call to the MERGE function to basically merge dependencies across the different scripts.

+

The order of the scripts on the command line (and within the MERGE +function) matters: given each library, PyInstaller will package common dependencies on the +leftmost script that first needs that dependency. You might want to tweak the order of +the script files accordingly.

+

Notice that the external references between binaries are hard-coded with respect to the +paths on the disk in which they are created in the output directory, and cannot be rearranged: +thus, if you use a one-file deploy, you will need to place all binaries in the same directory +when you install your application. Similarly, if you use one-dir deploy, you will need to +install all the binary directories within the same parent directory.

+

There are multipackage examples in the buildtests/multipackage directory.

+
+
+
+

PyInstaller Utilities

+
+

ArchiveViewer

+
+python utils/ArchiveViewer.py <archivefile>
+
+

ArchiveViewer lets you examine the contents of any archive build with +PyInstaller or executable (PYZ, PKG or exe). Invoke it with the target as the +first arg (It has been set up as a Send-To so it shows on the context menu in +Explorer). The archive can be navigated using these commands:

+
+
O <nm>
+
Open the embedded archive <nm> (will prompt if omitted).
+
U
+
Go up one level (go back to viewing the embedding archive).
+
X <nm>
+
Extract nm (will prompt if omitted). Prompts for output filename. If none +given, extracted to stdout.
+
Q
+
Quit.
+
+

Futhermore ArchiveViewer has some simple console commands:

+ +++ + + + + + + + + + + +
+-h, --helpShow help.
+-l, --logQuick contents log.
+-b, --briefPrint a python evaluable list of contents filenames.
+-r, --recursive
 Used with -l or -b, applies recusive behaviour.
+
+
+

BinDepend

+
+python utils/BinDepend.py <executable_or_dynamic_library>
+
+

BinDepend will analyze the executable you pass to it, and write to stdout all +its binary dependencies. This is handy to find out which DLLs are required by +an executable or another DLL. This module is used by PyInstaller itself to +follow the chain of dependencies of binary extensions and make sure that all +of them get included in the final package.

+
+
+

GrabVersion (Windows)

+
+python utils/GrabVersion.py <executable_with_version_resource>
+
+

GrabVersion outputs text which can be eval'ed by versionInfo.py to reproduce +a version resource. Invoke it with the full path name of a Windows executable +(with a version resource) as the first argument. If you cut & paste (or +redirect to a file), you can then edit the version information. The edited +text file can be used in a version = myversion.txt option on any executable +in an PyInstaller spec file.

+

This was done in this way because version resources are rather strange beasts, +and fully understanding them is probably impossible. Some elements are +optional, others required, but you could spend unbounded amounts of time +figuring this out, because it's not well documented. When you view the version +tab on a properties dialog, there's no straightforward relationship between +how the data is displayed and the structure of the resource itself. So the +easiest thing to do is find an executable that displays the kind of +information you want, grab it's resource and edit it. Certainly easier than +the Version resource wizard in VC++.

+
+
+

Analyzing Dependencies

+

You can interactively track down dependencies, including getting +cross-references by using mf.py, documented in section mf.py: A modulefinder +Replacement

+
+
+
+

Spec Files

+
+

Introduction

+

When you run utils/Makespec.py (documented +in section A spec file for your project), it generates a +spec file for you. In fact, +you can think of utils/Makespec.py just like a wizard that lets you generate +a standard spec file for most standard usages. But advanced users can +learn to edit spec files to fully customize PyInstaller behaviour to +their needs, giving beyond the standard settings provided by the wizard.

+

Spec files are in Python syntax. They are evaluated by pyinstaller.py. A simplistic +spec file might look like this:

+
+a = Analysis(['myscript.py'])
+pyz = PYZ(a.pure)
+exe = EXE(pyz, a.scripts, a.binaries, name="myapp.exe")
+
+

This creates a single file deployment with all binaries (extension modules and +their dependencies) packed into the executable.

+

A simplistic single directory deployment might look like this:

+
+a = Analysis(['myscript.py'])
+pyz = PYZ(a.pure)
+exe = EXE(a.scripts, pyz, name="myapp.exe", exclude_binaries=1)
+dist = COLLECT(exe, a.binaries, name="dist")
+
+

Note that neither of these examples are realistic. If you want to +start hacking a spec file, use utils/Makespec.py to create a basic specfile, +and tweak it (if necessary) from there.

+

All of the classes you see above are subclasses of Build.Target. A Target acts +like a rule in a makefile. It knows enough to cache its last inputs and +outputs. If its inputs haven't changed, it can assume its outputs wouldn't +change on recomputation. So a spec file acts much like a makefile, only +rebuilding as much as needs rebuilding. This means, for example, that if you +change an EXE from debug=1 to debug=0, the rebuild will be nearly +instantaneous.

+

The high level view is that an Analysis takes a list of scripts as input, +and generates three "outputs", held in attributes named scripts, pure +and binaries. A PYZ (a .pyz archive) is built from the modules in +pure. The EXE is built from the PYZ, the scripts and, in the case of a +single-file deployment, the binaries. In a single-directory deployment, a +directory is built containing a slim executable and the binaries.

+
+
+

TOC Class (Table of Contents)

+

Before you can do much with a spec file, you need to understand the +TOC (Table Of Contents) class.

+

A TOC appears to be a list of tuples of the form (name, path, typecode). +In fact, it's an ordered set, not a list. A TOC contains no duplicates, where +uniqueness is based on name only. Furthermore, within this constraint, a TOC +preserves order.

+

Besides the normal list methods and operations, TOC supports taking differences +and intersections (and note that adding or extending is really equivalent to +union). Furthermore, the operations can take a real list of tuples on the right +hand side. This makes excluding modules quite easy. For a pure Python module:

+
+pyz = PYZ(a.pure - [('badmodule', '', '')])
+
+

or for an extension module in a single-directory deployment:

+
+dist = COLLECT(..., a.binaries - [('badmodule', '', '')], ...)
+
+

or for a single-file deployment:

+
+exe = EXE(..., a.binaries - [('badmodule', '', '')], ...)
+
+

To add files to a TOC, you need to know about the typecodes (or the step using +the TOC won't know what to do with the entry).

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typecodedescriptionnamepath
'EXTENSION'An extension module.Python internal name.Full path name in build.
'PYSOURCE'A script.Python internal name.Full path name in build.
'PYMODULE'A pure Python module (including __init__ modules).Python internal name.Full path name in build.
'PYZ'A .pyz archive (archive_rt.ZlibArchive).Runtime name.Full path name in build.
'PKG'A pkg archive (carchive4.CArchive).Runtime name.Full path name in build.
'BINARY'A shared library.Runtime name.Full path name in build.
'DATA'Aribitrary files.Runtime name.Full path name in build.
'OPTION'A runtime runtime option (frozen into the executable).The option.Unused.
+

You can force the include of any file in much the same way you do excludes:

+
+collect = COLLECT(a.binaries +
+          [('readme', '/my/project/readme', 'DATA')], ...)
+
+

or even:

+
+collect = COLLECT(a.binaries,
+          [('readme', '/my/project/readme', 'DATA')], ...)
+
+

(that is, you can use a list of tuples in place of a TOC in most cases).

+

There's not much reason to use this technique for PYSOURCE, since an Analysis +takes a list of scripts as input. For PYMODULEs and EXTENSIONs, the hook +mechanism discussed here is better because you won't have to remember how you +got it working next time.

+

This technique is most useful for data files (see the Tree class below for a +way to build a TOC from a directory tree), and for runtime options. The options +the run executables understand are:

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionExampleNotes
vVerbose imports('v', '', 'OPTION')Same as Python -v ...
uUnbuffered stdio('u', '', 'OPTION')Same as Python -u ...
W specWarning option('W ignore', '', 'OPTION')Python 2.1+ only.
sUse site.py('s', '', 'OPTION')The opposite of Python's -S flag. Note that site.py must be in the executable's directory to be used.
+

Advanced users should note that by using set differences and intersections, it +becomes possible to factor out common modules, and deploy a project containing +multiple executables with minimal redundancy. You'll need some top level code +in each executable to mount the common PYZ.

+
+
+

Target Subclasses

+
+

Analysis

+
+Analysis(scripts, pathex=None, hookspath=None, excludes=None)
+
+
+
scripts
+
a list of scripts specified as file names.
+
pathex
+
an optional list of paths to be searched before sys.path.
+
hiddenimports
+
an optional list of additional (hidden) modules to include. Please +refer to Listing Hidden Imports for details.
+
hookspath
+
an optional list of additional paths to search for hooks +(hook-modules). Please refer to Listing Hidden Imports for details.
+
excludes
+
an optional list of module or package names (their Python names, not path +names) that will be ignored (as though they were not found).
+
+

An Analysis has five outputs, all TOCs accessed as attributes of the Analysis.

+
+
scripts
+
The scripts you gave Analysis as input, with any runtime hook scripts +prepended.
+
pure
+
The pure Python modules.
+
binaries
+
The extension modules and their dependencies. The secondary dependencies are +filtered. On Windows, a long list of MS dlls are excluded. On Linux/Unix, +any shared lib in /lib or /usr/lib is excluded.
+
datas
+
Data-file dependencies. These are data-file that are found to be needed by +modules. They can be anything: plugins, font files, etc.
+
zipfiles
+
The zipfiles dependencies (usually egg-files).
+
+
+
+

PYZ

+
+PYZ(toc, name=None, level=9)
+
+
+
toc
+
a TOC, normally an Analysis.pure.
+
name
+
A filename for the .pyz. Normally not needed, as the generated name will do fine.
+
level
+
The Zlib compression level to use. If 0, the zlib module is not required.
+
+
+
+

PKG

+

Generally, you will not need to create your own PKGs, as the EXE will do it for +you. This is one way to include read-only data in a single-file deployment, +however.

+
+PKG(toc, name=None, cdict=None, exclude_binaries=0)
+
+
+
toc
+
a TOC.
+
name
+
a filename for the PKG (optional).
+
cdict
+
a dictionary that specifies compression by typecode. For example, PYZ is +left uncompressed so that it can be accessed inside the PKG. The default +uses sensible values. If zlib is not available, no compression is used.
+
exclude_binaries
+
If 1, EXTENSIONs and BINARYs will be left out of the PKG, and +forwarded to its container (usually a COLLECT).
+
+
+
+

EXE

+
+EXE(*args, **kws)
+
+
+
args
+
One or more arguments which are either TOCs or Targets.
+
kws
+

Possible keyword arguments:

+
+
console
+
Always 1 on Linux/unix. On Windows, governs whether to use the console +executable, or the Windows subsystem executable.
+
debug
+
Setting to 1 gives you progress messages from the executable (for a +console=0, these will be annoying MessageBoxes).
+
name
+
The filename for the executable.
+
exclude_binaries
+
Forwarded to the PKG the EXE builds.
+
icon
+
Windows NT family only. icon='myicon.ico' to use an icon file, or +icon='notepad.exe,0' to grab an icon resource.
+
version
+
Windows NT family only. version='myversion.txt'. Use GrabVersion.py to +steal a version resource from an executable, and then edit the ouput to +create your own. (The syntax of version resources is so arcane that I +wouldn't attempt to write one from scratch.)
+
append_pkg
+
If True, then append the PKG archive to the EXE. If False, +place the PKG archive in a separate file exename.pkg. +The default depends +on whether Make.py was given the -n argument +when building the loader. The default is True on Windows. +On non-ELF platforms where concatenating arbitrary data to +an executable does not work, append_pkg must be set to False.
+
+
+
+
+
+

DLL

+

On Windows, this provides support for doing in-process COM servers. It is not +generalized. However, embedders can follow the same model to build a special +purpose DLL so the Python support in their app is hidden. You will need to +write your own dll, but thanks to Allan Green for refactoring the C code and +making that a managable task.

+
+
+

COLLECT

+
+COLLECT(*args, **kws)
+
+
+
args
+
One or more arguments which are either TOCs or Targets.
+
kws
+

Possible keyword arguments:

+
+
name
+
The name of the directory to be built.
+
+
+
+
+
+

Tree

+
+Tree(root, prefix=None, excludes=None)
+
+
+
root
+
The root of the tree (on the build system).
+
prefix
+
Optional prefix to the names on the target system.
+
excludes
+

A list of names to exclude. Two forms are allowed:

+
+
name
+
files with this basename will be excluded (do not include the path).
+
*.ext
+
any file with the given extension will be excluded.
+
+
+
+

Since a Tree is a TOC, you can also use the exclude technique described above +in the section on TOCs.

+
+
+

MERGE

+

With the MERGE function we can create a group of interdependent packages.

+
+MERGE(*args)
+
+
+
*args
+
This is a list of tuples. The first element of the tuple is an analysis object, +the second one is the script name without extension and the third one is the final name.
+
+

The MERGE function filters the analysis to avoid duplication of libraries and modules. +As a result the packages generated will be connected. Furthermore, to ensure the consistency +of dependencies, it replaces the temporary names with the actual names. +MERGE is used after the analysis phase and before EXE and COLLECT.

+

Here is spec file example with MERGE function:

+
+## Where the package folders will be built, and the shortcuts will reside
+TargetDir = os.path.abspath(os.path.join('..','..','Client','Programs'))
+
+## The application names
+AppNames = [d for d in os.listdir(os.getcwd())
+            if os.path.isdir(d)
+            and d[0]!='.'
+            and d[0:6]!='Common'
+            and d != 'build'
+            and d != 'dummy']
+
+## Build MERGE arguments (analysis object, script base name, final exe path)
+#  Start with the dummy package
+Analyses = [(Analysis([os.path.join('dummy','dummy.py')]),
+             'dummy', os.path.join('dummy','dummy.exe'))
+            ]
+
+#  NOTE: this assumes that the main script in each is appname.pyw in the appname folder
+Analyses += [(Analysis([os.path.join(appname, appname + '.pyw')]),
+              appname, os.path.join(appname,appname+'.exe'))
+             for appname in AppNames]
+
+## Merge all the dependencies
+MERGE(*Analyses)
+
+## Build each app
+for anal, basename, exename in Analyses:
+    pyz = PYZ(anal.pure)
+    exe = EXE(pyz,
+              anal.scripts,
+              anal.dependencies,
+              exclude_binaries=1,
+              name=exename,
+              version='FalconVersion.txt',
+              debug=False,
+              strip=False,
+              upx=True,
+              console=False )
+    dist = COLLECT(exe,
+                   anal.binaries,
+                   anal.zipfiles,
+                   anal.datas,
+                   strip=False,
+                   ###upx=True if (basename == 'dummy') else False,
+                   upx=False,
+                   name=os.path.join(TargetDir,basename))
+
+
+
+
+
+

When Things Go Wrong

+
+

Recipes and Instructions for special Modules

+

Code examples for some modules needing special care and some common +issues are available on our Recipe web-page.

+
+
+

Finding out What Went Wrong

+
+

Buildtime Warnings

+

When an Analysis step runs, it produces a warnings file (named warnproject.txt) +in the spec file's directory. Generally, most of these warnings are harmless. +For example, os.py (which is cross-platform) works by figuring out what +platform it is on, then importing (and rebinding names from) the appropriate +platform-specific module. So analyzing os.py will produce a set of warnings +like:

+
+WARNING: no module named dos (conditional import by os)
+WARNING: no module named ce (conditional import by os)
+WARNING: no module named os2 (conditional import by os)
+
+

Note that the analysis has detected that the import is within a conditional +block (an if statement). The analysis also detects if an import within a +function or class, (delayed) or at the top level. A top-level, non-conditional +import failure is really a hard error. There's at least a reasonable chance +that conditional and / or delayed import will be handled gracefully at runtime.

+

Ignorable warnings may also be produced when a class or function is declared in +a package (an __init__.py module), and the import specifies +package.name. In this case, the analysis can't tell if name is supposed to +refer to a submodule of package.

+

Warnings are also produced when an __import__, exec or eval statement is +encountered. The __import__ warnings should almost certainly be investigated. +Both exec and eval can be used to implement import hacks, but usually their use +is more benign.

+

Any problem detected here can be handled by hooking the analysis of the module. +See Listing Hidden Imports below for how to do it.

+
+
+

Getting Debug Messages

+

Debug messages for PyInstaller can be enabled by passing the --log-level +flag to the pyinstaller.py script:

+
+pyinstaller.py --log-level=DEBUG <scriptname>
+
+

Setting debug=1 on an EXE will cause the executable to put out progress +messages (for console apps, these go to stdout; for Windows apps, these show as +MessageBoxes). This can be useful if you are doing complex packaging, or your +app doesn't seem to be starting, or just to learn how the runtime works.

+
+
+

Getting Python's Verbose Imports

+

You can also pass a -v (verbose imports) flag to the embedded Python. This can +be extremely useful. I usually try it even on apparently working apps, just to +make sure that I'm always getting my copies of the modules and no import has +leaked out to the installed Python.

+

You set this (like the other runtime options) by feeding a phone TOC entry to +the EXE. The easiest way to do this is to change the EXE from:

+
+EXE(..., anal.scripts, ....)
+
+

to:

+
+EXE(..., anal.scripts + [('v', '', 'OPTION')], ...)
+
+

These messages will always go to stdout, so you won't see them on Windows if +console=0.

+
+
+
+

Helping PyInstaller Find Modules

+
+

Extending the Path

+

When the analysis phase cannot find needed modules, it may be that the code is +manipulating sys.path. The easiest thing to do in this case is tell Analysis +about the new directory through the second arg to the constructor:

+
+anal = Analysis(['somedir/myscript.py'],
+                ['path/to/thisdir', 'path/to/thatdir'])
+
+

In this case, the Analysis will have a search path:

+
+['somedir', 'path/to/thisdir', 'path/to/thatdir'] + sys.path
+
+

You can do the same when running utils/Makespec.py or pyinstaller.py:

+
+utils/Makespec.py --paths=path/to/thisdir;path/to/thatdir ...
+pyinstaller.py --paths=path/to/thisdir;path/to/thatdir ...
+
+

(on *nix, use : as the path separator).

+
+
+

Listing Hidden Imports

+

Hidden imports are fairly common. These can occur when the code is using +__import__ (or, perhaps exec or eval), in which case you will see a warning in +the warnproject.txt file. They can also occur when an extension module uses the +Python/C API to do an import, in which case Analysis can't detect +anything.

+

You +can verify that hidden import is the problem by using Python's verbose imports +flag. If the import messages say "module not found", but the warnproject.txt +file has no "no module named..." message for the same module, then the problem +is a hidden import.

+ +

Hidden imports are handled by hooking the module (the one doing the +hidden imports) at Analysis time. Do this as follows:

+
+
    +
  1. Create a file named hook-module.py (where module is the +fully-qualified Python name, eg, hook-xml.dom.py) and place it +somewhere. Remember the place as your private hooks directory.

    +
  2. +
  3. In the .spec file, pass your private hooks directory as +hookspath argument to Analysis so will be searched. +Example:

    +
    +a = Analysis(['myscript.py'], hookspath='/my/priv/hooks')
    +
    +
  4. +
+
+

In most cases the hook module will have only one line:

+
+hiddenimports = ['module1', 'module2']
+
+

When the Analysis finds this file, it will proceed exactly as +though the module explicitly imported module1 and module2.

+

If you successfully hook a publicly distributed module in this way, +please send us the hook so we can make it available to others.

+

You may want to have a look at already existing hooks in the +PyInstaller.hooks package under PyInstaller's root directory. +For full details on the analysis-time hook mechanism is in the Hooks +section.

+
+
+

Extending a Package's __path__

+

Python allows a package to extend the search path used to find modules and +sub-packages through the __path__ mechanism. Normally, a package's __path__ has +only one entry - the directory in which the __init__.py was found. But +__init__.py is free to extend its __path__ to include other directories. For +example, the win32com.shell.shell module actually resolves to +win32com/win32comext/shell/shell.pyd. This is because win32com/__init__.py +appends ../win32comext to its __path__.

+

Because the __init__.py is not actually run during an analysis, we use the same +hook mechanism we use for hidden imports. A static list of names won't do, +however, because the new entry on __path__ may well require computation. So +hook-module.py should define a method hook(mod). The mod argument is an +instance of mf.Module which has (more or less) the same attributes as a real +module object. The hook function should return a mf.Module instance - perhaps +a brand new one, but more likely the same one used as an arg, but mutated. +See mf.py: A Modulefinder Replacement for details, and PyInstaller/hooks/hook-win32com.py +for an example.

+

Note that manipulations of __path__ hooked in this way apply to the analysis, +and only the analysis. That is, at runtime win32com.shell is resolved the same +way as win32com.anythingelse, and win32com.__path__ knows nothing of ../win32comext.

+

Once in awhile, that's not enough.

+
+
+

Changing Runtime Behavior

+

More bizarre situations can be accomodated with runtime hooks. These are small +scripts that manipulate the environment before your main script runs, +effectively providing additional top-level code to your script.

+

At the tail end of an analysis, the module list is examined for matches in +support/rthooks.dat, which is the string representation of a +Python dictionary. The key is the module name, and the value is a list +of hook-script pathnames.

+

So putting an entry:

+
+'somemodule': ['path/to/somescript.py'],
+
+

into support/rthooks.dat is almost the same thing as doing this:

+
+anal = Analysis(['path/to/somescript.py', 'main.py'], ...
+
+

except that in using the hook, path/to/somescript.py will not be analyzed, +(that's not a feature - we just haven't found a sane way fit the recursion into +my persistence scheme).

+

Hooks done in this way, while they need to be careful of what they import, are +free to do almost anything. One provided hook sets things up so that win32com +can generate modules at runtime (to disk), and the generated modules can be +found in the win32com package.

+
+
+

Adapting to being "frozen"

+

In most sophisticated apps, it becomes necessary to figure out (at runtime) +whether you're running "live" or "frozen". For example, you might have a +configuration file that (running "live") you locate based on a module's +__file__ attribute. That won't work once the code is packaged up. You'll +probably want to look for it based on sys.executable instead.

+

The bootloaders set sys.frozen=1 (and, for in-process COM servers, the +embedding DLL sets sys.frozen='dll').

+

For really advanced users, you can access the iu.ImportManager as +sys.importManager. See iu.py for how you might make use of this fact.

+
+
+
+
+

Miscellaneous

+
+

Self-extracting executables

+

The ELF executable format (Windows, Linux and some others) allows arbitrary +data to be concatenated to the end of the executable without disturbing its +functionality. For this reason, a CArchive's Table of Contents is +at the end of the archive. The executable can open itself as a binary +file name, seek to the end and 'open' the CArchive (see figure 3).

+

On other platforms, the archive and the executable are separate, but the +archive is named executable.pkg, and expected to be in the same directory. +Other than that, the process is the same.

+
+

One Pass Execution

+

In a single directory deployment (--onedir, which is the default), +all of the binaries are already in the file system. In that case, the +embedding app:

+
    +
  • opens the archive
  • +
  • starts Python (on Windows, this is done with dynamic loading so one embedding +app binary can be used with any Python version)
  • +
  • imports all the modules which are at the top level of the archive (basically, +bootstraps the import hooks)
  • +
  • mounts the ZlibArchive(s) in the outer archive
  • +
  • runs all the scripts which are at the top level of the archive
  • +
  • finalizes Python
  • +
+
+
+

Two Pass Execution

+

There are a couple situations which require two passes:

+
    +
  • a --onefile deployment (on Windows, the files can't be cleaned +up afterwards because Python does not call FreeLibrary; on other +platforms, Python won't find them if they're extracted in the same +process that uses them)
  • +
  • LD_LIBRARY_PATH needs to be set to find the binaries (not +extension modules, but modules the extensions are linked to).
  • +
+

The first pass:

+
    +
  • opens the archive
  • +
  • extracts all the binaries in the archive (in PyInstaller 2.0, +this is always to a temporary directory).
  • +
  • sets a magic environment variable
  • +
  • sets LD_LIBRARY_PATH (non-Windows)
  • +
  • executes itself as a child process (letting the child use his stdin, stdout +and stderr)
  • +
  • waits for the child to exit (on *nix, the child actually replaces the parent)
  • +
  • cleans up the extracted binaries (so on *nix, this is done by the child)
  • +
+

The child process executes as in One Pass Execution above (the magic +environment variable is what tells it that this is pass two).

+

SE_exeImage figure 3 - Self Extracting Executable

+

There are, of course, quite a few differences between the Windows and +Unix/Linux versions. The major one is that because all of Python on Windows is +in pythonXX.dll, and dynamic loading is so simple-minded, that one +binary can be use with any version of Python. There's much in common, +though, and that C code can be found in source/common/launch.c.

+

The Unix/Linux build process (which you need to run just once for any version +of Python) makes use of the config information in your install (if you +installed from RPM, you need the Python-development RPM). It also overrides +getpath.c since we don't want it hunting around the filesystem to build +sys.path.

+

In both cases, while one PyInstaller download can be used with any Python +version, you need to have separate installations for each Python version.

+
+
+
+
+

PyInstaller Archives

+
+

Archives Introduction

+

You know what an archive is: a .tar file, a .jar file, a +.zip file. Two kinds of archives are used here. One is equivalent +to a Java .jar file - it allows Python modules to be stored +efficiently and, (with some import hooks) imported directly. This is a +ZlibArchive. The other (a CArchive) is equivalent to a +.zip file - a general way of packing up (and optionally +compressing) arbitrary blobs of data. It gets its name from the fact +that it can be manipulated easily from C, as well as from Python. Both +of these derive from a common base class, making it fairly easy to +create new kinds of archives.

+
+
+

ZlibArchive

+

A ZlibArchive contains compressed .pyc (or .pyo) files. +The Table of Contents is a marshalled dictionary, with the key (the +module's name as given in an import statement) associated with a +seek position and length. Because it is all marshalled Python, +ZlibArchives are completely cross-platform.

+

A ZlibArchive hooks in with iu.py so that, with a little setup, +the archived modules can be imported transparently. Even with +compression at level 9, this works out to being faster than the normal +import. Instead of searching sys.path, there's a lookup in the +dictionary. There's no stat-ing of the .py and .pyc and no +file opens (the file is already open). There's just a seek, a read and +a decompress. A traceback will point to the source file the archive +entry was created from (the __file__ attribute from the time the +.pyc was compiled). On a user's box with no source installed, this +is not terribly useful, but if they send you the traceback, at least +you can make sense of it.

+

ZlibArchiveImage

+
+
+

CArchive

+

A CArchive contains whatever you want to stuff into it. It's very +much like a .zip file. They are easy to create in Python and +unpack from C code. CArchives can be appended to other files (like +ELF and COFF executables, for example). To allow this, they are opened +from the end, so the TOC for a CArchive is at the back, +followed only by a cookie that tells you where the TOC starts and +where the archive itself starts.

+

CArchives can also be embedded within other CArchives. The +inner archive can be opened in place (without extraction).

+

Each TOC entry is variable length. The first field in the entry +tells you the length of the entry. The last field is the name of the +corresponding packed file. The name is null terminated. Compression is +optional by member.

+

There is also a type code associated with each entry. If you're using +a CArchive as a .zip file, you don't need to worry about this. +The type codes are used by the self-extracting executables.

+

CArchiveImage

+
+
+
+

License

+

PyInstaller is mainly distributed under the GPL License but it has +an exception such that you can use it to compile commercial products.

+

In a nutshell, the license is GPL for the source code with the exception that:

+
+
    +
  1. You may use PyInstaller to compile commercial applications out of your +source code.
  2. +
  3. The resulting binaries generated by PyInstaller from your source code can be +shipped with whatever license you want.
  4. +
  5. You may modify PyInstaller for your own needs but these changes to the +PyInstaller source code falls under the terms of the GPL license. In other +words, any modifications to will have to be distributed under GPL.
  6. +
+
+

For updated information or clarification see our +FAQ at PyInstaller home page.

+
+
+

Appendix

+ +
+

Building the bootloaders

+

PyInstaller comes with binary bootloaders for most platforms, shipped +in /your/path/to/pyinstaller//support/loader. If you need to build the bootloader +for your own platform (either because your platform is not officially +supported, or because you tweaked bootloader's source code), you can +follow this guide.

+
+

Development tools

+

On Debian/Ubuntu systems, you can run the following lines to install everything +required:

+
+sudo apt-get install build-essential python-dev
+
+

On Fedora/RHEL and derivates, you can run the following lines:

+
+su
+yum groupinstall "Development Tools"
+yum install python-devel
+
+

On Mac OS X you can get gcc by installing Xcode. It is a suite of tools +for developing software for Mac OS X. It can be also installed from your +Mac OS X Install DVD. It is not necessary to install the version 4 of Xcode.

+

On Solaris and AIX the bootloader is tested with gcc.

+

On Windows you can use MinGW (gcc for Windows) and Visual Studio C++ (msvc) +compilers. Python development libraries are usually installed together with +Python.

+

Note: There is no interdependence between the Visual Studio +version used to compile the bootloader and the Visual Studio version used to +compile Python. The bootloader is a self-contained static executable, +that imposes no restrictions on the version of Python being used. So +you can simply use any Visual Studio version you have around.

+

You can download and install or unpack MinGW distribution from one of the +following locations:

+
    +
  • MinGW - stable and mature, uses gcc 3.4 as its base
  • +
  • MinGW-w64 - more recent, uses gcc 4.4 and up.
  • +
  • TDM-GCC - MinGW and MinGW-w64 installers
  • +
+
+
+

Building

+

On Windows, when using MinGW, it is needed to add PATH_TO_MINGW\bin +to your system PATH. variable. In command prompt before building +bootloader run for example:

+
+set PATH=C:\MinGW\bin;%PATH%
+
+

Change to the /your/path/to/pyinstaller/ source subdirectory. Run:

+
+pyinstaller$ cd source
+pyinstaller/source$ python waf configure build install
+
+

This will produce support/loader/YOUR_OS/run, +support/loader/YOUR_OS/run_d, support/loader/YOUR_OS/runw and +support/loader/YOUR_OS/runw_d, which are the bootloaders.

+

On Windows this will produce in the support/loader/YOUR_OS directory: +run*.exe (bootloader for regular programs), and +inprocsrvr*.dll (bootloader for in-process COM servers).

+

Note: If you have multiple versions of Python, the Python you use to run +waf is the one whose configuration is used.

+

Note: On AIX the bootloader builds with gcc and is tested with gcc 4.2.0 +on AIX 6.1.

+
+
+

Linux Standard Base (LSB) binary

+

By default, the bootloaders on Linux are LSB binaries.

+

LSB is a set of open standards that should increase compatibility among Linux +distributions. PyInstaller is able produce bootloader as LSB binary in order +to increase compatibility for packaged applications among distributions.

+

Note: LSB version 4.0 is required for successfull building of bootloader.

+

On Debian- and Ubuntu-based distros, you can install LSB 4.0 tools by adding +the following repository to the sources.list file:

+
+deb http://ftp.linux-foundation.org/pub/lsb/repositories/debian lsb-4.0 main
+
+

then after having update the apt repository:

+
+sudo apt-get update
+
+

you can install LSB 4.0:

+
+sudo apt-get install lsb lsb-build-cc
+
+

Most other distributions contain only LSB 3.0 in their software +repositories and thus LSB build tools 4.0 must be downloaded by hand. +From Linux Foundation download LSB sdk 4.0 for your architecture.

+

Unpack it by:

+
+tar -xvzf lsb-sdk-4.0.3-1.ia32.tar.gz
+
+

To install it run:

+
+cd lsb-sdk
+./install.sh
+
+

After having installed the LSB tools, you can follow the standard building +instructions.

+

NOTE: if for some reason you want to avoid LSB compilation, you can +do so by specifying --no-lsb on the waf command line, as follows:

+
+pyinstaller/source$ python waf configure --no-lsb build install
+
+

This will also produce support/loader/YOUR_OS/run, +support/loader/YOUR_OS/run_d, support/loader/YOUR_OS/runw and +support/loader/YOUR_OS/runw_d, but they will not be LSB binaries.

+
+
+
+

mf.py: A Modulefinder Replacement

+

Module mf is modelled after iu.

+

It also uses ImportDirectors and Owners to partition the +import name space. Except for the fact that these return Module +instances instead of real module objects, they are identical.

+

Instead of an ImportManager, mf has an ImportTracker +managing things.

+
+

ImportTracker

+

ImportTracker can be called in two ways: analyze_one(name, +importername=None) or analyze_r(name, importername=None). The +second method does what modulefinder does - it recursively finds all +the module names that importing name would cause to appear in +sys.modules. The first method is non-recursive. This is useful, +because it is the only way of answering the question "Who imports +name?" But since it is somewhat unrealistic (very few real imports do +not involve recursion), it deserves some explanation.

+
+
+

analyze_one()

+

When a name is imported, there are structural and dynamic effects. The dynamic +effects are due to the execution of the top-level code in the module (or +modules) that get imported. The structural effects have to do with whether the +import is relative or absolute, and whether the name is a dotted name (if there +are N dots in the name, then N+1 modules will be imported even without any code +running).

+

The analyze_one method determines the structural effects, and defers +the dynamic effects. For example, analyze_one("B.C", "A") could +return ["B", "B.C"] or ["A.B", "A.B.C"] depending on whether +the import turns out to be relative or absolute. In addition, +ImportTracker's modules dict will have Module instances for them.

+
+
+

Module Classes

+

There are Module subclasses for builtins, extensions, packages and (normal) +modules. Besides the normal module object attributes, they have an attribute +imports. For packages and normal modules, imports is a list populated by +scanning the code object (and therefor, the names in this list may be relative +or absolute names - we don't know until they have been analyzed).

+

The highly astute will notice that there is a hole in +analyze_one() here. The first thing that happens when B.C is +being imported is that B is imported and it's top-level code +executed. That top-level code can do various things so that when the +import of B.C finally occurs, something completely different +happens (from what a structural analysis would predict). But mf can +handle this through it's hooks mechanism.

+
+
+

code scanning

+

Like modulefinder, mf scans the byte code of a module, looking for +imports. In addition, mf will pick out a module's __all__ +attribute, if it is built as a list of constant names. This means that +if a package declares an __all__ list as a list of names, +ImportTracker will track those names if asked to analyze +package.*. The code scan also notes the occurance of +__import__, exec and eval, and can issue warnings when +they're found.

+

The code scanning also keeps track (as well as it can) of the context of an +import. It recognizes when imports are found at the top-level, and when they +are found inside definitions (deferred imports). Within that, it also tracks +whether the import is inside a condition (conditional imports).

+
+
+

Hooks

+

In modulefinder, scanning the code takes the place of executing the +code object. ExtensionModules, of course, don't get scanned, so +there need to be a way of recording any imports they do.

+

Please read Listing Hidden Imports for more information.

+

mf goes further and allows a module to be hooked (after it has been +scanned, but before analyze_one is done with it). A hook is a module named +hook-fully.qualified.name in the PyInstaller.hooks package.

+

These modules should have one or more of the following three global +names defined:

+
+
hiddenimports
+

A list of modules names (relative or absolute) the +module imports in some untrackable way.

+

This extends the list of modules to be imported which is created +by scanning the code.

+

Example:

+
+hiddenimports = ['_proxy', 'utils', 'defs']
+
+
+
datas
+

A list of globs of files or directories to bundle as datafiles. For +each glob, a destination directory is specified.

+

Example:

+
+datas = [
+     ('/usr/share/icons/education_*.png', 'icons'),
+     ('/usr/share/libsmi/mibs/*', 'mibs'),
+     ]
+
+

This will copy all iconfiles matching education_*.png into the +subdirectory icons and recursively copy the content of +/usr/share/libsmi/mibs into mibs.

+
+
attrs
+

A list of (name, value) pairs (where value is normally +meaningless).

+

This will set the module-attribute name to value for each +pait in the list. The value is meaningless normally, since the +modules are not executed.

+

This exists mainly so that ImportTracker won't issue spurious +warnings when the rightmost node in a dotted name turns out to be +an attribute in a package, instead of a missing submodule.

+

Example: See PyInstaller/hooks/hook-xml.dom.ext.py.

+
+
hook(mod)
+

A function expecting a Module instance and +returning a Module instance (so it can modify or replace).

+

This exists for things like dynamic modification of a +package's __path__ or perverse situations, like +xml.__init__ replacing itself in sys.modules with +_xmlplus.__init__. (It takes nine hook modules to properly +trace through PyXML-using code, and I can't believe that it's any +easier for the poor programmer using that package).

+

The hook(mod) (if it exists) is called before looking at the +others - that way it can, for example, test sys.version and +adjust what's in hiddenimports.

+
+
+
+
+
+

Advanced Hook Usage

+

Since the hook module is imported like any other +module, you can use any Python code we need. For example for +colletiong additional data or files. See the existing hooks in +PyInstaller/hooks for some examples, esp. the django hooks.

+
+

Warnings

+

ImportTracker has a getwarnings() method that returns all the +warnings accumulated by the instance, and by the Module instances +in its modules dict. Generally, it is ImportTracker who will +accumulate the warnings generated during the structural phase, and +Modules that will get the warnings generated during the code scan.

+

Note that by using a hook module, you can silence some particularly tiresome +warnings, but not all of them.

+
+
+

Cross Reference

+

Once a full analysis (that is, an analyze_r call) has been done, +you can get a cross reference by using getxref(). This returns a +list of tuples. Each tuple is (modulename, importers), where +importers is a list of the (fully qualified) names of the modules +importing modulename. Both the returned list and the importers +list are sorted.

+
+
+

mf Usage

+

A simple example follows:

+
+
+>>> import mf
+>>> a = mf.ImportTracker()
+>>> a.analyze_r("os")
+['os', 'sys', 'posixpath', 'nt', 'stat', 'string', 'strop',
+'re', 'pcre', 'ntpath', 'dospath', 'macpath', 'win32api',
+'UserDict', 'copy', 'types', 'repr', 'tempfile']
+>>> a.analyze_one("os")
+['os']
+>>> a.modules['string'].imports
+[('strop', 0, 0), ('strop.*', 0, 0), ('re', 1, 1)]
+>>>
+
+
+

The tuples in the imports list are (name, delayed, conditional).

+
+
+>>> for w in a.modules['string'].warnings: print w
+...
+W: delayed  eval hack detected at line 359
+W: delayed  eval hack detected at line 389
+W: delayed  eval hack detected at line 418
+>>> for w in a.getwarnings(): print w
+...
+W: no module named pwd (delayed, conditional import by posixpath)
+W: no module named dos (conditional import by os)
+W: no module named os2 (conditional import by os)
+W: no module named posix (conditional import by os)
+W: no module named mac (conditional import by os)
+W: no module named MACFS (delayed, conditional import by tempfile)
+W: no module named macfs (delayed, conditional import by tempfile)
+W: top-level conditional exec statment detected at line 47
+   - os (C:\Program Files\Python\Lib\os.py)
+W: delayed  eval hack detected at line 359
+   - string (C:\Program Files\Python\Lib\string.py)
+W: delayed  eval hack detected at line 389
+   - string (C:\Program Files\Python\Lib\string.py)
+W: delayed  eval hack detected at line 418
+   - string (C:\Program Files\Python\Lib\string.py)
+>>>
+
+
+
+
+
+

iu.py: An imputil Replacement

+

Module iu grows out of the pioneering work that Greg Stein did +with imputil (actually, it includes some verbatim imputil +code, but since Greg didn't copyright it, we won't mention it). Both +modules can take over Python's builtin import and ease writing of at +least certain kinds of import hooks.

+

iu differs from imputil: +* faster +* better emulation of builtin import +* more managable

+

There is an ImportManager which provides the replacement for builtin import +and hides all the semantic complexities of a Python import request from it's +delegates.

+
+

ImportManager

+

ImportManager formalizes the concept of a metapath. This concept implicitly +exists in native Python in that builtins and frozen modules are searched +before sys.path, (on Windows there's also a search of the registry while on +Mac, resources may be searched). This metapath is a list populated with +ImportDirector instances. There are ImportDirector subclasses +for builtins, frozen modules, (on Windows) modules found through the +registry and a PathImportDirector for handling sys.path. For a +top-level import (that is, not an import of a module in a package), +ImportManager tries each director on it's metapath until one +succeeds.

+

ImportManager hides the semantic complexity of an import from the directors. +It's up to the ImportManager to decide if an import is relative or absolute; +to see if the module has already been imported; to keep sys.modules up to +date; to handle the fromlist and return the correct module object.

+
+
+

ImportDirector

+

An ImportDirector just needs to respond to getmod(name) by +returning a module object or None. As you will see, an +ImportDirector can consider name to be atomic - it has no need to +examine name to see if it is dotted.

+

To see how this works, we need to examine the PathImportDirector.

+
+
+

PathImportDirector

+

The PathImportDirector subclass manages a list of names - most +notably, sys.path. To do so, it maintains a shadowpath - a +dictionary mapping the names on its pathlist (eg, sys.path) to +their associated Owners. (It could do this directly, but the +assumption that sys.path is occupied solely by strings seems +ineradicable.) Owners of the appropriate kind are created as +needed (if all your imports are satisfied by the first two elements of +sys.path, the PathImportDirector's shadowpath will only have +two entries).

+
+
+

Owner

+

An Owner is much like an ImportDirector but manages a much +more concrete piece of turf. For example, a DirOwner manages one +directory. Since there are no other officially recognized +filesystem-like namespaces for importing, that's all that's included +in iu, but it's easy to imagine Owners for zip files (and I have +one for my own .pyz archive format) or even URLs.

+

As with ImportDirectors, an Owner just needs to respond to +getmod(name) by returning a module object or None, and it can +consider name to be atomic.

+

So structurally, we have a tree, rooted at the ImportManager. At +the next level, we have a set of ImportDirectors. At least one of +those directors, the PathImportDirector in charge of sys.path, +has another level beneath it, consisting of Owners. This much of +the tree covers the entire top-level import namespace.

+

The rest of the import namespace is covered by treelets, each rooted in a +package module (an __init__.py).

+
+
+

Packages

+

To make this work, Owners need to recognize when a module is a +package. For a DirOwner, this means that name is a subdirectory +which contains an __init__.py. The __init__ module is loaded +and its __path__ is initialized with the subdirectory. Then, a +PathImportDirector is created to manage this __path__. Finally +the new PathImportDirector's getmod is assigned to the +package's __importsub__ function.

+

When a module within the package is imported, the request is routed +(by the ImportManager) diretly to the package's __importsub__. +In a hierarchical namespace (like a filesystem), this means that +__importsub__ (which is really the bound getmod method of a +PathImportDirector instance) needs only the module name, not the +package name or the fully qualified name. And that's exactly what it +gets. (In a flat namespace - like most archives - it is perfectly easy +to route the request back up the package tree to the archive +Owner, qualifying the name at each step.)

+
+
+

Possibilities

+

Let's say we want to import from zip files. So, we subclass Owner. +The __init__ method should take a filename, and raise a +ValueError if the file is not an acceptable .zip file, (when a +new name is encountered on sys.path or a package's __path__, +registered Owners are tried until one accepts the name). The +getmod method would check the zip file's contents and return +None if the name is not found. Otherwise, it would extract the +marshalled code object from the zip, create a new module object and +perform a bit of initialization (12 lines of code all told for my own +archive format, including initializing a pack age with it's +__subimporter__).

+

Once the new Owner class is registered with iu, you can put a +zip file on sys.path. A package could even put a zip file on its +__path__.

+
+
+

Compatibility

+

This code has been tested with the PyXML, mxBase and Win32 packages, +covering over a dozen import hacks from manipulations of __path__ +to replacing a module in sys.modules with a different one. +Emulation of Python's native import is nearly exact, including the +names recorded in sys.modules and module attributes (packages +imported through iu have an extra attribute - __importsub__).

+
+
+

Performance

+

In most cases, iu is slower than builtin import (by 15 to 20%) but +faster than imputil (by 15 to 20%). By inserting archives at the +front of sys.path containing the standard lib and the package +being tested, this can be reduced to 5 to 10% slower (or, on my 1.52 +box, 10% faster!) than builtin import. A bit more can be shaved off by +manipulating the ImportManager's metapath.

+
+
+

Limitations

+

This module makes no attempt to facilitate policy import hacks. It is easy to +implement certain kinds of policies within a particular domain, but +fundamentally iu works by dividing up the import namespace into independent +domains.

+

Quite simply, I think cross-domain import hacks are a very bad idea. As author +of the original package on which PyInstaller is based, McMillan worked with +import hacks for many years. Many of them are highly fragile; they often rely +on undocumented (maybe even accidental) features of implementation. +A cross-domain import hack is not likely to work with PyXML, for example.

+

That rant aside, you can modify ImportManger to implement +different policies. For example, a version that implements three +import primitives: absolute import, relative import and +recursive-relative import. No idea what the Python syntax for those +should be, but __aimport__, __rimport__ and __rrimport__ +were easy to implement.

+
+
+

iu Usage

+

Here's a simple example of using iu as a builtin import replacement.

+
+
+>>> import iu
+>>> iu.ImportManager().install()
+>>>
+>>> import DateTime
+>>> DateTime.__importsub__
+<method PathImportDirector.getmod
+  of PathImportDirector instance at 825900>
+>>>
+
+
+
+
+
+
+ + + diff --git a/pyinstaller/doc/Manual.pdf b/pyinstaller/doc/Manual.pdf new file mode 100644 index 0000000..c745c8e Binary files /dev/null and b/pyinstaller/doc/Manual.pdf differ diff --git a/pyinstaller/doc/images/.svn/entries b/pyinstaller/doc/images/.svn/entries new file mode 100644 index 0000000..734003c --- /dev/null +++ b/pyinstaller/doc/images/.svn/entries @@ -0,0 +1,70 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/doc/images +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +ZlibArchive.png +file + + + +add + + + + + +has-props +has-prop-mods + +CArchive.png +file + + + +add + + + + + +has-props +has-prop-mods + +SE_exe.png +file + + + +add + + + + + +has-props +has-prop-mods + diff --git a/pyinstaller/doc/images/.svn/props/CArchive.png.svn-work b/pyinstaller/doc/images/.svn/props/CArchive.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/pyinstaller/doc/images/.svn/props/CArchive.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/pyinstaller/doc/images/.svn/props/SE_exe.png.svn-work b/pyinstaller/doc/images/.svn/props/SE_exe.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/pyinstaller/doc/images/.svn/props/SE_exe.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/pyinstaller/doc/images/.svn/props/ZlibArchive.png.svn-work b/pyinstaller/doc/images/.svn/props/ZlibArchive.png.svn-work new file mode 100644 index 0000000..5e9587e --- /dev/null +++ b/pyinstaller/doc/images/.svn/props/ZlibArchive.png.svn-work @@ -0,0 +1,5 @@ +K 13 +svn:mime-type +V 24 +application/octet-stream +END diff --git a/pyinstaller/doc/images/CArchive.png b/pyinstaller/doc/images/CArchive.png new file mode 100644 index 0000000..2e06588 Binary files /dev/null and b/pyinstaller/doc/images/CArchive.png differ diff --git a/pyinstaller/doc/images/SE_exe.png b/pyinstaller/doc/images/SE_exe.png new file mode 100644 index 0000000..69ef2d4 Binary files /dev/null and b/pyinstaller/doc/images/SE_exe.png differ diff --git a/pyinstaller/doc/images/ZlibArchive.png b/pyinstaller/doc/images/ZlibArchive.png new file mode 100644 index 0000000..b9fba5c Binary files /dev/null and b/pyinstaller/doc/images/ZlibArchive.png differ diff --git a/pyinstaller/doc/pyi-build.html b/pyinstaller/doc/pyi-build.html new file mode 100644 index 0000000..13c1ed1 --- /dev/null +++ b/pyinstaller/doc/pyi-build.html @@ -0,0 +1,91 @@ + + + + + + +pyi-build + + + + + +
+

pyi-build

+

Build for your PyInstaller project

+ +++ + + + + + + + + + +
Author:Giovanni Bajo
Copyright:2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc.
Version:PyInstaller 2.0
Manual section:1
+
+

SYNOPSIS

+

pyi-build <options> SPECFILE

+
+
+

DESCRIPTION

+

pyi-build builds the project as defined in the specfile.

+

Like with setuptools, by default directories build and dist +will be created. build is a private workspace for caching some +information The generated files will be placed within the dist +subdirectory; that's where the files you are interested in will be +placed.

+

In most cases, this will be all you have to do. If not, see When +things go wrong in the manual and be sure to read the introduction to +Spec Files.

+
+
+

OPTIONS

+ +++ + + + + + + + + + + + + + + + +
+-h, --helpshow this help message and exit
+--buildpath=BUILDPATH
 Buildpath (default: +SPECPATH/build/pyi.TARGET_PLATFORM/SPECNAME)
+-y, --noconfirm
 Remove output directory (default: SPECPATH/dist) +without confirmation
+--upx-dir=UPX_DIR
 Directory containing UPX (default: search in path).
+--log-level=LOGLEVEL
 Log level for Build.py (default: INFO, choose one +of DEBUG, INFO, WARN, ERROR, CRITICAL)
+
+
+

SEE ALSO

+

pyi-makespec(1), The PyInstaller Manual, pyinstaller(1)

+

Project Homepage http://www.pyinstaller.org

+
+
+ + + diff --git a/pyinstaller/doc/pyi-makeCOMServer.html b/pyinstaller/doc/pyi-makeCOMServer.html new file mode 100644 index 0000000..e42a79e --- /dev/null +++ b/pyinstaller/doc/pyi-makeCOMServer.html @@ -0,0 +1,75 @@ + + + + + + +pyi-makeCOMServer + + + + + +
+

pyi-makeCOMServer

+

Windows COM Server support for PyInstaller

+ +++ + + + + + + + + + +
Author:Giovanni Bajo
Copyright:2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc.
Version:PyInstaller 2.0
Manual section:1
+
+

SYNOPSIS

+

pyi-makeCOMServer <options> SCRIPT

+
+
+

DESCRIPTION

+

This will generate a new script drivescript.py and a spec file for +the script.

+

Please see the PyInstaller Manual for more information.

+
+
+

OPTIONS

+ +++ + + + + + + + + + +
+--debugUse the verbose version of the executable.
+--verboseRegister the COM server(s) with the quiet flag off.
+--asciido not include encodings (this is passed through to Makespec).
+--out <dir>Generate the driver script and spec file in dir.
+
+
+

SEE ALSO

+

pyi-makespec(1), The PyInstaller Manual, pyinstaller(1)

+

Project Homepage http://www.pyinstaller.org

+
+
+ + + diff --git a/pyinstaller/doc/pyi-makespec.html b/pyinstaller/doc/pyi-makespec.html new file mode 100644 index 0000000..d0bd063 --- /dev/null +++ b/pyinstaller/doc/pyi-makespec.html @@ -0,0 +1,211 @@ + + + + + + +pyi-makespec + + + + + +
+

pyi-makespec

+

Create a spec file for your PyInstaller project

+ +++ + + + + + + + + + +
Author:Giovanni Bajo
Copyright:2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc.
Version:PyInstaller 2.0
Manual section:1
+
+

SYNOPSIS

+

pyi-makespec <options> SCRIPT [SCRIPT ...]

+
+
+

DESCRIPTION

+

The spec file is the description of what you want PyInstaller to do +with your program. pyi-makespec is a simple wizard to create spec +files that cover basic usages:

+
+py-Makespec [--onefile] yourprogram.py
+
+

By default, pyi-makespec generates a spec file that tells +PyInstaller to create a distribution directory contains the main +executable and the dynamic libraries. The option --onefile +specifies that you want PyInstaller to build a single file with +everything inside.

+

In most cases the specfile generated by pyi-makespec is all you +need. If not, see When things go wrong in the manual and be sure to +read the introduction to Spec Files.

+
+
+

OPTIONS

+
+

General Options

+ +++ + + + + + + +
+-h, --helpshow this help message and exit
+--log-level=LOGLEVEL
 Log level for MakeSpec.py (default: INFO, choose +one of DEBUG, INFO, WARN, ERROR, CRITICAL)
+
+
+

What to generate

+ +++ + + + + + + + + + + + +
+-F, --onefilecreate a single file deployment
+-D, --onedircreate a single directory deployment (default)
+-o DIR, --out=DIR
 generate the spec file in the specified directory +(default: current directory)
+-n NAME, --name=NAME
 name to assign to the project (default: first script's +basename)
+
+ +
+

How to generate

+ +++ + + + + + + + +
+-d, --debuguse the debug (verbose) build of the executable for +packaging. This will make the packaged executable be +more verbose when run.
+-s, --stripstrip the exe and shared libs (don't try this on +Windows)
+-X, --upxuse UPX if available (works differently between +Windows and *nix)
+
+
+

Windows specific options

+ +++ + + + + + + + + + + + + + + + + + + + +
+-c, --console, --nowindowed
 use a console subsystem executable (Windows only) +(default)
+-w, --windowed, --noconsole
 use a Windows subsystem executable (Windows only)
+-v FILE, --version=FILE
 add a version resource from FILE to the exe +(Windows only)
+-i ICON_or_FILE_ID, --icon=ICON_or_FILE_ID
 If file is an .ico file, add the icon to the final +executable. Otherwise, the syntax 'file.exe,id' to +extract the icon with the specified id from file.exe +and add it to the final executable
+-m FILE_or_XML, --manifest=FILE_or_XML
 add manifest FILE or XML to the exe (Windows only)
+-r RESOURCE, --resource=RESOURCE
 

RESOURCE is of format FILE[,TYPE[,NAME[,LANGUAGE]]].

+

Add or update resource of the given type, name and +language from FILE to the final executable. FILE +can be a data file or an exe/dll. For data files, +atleast TYPE and NAME need to be specified, +LANGUAGE defaults to 0 or may be specified as +wildcard * to update all resources of the given +TYPE and NAME. For exe/dll files, all resources +from FILE will be added/updated to the final +executable if TYPE, NAME and LANGUAGE are omitted +or specified as wildcard *. Multiple resources +are allowed, using this option multiple times.

+
+
+
+
+

SEE ALSO

+

pyi-build(1), The PyInstaller Manual, pyinstaller(1)

+

Project Homepage http://www.pyinstaller.org

+
+
+ + + diff --git a/pyinstaller/doc/pyinstaller.html b/pyinstaller/doc/pyinstaller.html new file mode 100644 index 0000000..f61834b --- /dev/null +++ b/pyinstaller/doc/pyinstaller.html @@ -0,0 +1,62 @@ + + + + + + +pyinstaller + + + + + +
+

pyinstaller

+

Configure and build y PyInstaller project in one run

+ +++ + + + + + + + + + +
Author:Giovanni Bajo
Copyright:2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc.
Version:PyInstaller 2.0
Manual section:1
+
+

SYNOPSIS

+

pyinstaller <options> SCRIPT

+
+
+

DESCRIPTION

+

Automatically calls pyi-configure, pyi-makespec and pyi-build in one +run. In most cases, running pyinstaller will be all you have to +do.

+

Please see the PyInstaller Manual for more information.

+
+
+

OPTIONS

+

For now, please use pyinstaller --help to get all options. +Basically these are the same as for pyi-configure, pyi-makespec and +pyi-build together.

+
+
+

SEE ALSO

+

pyi-configure(1), pyi-makespec(1), pyi-build(1), The +PyInstaller Manual, pyinstaller(1)

+

Project Homepage http://www.pyinstaller.org

+
+
+ + + diff --git a/pyinstaller/doc/source/.svn/entries b/pyinstaller/doc/source/.svn/entries new file mode 100644 index 0000000..27f6727 --- /dev/null +++ b/pyinstaller/doc/source/.svn/entries @@ -0,0 +1,91 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/doc/source +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +tools +dir + + + +add + +docutils-man.conf +file + + + +add + +Manual.rst +file + + + +add + +pyi-build.rst +file + + + +add + +pyi-makeCOMServer.rst +file + + + +add + +docutils.conf +file + + + +add + +Makefile +file + + + +add + +pyinstaller.rst +file + + + +add + +pyi-makespec.rst +file + + + +add + diff --git a/pyinstaller/doc/source/Makefile b/pyinstaller/doc/source/Makefile new file mode 100644 index 0000000..8e59f5a --- /dev/null +++ b/pyinstaller/doc/source/Makefile @@ -0,0 +1,74 @@ +# +# Generate PyInstaller documentation +# + +.PHONY: all doc pdf man html clean cleanall cleanlogs + +all: + @echo "make doc ===> To generate html and pdf of the documentation and man pages" + @echo "make html ===> To generate a html of the documentation" + @echo "make pdf ===> To generate a pdf of the documentation" + @echo "make man ===> To generate man pages documentation" + + +doc: html pdf man cleanlogs + @echo "Documentation generated: Please see ../*.html, ../*.pdf and ../*.1 for files" + +manpages := pyinstaller pyi-makespec pyi-build pyi-makeCOMServer + +html: ../Manual.html $(foreach manpage,${manpages},../${manpage}.html) +pdf: ../Manual.pdf +man: $(foreach manpage,${manpages},../${manpage}.1) + +../%.1: %.rst docutils-man.conf docutils.conf _definitions.txt + rst2man.py --config=docutils-man.conf $< $@ + +../%.html: %.rst docutils.conf _definitions.txt + rst2html --config=docutils.conf $< $@ + +Manual.tex: Manual.rst _definitions.txt + tools/rst2newlatex.py --stylesheet-path=stylesheets/latex.tex Manual.rst Manual.tex + +../Manual.pdf: srcdir=$(dir $(abspath $<)) +../Manual.pdf: Manual.rst _definitions.txt + cp _definitions.txt .. + cd ${srcdir}/.. && \ + rst2pdf < ${srcdir}/$< -o ${srcdir}/$@ \ + --header='###Title### - ###Section###' \ + --footer='###Page###' \ + --default-dpi=75 + rm ../_definitions.txt + +_definitions.txt: + # _definitions.txt is overwritten. + echo '.. -*- mode: rst ; ispell-local-dictionary: "american" -*-' > $@ + echo '' >> $@ # Empty line. + # Append other lines. + echo '.. _PyInstaller: http://www.pyinstaller.org' >> $@ + echo '' >> $@ + echo '.. |Homepage| replace:: http://www.pyinstaller.org' >> $@ + echo '.. |PyInstaller| replace:: `PyInstaller`' >> $@ + # run `pyinstaller.py -v` to get the version + echo '.. |Version| replace::' `python ../../pyinstaller.py -v` >> $@ + echo '.. |PyInstallerVersion| replace:: PyInstaller |Version|' >> $@ + echo '.. |install_path| replace:: /your/path/to/pyinstaller/' >> $@ + +clean: + @echo "make cleanall ===> To clean everything" + @echo "make cleanlogs ===> To clean *.log, *.aux, etc. but not the .html or .pdf" + + +cleanall: cleanlogs + rm -f ../*.pdf ../*.html ../*.1 + rm _definitions.txt + @echo "All logs and documentation removed." + + +cleanlogs: + rm -f ../*.aux ../*.out ../*.log + rm -f .log *.log + @echo "All logs and aux removed." + +# +# END OF FILE +# diff --git a/pyinstaller/doc/source/Manual.rst b/pyinstaller/doc/source/Manual.rst new file mode 100644 index 0000000..5382c90 --- /dev/null +++ b/pyinstaller/doc/source/Manual.rst @@ -0,0 +1,2014 @@ +.. -*- mode: rst ; ispell-local-dictionary: "american" -*- + +================== +PyInstaller Manual +================== + +:Version: |PyInstallerVersion| +:Homepage: |Homepage| +:Author: Giovanni Bajo & William Caban (based on Gordon McMillan's manual) +:Contact: rasky@develer.com +:Revision: $Rev$ +:Source URL: $HeadURL$ +:Copyright: This document has been placed in the public domain. + +.. contents:: + + +Requirements +============ + +.. Keep this list in sync with the README.txt + +**Windows** + * Windows XP or newer. + * PyWin32_ + Python extensions for Windows is only necessary for users of Python 2.6+. + +**Linux** + * ldd + - Console application to print the shared libraries required by each program + or shared library. + + * objdump + - Console application to display information from object files. + +**Mac OS X** + * Mac OS X 10.4 (Tiger) or newer (Leopard, Snow Leopard, Lion, Mountain Lion). + +**Solaris** + * ldd + * objdump + +**AIX** + * AIX 6.1 or newer. + Python executables created using PyInstaller on AIX 6.1 should work + on AIX 5.2/5.3. PyInstaller will not work with statically linked Python + libraries which has been encountered in Python 2.2 installations on AIX 5.x. + * ldd + * objdump + + +Installing |PyInstaller| +======================== + +1) Unpack the archive on you path of choice. For the purpose of this + documentation we will assume |install_path|. + + You will be using a couple of scripts in the |install_path| + directory, and these will find everything they need from their own + location. For convenience, keep the paths to these scripts short + (don't install in a deeply nested subdirectory). + + **Please note:** Installer is `not` a Python package, so it + doesn't need to go in site-packages, or have a .pth file. + +2) For Windows (32/64bit), Linux (32/64bit) and Mac OS X (32/64bit) + precompiled boot-loaders are available. So the installation is + complete now. + + For other platforms (Solaris, AIX, etc.), users should first try to build the + boot-loader: + + cd source + python ./waf configure build install + + +Getting Started +=============== + + +For the purpose of this documentation we will assume |PyInstaller| as +installed into |install_path|. + + +Build your project +~~~~~~~~~~~~~~~~~~ + +For building a Windows COM server, please see section `Windows COM +Server Support`_ below. + +In the |install_path| directory, run:: + + python pyinstaller.py [opts] yourprogram.py + +This will create a sub-directory ``your-program`` in the |install_path| +directory. The generated files will be placed within the sub-directory +``your-program/dist``; that's where the files you are interested in +will be placed. A `spec` file called ``your-program.spec`` will be +created in the sub-directory ``your-program``, too. Additionally a +subtracts ``your-program/build`` is created where intermediate build +files are kept. + +If your current working directory is not |install_path|, the +directories ``dist`` and ``build`` and the `spec` file will be +created in the current working directory. Say: the intermediate +directory ``your-program`` will be skipped. + + +If you have already created a `spec` file for your project then in +the |install_path| directory run:: + + python pyinstaller.py [opts] your-program.spec + +If your current working directory is not |install_path|, this works +analogously. + +If everything is working and you are happy with the default settings, +this will be all you have to do. If not, see `Allowed OPTIONS`_, `When +things go wrong`_ and be sure to read the introduction to `Spec +Files`_. + + +Allowed Options +~~~~~~~~~~~~~~~ + +By default, ``pyinstaller.py`` creates a distribution directory containing the main +executable and the dynamic libraries. The option ``--onefile`` (specifies that you want +PyInstaller to build a single file with everything inside. + +The syntax to use ``pyinstaller.py`` is the following:: + + python pyinstaller.py [opts] [ ...] | + +Allowed OPTIONS are: + +-h, --help + show this help message and exit + +-v, --version + show program version + +--upx-dir=UPX_DIR + Directory containing UPX. + +-a, --ascii + do NOT include unicode encodings (default: included if + available) + +--buildpath=BUILDPATH + Buildpath (default: + SPECPATH/build/pyi.TARGET_PLATFORM/SPECNAME) + +-y, --noconfirm + Remove output directory (default: + SPECPATH/dist/SPECNAME) without confirmation + +--log-level=LOGLEVEL Log level (default: INFO, choose one of DEBUG, INFO, + WARN, ERROR, CRITICAL) + +What to generate: + +-F, --onefile + create a single file deployment + +-D, --onedir + create a single directory deployment (default) + +-o DIR, --out=DIR + create the spec file in *directory*. If not specified, and the current + directory is Installer's root directory, an output subdirectory will be + created. Otherwise the current directory is used. + +-n NAME, --name=NAME + optional *name* to assign to the project (from which the spec file name is + generated). If omitted, the basename of the (first) script is used. + +What to bundle, where to search: + +-p DIR, --paths=DIR + set base path for import (like using PYTHONPATH). Multiple directories are + allowed, separating them with the path separator (';' under Windows, ':' + under Linux), or using this option multiple times. + +--hidden-import=MODULENAME + import hidden in the script(s). This option can be + used multiple times. + +--additional-hooks-dir=HOOKSPATH + Additional path to search for hooks. This will go into the extend + the `hookspath`, see `Analysis`_ below. This option may be given + several times. + +How to generate: + +-d, --debug + use the debug (verbose) build of the executable + +-s, --strip + the executable and all shared libraries will be run through strip. Note + that cygwin's strip tends to render normal Win32 dlls unusable. + +--noupx + do not use UPX even if available (works differently + between Windows and \*nix) + +Windows and Mac OS X specific options: + +-c, --console, --nowindowed + use a console subsystem executable (default) + +-w, --windowed, --noconsole + use a windowed subsystem executable, which on Windows + does not open the console when the program is launched. + On Mac OS X it allows running gui applications and also + creates also .app bundle. + **This option is mandatory when freezing an gui application on Mac OS X. + Otherwise the application will not start.**. + +-i FILE.ICO, -i FILE.EXE,ID, -i FILE.ICNS, --icon=FILE.ICO, --icon=FILE.EXE,ID, --icon=FILE.ICNS + If FILE is an .ico file, add the icon to the final + executable. Otherwise, the syntax 'file.exe,id' to + extract the icon with the specified id from file.exe + and add it to the final executable. If FILE is an + .icns file, add the icon to the final .app bundle on + Mac OS X (for Mac not yet implemented) + +Windows specific options: + +--version-file=FILE + add a version resource from FILE to the exe + +-m FILE, -m XML, --manifest=FILE, --manifest=XML + add manifest FILE or XML to the exe + +-r FILE[,TYPE[,NAME[,LANGUAGE]]], --resource=FILE[,TYPE[,NAME[,LANGUAGE]]] + add/update resource of the given type, name and + language from FILE to the final executable. FILE can + be a data file or an exe/dll. For data files, atleast + TYPE and NAME need to be specified, LANGUAGE defaults + to 0 or may be specified as wildcard * to update all + resources of the given TYPE and NAME. For exe/dll + files, all resources from FILE will be added/updated + to the final executable if TYPE, NAME and LANGUAGE are + omitted or specified as wildcard \*.Multiple resources + are allowed, using this option multiple times. + +For building with optimization on (like ``Python -O``), see section +`Building Optimized`_. + + +A spec file for your project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The spec file is the description of what you want |PyInstaller| to do with +your program. By deafult, ``pyinstaller.py`` generates a spec file automatically. +For simple projects, the generated spec file will be probably sufficient. + +For more complex projects, it should be regarded as a template. The spec file is +actually Python code, and modifying it should be ease. See `Spec Files`_ for +details. + +In the root directory of |PyInstaller|, there is a simple wizard to create simple +spec files that cover all basic usages:: + + python utils/Makespec.py [--onefile] yourprogram.py + +Elaborating on Makespec.py, this is the supported command line:: + + python utils/Makespec.py [opts] [ ...] + +Script ``Makespec.py`` shares some options with ``pyinstaller.py``. For allowed options see:: + + python utils/Makespec.py --help + + +Windows COM Server support +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Windows COM support execute:: + + python MakeComServer.py [OPTION] script... + + +This will generate a new script ``drivescript.py`` and a spec file for the script. + +These options are allowed: + +--debug + Use the verbose version of the executable. + +--verbose + Register the COM server(s) with the quiet flag off. + +--ascii + do not include encodings (this is passed through to Makespec). + +--out + Generate the driver script and spec file in dir. + +Now `Build your project`_ on the generated spec file. + +If you have the win32dbg package installed, you can use it with the generated +COM server. In the driver script, set ``debug=1`` in the registration line. + +**Warnings**: the inprocess COM server support will not work when the client +process already has Python loaded. It would be rather tricky to +non-obtrusively hook into an already running Python, but the show-stopper is +that the Python/C API won't let us find out which interpreter instance I should +hook into. (If this is important to you, you might experiment with using +apartment threading, which seems the best possibility to get this to work). To +use a "frozen" COM server from a Python process, you'll have to load it as an +exe:: + + o = win32com.client.Dispatch(progid, + clsctx=pythoncom.CLSCTX_LOCAL_SERVER) + + +MakeCOMServer also assumes that your top level code (registration etc.) is +"normal". If it's not, you will have to edit the generated script. + + +Building Optimized +~~~~~~~~~~~~~~~~~~ + +There are two facets to running optimized: gathering ``.pyo``'s, and setting the +``Py_OptimizeFlag``. Installer will gather ``.pyo``'s if it is run optimized:: + + python -O pyinstaller.py ... + + +The ``Py_OptimizeFlag`` will be set if you use a ``('O','','OPTION')`` in one of +the ``TOCs`` building the ``EXE``:: + + exe = EXE(pyz, + a.scripts + [('O','','OPTION')], + ... + +See `Spec Files`_ for details. + + +A Note on using UPX +~~~~~~~~~~~~~~~~~~~ + +On both Windows and Linux, UPX can give truly startling compression - the days +of fitting something useful on a diskette are not gone forever! Installer has +been tested with many UPX versions without problems. Just get it and install it +on your PATH. + +For Windows, there is a problem of compatibility between UPX and executables +generated by Microsoft Visual Studio .NET 2003 (or the equivalent free +toolkit available for download). This is especially worrisome for users of +Python 2.4+, where most extensions (and Python itself) are compiled with that +compiler. This issue has been fixed in later beta versions of UPX, so you +will need at least UPX 1.92 beta. ``pyinstaller.py`` will check this for you +and complain if you have an older version of UPX and you are using Python 2.4. + +.. sidebar:: UPX and Unix + + Under UNIX, old versions of UPX were not able to expand and execute the + executable in memory, and they were extracting it into a temporary file + in the filesystem, before spawning it. This is no longer valid under Linux, + but the information in this paragraph still needs to be updated. + +For Linux, a bit more discussion is in order. First, UPX is only useful on +executables, not shared libs. Installer accounts for that, but to get the full +benefit, you might rebuild Python with more things statically linked. + +More importantly, when ``run`` finds that its ``sys.argv[0]`` does not contain a path, +it will use ``/proc/pid/exe`` to find itself (if it can). This happens, for +example, when executed by Apache. If it has been upx-ed, this symbolic link +points to the tempfile created by the upx stub and |PyInstaller| will fail (please +see the UPX docs for more information). So for now, at least, you can't use upx +for CGI's executed by Apache. Otherwise, you can ignore the warnings in the UPX +docs, since what PyInstaller opens is the executable Installer created, not the +temporary upx-created executable. + + +Accessing Data Files +~~~~~~~~~~~~~~~~~~~~~~~ + +If your application needs to access data files, e.g configuration +files or icons images, you need some minor changes to you application +and you need to collect the file into distribution directory tree (in +`--onedir` mode) resp. into the executable (in `--onefile` mode). + +Adopt your application +------------------------ + +Instead of:: + + basedir = os.path.dirname(__file__) + +use:: + + if getattr(sys, 'frozen', None): + basedir = sys._MEIPASS + else: + basedir = os.path.dirname(__file__) + +``sys._MEIPASS`` points in `--onedir` mode to the directory containing +the created executable and in `--onefile` mode to the temporary directory +where binaries get extracted. + +Collect your data files +------------------------ + +Collecting the data-files is easy: pass a list of your data files (in +``TOC`` format) to the ``COLLECT``. The ``name`` in the ``(name, path, +'DATA')`` tuple can be a relative path name. + +Then, at runtime, you can use code like this to find the file:: + + os.path.join(basedir, relativename) + +In a ``--onedir`` distribution, the files will listed in the +``COLLECT`` will show up in the distribution directory tree, so you +can simply pack them into your isntaller or distribution archive. + +In a ``--onefile`` distribution, data files are bundled within the +executable and then at runtime extracted into the work directory. This +is done by the C code which is also able to reconstruct directory +trees. + + +How one-file mode works +~~~~~~~~~~~~~~~~~~~~~~~ + +.. sidebar:: Bootloader + + The bootloader (also known as *stub* in literature) is the small program + which starts up your packaged program. Usually, the archive containing the + bytecoded modules of your program is simply appended to it. See + `Self-extracting executables`_ for more details on the process. + +A ``--onefile`` works by packing all the shared libs / dlls into the archive +attached to the bootloader executable (or next to the executable in a non-elf +configuration). When first started, it finds that it needs to extract these +files before it can run "for real". That's because locating and loading a +shared lib or linked-in dll is a system level action, not user-level. With +|PyInstallerVersion| it always uses a temporary directory (``_MEIXXXXX``, +where ``XXXXX`` is a random number to avoid conflicts) in the +user's temp directory. It then executes itself again, setting things up so +the system will be able to load the shared libs / dlls. When execution is +complete, it recursively removes the entire directory it created. + +The temporary directory is exported to the program's environment as +``sys._MEIPASS``. This can be used in case you manually modified +the spec file to tell PyInstaller to add additional files (eg: data files) +within the executable (see also `Accessing Data Files`_). + +This has a number of implications: + +* You can run multiple copies - they won't collide. + +* Running multiple copies will be rather expensive to the system (nothing is + shared). + +* On Windows, using Task Manager to kill the parent process will leave the + directory behind. + +* On \*nix, a kill -9 (or crash) will leave the directory behind. + +* Otherwise, on both platforms, the directory will be recursively deleted. + +* So any files you might create in ``sys._MEIPASS`` will be deleted. + +* The executable can be in a protected or read-only directory. + +**Notes for \*nix users**: Take notice that if the executable does a setuid root, +a determined hacker could possibly (given enough tries) introduce a malicious +lookalike of one of the shared libraries during the hole between when the +library is extracted into the temporary directory and when it gets loaded +by the execvp'd process. So maybe you shouldn't do setuid root programs +using ``--onefile``. **In fact, we do not recomend the use of --onefile +on setuid programs.** + + +.egg files and setuptools +~~~~~~~~~~~~~~~~~~~~~~~~~ +`setuptools`_ is a distutils extensions which provide many benefits, including +the ability to distribute the extension as ``eggs``. Together with the +nifty `easy_install`_ (a tool which automatically locates, downloads and +installs Python extensions), ``eggs`` are becoming more and more +widespread as a way for distributing Python extensions. + +``eggs`` can be either files or directories. An ``egg`` directory is basically +a standard Python package, with some additional metadata that can be used for +advanced `setuptools`_ features like entry-points. An ``egg`` file is simply a +ZIP file, and it works as a package as well because Python 2.3+ is able to +transparently import modules stored within ZIP files. + +|PyInstaller| supports ``eggs`` at a good level. In fact: + +* It is able to follow dependencies within ``eggs`` (both files and directories). + So if your program imports a package shipped in ``egg`` format, and this package + requires additional libraries, |PyInstaller| will correctly include everything + within the generated executable. + +* ``egg-files`` are fully supported. To let everything works (entry-points, + ``pkg_resource`` library, etc.), |PyInstaller| either copy the ``egg-files`` + into the distribution directory (in one-dir mode) or packs them as-is within + the generated executable and unpack them at startup into the temporary directory + (see `How one-file mode works`_). + +* ``egg-directories`` are partially supported. In fact, |PyInstaller| at build + time treat them as regular package. This means that all advanced features requiring + ``egg`` metadatas will not work. + +Improved support for ``eggs`` is planned for a future release of |PyInstaller|. + +.. _`setuptools`: http://peak.telecommunity.com/DevCenter/setuptools +.. _`easy_install`: http://peak.telecommunity.com/DevCenter/EasyInstall + + +Multipackage function +~~~~~~~~~~~~~~~~~~~~~ + +Some applications are made of several different binaries, that might rely on the same +third-party libraries and/or share lots of code. When packaging such applications, it +would be a pity to treat each application binary separately and repackage all its +dependencies, potentially duplicating lots of code and libraries. + +With Pyinstaller, you can use the multipackage feature to create multiple binaries that +might share libraries among themselves: each dependency is packaged only once in one of +the binaries, while the others simply have an "external reference" to it, that tells them +to go finding that dependency in the binary contains it. + +The easiest way to access this function is to simply pass multiple script files to +``pyinstaller.py`` (or ``utils/Makespec.py```). It will generate a spec file that contains +a call to the `MERGE`_ function to basically merge dependencies across the different scripts. + +The order of the scripts on the command line (and within the `MERGE`_ +function) matters: given each library, PyInstaller will package common dependencies on the +leftmost script that first needs that dependency. You might want to tweak the order of +the script files accordingly. + +Notice that the external references between binaries are hard-coded with respect to the +paths on the disk in which they are created in the output directory, and cannot be rearranged: +thus, if you use a one-file deploy, you will need to place all binaries in the same directory +when you install your application. Similarly, if you use one-dir deploy, you will need to +install all the binary directories within the same parent directory. + +There are multipackage examples in the ``buildtests/multipackage`` directory. + + +PyInstaller Utilities +===================== + +ArchiveViewer +~~~~~~~~~~~~~ + +:: + + python utils/ArchiveViewer.py + + +ArchiveViewer lets you examine the contents of any archive build with +|PyInstaller| or executable (PYZ, PKG or exe). Invoke it with the target as the +first arg (It has been set up as a Send-To so it shows on the context menu in +Explorer). The archive can be navigated using these commands: + +O + Open the embedded archive (will prompt if omitted). + +U + Go up one level (go back to viewing the embedding archive). + +X + Extract nm (will prompt if omitted). Prompts for output filename. If none + given, extracted to stdout. + +Q + Quit. + +Futhermore ArchiveViewer has some simple console commands: + +-h, --help + Show help. + +-l, --log + Quick contents log. + +-b, --brief + Print a python evaluable list of contents filenames. + +-r, --recursive + Used with -l or -b, applies recusive behaviour. + + +BinDepend +~~~~~~~~~ + +:: + + python utils/BinDepend.py + +BinDepend will analyze the executable you pass to it, and write to stdout all +its binary dependencies. This is handy to find out which DLLs are required by +an executable or another DLL. This module is used by |PyInstaller| itself to +follow the chain of dependencies of binary extensions and make sure that all +of them get included in the final package. + + +GrabVersion (Windows) +~~~~~~~~~~~~~~~~~~~~~ + +:: + + python utils/GrabVersion.py + + +GrabVersion outputs text which can be eval'ed by ``versionInfo.py`` to reproduce +a version resource. Invoke it with the full path name of a Windows executable +(with a version resource) as the first argument. If you cut & paste (or +redirect to a file), you can then edit the version information. The edited +text file can be used in a ``version = myversion.txt`` option on any executable +in an |PyInstaller| spec file. + +This was done in this way because version resources are rather strange beasts, +and fully understanding them is probably impossible. Some elements are +optional, others required, but you could spend unbounded amounts of time +figuring this out, because it's not well documented. When you view the version +tab on a properties dialog, there's no straightforward relationship between +how the data is displayed and the structure of the resource itself. So the +easiest thing to do is find an executable that displays the kind of +information you want, grab it's resource and edit it. Certainly easier than +the Version resource wizard in VC++. + + +Analyzing Dependencies +~~~~~~~~~~~~~~~~~~~~~~ + +You can interactively track down dependencies, including getting +cross-references by using ``mf.py``, documented in section `mf.py: A modulefinder +Replacement`_ + + +Spec Files +========== + +Introduction +~~~~~~~~~~~~ + +When you run ``utils/Makespec.py`` (documented +in section `A spec file for your project`_), it generates a +spec file for you. In fact, +you can think of ``utils/Makespec.py`` just like a wizard that lets you generate +a standard spec file for most standard usages. But advanced users can +learn to edit spec files to fully customize PyInstaller behaviour to +their needs, giving beyond the standard settings provided by the wizard. + +Spec files are in Python syntax. They are evaluated by pyinstaller.py. A simplistic +spec file might look like this:: + + a = Analysis(['myscript.py']) + pyz = PYZ(a.pure) + exe = EXE(pyz, a.scripts, a.binaries, name="myapp.exe") + +This creates a single file deployment with all binaries (extension modules and +their dependencies) packed into the executable. + +A simplistic single directory deployment might look like this:: + + a = Analysis(['myscript.py']) + pyz = PYZ(a.pure) + exe = EXE(a.scripts, pyz, name="myapp.exe", exclude_binaries=1) + dist = COLLECT(exe, a.binaries, name="dist") + + +Note that neither of these examples are realistic. If you want to +start hacking a spec file, use ``utils/Makespec.py`` to create a basic specfile, +and tweak it (if necessary) from there. + +All of the classes you see above are subclasses of ``Build.Target``. A Target acts +like a rule in a makefile. It knows enough to cache its last inputs and +outputs. If its inputs haven't changed, it can assume its outputs wouldn't +change on recomputation. So a spec file acts much like a makefile, only +rebuilding as much as needs rebuilding. This means, for example, that if you +change an ``EXE`` from ``debug=1`` to ``debug=0``, the rebuild will be nearly +instantaneous. + +The high level view is that an ``Analysis`` takes a list of scripts as input, +and generates three "outputs", held in attributes named ``scripts``, ``pure`` +and ``binaries``. A ``PYZ`` (a ``.pyz`` archive) is built from the modules in +pure. The ``EXE`` is built from the ``PYZ``, the scripts and, in the case of a +single-file deployment, the binaries. In a single-directory deployment, a +directory is built containing a slim executable and the binaries. + +TOC Class (Table of Contents) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before you can do much with a spec file, you need to understand the +``TOC`` (Table Of Contents) class. + +A ``TOC`` appears to be a list of tuples of the form (name, path, typecode). +In fact, it's an ordered set, not a list. A TOC contains no duplicates, where +uniqueness is based on name only. Furthermore, within this constraint, a TOC +preserves order. + +Besides the normal list methods and operations, TOC supports taking differences +and intersections (and note that adding or extending is really equivalent to +union). Furthermore, the operations can take a real list of tuples on the right +hand side. This makes excluding modules quite easy. For a pure Python module:: + + pyz = PYZ(a.pure - [('badmodule', '', '')]) + + +or for an extension module in a single-directory deployment:: + + dist = COLLECT(..., a.binaries - [('badmodule', '', '')], ...) + + +or for a single-file deployment:: + + exe = EXE(..., a.binaries - [('badmodule', '', '')], ...) + +To add files to a TOC, you need to know about the typecodes (or the step using +the TOC won't know what to do with the entry). + ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| **typecode** | **description** | **name** | **path** | ++===============+=======================================================+=======================+===============================+ +| 'EXTENSION' | An extension module. | Python internal name. | Full path name in build. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| 'PYSOURCE' | A script. | Python internal name. | Full path name in build. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| 'PYMODULE' | A pure Python module (including __init__ modules). | Python internal name. | Full path name in build. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| 'PYZ' | A .pyz archive (archive_rt.ZlibArchive). | Runtime name. | Full path name in build. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| 'PKG' | A pkg archive (carchive4.CArchive). | Runtime name. | Full path name in build. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| 'BINARY' | A shared library. | Runtime name. | Full path name in build. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| 'DATA' | Aribitrary files. | Runtime name. | Full path name in build. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ +| 'OPTION' | A runtime runtime option (frozen into the executable).| The option. | Unused. | ++---------------+-------------------------------------------------------+-----------------------+-------------------------------+ + +You can force the include of any file in much the same way you do excludes:: + + collect = COLLECT(a.binaries + + [('readme', '/my/project/readme', 'DATA')], ...) + + +or even:: + + collect = COLLECT(a.binaries, + [('readme', '/my/project/readme', 'DATA')], ...) + + +(that is, you can use a list of tuples in place of a ``TOC`` in most cases). + +There's not much reason to use this technique for ``PYSOURCE``, since an ``Analysis`` +takes a list of scripts as input. For ``PYMODULEs`` and ``EXTENSIONs``, the hook +mechanism discussed here is better because you won't have to remember how you +got it working next time. + +This technique is most useful for data files (see the ``Tree`` class below for a +way to build a ``TOC`` from a directory tree), and for runtime options. The options +the run executables understand are: + ++---------------+-----------------------+-------------------------------+-------------------------------------------------------------------------------------------------------+ +| **Option** | **Description** | **Example** | **Notes** | ++===============+=======================+===============================+=======================================================================================================+ +| v | Verbose imports | ('v', '', 'OPTION') | Same as Python -v ... | ++---------------+-----------------------+-------------------------------+-------------------------------------------------------------------------------------------------------+ +| u | Unbuffered stdio | ('u', '', 'OPTION') | Same as Python -u ... | ++---------------+-----------------------+-------------------------------+-------------------------------------------------------------------------------------------------------+ +| W spec | Warning option | ('W ignore', '', 'OPTION') | Python 2.1+ only. | ++---------------+-----------------------+-------------------------------+-------------------------------------------------------------------------------------------------------+ +| s | Use site.py | ('s', '', 'OPTION') | The opposite of Python's -S flag. Note that site.py must be in the executable's directory to be used. | ++---------------+-----------------------+-------------------------------+-------------------------------------------------------------------------------------------------------+ + +Advanced users should note that by using set differences and intersections, it +becomes possible to factor out common modules, and deploy a project containing +multiple executables with minimal redundancy. You'll need some top level code +in each executable to mount the common ``PYZ``. + +Target Subclasses +~~~~~~~~~~~~~~~~~ + +Analysis +-------- + +:: + + Analysis(scripts, pathex=None, hookspath=None, excludes=None) + + +``scripts`` + a list of scripts specified as file names. + +``pathex`` + an optional list of paths to be searched before sys.path. + +``hiddenimports`` + an optional list of additional (hidden) modules to include. Please + refer to `Listing Hidden Imports`_ for details. + +``hookspath`` + an optional list of additional paths to search for hooks + (hook-modules). Please refer to `Listing Hidden Imports`_ for details. + +``excludes`` + an optional list of module or package names (their Python names, not path + names) that will be ignored (as though they were not found). + +An Analysis has five outputs, all ``TOCs`` accessed as attributes of the ``Analysis``. + +``scripts`` + The scripts you gave Analysis as input, with any runtime hook scripts + prepended. + +``pure`` + The pure Python modules. + +``binaries`` + The extension modules and their dependencies. The secondary dependencies are + filtered. On Windows, a long list of MS dlls are excluded. On Linux/Unix, + any shared lib in ``/lib`` or ``/usr/lib`` is excluded. + +``datas`` + Data-file dependencies. These are data-file that are found to be needed by + modules. They can be anything: plugins, font files, etc. + +``zipfiles`` + The zipfiles dependencies (usually ``egg-files``). + +PYZ +------- + +:: + + PYZ(toc, name=None, level=9) + + +``toc`` + a ``TOC``, normally an ``Analysis.pure``. + +``name`` + A filename for the ``.pyz``. Normally not needed, as the generated name will do fine. + +``level`` + The Zlib compression level to use. If 0, the zlib module is not required. + + +PKG +------- + +Generally, you will not need to create your own ``PKGs``, as the ``EXE`` will do it for +you. This is one way to include read-only data in a single-file deployment, +however. + +:: + + PKG(toc, name=None, cdict=None, exclude_binaries=0) + + +``toc`` + a ``TOC``. + +``name`` + a filename for the ``PKG`` (optional). + +``cdict`` + a dictionary that specifies compression by typecode. For example, ``PYZ`` is + left uncompressed so that it can be accessed inside the ``PKG``. The default + uses sensible values. If zlib is not available, no compression is used. + +``exclude_binaries`` + If 1, ``EXTENSIONs`` and ``BINARYs`` will be left out of the ``PKG``, and + forwarded to its container (usually a ``COLLECT``). + +EXE +------- + +:: + + EXE(*args, **kws) + + +``args`` + One or more arguments which are either ``TOCs`` or ``Targets``. + +``kws`` + Possible keyword arguments: + + ``console`` + Always 1 on Linux/unix. On Windows, governs whether to use the console + executable, or the Windows subsystem executable. + + ``debug`` + Setting to 1 gives you progress messages from the executable (for a + ``console=0``, these will be annoying MessageBoxes). + + ``name`` + The filename for the executable. + + ``exclude_binaries`` + Forwarded to the ``PKG`` the ``EXE`` builds. + + ``icon`` + Windows NT family only. ``icon='myicon.ico'`` to use an icon file, or + ``icon='notepad.exe,0'`` to grab an icon resource. + + ``version`` + Windows NT family only. ``version='myversion.txt'``. Use ``GrabVersion.py`` to + steal a version resource from an executable, and then edit the ouput to + create your own. (The syntax of version resources is so arcane that I + wouldn't attempt to write one from scratch.) + + ``append_pkg`` + If ``True``, then append the PKG archive to the EXE. If ``False``, + place the PKG archive in a separate file ``exename.pkg``. + The default depends + on whether Make.py was given the ``-n`` argument + when building the loader. The default is ``True`` on Windows. + On non-ELF platforms where concatenating arbitrary data to + an executable does not work, ``append_pkg`` must be set to ``False``. + + +DLL +------- + +On Windows, this provides support for doing in-process COM servers. It is not +generalized. However, embedders can follow the same model to build a special +purpose DLL so the Python support in their app is hidden. You will need to +write your own dll, but thanks to Allan Green for refactoring the C code and +making that a managable task. + +COLLECT +------- + +:: + + COLLECT(*args, **kws) + + +``args`` + One or more arguments which are either ``TOCs`` or ``Targets``. + +``kws`` + Possible keyword arguments: + + ``name`` + The name of the directory to be built. + +Tree +------- + +:: + + Tree(root, prefix=None, excludes=None) + + +``root`` + The root of the tree (on the build system). + +``prefix`` + Optional prefix to the names on the target system. + +``excludes`` + A list of names to exclude. Two forms are allowed: + + ``name`` + files with this basename will be excluded (do not include the path). + + ``*.ext`` + any file with the given extension will be excluded. + +Since a ``Tree`` is a ``TOC``, you can also use the exclude technique described above +in the section on ``TOCs``. + + +MERGE +------- + +With the MERGE function we can create a group of interdependent packages. + +:: + + MERGE(*args) + + +``*args`` + This is a list of tuples. The first element of the tuple is an analysis object, + the second one is the script name without extension and the third one is the final name. + + +The ``MERGE`` function filters the analysis to avoid duplication of libraries and modules. +As a result the packages generated will be connected. Furthermore, to ensure the consistency +of dependencies, it replaces the temporary names with the actual names. +MERGE is used after the analysis phase and before ``EXE`` and ``COLLECT``. + +Here is spec file example with ``MERGE`` function:: + + ## Where the package folders will be built, and the shortcuts will reside + TargetDir = os.path.abspath(os.path.join('..','..','Client','Programs')) + + ## The application names + AppNames = [d for d in os.listdir(os.getcwd()) + if os.path.isdir(d) + and d[0]!='.' + and d[0:6]!='Common' + and d != 'build' + and d != 'dummy'] + + ## Build MERGE arguments (analysis object, script base name, final exe path) + # Start with the dummy package + Analyses = [(Analysis([os.path.join('dummy','dummy.py')]), + 'dummy', os.path.join('dummy','dummy.exe')) + ] + + # NOTE: this assumes that the main script in each is appname.pyw in the appname folder + Analyses += [(Analysis([os.path.join(appname, appname + '.pyw')]), + appname, os.path.join(appname,appname+'.exe')) + for appname in AppNames] + + ## Merge all the dependencies + MERGE(*Analyses) + + ## Build each app + for anal, basename, exename in Analyses: + pyz = PYZ(anal.pure) + exe = EXE(pyz, + anal.scripts, + anal.dependencies, + exclude_binaries=1, + name=exename, + version='FalconVersion.txt', + debug=False, + strip=False, + upx=True, + console=False ) + dist = COLLECT(exe, + anal.binaries, + anal.zipfiles, + anal.datas, + strip=False, + ###upx=True if (basename == 'dummy') else False, + upx=False, + name=os.path.join(TargetDir,basename)) + + +When Things Go Wrong +==================== + +Recipes and Instructions for special Modules +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Code examples for some modules needing special care and some common +issues are available on our Recipe_ web-page. + + +Finding out What Went Wrong +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Buildtime Warnings +------------------ + +When an ``Analysis`` step runs, it produces a warnings file (named ``warnproject.txt``) +in the spec file's directory. Generally, most of these warnings are harmless. +For example, ``os.py`` (which is cross-platform) works by figuring out what +platform it is on, then importing (and rebinding names from) the appropriate +platform-specific module. So analyzing ``os.py`` will produce a set of warnings +like:: + + WARNING: no module named dos (conditional import by os) + WARNING: no module named ce (conditional import by os) + WARNING: no module named os2 (conditional import by os) + + +Note that the analysis has detected that the import is within a conditional +block (an if statement). The analysis also detects if an import within a +function or class, (delayed) or at the top level. A top-level, non-conditional +import failure is really a hard error. There's at least a reasonable chance +that conditional and / or delayed import will be handled gracefully at runtime. + +Ignorable warnings may also be produced when a class or function is declared in +a package (an ``__init__.py`` module), and the import specifies +``package.name``. In this case, the analysis can't tell if name is supposed to +refer to a submodule of package. + +Warnings are also produced when an ``__import__``, ``exec`` or ``eval`` statement is +encountered. The ``__import__`` warnings should almost certainly be investigated. +Both ``exec`` and ``eval`` can be used to implement import hacks, but usually their use +is more benign. + +Any problem detected here can be handled by hooking the analysis of the module. +See `Listing Hidden Imports`_ below for how to do it. + +Getting Debug Messages +---------------------- + +Debug messages for PyInstaller can be enabled by passing the ``--log-level`` +flag to the ``pyinstaller.py`` script:: + + pyinstaller.py --log-level=DEBUG + +Setting ``debug=1`` on an ``EXE`` will cause the executable to put out progress +messages (for console apps, these go to stdout; for Windows apps, these show as +MessageBoxes). This can be useful if you are doing complex packaging, or your +app doesn't seem to be starting, or just to learn how the runtime works. + +Getting Python's Verbose Imports +-------------------------------- + +You can also pass a ``-v`` (verbose imports) flag to the embedded Python. This can +be extremely useful. I usually try it even on apparently working apps, just to +make sure that I'm always getting my copies of the modules and no import has +leaked out to the installed Python. + +You set this (like the other runtime options) by feeding a phone ``TOC`` entry to +the ``EXE``. The easiest way to do this is to change the ``EXE`` from:: + + EXE(..., anal.scripts, ....) + +to:: + + EXE(..., anal.scripts + [('v', '', 'OPTION')], ...) + +These messages will always go to ``stdout``, so you won't see them on Windows if +``console=0``. + +Helping PyInstaller Find Modules +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Extending the Path +------------------ + +When the analysis phase cannot find needed modules, it may be that the code is +manipulating ``sys.path``. The easiest thing to do in this case is tell ``Analysis`` +about the new directory through the second arg to the constructor:: + + anal = Analysis(['somedir/myscript.py'], + ['path/to/thisdir', 'path/to/thatdir']) + + +In this case, the ``Analysis`` will have a search path:: + + ['somedir', 'path/to/thisdir', 'path/to/thatdir'] + sys.path + + +You can do the same when running ``utils/Makespec.py`` or ``pyinstaller.py``:: + + utils/Makespec.py --paths=path/to/thisdir;path/to/thatdir ... + pyinstaller.py --paths=path/to/thisdir;path/to/thatdir ... + + +(on \*nix, use ``:`` as the path separator). + +Listing Hidden Imports +---------------------- + +Hidden imports are fairly common. These can occur when the code is using +``__import__`` (or, perhaps ``exec`` or ``eval``), in which case you will see a warning in +the ``warnproject.txt`` file. They can also occur when an extension module uses the +Python/C API to do an import, in which case Analysis can't detect +anything. + +You +can verify that hidden import is the problem by using Python's verbose imports +flag. If the import messages say "module not found", but the ``warnproject.txt`` +file has no "no module named..." message for the same module, then the problem +is a hidden import. + +.. sidebar:: Standard hidden imports are already included! + + If you are getting worried while reading this paragraph, do not worry: + having hidden imports is the exception, not the norm! And anyway, + PyInstaller already ships with a large set of hooks that take care of + hidden imports for the most common packages out there. For instance, + PIL_, PyWin32_, PyQt_ are already taken care of. + +Hidden imports are handled by hooking the module (the one doing the +hidden imports) at ``Analysis`` time. Do this as follows: + + 1. Create a file named ``hook-module.py`` (where `module` is the + fully-qualified Python name, eg, ``hook-xml.dom.py``) and place it + somewhere. Remember the place as `your private hooks directory`. + + 2. In the .spec file, pass `your private hooks directory` as + ``hookspath`` argument to ``Analysis`` so will be searched. + Example:: + + a = Analysis(['myscript.py'], hookspath='/my/priv/hooks') + +In most cases the hook module will have only one line:: + + hiddenimports = ['module1', 'module2'] + +When the ``Analysis`` finds this file, it will proceed exactly as +though the module explicitly imported ``module1`` and ``module2``. + +If you successfully hook a publicly distributed module in this way, +please send us the hook so we can make it available to others. + +You may want to have a look at already existing hooks in the +``PyInstaller.hooks`` package under |PyInstaller|'s root directory. +For full details on the analysis-time hook mechanism is in the `Hooks`_ +section. + + + +Extending a Package's ``__path__`` +---------------------------------- + +Python allows a package to extend the search path used to find modules and +sub-packages through the ``__path__`` mechanism. Normally, a package's ``__path__`` has +only one entry - the directory in which the ``__init__.py`` was found. But +``__init__.py`` is free to extend its ``__path__`` to include other directories. For +example, the ``win32com.shell.shell`` module actually resolves to +``win32com/win32comext/shell/shell.pyd``. This is because ``win32com/__init__.py`` +appends ``../win32comext`` to its ``__path__``. + +Because the ``__init__.py`` is not actually run during an analysis, we use the same +hook mechanism we use for hidden imports. A static list of names won't do, +however, because the new entry on ``__path__`` may well require computation. So +``hook-module.py`` should define a method ``hook(mod)``. The mod argument is an +instance of ``mf.Module`` which has (more or less) the same attributes as a real +module object. The hook function should return a ``mf.Module`` instance - perhaps +a brand new one, but more likely the same one used as an arg, but mutated. +See `mf.py: A Modulefinder Replacement`_ for details, and `PyInstaller\/hooks\/hook-win32com.py`_ +for an example. + +Note that manipulations of ``__path__`` hooked in this way apply to the analysis, +and only the analysis. That is, at runtime ``win32com.shell`` is resolved the same +way as ``win32com.anythingelse``, and ``win32com.__path__`` knows nothing of ``../win32comext``. + +Once in awhile, that's not enough. + +Changing Runtime Behavior +------------------------- + +More bizarre situations can be accomodated with runtime hooks. These are small +scripts that manipulate the environment before your main script runs, +effectively providing additional top-level code to your script. + +At the tail end of an analysis, the module list is examined for matches in +``support/rthooks.dat``, which is the string representation of a +Python dictionary. The key is the module name, and the value is a list +of hook-script pathnames. + +So putting an entry:: + + 'somemodule': ['path/to/somescript.py'], + +into ``support/rthooks.dat`` is almost the same thing as doing this:: + + anal = Analysis(['path/to/somescript.py', 'main.py'], ... + + +except that in using the hook, ``path/to/somescript.py`` will not be analyzed, +(that's not a feature - we just haven't found a sane way fit the recursion into +my persistence scheme). + +Hooks done in this way, while they need to be careful of what they import, are +free to do almost anything. One provided hook sets things up so that win32com +can generate modules at runtime (to disk), and the generated modules can be +found in the win32com package. + +Adapting to being "frozen" +-------------------------- + +In most sophisticated apps, it becomes necessary to figure out (at runtime) +whether you're running "live" or "frozen". For example, you might have a +configuration file that (running "live") you locate based on a module's +``__file__`` attribute. That won't work once the code is packaged up. You'll +probably want to look for it based on ``sys.executable`` instead. + +The bootloaders set ``sys.frozen=1`` (and, for in-process COM servers, the +embedding DLL sets ``sys.frozen='dll'``). + +For really advanced users, you can access the ``iu.ImportManager`` as +``sys.importManager``. See `iu.py`_ for how you might make use of this fact. + + +Miscellaneous +============= + +Self-extracting executables +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ELF executable format (Windows, Linux and some others) allows arbitrary +data to be concatenated to the end of the executable without disturbing its +functionality. For this reason, a ``CArchive``'s Table of Contents is +at the end of the archive. The executable can open itself as a binary +file name, seek to the end and 'open' the ``CArchive`` (see figure 3). + +On other platforms, the archive and the executable are separate, but the +archive is named ``executable.pkg``, and expected to be in the same directory. +Other than that, the process is the same. + +One Pass Execution +------------------ + +In a single directory deployment (``--onedir``, which is the default), +all of the binaries are already in the file system. In that case, the +embedding app: + +* opens the archive + +* starts Python (on Windows, this is done with dynamic loading so one embedding + app binary can be used with any Python version) + +* imports all the modules which are at the top level of the archive (basically, + bootstraps the import hooks) + +* mounts the ``ZlibArchive(s)`` in the outer archive + +* runs all the scripts which are at the top level of the archive + +* finalizes Python + +Two Pass Execution +------------------ + +There are a couple situations which require two passes: + +* a ``--onefile`` deployment (on Windows, the files can't be cleaned + up afterwards because Python does not call ``FreeLibrary``; on other + platforms, Python won't find them if they're extracted in the same + process that uses them) + +* ``LD_LIBRARY_PATH`` needs to be set to find the binaries (not + extension modules, but modules the extensions are linked to). + +The first pass: + +* opens the archive + +* extracts all the binaries in the archive (in |PyInstallerVersion|, + this is always to a temporary directory). + +* sets a magic environment variable + +* sets ``LD_LIBRARY_PATH`` (non-Windows) + +* executes itself as a child process (letting the child use his stdin, stdout + and stderr) + +* waits for the child to exit (on \*nix, the child actually replaces the parent) + +* cleans up the extracted binaries (so on \*nix, this is done by the child) + +The child process executes as in `One Pass Execution`_ above (the magic +environment variable is what tells it that this is pass two). + +|SE_exeImage| figure 3 - Self Extracting Executable + +There are, of course, quite a few differences between the Windows and +Unix/Linux versions. The major one is that because all of Python on Windows is +in ``pythonXX.dll``, and dynamic loading is so simple-minded, that one +binary can be use with any version of Python. There's much in common, +though, and that C code can be found in `source/common/launch.c`_. + +The Unix/Linux build process (which you need to run just once for any version +of Python) makes use of the config information in your install (if you +installed from RPM, you need the Python-development RPM). It also overrides +``getpath.c`` since we don't want it hunting around the filesystem to build +``sys.path``. + +In both cases, while one |PyInstaller| download can be used with any Python +version, you need to have separate installations for each Python version. + +PyInstaller Archives +==================== + +Archives Introduction +~~~~~~~~~~~~~~~~~~~~~ + +You know what an archive is: a ``.tar`` file, a ``.jar`` file, a +``.zip`` file. Two kinds of archives are used here. One is equivalent +to a Java ``.jar`` file - it allows Python modules to be stored +efficiently and, (with some import hooks) imported directly. This is a +``ZlibArchive``. The other (a ``CArchive``) is equivalent to a +``.zip`` file - a general way of packing up (and optionally +compressing) arbitrary blobs of data. It gets its name from the fact +that it can be manipulated easily from C, as well as from Python. Both +of these derive from a common base class, making it fairly easy to +create new kinds of archives. + +``ZlibArchive`` +~~~~~~~~~~~~~~~ + +A ``ZlibArchive`` contains compressed ``.pyc`` (or ``.pyo``) files. +The Table of Contents is a marshalled dictionary, with the key (the +module's name as given in an ``import`` statement) associated with a +seek position and length. Because it is all marshalled Python, +``ZlibArchives`` are completely cross-platform. + +A ``ZlibArchive`` hooks in with `iu.py`_ so that, with a little setup, +the archived modules can be imported transparently. Even with +compression at level 9, this works out to being faster than the normal +import. Instead of searching ``sys.path``, there's a lookup in the +dictionary. There's no ``stat``-ing of the ``.py`` and ``.pyc`` and no +file opens (the file is already open). There's just a seek, a read and +a decompress. A traceback will point to the source file the archive +entry was created from (the ``__file__`` attribute from the time the +``.pyc`` was compiled). On a user's box with no source installed, this +is not terribly useful, but if they send you the traceback, at least +you can make sense of it. + +|ZlibArchiveImage| + +``CArchive`` +~~~~~~~~~~~~ + +A ``CArchive`` contains whatever you want to stuff into it. It's very +much like a ``.zip`` file. They are easy to create in Python and +unpack from C code. ``CArchives`` can be appended to other files (like +ELF and COFF executables, for example). To allow this, they are opened +from the end, so the ``TOC`` for a ``CArchive`` is at the back, +followed only by a cookie that tells you where the ``TOC`` starts and +where the archive itself starts. + +``CArchives`` can also be embedded within other ``CArchives``. The +inner archive can be opened in place (without extraction). + +Each ``TOC`` entry is variable length. The first field in the entry +tells you the length of the entry. The last field is the name of the +corresponding packed file. The name is null terminated. Compression is +optional by member. + +There is also a type code associated with each entry. If you're using +a ``CArchive`` as a ``.zip`` file, you don't need to worry about this. +The type codes are used by the self-extracting executables. + +|CArchiveImage| + +License +======= + +PyInstaller is mainly distributed under the `GPL License`_ but it has +an exception such that you can use it to compile commercial products. + +In a nutshell, the license is GPL for the source code with the exception that: + + #. You may use PyInstaller to compile commercial applications out of your + source code. + + #. The resulting binaries generated by PyInstaller from your source code can be + shipped with whatever license you want. + + #. You may modify PyInstaller for your own needs but *these* changes to the + PyInstaller source code falls under the terms of the GPL license. In other + words, any modifications to will *have* to be distributed under GPL. + +For updated information or clarification see our +`FAQ`_ at `PyInstaller`_ home page. + + +Appendix +======== + +.. sidebar:: You can stop reading here... + + ... if you are not interested in technical details. This appendix + contains insights of the internal workings of |PyInstaller|, and + you do not need this information unless you plan to work on + |PyInstaller| itself. + + +Building the bootloaders +~~~~~~~~~~~~~~~~~~~~~~~~ + +PyInstaller comes with binary bootloaders for most platforms, shipped +in |install_path|/support/loader. If you need to build the bootloader +for your own platform (either because your platform is not officially +supported, or because you tweaked bootloader's source code), you can +follow this guide. + +Development tools +----------------- + +On Debian/Ubuntu systems, you can run the following lines to install everything +required:: + + sudo apt-get install build-essential python-dev + +On Fedora/RHEL and derivates, you can run the following lines:: + + su + yum groupinstall "Development Tools" + yum install python-devel + +On Mac OS X you can get gcc by installing Xcode_. It is a suite of tools +for developing software for Mac OS X. It can be also installed from your +``Mac OS X Install DVD``. It is not necessary to install the version 4 of Xcode. + +On Solaris and AIX the bootloader is tested with gcc. + +On Windows you can use MinGW (gcc for Windows) and Visual Studio C++ (msvc) +compilers. Python development libraries are usually installed together with +Python. + +*Note:* There is no interdependence between the Visual Studio +version used to compile the bootloader and the Visual Studio version used to +compile Python. The bootloader is a self-contained static executable, +that imposes no restrictions on the version of Python being used. So +you can simply use any Visual Studio version you have around. + +You can download and install or unpack MinGW distribution from one of the +following locations: + +* `MinGW`_ - stable and mature, uses gcc 3.4 as its base + +* `MinGW-w64`_ - more recent, uses gcc 4.4 and up. + +* `TDM-GCC`_ - MinGW and MinGW-w64 installers + + +Building +-------- + +On Windows, when using MinGW, it is needed to add ``PATH_TO_MINGW\bin`` +to your system ``PATH``. variable. In command prompt before building +bootloader run for example:: + + set PATH=C:\MinGW\bin;%PATH% + +Change to the |install_path| ``source`` subdirectory. Run:: + + pyinstaller$ cd source + pyinstaller/source$ python waf configure build install + +This will produce ``support/loader/YOUR_OS/run``, +``support/loader/YOUR_OS/run_d``, ``support/loader/YOUR_OS/runw`` and +``support/loader/YOUR_OS/runw_d``, which are the bootloaders. + +On Windows this will produce in the ``support/loader/YOUR_OS`` directory: +``run*.exe`` (bootloader for regular programs), and +``inprocsrvr*.dll`` (bootloader for in-process COM servers). + +*Note:* If you have multiple versions of Python, the Python you use to run +``waf`` is the one whose configuration is used. + +*Note:* On AIX the bootloader builds with gcc and is tested with gcc 4.2.0 +on AIX 6.1. + +Linux Standard Base (LSB) binary +-------------------------------- + +By default, the bootloaders on Linux are LSB binaries. + +LSB is a set of open standards that should increase compatibility among Linux +distributions. |PyInstaller| is able produce bootloader as LSB binary in order +to increase compatibility for packaged applications among distributions. + +*Note:* LSB version 4.0 is required for successfull building of bootloader. + +On Debian- and Ubuntu-based distros, you can install LSB 4.0 tools by adding +the following repository to the sources.list file:: + + deb http://ftp.linux-foundation.org/pub/lsb/repositories/debian lsb-4.0 main + +then after having update the apt repository:: + + sudo apt-get update + +you can install LSB 4.0:: + + sudo apt-get install lsb lsb-build-cc + +Most other distributions contain only LSB 3.0 in their software +repositories and thus LSB build tools 4.0 must be downloaded by hand. +From Linux Foundation download `LSB sdk 4.0`_ for your architecture. + +Unpack it by:: + + tar -xvzf lsb-sdk-4.0.3-1.ia32.tar.gz + +To install it run:: + + cd lsb-sdk + ./install.sh + + +After having installed the LSB tools, you can follow the standard building +instructions. + +*NOTE:* if for some reason you want to avoid LSB compilation, you can +do so by specifying --no-lsb on the waf command line, as follows:: + + pyinstaller/source$ python waf configure --no-lsb build install + +This will also produce ``support/loader/YOUR_OS/run``, +``support/loader/YOUR_OS/run_d``, ``support/loader/YOUR_OS/runw`` and +``support/loader/YOUR_OS/runw_d``, but they will not be LSB binaries. + + +``mf.py``: A Modulefinder Replacement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Module ``mf`` is modelled after ``iu``. + +It also uses ``ImportDirectors`` and ``Owners`` to partition the +import name space. Except for the fact that these return ``Module`` +instances instead of real module objects, they are identical. + +Instead of an ``ImportManager``, ``mf`` has an ``ImportTracker`` +managing things. + +ImportTracker +------------- + +``ImportTracker`` can be called in two ways: ``analyze_one(name, +importername=None)`` or ``analyze_r(name, importername=None)``. The +second method does what modulefinder does - it recursively finds all +the module names that importing name would cause to appear in +``sys.modules``. The first method is non-recursive. This is useful, +because it is the only way of answering the question "Who imports +name?" But since it is somewhat unrealistic (very few real imports do +not involve recursion), it deserves some explanation. + +``analyze_one()`` +----------------- + +When a name is imported, there are structural and dynamic effects. The dynamic +effects are due to the execution of the top-level code in the module (or +modules) that get imported. The structural effects have to do with whether the +import is relative or absolute, and whether the name is a dotted name (if there +are N dots in the name, then N+1 modules will be imported even without any code +running). + +The analyze_one method determines the structural effects, and defers +the dynamic effects. For example, ``analyze_one("B.C", "A")`` could +return ``["B", "B.C"]`` or ``["A.B", "A.B.C"]`` depending on whether +the import turns out to be relative or absolute. In addition, +ImportTracker's modules dict will have Module instances for them. + +Module Classes +-------------- + +There are Module subclasses for builtins, extensions, packages and (normal) +modules. Besides the normal module object attributes, they have an attribute +imports. For packages and normal modules, imports is a list populated by +scanning the code object (and therefor, the names in this list may be relative +or absolute names - we don't know until they have been analyzed). + +The highly astute will notice that there is a hole in +``analyze_one()`` here. The first thing that happens when ``B.C`` is +being imported is that ``B`` is imported and it's top-level code +executed. That top-level code can do various things so that when the +import of ``B.C`` finally occurs, something completely different +happens (from what a structural analysis would predict). But mf can +handle this through it's hooks mechanism. + +code scanning +------------- + +Like modulefinder, ``mf`` scans the byte code of a module, looking for +imports. In addition, ``mf`` will pick out a module's ``__all__`` +attribute, if it is built as a list of constant names. This means that +if a package declares an ``__all__`` list as a list of names, +ImportTracker will track those names if asked to analyze +``package.*``. The code scan also notes the occurance of +``__import__``, ``exec`` and ``eval``, and can issue warnings when +they're found. + +The code scanning also keeps track (as well as it can) of the context of an +import. It recognizes when imports are found at the top-level, and when they +are found inside definitions (deferred imports). Within that, it also tracks +whether the import is inside a condition (conditional imports). + +Hooks +------- + +In modulefinder, scanning the code takes the place of executing the +code object. ``ExtensionModules``, of course, don't get scanned, so +there need to be a way of recording any imports they do. + +Please read `Listing Hidden Imports`_ for more information. + +``mf`` goes further and allows a module to be hooked (after it has been +scanned, but before analyze_one is done with it). A hook is a module named +``hook-fully.qualified.name`` in the ``PyInstaller.hooks`` package. + +These modules should have one or more of the following three global +names defined: + + +``hiddenimports`` + A list of modules names (relative or absolute) the + module imports in some untrackable way. + + This extends the list of modules to be imported which is created + by scanning the code. + + Example:: + + hiddenimports = ['_proxy', 'utils', 'defs'] + +``datas`` + A list of globs of files or directories to bundle as datafiles. For + each glob, a destination directory is specified. + + Example:: + + datas = [ + ('/usr/share/icons/education_*.png', 'icons'), + ('/usr/share/libsmi/mibs/*', 'mibs'), + ] + + This will copy all iconfiles matching `education_*.png` into the + subdirectory `icons` and recursively copy the content of + `/usr/share/libsmi/mibs` into `mibs`. + +``attrs`` + A list of ``(name, value)`` pairs (where value is normally + meaningless). + + This will set the module-attribute ``name`` to ``value`` for each + pait in the list. The value is meaningless normally, since the + modules are not executed. + + This exists mainly so that ImportTracker won't issue spurious + warnings when the rightmost node in a dotted name turns out to be + an attribute in a package, instead of a missing submodule. + + Example: See ``PyInstaller/hooks/hook-xml.dom.ext.py``. + + +``hook(mod)`` + A function expecting a ``Module`` instance and + returning a ``Module`` instance (so it can modify or replace). + + This exists for things like dynamic modification of a + package's ``__path__`` or perverse situations, like + ``xml.__init__`` replacing itself in ``sys.modules`` with + ``_xmlplus.__init__``. (It takes nine hook modules to properly + trace through PyXML-using code, and I can't believe that it's any + easier for the poor programmer using that package). + + The ``hook(mod)`` (if it exists) is called before looking at the + others - that way it can, for example, test ``sys.version`` and + adjust what's in ``hiddenimports``. + + +Advanced Hook Usage +~~~~~~~~~~~~~~~~~~~ + +Since the hook module is imported like any other +module, you can use any Python code we need. For example for +colletiong additional data or files. See the existing hooks in +``PyInstaller/hooks`` for some examples, esp. the ``django`` hooks. + +Warnings +-------- + +``ImportTracker`` has a ``getwarnings()`` method that returns all the +warnings accumulated by the instance, and by the ``Module`` instances +in its modules dict. Generally, it is ``ImportTracker`` who will +accumulate the warnings generated during the structural phase, and +``Modules`` that will get the warnings generated during the code scan. + +Note that by using a hook module, you can silence some particularly tiresome +warnings, but not all of them. + +Cross Reference +--------------- + +Once a full analysis (that is, an ``analyze_r`` call) has been done, +you can get a cross reference by using ``getxref()``. This returns a +list of tuples. Each tuple is ``(modulename, importers)``, where +importers is a list of the (fully qualified) names of the modules +importing ``modulename``. Both the returned list and the importers +list are sorted. + +mf Usage +-------- + +A simple example follows: + + >>> import mf + >>> a = mf.ImportTracker() + >>> a.analyze_r("os") + ['os', 'sys', 'posixpath', 'nt', 'stat', 'string', 'strop', + 're', 'pcre', 'ntpath', 'dospath', 'macpath', 'win32api', + 'UserDict', 'copy', 'types', 'repr', 'tempfile'] + >>> a.analyze_one("os") + ['os'] + >>> a.modules['string'].imports + [('strop', 0, 0), ('strop.*', 0, 0), ('re', 1, 1)] + >>> + + +The tuples in the imports list are (name, delayed, conditional). + + >>> for w in a.modules['string'].warnings: print w + ... + W: delayed eval hack detected at line 359 + W: delayed eval hack detected at line 389 + W: delayed eval hack detected at line 418 + >>> for w in a.getwarnings(): print w + ... + W: no module named pwd (delayed, conditional import by posixpath) + W: no module named dos (conditional import by os) + W: no module named os2 (conditional import by os) + W: no module named posix (conditional import by os) + W: no module named mac (conditional import by os) + W: no module named MACFS (delayed, conditional import by tempfile) + W: no module named macfs (delayed, conditional import by tempfile) + W: top-level conditional exec statment detected at line 47 + - os (C:\Program Files\Python\Lib\os.py) + W: delayed eval hack detected at line 359 + - string (C:\Program Files\Python\Lib\string.py) + W: delayed eval hack detected at line 389 + - string (C:\Program Files\Python\Lib\string.py) + W: delayed eval hack detected at line 418 + - string (C:\Program Files\Python\Lib\string.py) + >>> + + + +.. _iu.py: + +``iu.py``: An *imputil* Replacement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Module ``iu`` grows out of the pioneering work that Greg Stein did +with ``imputil`` (actually, it includes some verbatim ``imputil`` +code, but since Greg didn't copyright it, we won't mention it). Both +modules can take over Python's builtin import and ease writing of at +least certain kinds of import hooks. + +``iu`` differs from ``imputil``: +* faster +* better emulation of builtin import +* more managable + +There is an ``ImportManager`` which provides the replacement for builtin import +and hides all the semantic complexities of a Python import request from it's +delegates. + +``ImportManager`` +----------------- + +``ImportManager`` formalizes the concept of a metapath. This concept implicitly +exists in native Python in that builtins and frozen modules are searched +before ``sys.path``, (on Windows there's also a search of the registry while on +Mac, resources may be searched). This metapath is a list populated with +``ImportDirector`` instances. There are ``ImportDirector`` subclasses +for builtins, frozen modules, (on Windows) modules found through the +registry and a ``PathImportDirector`` for handling ``sys.path``. For a +top-level import (that is, not an import of a module in a package), +``ImportManager`` tries each director on it's metapath until one +succeeds. + +``ImportManager`` hides the semantic complexity of an import from the directors. +It's up to the ``ImportManager`` to decide if an import is relative or absolute; +to see if the module has already been imported; to keep ``sys.modules`` up to +date; to handle the fromlist and return the correct module object. + +``ImportDirector`` +------------------ + +An ``ImportDirector`` just needs to respond to ``getmod(name)`` by +returning a module object or ``None``. As you will see, an +``ImportDirector`` can consider name to be atomic - it has no need to +examine name to see if it is dotted. + +To see how this works, we need to examine the ``PathImportDirector``. + +``PathImportDirector`` +---------------------- + +The ``PathImportDirector`` subclass manages a list of names - most +notably, ``sys.path``. To do so, it maintains a shadowpath - a +dictionary mapping the names on its pathlist (eg, ``sys.path``) to +their associated ``Owners``. (It could do this directly, but the +assumption that sys.path is occupied solely by strings seems +ineradicable.) ``Owners`` of the appropriate kind are created as +needed (if all your imports are satisfied by the first two elements of +``sys.path``, the ``PathImportDirector``'s shadowpath will only have +two entries). + +``Owner`` +--------- + +An ``Owner`` is much like an ``ImportDirector`` but manages a much +more concrete piece of turf. For example, a ``DirOwner`` manages one +directory. Since there are no other officially recognized +filesystem-like namespaces for importing, that's all that's included +in iu, but it's easy to imagine ``Owners`` for zip files (and I have +one for my own ``.pyz`` archive format) or even URLs. + +As with ``ImportDirectors``, an ``Owner`` just needs to respond to +``getmod(name)`` by returning a module object or ``None``, and it can +consider name to be atomic. + +So structurally, we have a tree, rooted at the ``ImportManager``. At +the next level, we have a set of ``ImportDirectors``. At least one of +those directors, the ``PathImportDirector`` in charge of ``sys.path``, +has another level beneath it, consisting of ``Owners``. This much of +the tree covers the entire top-level import namespace. + +The rest of the import namespace is covered by treelets, each rooted in a +package module (an ``__init__.py``). + +Packages +-------- + +To make this work, ``Owners`` need to recognize when a module is a +package. For a ``DirOwner``, this means that name is a subdirectory +which contains an ``__init__.py``. The ``__init__`` module is loaded +and its ``__path__`` is initialized with the subdirectory. Then, a +``PathImportDirector`` is created to manage this ``__path__``. Finally +the new ``PathImportDirector``'s ``getmod`` is assigned to the +package's ``__importsub__`` function. + +When a module within the package is imported, the request is routed +(by the ``ImportManager``) diretly to the package's ``__importsub__``. +In a hierarchical namespace (like a filesystem), this means that +``__importsub__`` (which is really the bound getmod method of a +``PathImportDirector`` instance) needs only the module name, not the +package name or the fully qualified name. And that's exactly what it +gets. (In a flat namespace - like most archives - it is perfectly easy +to route the request back up the package tree to the archive +``Owner``, qualifying the name at each step.) + +Possibilities +------------- + +Let's say we want to import from zip files. So, we subclass ``Owner``. +The ``__init__`` method should take a filename, and raise a +``ValueError`` if the file is not an acceptable ``.zip`` file, (when a +new name is encountered on ``sys.path`` or a package's ``__path__``, +registered Owners are tried until one accepts the name). The +``getmod`` method would check the zip file's contents and return +``None`` if the name is not found. Otherwise, it would extract the +marshalled code object from the zip, create a new module object and +perform a bit of initialization (12 lines of code all told for my own +archive format, including initializing a pack age with it's +``__subimporter__``). + +Once the new ``Owner`` class is registered with ``iu``, you can put a +zip file on ``sys.path``. A package could even put a zip file on its +``__path__``. + +Compatibility +------------- + +This code has been tested with the PyXML, mxBase and Win32 packages, +covering over a dozen import hacks from manipulations of ``__path__`` +to replacing a module in ``sys.modules`` with a different one. +Emulation of Python's native import is nearly exact, including the +names recorded in ``sys.modules`` and module attributes (packages +imported through ``iu`` have an extra attribute - ``__importsub__``). + +Performance +----------- + +In most cases, ``iu`` is slower than builtin import (by 15 to 20%) but +faster than ``imputil`` (by 15 to 20%). By inserting archives at the +front of ``sys.path`` containing the standard lib and the package +being tested, this can be reduced to 5 to 10% slower (or, on my 1.52 +box, 10% faster!) than builtin import. A bit more can be shaved off by +manipulating the ``ImportManager``'s metapath. + +Limitations +----------- + +This module makes no attempt to facilitate policy import hacks. It is easy to +implement certain kinds of policies within a particular domain, but +fundamentally iu works by dividing up the import namespace into independent +domains. + +Quite simply, I think cross-domain import hacks are a very bad idea. As author +of the original package on which |PyInstaller| is based, McMillan worked with +import hacks for many years. Many of them are highly fragile; they often rely +on undocumented (maybe even accidental) features of implementation. +A cross-domain import hack is not likely to work with PyXML, for example. + +That rant aside, you can modify ``ImportManger`` to implement +different policies. For example, a version that implements three +import primitives: absolute import, relative import and +recursive-relative import. No idea what the Python syntax for those +should be, but ``__aimport__``, ``__rimport__`` and ``__rrimport__`` +were easy to implement. + +iu Usage +-------- + +Here's a simple example of using ``iu`` as a builtin import replacement. + + >>> import iu + >>> iu.ImportManager().install() + >>> + >>> import DateTime + >>> DateTime.__importsub__ + + >>> + +.. _PyInstaller\/hooks\/hook-win32com.py: http://www.pyinstaller.org/browser/trunk/PyInstaller/hooks/hook-win32com.py?rev=latest +.. _source/common/launch.c: http://www.pyinstaller.org/browser/trunk/source/common/launch.c?rev=latest +.. _PIL: http://www.pythonware.com/products/pil/ +.. _PyQt: http://www.riverbankcomputing.co.uk/pyqt/index.php +.. _PyWin32: http://sourceforge.net/projects/pywin32/files/ +.. _Xcode: http://developer.apple.com/xcode +.. _`GPL License`: http://www.pyinstaller.org/browser/trunk/doc/LICENSE.GPL?rev=latest +.. _FAQ: http://www.pyinstaller.org/wiki/FAQ +.. _Recipe: http://www.pyinstaller.org/wiki/Recipe +.. _MinGW: http://sourceforge.net/downloads/mingw/ +.. _MinGW-w64: http://mingw-w64.sourceforge.net/ +.. _TDM-GCC: http://tdm-gcc.tdragon.net/ +.. _`LSB sdk 4.0`: http://ftp.linuxfoundation.org/pub/lsb/bundles/released-4.0.0/sdk/ +.. |ZlibArchiveImage| image:: images/ZlibArchive.png +.. |CArchiveImage| image:: images/CArchive.png +.. |SE_exeImage| image:: images/SE_exe.png + +.. include:: _definitions.txt diff --git a/pyinstaller/doc/source/docutils-man.conf b/pyinstaller/doc/source/docutils-man.conf new file mode 100644 index 0000000..e85bb71 --- /dev/null +++ b/pyinstaller/doc/source/docutils-man.conf @@ -0,0 +1,4 @@ +[general] +source-link: off +generator: off +datestamp: diff --git a/pyinstaller/doc/source/docutils.conf b/pyinstaller/doc/source/docutils.conf new file mode 100644 index 0000000..ee370b1 --- /dev/null +++ b/pyinstaller/doc/source/docutils.conf @@ -0,0 +1,13 @@ +[general] +# These entries affect all processing: +source-link: yes +datestamp: %Y-%m-%d %H:%M UTC +generator: on +embed-stylesheet: no +strip-comments: on + +[html4css1 writer] +# These entries affect HTML output: +stylesheet-path: ../stylesheets/default.css +field-name-limit: 20 + diff --git a/pyinstaller/doc/source/pyi-build.rst b/pyinstaller/doc/source/pyi-build.rst new file mode 100644 index 0000000..eb4886f --- /dev/null +++ b/pyinstaller/doc/source/pyi-build.rst @@ -0,0 +1,63 @@ +.. -*- mode: rst ; ispell-local-dictionary: "american" -*- + +========================== +pyi-build +========================== +------------------------------------------------------------- +Build for your |PyInstaller| project +------------------------------------------------------------- +:Author: Giovanni Bajo +:Copyright: 2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc. +:Version: |PyInstallerVersion| +:Manual section: 1 + +.. raw:: manpage + + .\" disable justification (adjust text to left margin only) + .ad l + + +SYNOPSIS +========== + +``pyi-build`` SPECFILE + +DESCRIPTION +============ + +``pyi-build`` builds the project as defined in the specfile. + +Like with setuptools, by default directories ``build`` and ``dist`` +will be created. ``build`` is a private workspace for caching some +information The generated files will be placed within the ``dist`` +subdirectory; that's where the files you are interested in will be +placed. + +In most cases, this will be all you have to do. If not, see `When +things go wrong` in the manual and be sure to read the introduction to +`Spec Files`. + + + +OPTIONS +======== + +-h, --help show this help message and exit +--buildpath=BUILDPATH + Buildpath (default: + SPECPATH/build/pyi.TARGET_PLATFORM/SPECNAME) +-y, --noconfirm Remove output directory (default: SPECPATH/dist) + without confirmation +--upx-dir=UPX_DIR Directory containing UPX (default: search in path). +--log-level=LOGLEVEL Log level for Build.py (default: INFO, choose one + of DEBUG, INFO, WARN, ERROR, CRITICAL) + + +SEE ALSO +============= + +``pyi-makespec``\(1), The PyInstaller Manual, ``pyinstaller``\(1) + +Project Homepage |Homepage| + +.. include:: _definitions.txt diff --git a/pyinstaller/doc/source/pyi-makeCOMServer.rst b/pyinstaller/doc/source/pyi-makeCOMServer.rst new file mode 100644 index 0000000..4bd7135 --- /dev/null +++ b/pyinstaller/doc/source/pyi-makeCOMServer.rst @@ -0,0 +1,57 @@ +.. -*- mode: rst ; ispell-local-dictionary: "american" -*- + +========================== +pyi-makeCOMServer +========================== +------------------------------------------------------------- +Windows COM Server support for |PyInstaller| +------------------------------------------------------------- +:Author: Giovanni Bajo +:Copyright: 2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc. +:Version: |PyInstallerVersion| +:Manual section: 1 + +.. raw:: manpage + + .\" disable justification (adjust text to left margin only) + .ad l + + +SYNOPSIS +========== + +``pyi-makeCOMServer`` SCRIPT + +DESCRIPTION +============ + +This will generate a new script ``drivescript.py`` and a spec file for +the script. + +Please see the PyInstaller Manual for more information. + + +OPTIONS +======== + +--debug + Use the verbose version of the executable. + +--verbose + Register the COM server(s) with the quiet flag off. + +--ascii + do not include encodings (this is passed through to Makespec). + +--out + Generate the driver script and spec file in dir. + + +SEE ALSO +============= + +``pyi-makespec``\(1), The PyInstaller Manual, ``pyinstaller``\(1) + +Project Homepage |Homepage| + +.. include:: _definitions.txt diff --git a/pyinstaller/doc/source/pyi-makespec.rst b/pyinstaller/doc/source/pyi-makespec.rst new file mode 100644 index 0000000..fe2d349 --- /dev/null +++ b/pyinstaller/doc/source/pyi-makespec.rst @@ -0,0 +1,135 @@ +.. -*- mode: rst ; ispell-local-dictionary: "american" -*- + +========================== +pyi-makespec +========================== +------------------------------------------------------------- +Create a spec file for your |PyInstaller| project +------------------------------------------------------------- + +:Author: Giovanni Bajo +:Copyright: 2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc. +:Version: |PyInstallerVersion| +:Manual section: 1 + +.. raw:: manpage + + .\" disable justification (adjust text to left margin only) + .ad l + + +SYNOPSIS +========== + +``pyi-makespec`` SCRIPT [SCRIPT ...] + +DESCRIPTION +============ + +The spec file is the description of what you want |PyInstaller| to do +with your program. ``pyi-makespec`` is a simple wizard to create spec +files that cover basic usages:: + + py-Makespec [--onefile] yourprogram.py + +By default, ``pyi-makespec`` generates a spec file that tells +|PyInstaller| to create a distribution directory contains the main +executable and the dynamic libraries. The option ``--onefile`` +specifies that you want PyInstaller to build a single file with +everything inside. + +In most cases the specfile generated by ``pyi-makespec`` is all you +need. If not, see `When things go wrong` in the manual and be sure to +read the introduction to `Spec Files`. + + +OPTIONS +======== + +General Options +-------------------- + +-h, --help show this help message and exit + +--log-level=LOGLEVEL Log level for MakeSpec.py (default: INFO, choose + one of DEBUG, INFO, WARN, ERROR, CRITICAL) + +What to generate +------------------ + +-F, --onefile create a single file deployment +-D, --onedir create a single directory deployment (default) +-o DIR, --out=DIR generate the spec file in the specified directory + (default: current directory) +-n NAME, --name=NAME + name to assign to the project (default: first script's + basename) + +What to bundle, where to search +--------------------------------- + +-p DIR, --paths=DIR + set base path for import (like using PYTHONPATH). + Multiple directories are allowed, separating them with + ':', or using this option multiple times +--additional-hooks-dir=HOOKSPATH + additional path to search for hooks (may be given + several times) +-a, --ascii do NOT include unicode encodings (default: included if + available) +--hidden-import=MODULENAME + import hidden in the script(s). This option can be + used multiple times. + +How to generate +------------------- + +-d, --debug use the debug (verbose) build of the executable for + packaging. This will make the packaged executable be + more verbose when run. +-s, --strip strip the exe and shared libs (don't try this on + Windows) +-X, --upx use UPX if available (works differently between + Windows and \*nix) + +Windows specific options +------------------------- + +-c, --console, --nowindowed + use a console subsystem executable (Windows only) + (default) +-w, --windowed, --noconsole + use a Windows subsystem executable (Windows only) +-v FILE, --version=FILE + add a version resource from FILE to the exe + (Windows only) +-i ICON_or_FILE_ID, --icon=ICON_or_FILE_ID + If file is an .ico file, add the icon to the final + executable. Otherwise, the syntax 'file.exe,id' to + extract the icon with the specified id from file.exe + and add it to the final executable +-m FILE_or_XML, --manifest=FILE_or_XML + add manifest FILE or XML to the exe (Windows only) +-r RESOURCE, --resource=RESOURCE + RESOURCE is of format FILE[,TYPE[,NAME[,LANGUAGE]]]. + + Add or update resource of the given type, name and + language from FILE to the final executable. FILE + can be a data file or an exe/dll. For data files, + atleast TYPE and NAME need to be specified, + LANGUAGE defaults to 0 or may be specified as + wildcard \* to update all resources of the given + TYPE and NAME. For exe/dll files, all resources + from FILE will be added/updated to the final + executable if TYPE, NAME and LANGUAGE are omitted + or specified as wildcard \*. Multiple resources + are allowed, using this option multiple times. + +SEE ALSO +============= + +``pyi-build``\(1), The PyInstaller Manual, ``pyinstaller``\(1) + +Project Homepage |Homepage| + +.. include:: _definitions.txt diff --git a/pyinstaller/doc/source/pyinstaller.rst b/pyinstaller/doc/source/pyinstaller.rst new file mode 100644 index 0000000..a46b1a8 --- /dev/null +++ b/pyinstaller/doc/source/pyinstaller.rst @@ -0,0 +1,52 @@ +.. -*- mode: rst ; ispell-local-dictionary: "american" -*- + +========================== +pyinstaller +========================== +------------------------------------------------------------- +Configure and build y |PyInstaller| project in one run +------------------------------------------------------------- + +:Author: Giovanni Bajo +:Copyright: 2005-2011 by Giovanni Bajo, based on previous work under copyright 2002 McMillan Enterprises, Inc. +:Version: |PyInstallerVersion| +:Manual section: 1 + +.. raw:: manpage + + .\" disable justification (adjust text to left margin only) + .ad l + + +SYNOPSIS +========== + +``pyinstaller`` SCRIPT + +DESCRIPTION +============ + +Automatically calls pyi-configure, pyi-makespec and pyi-build in one +run. In most cases, running ``pyinstaller`` will be all you have to +do. + +Please see the PyInstaller Manual for more information. + + +OPTIONS +======== + +For now, please use ``pyinstaller --help`` to get all options. +Basically these are the same as for pyi-configure, pyi-makespec and +pyi-build together. + + +SEE ALSO +============= + +``pyi-configure``\(1), ``pyi-makespec``\(1), ``pyi-build``\(1), The +PyInstaller Manual, ``pyinstaller``\(1) + +Project Homepage |Homepage| + +.. include:: _definitions.txt diff --git a/pyinstaller/doc/source/tools/.svn/entries b/pyinstaller/doc/source/tools/.svn/entries new file mode 100644 index 0000000..c8637ac --- /dev/null +++ b/pyinstaller/doc/source/tools/.svn/entries @@ -0,0 +1,49 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/doc/source/tools +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +rst2xml.py +file + + + +add + +rst2newlatex.py +file + + + +add + +README +file + + + +add + diff --git a/pyinstaller/doc/source/tools/README b/pyinstaller/doc/source/tools/README new file mode 100644 index 0000000..a66807e --- /dev/null +++ b/pyinstaller/doc/source/tools/README @@ -0,0 +1,16 @@ +# +# About doc/tools +# + +These tools require docutils available from http://docutils.sourceforge.net + +These file are available from docutils respository but since they are not +installed by default as on "Sep 8 2005" we are including them here as +reference. If any of them has been modified it is specified in the file header. + +We use these and other tools from docutils to generate the documentation on +different formats + +# +# END OF FILE +# diff --git a/pyinstaller/doc/source/tools/rst2newlatex.py b/pyinstaller/doc/source/tools/rst2newlatex.py new file mode 100644 index 0000000..4458176 --- /dev/null +++ b/pyinstaller/doc/source/tools/rst2newlatex.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +# Author: David Goodger +# Contact: goodger@users.sourceforge.net +# Revision: $Revision: 3260 $ +# Date: $Date: 2005-04-27 01:03:00 +0200 (Wed, 27 Apr 2005) $ +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing LaTeX using +the new LaTeX writer. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates LaTeX documents from standalone reStructuredText ' + 'sources. This writer is EXPERIMENTAL and should not be used ' + 'in a production environment. ' + default_description) + +publish_cmdline(writer_name='newlatex2e', description=description) diff --git a/pyinstaller/doc/source/tools/rst2xml.py b/pyinstaller/doc/source/tools/rst2xml.py new file mode 100644 index 0000000..8d61642 --- /dev/null +++ b/pyinstaller/doc/source/tools/rst2xml.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +# Author: David Goodger +# Contact: goodger@users.sourceforge.net +# Revision: $Revision: 2468 $ +# Date: $Date: 2004-07-27 18:25:22 +0200 (Tue, 27 Jul 2004) $ +# Copyright: This module has been placed in the public domain. + +""" +A minimal front end to the Docutils Publisher, producing Docutils XML. +""" + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description + + +description = ('Generates Docutils-native XML from standalone ' + 'reStructuredText sources. ' + default_description) + +publish_cmdline(writer_name='xml', description=description) diff --git a/pyinstaller/doc/stylesheets/.svn/entries b/pyinstaller/doc/stylesheets/.svn/entries new file mode 100644 index 0000000..2baabe5 --- /dev/null +++ b/pyinstaller/doc/stylesheets/.svn/entries @@ -0,0 +1,56 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/doc/stylesheets +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +style.tex +file + + + +add + +docutils.conf +file + + + +add + +default.css +file + + + +add + +latex.tex +file + + + +add + diff --git a/pyinstaller/doc/stylesheets/default.css b/pyinstaller/doc/stylesheets/default.css new file mode 100644 index 0000000..693c4ef --- /dev/null +++ b/pyinstaller/doc/stylesheets/default.css @@ -0,0 +1,187 @@ +/* + * CSS for PyInstaller Documentation + */ + +body { + font-family: Verdana, Arial, sans-serif; + font-size: 10pt; + background-color: white; +} + +tt, span.literal { + font-family: monospace; + font-size: 0.95em; + background-color: #ECF0F3; + padding: 0 1px; +} + +div.document { + padding-left: 2em; + padding-right: 2em; + padding: 0 20px 30px; +} +dl, table { + margin-left: 2em; + margin-right: 2em; +} + +h1, h2, h3, h4, h5, h6 { + background-color: #F2F2F2; + border-bottom: 1px solid #CCCCCC; + color: #20435C !important; + font-family: 'Trebuchet MS',sans-serif; + font-weight: normal; + margin: 20px -20px 10px; + padding: 3px 0 3px 10px; +} + +h1 { + text-align: center; + font-variant: small-caps; +} +div#contents p.topic-title.first { + font-size: large; + font-family: 'Trebuchet MS',sans-serif; + font-weight: normal; +} + +a:link, a:visited { color: #006699; } +a:hover { background-color:#006699; color:white; } + +h1 a, h2 a, h3 a, h4 a, h5 a, h6 a, +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover, +h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited { + text-decoration: inherit; + background-color: inherit; + color:inherit; +} +h2 a:hover::after, h3 a:hover::after { + color: red; + content: " back to contents"; + text-decoration: unterline; + font-size: x-small; + margin-left: 1em; +} + +table.docutils { + border: 0 solid #DDCCEE; + border-collapse: collapse; +} +table.docutils td, table.docutils th { + background-color: #EEEEFF; + border-left: 0 none; + padding: 2px 5px; +} +table.docutils td p.last, table.docutils th p.last { + margin-bottom: 0; +} +table.field-list td, table.field-list th { + border: 0 none !important; +} +table.footnote td, table.footnote th { + border: 0 none !important; +} +table.docutils th { + background-color: #EEDDEE; + border-top: 1px solid #CCAACC; +} + +table.docinfo { + border: 0 solid black; + border-collapse: collapse; +} +table.docinfo th { + background-color: #EEDDEE; + border: solid thin #CCAACC; + border-top: 0; + border-left: 0; + padding: 2px 5px; +} +table.docinfo td { + background-color: #EEEEFF; + border: solid thin #EEDDFF; + border-width: 0 0 thin; + padding: 2px 5px; +} + + +table.option-list td.option-group { + padding-left: 0; + background-color: #EEDDEE; +} +table.option-list td.option-group[colspan="2"] { + background-color: #EEEEFF; +} +table.option-list td + td { + padding-bottom: 0.5em; +} +table.option-list kbd { + padding: 3px; + background-color: #EEDDEE; + white-space: nowrap; +} + +th { + padding-right: 5px; + text-align: left; + background: #FFD596; + font-weight: bold; +} +th.head { + text-align: center; +} + +td { + background: white; + text-align: left; +} + + +dl { + margin-bottom: 15px; +} + +dt { + white-space: nowrap; + font-weight: bold; +} + +pre { + background-color: #EEFFCC; + border-color: #AACC99; + border-style: solid none; + border-width: 1px medium; + color: #333333; + line-height: 120%; + padding: 0.5em; + margin-left: 4em; + margin-right: 4em; + font-family: monospace; + font-size: 0.95em; +} + +/*--- sidebar --- */ + +div.sidebar { + margin-left: 1em; + margin-bottom: 1em; + font-size: smaller; + border: medium outset; + padding: 0.5em 1em; /* needed by IE */ + background-color: #ffffee; + width: 35%; + float: right; + clear: right; +} + +p.sidebar-title { + font-weight: bold; + font-size: larger; + color: #006699; + font-variant: small-caps; +} + + +/* + * END OF FILE + */ diff --git a/pyinstaller/doc/stylesheets/docutils.conf b/pyinstaller/doc/stylesheets/docutils.conf new file mode 100644 index 0000000..cdce8d6 --- /dev/null +++ b/pyinstaller/doc/stylesheets/docutils.conf @@ -0,0 +1,5 @@ +# This configuration file is to prevent tools/buildhtml.py from +# processing text files in and below this directory. + +[buildhtml application] +prune: . diff --git a/pyinstaller/doc/stylesheets/latex.tex b/pyinstaller/doc/stylesheets/latex.tex new file mode 100644 index 0000000..aee91b5 --- /dev/null +++ b/pyinstaller/doc/stylesheets/latex.tex @@ -0,0 +1,1082 @@ +\makeatletter + +% Development notes at +% http://docutils.python-hosting.com/wiki/NewLatex + + +\providecommand{\Dprinting}{false} + + +\providecommand{\DSearly}{} +\providecommand{\DSlate}{} + +\providecommand{\Ddocumentclass}{scrartcl} +\providecommand{\Ddocumentoptions}{a4paper} + +\documentclass[\Ddocumentoptions]{\Ddocumentclass} + +\DSearly + + +\providecommand{\DSfontencoding}{ + % Set up font encoding. + % AE is a T1-emulation. It provides most characters and features + % as T1-encoded fonts but doesn't use ugly bitmap fonts. + \usepackage{ae} + % Provide the characters not contained in AE from EC bitmap fonts. + \usepackage{aecompl} + % Guillemets ("<<", ">>") in AE. + \usepackage{aeguill} +} + + +\providecommand{\DSsymbols}{% + % Fix up symbols. + % The Euro symbol in Computer Modern looks, um, funny. Let's get a + % proper Euro symbol. + \RequirePackage{eurosym}% + \renewcommand{\texteuro}{\euro}% +} + + +% Taken from +% +% and modified. Used with permission. +\providecommand{\Dprovidelength}[2]{% + \begingroup% + \escapechar\m@ne% + \xdef\@gtempa{{\string#1}}% + \endgroup% + \expandafter\@ifundefined\@gtempa% + {\newlength{#1}\setlength{#1}{#2}}% + {}% +} + +\providecommand{\Dprovidecounter}[1]{% + % Like \newcounter except that it doesn't crash if the counter + % already exists. + \@ifundefined{c@#1}{\newcounter{#1}}{} +} + +\Dprovidelength{\Dboxparindent}{\parindent} +\providecommand{\Dmakeboxminipage}[1]{% + % Make minipage for use in a box created by \Dmakefbox. + \begin{minipage}[t]{0.9\linewidth}% + \setlength{\parindent}{\Dboxparindent}% + #1% + \end{minipage}% +} +\providecommand{\Dmakefbox}[1]{% + % Make a centered, framed box. Useful e.g. for admonitions. + \vspace{0.4\baselineskip}% + \begin{center}% + \fbox{\Dmakeboxminipage{#1}}% + \end{center}% + \vspace{0.4\baselineskip}% +} +\providecommand{\Dmakebox}[1]{% + % Make a centered, frameless box. Useful e.g. for block quotes. + % Do not use minipages here, but create pseudo-lists to allow + % page-breaking. (Don't use KOMA-script's addmargin environment + % because it messes up bullet lists.) + \Dmakelistenvironment{}{}{% + \setlength{\parskip}{0pt}% + \setlength{\parindent}{\Dboxparindent}% + \item{#1}% + }% +} + + +\RequirePackage{ifthen} +\providecommand{\Dfrenchspacing}{true} +\ifthenelse{\equal{\Dfrenchspacing}{true}}{\frenchspacing}{} + + +\Dprovidelength{\Dblocklevelvspace}{% + % Space between block-level elements other than paragraphs. + 0.7\baselineskip plus 0.3\baselineskip minus 0.2\baselineskip% +} +\providecommand{\Dauxiliaryspace}{% + \ifthenelse{\equal{\Dneedvspace}{true}}{\vspace{\Dblocklevelvspace}}{}% + \par\noindent% +} +\providecommand{\Dauxiliaryparspace}{% + \ifthenelse{\equal{\Dneedvspace}{true}}{\vspace{\Dblocklevelvspace}}{}% + \par% +} +\providecommand{\Dparagraphspace}{\par} +\providecommand{\Dneedvspace}{true} + + +\providecommand{\DSlinks}{ + % Targets and references. + \RequirePackage[colorlinks=false,pdfborder={0 0 0}]{hyperref} + + \providecommand{\Draisedlink}[1]{\Hy@raisedlink{##1}} + + % References. + % We're assuming here that the "refid" and "refuri" attributes occur + % only in inline context (in TextElements). + \providecommand{\DArefid}[5]{% + \ifthenelse{\equal{##4}{reference}}{% + \Dexplicitreference{\###3}{##5}% + }{% + % If this is not a target node (targets with refids are + % uninteresting and should be silently dropped). + \ifthenelse{\not\equal{##4}{target}}{% + % If this is a footnote reference, call special macro. + \ifthenelse{\equal{##4}{footnotereference}}{% + \Dimplicitfootnotereference{\###3}{##5}% + }{% + \ifthenelse{\equal{##4}{citationreference}}{% + \Dimplicitcitationreference{\###3}{##5}% + }{% + \Dimplicitreference{\###3}{##5}% + }% + }% + }{}% + }% + } + \providecommand{\DArefuri}[5]{% + \ifthenelse{\equal{##4}{target}}{% + % Hyperlink targets can (and should be) ignored because they are + % invisible. + }{% + % We only have explicit URI references, so one macro suffices. + \Durireference{##3}{##5}% + }% + } + % Targets. + \providecommand{\DAids}[5]{% + \label{##3}% + \ifthenelse{\equal{##4}{footnotereference}}{% + {% + \renewcommand{\HyperRaiseLinkDefault}{% + % Dirty hack to make backrefs to footnote references work. + % For some reason, \baselineskip is 0pt in fn references. + 0.5\Doriginalbaselineskip% + }% + \Draisedlink{\hypertarget{##3}{}}##5% + }% + }{% + \Draisedlink{\hypertarget{##3}{}}##5% + }% + } + % Color in references. + \RequirePackage{color} + \providecommand{\Dimplicitreference}[2]{% + % Create implicit reference to ID. Implicit references occur + % e.g. in TOC-backlinks of section titles. Parameters: + % 1. Target. + % 2. Link text. + \href{##1}{##2}% + } + \providecommand{\Dimplicitfootnotereference}[2]{% + % Ditto, but for the special case of footnotes. + % We want them to be rendered like explicit references. + \Dexplicitreference{##1}{##2}% + } + \providecommand{\Dimplicitcitationreference}[2]{% + % Ditto for citation references. + \Dimplicitfootnotereference{##1}{##2}% + } + \ifthenelse{\equal{\Dprinting}{true}}{ + \providecommand{\Dexplicitreferencecolor}{black} + }{ + \providecommand{\Dexplicitreferencecolor}{blue} + } + \providecommand{\Dexplicitreference}[2]{% + % Create explicit reference to ID, e.g. created with "foo_". + % Parameters: + % 1. Target. + % 2. Link text. + \href{##1}{{\color{\Dexplicitreferencecolor}##2}}% + } + \providecommand{\Durireferencecolor}{\Dexplicitreferencecolor} + \providecommand{\Durireference}[2]{% + % Create reference to URI. Parameters: + % 1. Target. + % 2. Link text. + \href{##1}{{\color{\Durireferencecolor}##2}}% + } +} + + +\providecommand{\DSlanguage}{% + % Set up babel. + \ifthenelse{\equal{\Dlanguagebabel}{}}{}{ + \RequirePackage[\Dlanguagebabel]{babel} + } +} + + + + +\providecommand{\DAclasses}[5]{% + \Difdefined{DN#4C#3}{% + % Pass only contents, nothing else! + \csname DN#4C#3\endcsname{#5}% + }{% + \Difdefined{DC#3}{% + \csname DC#3\endcsname{#5}% + }{% + #5% + }% + }% +} + +\providecommand{\Difdefined}[3]{\@ifundefined{#1}{#3}{#2}} + +\providecommand{\Dattr}[5]{% + % Global attribute dispatcher. + % Parameters: + % 1. Attribute number. + % 2. Attribute name. + % 3. Attribute value. + % 4. Node name. + % 5. Node contents. + \Difdefined{DN#4A#2V#3}{% + \csname DN#4A#2V#3\endcsname{#1}{#2}{#3}{#4}{#5}% + }{\Difdefined{DN#4A#2}{% + \csname DN#4A#2\endcsname{#1}{#2}{#3}{#4}{#5}% + }{\Difdefined{DA#2V#3}{% + \csname DA#2V#3\endcsname{#1}{#2}{#3}{#4}{#5}% + }{\Difdefined{DA#2}{% + \csname DA#2\endcsname{#1}{#2}{#3}{#4}{#5}% + }{#5% + }}}}% +} + +\providecommand{\DNparagraph}[1]{#1} +\providecommand{\Dformatboxtitle}[1]{{\Large\textbf{#1}}} +\providecommand{\Dformatboxsubtitle}[1]{{\large\textbf{#1}}} +\providecommand{\Dtopictitle}[1]{% + \Difinsidetoc{\vspace{1em}\par}{}% + \noindent\Dformatboxtitle{#1}% + \ifthenelse{\equal{\Dhassubtitle}{false}}{\vspace{1em}}{\vspace{0.5em}}% + \par% +} +\providecommand{\Dtopicsubtitle}[1]{% + \noindent\Dformatboxsubtitle{#1}% + \vspace{1em}% + \par% +} +\providecommand{\Dsidebartitle}[1]{\Dtopictitle{#1}} +\providecommand{\Dsidebarsubtitle}[1]{\Dtopicsubtitle{#1}} +\providecommand{\Ddocumenttitle}[1]{% + \begin{center}{\Huge#1}\end{center}% + \ifthenelse{\equal{\Dhassubtitle}{true}}{\vspace{0.1cm}}{\vspace{1cm}}% +} +\providecommand{\Ddocumentsubtitle}[1]{% + \begin{center}{\huge#1}\end{center}% + \vspace{1cm}% +} +% Can be overwritten by user stylesheet. +\providecommand{\Dformatsectiontitle}[1]{#1} +\providecommand{\Dformatsectionsubtitle}[1]{\Dformatsectiontitle{#1}} +\providecommand{\Dbookmarksectiontitle}[1]{% + % Return text suitable for use in \section*, \subsection*, etc., + % containing a PDF bookmark. Parameter: The title (as node tree). + \Draisedlink{\Dpdfbookmark{\Dtitleastext}}% + #1% +} +\providecommand{\Dsectiontitlehook}[1]{#1} +\providecommand{\Dsectiontitle}[1]{% + \Dsectiontitlehook{% + \Ddispatchsectiontitle{\Dbookmarksectiontitle{\Dformatsectiontitle{#1}}}% + }% +} +\providecommand{\Ddispatchsectiontitle}[1]{% + \@ifundefined{Dsectiontitle\roman{Dsectionlevel}}{% + \Ddeepsectiontitle{#1}% + }{% + \csname Dsectiontitle\roman{Dsectionlevel}\endcsname{#1}% + }% +} +\providecommand{\Ddispatchsectionsubtitle}[1]{% + \Ddispatchsectiontitle{#1}% +} +\providecommand{\Dsectiontitlei}[1]{\section*{#1}} +\providecommand{\Dsectiontitleii}[1]{\subsection*{#1}} +\providecommand{\Ddeepsectiontitle}[1]{% + % Anything below \subsubsection (like \paragraph or \subparagraph) + % is useless because it uses the same font. The only way to + % (visually) distinguish such deeply nested sections is to use + % section numbering. + \subsubsection*{#1}% +} +\providecommand{\Dsectionsubtitlehook}[1]{#1} +\Dprovidelength{\Dsectionsubtitleraisedistance}{0.7em} +\providecommand{\Dsectionsubtitlescaling}{0.85} +\providecommand{\Dsectionsubtitle}[1]{% + \Dsectionsubtitlehook{% + % Move the subtitle nearer to the title. + \vspace{-\Dsectionsubtitleraisedistance}% + % Don't create a PDF bookmark. + \Ddispatchsectionsubtitle{% + \Dformatsectionsubtitle{\scalebox{\Dsectionsubtitlescaling}{#1}}% + }% + }% +} +% Boolean variable. +\providecommand{\Dhassubtitle}{false} +\providecommand{\DNtitle}[1]{% + \csname D\Dparent title\endcsname{#1}% +} +\providecommand{\DNsubtitle}[1]{% + \csname D\Dparent subtitle\endcsname{#1}% +} +\newcounter{Dpdfbookmarkid} +\setcounter{Dpdfbookmarkid}{0} +\providecommand{\Dpdfbookmark}[1]{% + % Temporarily decrement Desctionlevel counter. + \addtocounter{Dsectionlevel}{-1}% + %\typeout{\arabic{Dsectionlevel}}% + %\typeout{#1}% + %\typeout{docutils\roman{Dpdfbookmarkid}}% + %\typeout{}% + \pdfbookmark[\arabic{Dsectionlevel}]{#1}{docutils\arabic{Dpdfbookmarkid}}% + \addtocounter{Dsectionlevel}{1}% + \addtocounter{Dpdfbookmarkid}{1}% +} + +%\providecommand{\DNliteralblock}[1]{\begin{quote}\ttfamily\raggedright#1\end{quote}} +\providecommand{\DNliteralblock}[1]{% + \Dmakelistenvironment{}{% + \ifthenelse{\equal{\Dinsidetabular}{true}}{% + \setlength{\leftmargin}{0pt}% + }{}% + \setlength{\rightmargin}{0pt}% + }{% + \raggedright\item\noindent\nohyphens{\textnhtt{#1\Dfinalstrut}}% + }% +} +\providecommand{\DNdoctestblock}[1]{% + % Treat doctest blocks the same as literal blocks. + \DNliteralblock{#1}% +} +\RequirePackage{hyphenat} +\providecommand{\DNliteral}[1]{\textnhtt{#1}} +\providecommand{\DNemphasis}[1]{\emph{#1}} +\providecommand{\DNstrong}[1]{\textbf{#1}} +\providecommand{\Dvisitdocument}{\begin{document}\noindent} +\providecommand{\Ddepartdocument}{\end{document}} +\providecommand{\DNtopic}[1]{% + \ifthenelse{\equal{\DcurrentNtopicAcontents}{1}}{% + \addtocounter{Dtoclevel}{1}% + \par\noindent% + #1% + \addtocounter{Dtoclevel}{-1}% + }{% + \par\noindent% + \Dmakebox{#1}% + }% +} +\providecommand{\Dformatrubric}[1]{\textbf{#1}} +\Dprovidelength{\Dprerubricspace}{0.3em} +\providecommand{\DNrubric}[1]{% + \vspace{\Dprerubricspace}\par\noindent\Dformatrubric{#1}\par% +} + +\providecommand{\Dbullet}{} +\providecommand{\Dsetbullet}[1]{\renewcommand{\Dbullet}{#1}} +\providecommand{\DNbulletlist}[1]{% + \Difinsidetoc{% + \Dtocbulletlist{#1}% + }{% + \Dmakelistenvironment{\Dbullet}{}{#1}% + }% +} +\renewcommand{\@pnumwidth}{2.2em} +\providecommand{\DNlistitem}[1]{% + \Difinsidetoc{% + \ifthenelse{\equal{\theDtoclevel}{1}\and\equal{\Dlocaltoc}{false}}{% + {% + \par\addvspace{1em}\noindent% + \sectfont% + #1\hfill\pageref{\DcurrentNlistitemAtocrefid}% + }% + }{% + \@dottedtocline{0}{\Dtocindent}{0em}{#1}{% + \pageref{\DcurrentNlistitemAtocrefid}% + }% + }% + }{% + \item{#1}% + }% +} +\providecommand{\DNenumeratedlist}[1]{#1} +\newcounter{Dsectionlevel} +\providecommand{\Dvisitsectionhook}{} +\providecommand{\Ddepartsectionhook}{} +\providecommand{\Dvisitsection}{% + \addtocounter{Dsectionlevel}{1}% + \Dvisitsectionhook% +} +\providecommand{\Ddepartsection}{% + \Ddepartsectionhook% + \addtocounter{Dsectionlevel}{-1}% +} + +% Using \_ will cause hyphenation after _ even in \textnhtt-typewriter +% because the hyphenat package redefines \_. So we use +% \textunderscore here. +\providecommand{\Dtextunderscore}{\textunderscore} + +\providecommand{\Dtextinlineliteralfirstspace}{{ }} +\providecommand{\Dtextinlineliteralsecondspace}{{~}} + +\Dprovidelength{\Dlistspacing}{0.8\baselineskip} + +\providecommand{\Dsetlistrightmargin}{% + \ifthenelse{\lengthtest{\linewidth>10em}}{% + % Equal margins. + \setlength{\rightmargin}{\leftmargin}% + }{% + % If the line is narrower than 10em, we don't remove any further + % space from the right. + \setlength{\rightmargin}{0pt}% + }% +} +\providecommand{\Dresetlistdepth}{false} +\Dprovidelength{\Doriginallabelsep}{\labelsep} +\providecommand{\Dmakelistenvironment}[3]{% + % Make list environment with support for unlimited nesting and with + % reasonable default lengths. Parameters: + % 1. Label (same as in list environment). + % 2. Spacing (same as in list environment). + % 3. List contents (contents of list environment). + \ifthenelse{\equal{\Dinsidetabular}{true}}{% + % Unfortunately, vertical spacing doesn't work correctly when + % using lists inside tabular environments, so we use a minipage. + \begin{minipage}[t]{\linewidth}% + }{}% + {% + \renewcommand{\Dneedvspace}{false}% + % \parsep0.5\baselineskip + \renewcommand{\Dresetlistdepth}{false}% + \ifnum \@listdepth>5% + \protect\renewcommand{\Dresetlistdepth}{true}% + \@listdepth=5% + \fi% + \begin{list}{% + #1% + }{% + \setlength{\itemsep}{0pt}% + \setlength{\partopsep}{0pt}% + \setlength{\topsep}{0pt}% + % List should take 90% of total width. + \setlength{\leftmargin}{0.05\linewidth}% + \ifthenelse{\lengthtest{\leftmargin<1.8em}}{% + \setlength{\leftmargin}{1.8em}% + }{}% + \setlength{\labelsep}{\Doriginallabelsep}% + \Dsetlistrightmargin% + #2% + }{% + #3% + }% + \end{list}% + \ifthenelse{\equal{\Dresetlistdepth}{true}}{\@listdepth=5}{}% + }% + \ifthenelse{\equal{\Dinsidetabular}{true}}{\end{minipage}}{}% +} +\providecommand{\Dfinalstrut}{\@finalstrut\@arstrutbox} +\providecommand{\DAlastitem}[5]{#5\Dfinalstrut} + +\Dprovidelength{\Ditemsep}{0pt} +\providecommand{\Dmakeenumeratedlist}[6]{% + % Make enumerated list. + % Parameters: + % - prefix + % - type (\arabic, \roman, ...) + % - suffix + % - suggested counter name + % - start number - 1 + % - list contents + \newcounter{#4}% + \Dmakelistenvironment{#1#2{#4}#3}{% + % Use as much space as needed for the label. + \setlength{\labelwidth}{10em}% + % Reserve enough space so that the label doesn't go beyond the + % left margin of preceding paragraphs. Like that: + % + % A paragraph. + % + % 1. First item. + \setlength{\leftmargin}{2.5em}% + \Dsetlistrightmargin% + \setlength{\itemsep}{\Ditemsep}% + % Use counter recommended by Python module. + \usecounter{#4}% + % Set start value. + \addtocounter{#4}{#5}% + }{% + % The list contents. + #6% + }% +} + + +% Single quote in literal mode. \textquotesingle from package +% textcomp has wrong width when using package ae, so we use a normal +% single curly quote here. +\providecommand{\Dtextliteralsinglequote}{'} + + +% "Tabular lists" are field lists and options lists (not definition +% lists because there the term always appears on its own line). We'll +% use the terminology of field lists now ("field", "field name", +% "field body"), but the same is also analogously applicable to option +% lists. +% +% We want these lists to be breakable across pages. We cannot +% automatically get the narrowest possible size for the left column +% (i.e. the field names or option groups) because tabularx does not +% support multi-page tables, ltxtable needs to have the table in an +% external file and we don't want to clutter the user's directories +% with auxiliary files created by the filecontents environment, and +% ltablex is not included in teTeX. +% +% Thus we set a fixed length for the left column and use list +% environments. This also has the nice side effect that breaking is +% now possible anywhere, not just between fields. +% +% Note that we are creating a distinct list environment for each +% field. There is no macro for a whole tabular list! +\Dprovidelength{\Dtabularlistfieldnamewidth}{6em} +\Dprovidelength{\Dtabularlistfieldnamesep}{0.5em} +\providecommand{\Dinsidetabular}{false} +\providecommand{\Dsavefieldname}{} +\providecommand{\Dsavefieldbody}{} +\Dprovidelength{\Dusedfieldnamewidth}{0pt} +\Dprovidelength{\Drealfieldnamewidth}{0pt} +\providecommand{\Dtabularlistfieldname}[1]{\renewcommand{\Dsavefieldname}{#1}} +\providecommand{\Dtabularlistfieldbody}[1]{\renewcommand{\Dsavefieldbody}{#1}} +\Dprovidelength{\Dparskiptemp}{0pt} +\providecommand{\Dtabularlistfield}[1]{% + {% + % This only saves field name and field body in \Dsavefieldname and + % \Dsavefieldbody, resp. It does not insert any text into the + % document. + #1% + % Recalculate the real field name width everytime we encounter a + % tabular list field because it may have been changed using a + % "raw" node. + \setlength{\Drealfieldnamewidth}{\Dtabularlistfieldnamewidth}% + \addtolength{\Drealfieldnamewidth}{\Dtabularlistfieldnamesep}% + \Dmakelistenvironment{% + \makebox[\Drealfieldnamewidth][l]{\Dsavefieldname}% + }{% + \setlength{\labelwidth}{\Drealfieldnamewidth}% + \setlength{\leftmargin}{\Drealfieldnamewidth}% + \setlength{\rightmargin}{0pt}% + \setlength{\labelsep}{0pt}% + }{% + \item% + \settowidth{\Dusedfieldnamewidth}{\Dsavefieldname}% + \setlength{\Dparskiptemp}{\parskip}% + \ifthenelse{% + \lengthtest{\Dusedfieldnamewidth>\Dtabularlistfieldnamewidth}% + }{% + \mbox{}\par% + \setlength{\parskip}{0pt}% + }{}% + \Dsavefieldbody% + \setlength{\parskip}{\Dparskiptemp}% + %XXX Why did we need this? + %\@finalstrut\@arstrutbox% + }% + \par% + }% +} + +\providecommand{\Dformatfieldname}[1]{\textbf{#1:}} +\providecommand{\DNfieldlist}[1]{#1} +\providecommand{\DNfield}[1]{\Dtabularlistfield{#1}} +\providecommand{\DNfieldname}[1]{% + \Dtabularlistfieldname{% + \Dformatfieldname{#1}% + }% +} +\providecommand{\DNfieldbody}[1]{\Dtabularlistfieldbody{#1}} + +\providecommand{\Dformatoptiongroup}[1]{% + % Format option group, e.g. "-f file, --input file". + \texttt{#1}% +} +\providecommand{\Dformatoption}[1]{% + % Format option, e.g. "-f file". + % Put into mbox to avoid line-breaking at spaces. + \mbox{#1}% +} +\providecommand{\Dformatoptionstring}[1]{% + % Format option string, e.g. "-f". + #1% +} +\providecommand{\Dformatoptionargument}[1]{% + % Format option argument, e.g. "file". + \textsl{#1}% +} +\providecommand{\Dformatoptiondescription}[1]{% + % Format option description, e.g. + % "\DNparagraph{Read input data from file.}" + #1% +} +\providecommand{\DNoptionlist}[1]{#1} +\providecommand{\Doptiongroupjoiner}{,{ }} +\providecommand{\Disfirstoption}{% + % Auxiliary macro indicating if a given option is the first child + % of its option group (if it's not, it has to preceded by + % \Doptiongroupjoiner). + false% +} +\providecommand{\DNoptionlistitem}[1]{% + \Dtabularlistfield{#1}% +} +\providecommand{\DNoptiongroup}[1]{% + \renewcommand{\Disfirstoption}{true}% + \Dtabularlistfieldname{\Dformatoptiongroup{#1}}% +} +\providecommand{\DNoption}[1]{% + % If this is not the first option in this option group, add a + % joiner. + \ifthenelse{\equal{\Disfirstoption}{true}}{% + \renewcommand{\Disfirstoption}{false}% + }{% + \Doptiongroupjoiner% + }% + \Dformatoption{#1}% +} +\providecommand{\DNoptionstring}[1]{\Dformatoptionstring{#1}} +\providecommand{\DNoptionargument}[1]{{ }\Dformatoptionargument{#1}} +\providecommand{\DNdescription}[1]{% + \Dtabularlistfieldbody{\Dformatoptiondescription{#1}}% +} + +\providecommand{\DNdefinitionlist}[1]{% + \begin{description}% + \parskip0pt% + #1% + \end{description}% +} +\providecommand{\DNdefinitionlistitem}[1]{% + % LaTeX expects the label in square brackets; we provide an empty + % label. + \item[]#1% +} +\providecommand{\Dformatterm}[1]{#1} +\providecommand{\DNterm}[1]{\hspace{-5pt}\Dformatterm{#1}} +% I'm still not sure what's the best rendering for classifiers. The +% colon syntax is used by reStructuredText, so it's at least WYSIWYG. +% Use slanted text because italic would cause too much emphasis. +\providecommand{\Dformatclassifier}[1]{\textsl{#1}} +\providecommand{\DNclassifier}[1]{~:~\Dformatclassifier{#1}} +\providecommand{\Dformatdefinition}[1]{#1} +\providecommand{\DNdefinition}[1]{\par\Dformatdefinition{#1}} + +\providecommand{\Dlineblockindentation}{2.5em} +\providecommand{\DNlineblock}[1]{% + \Dmakelistenvironment{}{% + \ifthenelse{\equal{\Dparent}{lineblock}}{% + % Parent is a line block, so indent. + \setlength{\leftmargin}{\Dlineblockindentation}% + }{% + % At top level; don't indent. + \setlength{\leftmargin}{0pt}% + }% + \setlength{\rightmargin}{0pt}% + \setlength{\parsep}{0pt}% + }{% + #1% + }% +} +\providecommand{\DNline}[1]{\item#1} + + +\providecommand{\DNtransition}{% + \raisebox{0.25em}{\parbox{\linewidth}{\hspace*{\fill}\hrulefill\hrulefill\hspace*{\fill}}}% +} + + +\providecommand{\Dformatblockquote}[1]{% + % Format contents of block quote. + % This occurs in block-level context, so we cannot use \textsl. + {\slshape#1}% +} +\providecommand{\Dformatattribution}[1]{---\textup{#1}} +\providecommand{\DNblockquote}[1]{% + \Dmakebox{% + \Dformatblockquote{#1} + }% +} +\providecommand{\DNattribution}[1]{% + \par% + \begin{flushright}\Dformatattribution{#1}\end{flushright}% +} + + +% Sidebars: +\RequirePackage{picins} +% Vertical and horizontal margins. +\Dprovidelength{\Dsidebarvmargin}{0.5em} +\Dprovidelength{\Dsidebarhmargin}{1em} +% Padding (space between contents and frame). +\Dprovidelength{\Dsidebarpadding}{1em} +% Frame width. +\Dprovidelength{\Dsidebarframewidth}{2\fboxrule} +% Position ("l" or "r"). +\providecommand{\Dsidebarposition}{r} +% Width. +\Dprovidelength{\Dsidebarwidth}{0.45\linewidth} +\providecommand{\DNsidebar}[1]{ + \parpic[\Dsidebarposition]{% + \begin{minipage}[t]{\Dsidebarwidth}% + % Doing this with nested minipages is ugly, but I haven't found + % another way to place vertical space before and after the fbox. + \vspace{\Dsidebarvmargin}% + {% + \setlength{\fboxrule}{\Dsidebarframewidth}% + \setlength{\fboxsep}{\Dsidebarpadding}% + \fbox{% + \begin{minipage}[t]{\linewidth}% + \setlength{\parindent}{\Dboxparindent}% + #1% + \end{minipage}% + }% + }% + \vspace{\Dsidebarvmargin}% + \end{minipage}% + }% +} + + +% Citations and footnotes. +\providecommand{\Dformatfootnote}[1]{% + % Format footnote. + {% + \footnotesize#1% + % \par is necessary for LaTeX to adjust baselineskip to the + % changed font size. + \par% + }% +} +\providecommand{\Dformatcitation}[1]{\Dformatfootnote{#1}} +\Dprovidelength{\Doriginalbaselineskip}{0pt} +\providecommand{\DNfootnotereference}[1]{% + {% + % \baselineskip is 0pt in \textsuperscript, so we save it here. + \setlength{\Doriginalbaselineskip}{\baselineskip}% + \textsuperscript{#1}% + }% +} +\providecommand{\DNcitationreference}[1]{{[}#1{]}} +\Dprovidelength{\Dfootnotesep}{3.5pt} +\providecommand{\Dsetfootnotespacing}{% + % Spacing commands executed at the beginning of footnotes. + \setlength{\parindent}{0pt}% + \hspace{1em}% +} +\providecommand{\DNfootnote}[1]{% + % See ltfloat.dtx for details. + {% + \insert\footins{% + \vspace{\Dfootnotesep}% + \Dsetfootnotespacing% + \Dformatfootnote{#1}% + }% + }% +} +\providecommand{\DNcitation}[1]{\DNfootnote{#1}} +\providecommand{\Dformatfootnotelabel}[1]{% + % Keep \footnotesize in footnote labels (\textsuperscript would + % reduce the font size even more). + \textsuperscript{\footnotesize#1{ }}% +} +\providecommand{\Dformatcitationlabel}[1]{{[}#1{]}{ }} +\providecommand{\Dformatmultiplebackrefs}[1]{% + % If in printing mode, do not write out multiple backrefs. + \ifthenelse{\equal{\Dprinting}{true}}{}{\textsl{#1}}% +} +\providecommand{\Dthislabel}{} +\providecommand{\DNlabel}[1]{% + \renewcommand{\Dthislabel}{#1} + \ifthenelse{\not\equal{\Dsinglebackref}{}}{% + \let\Doriginallabel=\Dthislabel% + \def\Dthislabel{% + \Dsinglefootnotebacklink{\Dsinglebackref}{\Doriginallabel}% + }% + }{}% + \ifthenelse{\equal{\Dparent}{footnote}}{% + % Footnote label. + \Dformatfootnotelabel{\Dthislabel}% + }{% + \ifthenelse{\equal{\Dparent}{citation}}{% + % Citation label. + \Dformatcitationlabel{\Dthislabel}% + }{}% + }% + % If there are multiple backrefs, add them now. + \Dformatmultiplebackrefs{\Dmultiplebackrefs}% +} +\providecommand{\Dsinglefootnotebacklink}[2]{% + % Create normal backlink of a footnote label. Parameters: + % 1. ID. + % 2. Link text. + % Treat like a footnote reference. + \Dimplicitfootnotereference{\##1}{#2}% +} +\providecommand{\Dmultifootnotebacklink}[2]{% + % Create generated backlink, as in (1, 2). Parameters: + % 1. ID. + % 2. Link text. + % Treat like a footnote reference. + \Dimplicitfootnotereference{\##1}{#2}% +} +\providecommand{\Dsinglecitationbacklink}[2]{\Dsinglefootnotebacklink{#1}{#2}} +\providecommand{\Dmulticitationbacklink}[2]{\Dmultifootnotebacklink{#1}{#2}} + + +\RequirePackage{longtable} +\providecommand{\Dmaketable}[2]{% + % Make table. Parameters: + % 1. Table spec (like "|p|p|"). + % 2. Table contents. + {% + \renewcommand{\Dinsidetabular}{true}% + \begin{longtable}{#1}% + \hline% + #2% + \end{longtable}% + }% +} +\providecommand{\DNthead}[1]{% + #1% + \endhead% +} +\providecommand{\DNrow}[1]{% + #1\tabularnewline% + \hline% +} +\providecommand{\Dcolspan}[2]{% + % Take care of the morecols attribute (but incremented by 1). + &\multicolumn{#1}{l|}{#2}% +} +\providecommand{\Dcolspanleft}[2]{% + % Like \Dmorecols, but called for the leftmost entries in a table + % row. + \multicolumn{#1}{|l|}{#2}% +} +\providecommand{\Dsubsequententry}[1]{% + % +} +% \DNentry is not used because we set the ampersand ("&") in the +% \DAcolspan... macros. +\providecommand{\DAtableheaderentry}[5]{\Dformattableheaderentry{#5}} +\providecommand{\Dformattableheaderentry}[1]{{\bfseries#1}} + + +\providecommand{\DNsystemmessage}[1]{% + {% + \ifthenelse{\equal{\Dprinting}{false}}{\color{red}}{}% + \bfseries% + #1% + }% +} + + +\providecommand{\Dinsidehalign}{false} +\newsavebox{\Dalignedimagebox} +\Dprovidelength{\Dalignedimagewidth}{0pt} +\providecommand{\Dhalign}[2]{% + % Horizontally align the contents to the left or right so that the + % text flows around it. + % Parameters: + % 1. l or r + % 2. Contents. + \renewcommand{\Dinsidehalign}{true}% + % For some obscure reason \parpic consumes some vertical space. + \vspace{-3pt}% + % Now we do something *really* ugly, but this enables us to wrap the + % image in a minipage while still allowing tight frames when + % class=border (see \DNimageCborder). + \sbox{\Dalignedimagebox}{#2}% + \settowidth{\Dalignedimagewidth}{\usebox{\Dalignedimagebox}}% + \parpic[#1]{% + \begin{minipage}[b]{\Dalignedimagewidth}% + % Compensate for previously added space, but not entirely. + \vspace*{2.0pt}% + \vspace*{\Dfloatimagetopmargin}% + \usebox{\Dalignedimagebox}% + \vspace*{1.5pt}% + \vspace*{\Dfloatimagebottommargin}% + \end{minipage}% + }% + \renewcommand{\Dinsidehalign}{false}% +} + + +\RequirePackage{graphicx} +% Maximum width of an image. +\providecommand{\Dimagemaxwidth}{\linewidth} +\providecommand{\Dfloatimagemaxwidth}{0.5\linewidth} +% Auxiliary variable. +\Dprovidelength{\Dcurrentimagewidth}{0pt} +\providecommand{\DNimageAalign}[5]{% + \ifthenelse{\equal{#3}{left}}{% + \Dhalign{l}{#5}% + }{% + \ifthenelse{\equal{#3}{right}}{% + \Dhalign{r}{#5}% + }{% + \ifthenelse{\equal{#3}{center}}{% + % Text floating around centered figures is a bad idea. Thus + % we use a center environment. Note that no extra space is + % added by the writer, so the space added by the center + % environment is fine. + \begin{center}#5\end{center}% + }{% + #5% + }% + }% + }% +} +% Base path for images. +\providecommand{\Dimagebase}{} +% Auxiliary command. Current image path. +\providecommand{\Dimagepath}{} +\providecommand{\DNimageAuri}[5]{% + % Insert image. We treat the URI like a path here. + \renewcommand{\Dimagepath}{\Dimagebase#3}% + \Difdefined{DcurrentNimageAwidth}{% + \Dwidthimage{\DcurrentNimageAwidth}{\Dimagepath}% + }{% + \Dsimpleimage{\Dimagepath}% + }% +} +\Dprovidelength{\Dfloatimagevmargin}{0pt} +\providecommand{\Dfloatimagetopmargin}{\Dfloatimagevmargin} +\providecommand{\Dfloatimagebottommargin}{\Dfloatimagevmargin} +\providecommand{\Dwidthimage}[2]{% + % Image with specified width. + % Parameters: + % 1. Image width. + % 2. Image path. + % Need to make bottom-alignment dependent on align attribute (add + % functional test first). Need to observe height attribute. + %\begin{minipage}[b]{#1}% + \includegraphics[width=#1,height=\textheight,keepaspectratio]{#2}% + %\end{minipage}% +} +\providecommand{\Dcurrentimagemaxwidth}{} +\providecommand{\Dsimpleimage}[1]{% + % Insert image, without much parametrization. + \settowidth{\Dcurrentimagewidth}{\includegraphics{#1}}% + \ifthenelse{\equal{\Dinsidehalign}{true}}{% + \renewcommand{\Dcurrentimagemaxwidth}{\Dfloatimagemaxwidth}% + }{% + \renewcommand{\Dcurrentimagemaxwidth}{\Dimagemaxwidth}% + }% + \ifthenelse{\lengthtest{\Dcurrentimagewidth>\Dcurrentimagemaxwidth}}{% + \Dwidthimage{\Dcurrentimagemaxwidth}{#1}% + }{% + \Dwidthimage{\Dcurrentimagewidth}{#1}% + }% +} +\providecommand{\Dwidthimage}[2]{% + % Image with specified width. + % Parameters: + % 1. Image width. + % 2. Image path. + \Dwidthimage{#1}{#2}% +} + +% Figures. +\providecommand{\DNfigureAalign}[5]{% + % Hack to make it work Right Now. + %\def\DcurrentNimageAwidth{\DcurrentNfigureAwidth}% + % + %\def\DcurrentNimageAwidth{\linewidth}% + \DNimageAalign{#1}{#2}{#3}{#4}{% + \begin{minipage}[b]{0.4\linewidth}#5\end{minipage}}% + %\let\DcurrentNimageAwidth=\relax% + % + %\let\DcurrentNimageAwidth=\relax% +} +\providecommand{\DNcaption}[1]{\par\noindent{\slshape#1}} +\providecommand{\DNlegend}[1]{\Dauxiliaryspace#1} + +\providecommand{\DCborder}[1]{\fbox{#1}} +% No padding between image and border. +\providecommand{\DNimageCborder}[1]{\frame{#1}} + + +% Need to replace with language-specific stuff. Maybe look at +% csquotes.sty and ask the author for permission to use parts of it. +\providecommand{\Dtextleftdblquote}{``} +\providecommand{\Dtextrightdblquote}{''} + +% Table of contents: +\Dprovidelength{\Dtocininitialsectnumwidth}{2.4em} +\Dprovidelength{\Dtocadditionalsectnumwidth}{0.7em} +% Level inside a table of contents. While this is at -1, we are not +% inside a TOC. +\Dprovidecounter{Dtoclevel}% +\setcounter{Dtoclevel}{-1} +\providecommand{\Dlocaltoc}{false}% +\providecommand{\DNtopicClocal}[1]{% + \renewcommand{\Dlocaltoc}{true}% + \addtolength{\Dtocsectnumwidth}{2\Dtocadditionalsectnumwidth}% + \addtolength{\Dtocindent}{-2\Dtocadditionalsectnumwidth}% + #1% + \addtolength{\Dtocindent}{2\Dtocadditionalsectnumwidth}% + \addtolength{\Dtocsectnumwidth}{-2\Dtocadditionalsectnumwidth}% + \renewcommand{\Dlocaltoc}{false}% +} +\Dprovidelength{\Dtocindent}{0pt}% +\Dprovidelength{\Dtocsectnumwidth}{\Dtocininitialsectnumwidth} +% Compensate for one additional TOC indentation space so that the +% top-level is unindented. +\addtolength{\Dtocsectnumwidth}{-\Dtocadditionalsectnumwidth} +\addtolength{\Dtocindent}{-\Dtocsectnumwidth} +\providecommand{\Difinsidetoc}[2]{% + \ifthenelse{\not\equal{\theDtoclevel}{-1}}{#1}{#2}% +} +\providecommand{\DNgeneratedCsectnum}[1]{% + \Difinsidetoc{% + % Section number inside TOC. + \makebox[\Dtocsectnumwidth][l]{#1}% + }{% + % Section number inside section title. + #1\quad% + }% +} +\providecommand{\Dtocbulletlist}[1]{% + \addtocounter{Dtoclevel}{1}% + \addtolength{\Dtocindent}{\Dtocsectnumwidth}% + \addtolength{\Dtocsectnumwidth}{\Dtocadditionalsectnumwidth}% + #1% + \addtolength{\Dtocsectnumwidth}{-\Dtocadditionalsectnumwidth}% + \addtolength{\Dtocindent}{-\Dtocsectnumwidth}% + \addtocounter{Dtoclevel}{-1}% +} + + +% For \Dpixelunit, the length value is pre-multiplied with 0.75, so by +% specifying "pt" we get the same notion of "pixel" as graphicx. +\providecommand{\Dpixelunit}{pt} +% Normally lengths are relative to the current linewidth. +\providecommand{\Drelativeunit}{\linewidth} + + +%\RequirePackage{fixmath} +%\RequirePackage{amsmath} + + +\DSfontencoding +\DSlanguage +\DSlinks +\DSsymbols +\DSlate + +\makeatother diff --git a/pyinstaller/doc/stylesheets/style.tex b/pyinstaller/doc/stylesheets/style.tex new file mode 100644 index 0000000..6c84be9 --- /dev/null +++ b/pyinstaller/doc/stylesheets/style.tex @@ -0,0 +1,74 @@ +% latex include file for docutils latex writer +% -------------------------------------------- +% +% CVS: $Id: style.tex 2548 2004-08-29 20:04:53Z felixwiemann $ +% +% This is included at the end of the latex header in the generated file, +% to allow overwriting defaults, although this could get hairy. +% Generated files should process well standalone too, LaTeX might give a +% message about a missing file. + +% donot indent first line of paragraph. +\setlength{\parindent}{0pt} +\setlength{\parskip}{5pt plus 2pt minus 1pt} + +% sloppy +% ------ +% Less strict (opposite to default fussy) space size between words. Therefore +% less hyphenation. +\sloppy + +% fonts +% ----- +% times for pdf generation, gives smaller pdf files. +% +% But in standard postscript fonts: courier and times/helvetica do not fit. +% Maybe use pslatex. +\usepackage{times} + +% pagestyle +% --------- +% headings might put section titles in the page heading, but not if +% the table of contents is done by docutils. +% If pagestyle{headings} is used, \geometry{headheight=10pt,headsep=1pt} +% should be set too. +%\pagestyle{plain} +% +% or use fancyhdr (untested !) +%\usepackage{fancyhdr} +%\pagestyle{fancy} +%\addtolength{\headheight}{\\baselineskip} +%\renewcommand{\sectionmark}[1]{\markboth{#1}{}} +%\renewcommand{\subsectionmark}[1]{\markright{#1}} +%\fancyhf{} +%\fancyhead[LE,RO]{\\bfseries\\textsf{\Large\\thepage}} +%\fancyhead[LO]{\\textsf{\\footnotesize\\rightmark}} +%\fancyhead[RE]{\\textsc{\\textsf{\\footnotesize\leftmark}}} +%\\fancyfoot[LE,RO]{\\bfseries\\textsf{\scriptsize Docutils}} +%\fancyfoot[RE,LO]{\\textsf{\scriptsize\\today}} + +% geometry +% -------- +% = papersizes and margins +%\geometry{a4paper,twoside,tmargin=1.5cm, +% headheight=1cm,headsep=0.75cm} + +% Do section number display +% ------------------------- +%\makeatletter +%\def\@seccntformat#1{} +%\makeatother +% no numbers in toc +%\renewcommand{\numberline}[1]{} + + +% change maketitle +% ---------------- +%\renewcommand{\maketitle}{ +% \begin{titlepage} +% \begin{center} +% \textsf{TITLE \@title} \\ +% Date: \today +% \end{center} +% \end{titlepage} +%} diff --git a/pyinstaller/e2etests/.svn/entries b/pyinstaller/e2etests/.svn/entries new file mode 100644 index 0000000..e202400 --- /dev/null +++ b/pyinstaller/e2etests/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/e2etests +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +win32 +dir + + + +add + +common +dir + + + +add + diff --git a/pyinstaller/e2etests/common/.svn/entries b/pyinstaller/e2etests/common/.svn/entries new file mode 100644 index 0000000..dfa2167 --- /dev/null +++ b/pyinstaller/e2etests/common/.svn/entries @@ -0,0 +1,42 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/e2etests/common +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +hanoi.py +file + + + +add + +maketests.py +file + + + +add + diff --git a/pyinstaller/e2etests/common/hanoi.py b/pyinstaller/e2etests/common/hanoi.py new file mode 100644 index 0000000..e6bfe94 --- /dev/null +++ b/pyinstaller/e2etests/common/hanoi.py @@ -0,0 +1,161 @@ +# Animated Towers of Hanoi using Tk with optional bitmap file in +# background. +# +# Usage: tkhanoi [n [bitmapfile]] +# +# n is the number of pieces to animate; default is 4, maximum 15. +# +# The bitmap file can be any X11 bitmap file (look in +# /usr/include/X11/bitmaps for samples); it is displayed as the +# background of the animation. Default is no bitmap. + +# This uses Steen Lumholt's Tk interface +from Tkinter import * + + +# Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c +# as temporary. For each move, call report() +def hanoi(n, a, b, c, report): + if n <= 0: return + hanoi(n-1, a, c, b, report) + report(n, a, b) + hanoi(n-1, c, b, a, report) + + +# The graphical interface +class Tkhanoi: + + # Create our objects + def __init__(self, n, bitmap = None): + self.n = n + self.tk = tk = Tk() + + Label(text="Press to exit").pack() + tk.bind("", lambda x: tk.destroy()) #quit()) + + self.canvas = c = Canvas(tk) + c.pack() + width, height = tk.getint(c['width']), tk.getint(c['height']) + + # Add background bitmap + if bitmap: + self.bitmap = c.create_bitmap(width/2, height/2, + bitmap=bitmap, + foreground='blue') + + # Generate pegs + pegwidth = 10 + pegheight = height/2 + pegdist = width/3 + x1, y1 = (pegdist-pegwidth)/2, height*1/3 + x2, y2 = x1+pegwidth, y1+pegheight + self.pegs = [] + p = c.create_rectangle(x1, y1, x2, y2, fill='black') + self.pegs.append(p) + x1, x2 = x1+pegdist, x2+pegdist + p = c.create_rectangle(x1, y1, x2, y2, fill='black') + self.pegs.append(p) + x1, x2 = x1+pegdist, x2+pegdist + p = c.create_rectangle(x1, y1, x2, y2, fill='black') + self.pegs.append(p) + self.tk.update() + + # Generate pieces + pieceheight = pegheight/16 + maxpiecewidth = pegdist*2/3 + minpiecewidth = 2*pegwidth + self.pegstate = [[], [], []] + self.pieces = {} + x1, y1 = (pegdist-maxpiecewidth)/2, y2-pieceheight-2 + x2, y2 = x1+maxpiecewidth, y1+pieceheight + dx = (maxpiecewidth-minpiecewidth) / (2*max(1, n-1)) + for i in range(n, 0, -1): + p = c.create_rectangle(x1, y1, x2, y2, fill='red') + self.pieces[i] = p + self.pegstate[0].append(i) + x1, x2 = x1 + dx, x2-dx + y1, y2 = y1 - pieceheight-2, y2-pieceheight-2 + self.tk.update() + self.tk.after(25) + + # Run -- never returns - press esc or close window to exit + def run(self): + try: + while 1: + hanoi(self.n, 0, 1, 2, self.report) + hanoi(self.n, 1, 2, 0, self.report) + hanoi(self.n, 2, 0, 1, self.report) + hanoi(self.n, 0, 2, 1, self.report) + hanoi(self.n, 2, 1, 0, self.report) + hanoi(self.n, 1, 0, 2, self.report) + except TclError: + pass + + # Reporting callback for the actual hanoi function + def report(self, i, a, b): + if self.pegstate[a][-1] != i: raise RuntimeError # Assertion + del self.pegstate[a][-1] + p = self.pieces[i] + c = self.canvas + + # Lift the piece above peg a + ax1, ay1, ax2, ay2 = c.bbox(self.pegs[a]) + while 1: + x1, y1, x2, y2 = c.bbox(p) + if y2 < ay1: break + c.move(p, 0, -1) + self.tk.update() + + # Move it towards peg b + bx1, by1, bx2, by2 = c.bbox(self.pegs[b]) + newcenter = (bx1+bx2)/2 + while 1: + x1, y1, x2, y2 = c.bbox(p) + center = (x1+x2)/2 + if center == newcenter: break + if center > newcenter: c.move(p, -1, 0) + else: c.move(p, 1, 0) + self.tk.update() + + # Move it down on top of the previous piece + pieceheight = y2-y1 + newbottom = by2 - pieceheight*len(self.pegstate[b]) - 2 + while 1: + x1, y1, x2, y2 = c.bbox(p) + if y2 >= newbottom: break + c.move(p, 0, 1) + self.tk.update() + + # Update peg state + self.pegstate[b].append(i) + + +# Main program +def main(): + import sys, string + + # First argument is number of pegs, default 4 + if sys.argv[1:]: + n = string.atoi(sys.argv[1]) + else: + n = 4 + + # Second argument is bitmap file, default none + if sys.argv[2:]: + bitmap = sys.argv[2] + # Reverse meaning of leading '@' compared to Tk + if bitmap[0] == '@': bitmap = bitmap[1:] + else: bitmap = '@' + bitmap + else: + bitmap = None + + # Create the graphical objects... + h = Tkhanoi(n, bitmap) + + # ...and run! + h.run() + + +# Call main when run as script +if __name__ == '__main__': + main() diff --git a/pyinstaller/e2etests/common/maketests.py b/pyinstaller/e2etests/common/maketests.py new file mode 100644 index 0000000..46023ed --- /dev/null +++ b/pyinstaller/e2etests/common/maketests.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# Copyright (C) 2011, Hartmut Goebel +# 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 os +import optparse +import shutil + +try: + import PyInstaller +except ImportError: + # if importing PyInstaller fails, try to load from parent + # directory to support running without installation + import imp + import os + if not hasattr(os, "getuid") or os.getuid() != 0: + imp.load_module('PyInstaller', *imp.find_module('PyInstaller', + [os.path.abspath(os.path.join(__file__, '..','..','..'))])) + +from PyInstaller import is_win, is_linux +from PyInstaller import compat + +utils_dir = os.path.normpath(os.path.join(__file__, '..', '..', '..', 'utils')) +makespec = os.path.join(utils_dir, 'Makespec.py') +build = os.path.join(utils_dir, 'Build.py') + +if is_win: + stripopts = ('',) + consoleopts = ('', '--noconsole') +else: + stripopts = ('', '--strip') + consoleopts = ('',) + +out_pattern = 't%d' +if is_linux: + import tempfile + out_pattern = os.path.join(tempfile.gettempdir(), 'hanoi', out_pattern) +dist_pattern_dir = os.path.join(out_pattern, 'dist', 'hanoi', 'hanoi') +dist_pattern_file = os.path.join(out_pattern, 'dist', 'hanoi') + +script_name = os.path.abspath(os.path.join(__file__, '..', 'hanoi.py')) + +def build_test(cnt, bldconfig, *options, **kwopts): + options = filter(None, options) + if kwopts['clean'] and os.path.isdir(out_pattern % cnt): + # remove/clean the working directory + shutil.rmtree(out_pattern % cnt) + compat.exec_python_rc(makespec, script_name, + '--out', out_pattern % cnt, bldconfig, *options) + compat.exec_python_rc(build, os.path.join(out_pattern % cnt, 'hanoi.spec'), + '--noconfirm') + if is_linux: + # create symlinks + if os.path.islink('hanoi%d' % cnt): + os.remove('hanoi%d' % cnt) + if bldconfig == '--onedir': + os.symlink(dist_pattern_dir % cnt, 'hanoi%d' % cnt) + else: + os.symlink(dist_pattern_file % cnt, 'hanoi%d' % cnt) + +parser = optparse.OptionParser('%prog [NUM ...]') +parser.add_option('--clean', action='store_true', + help=('Perform clean builds ' + '(remove target dirs prior to building).')) +opts, args = parser.parse_args() +args = map(int, args) +i = 1 +for bldconfig in ('--onedir', '--onefile'): + for console in consoleopts: + for dbg in ('--debug', ''): + for stripopt in stripopts: + if not args or i in args: + build_test(i, bldconfig, console, dbg, stripopt, **opts.__dict__) + i += 1 diff --git a/pyinstaller/e2etests/win32/.svn/entries b/pyinstaller/e2etests/win32/.svn/entries new file mode 100644 index 0000000..e089c6c --- /dev/null +++ b/pyinstaller/e2etests/win32/.svn/entries @@ -0,0 +1,70 @@ +10 + +dir +0 +https://softchord.svn.sourceforge.net/svnroot/softchord/pyinstaller/e2etests/win32 +https://softchord.svn.sourceforge.net/svnroot/softchord +add + + + + + + + + + + + + + + + + + + + +be02edf8-907f-434d-9d52-e0ffb5e9bc3f + +NextID.py +file + + + +add + +testMSOffice.py +file + + + +add + +testNextID.vbs +file + + + +add + +testcomext.py +file + + + +add + +testEnsureDispatch.py +file + + + +add + +readme.txt +file + + + +add + diff --git a/pyinstaller/e2etests/win32/NextID.py b/pyinstaller/e2etests/win32/NextID.py new file mode 100644 index 0000000..ed74f4f --- /dev/null +++ b/pyinstaller/e2etests/win32/NextID.py @@ -0,0 +1,78 @@ +# 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 pythoncom +pycomCLSCTX_INPROC = 3 +pycomCLSCTX_LOCAL_SERVER = 4 +import os +d = {} + +class NextID: + _reg_clsid_ = '{25E06E61-2D18-11D5-945F-00609736B700}' + _reg_desc_ = 'Text COM server' + _reg_progid_ = 'MEInc.NextID' + _reg_clsctx_ = pycomCLSCTX_INPROC | pycomCLSCTX_LOCAL_SERVER + _public_methods_ = [ + 'getNextID' + ] + def __init__(self): + import win32api + win32api.MessageBox(0, "NextID.__init__ started", "NextID.py") + global d + if sys.frozen: + for entry in sys.path: + if entry.find('?') > -1: + here = os.path.dirname(entry.split('?')[0]) + break + else: + here = os.getcwd() + else: + here = os.path.dirname(__file__) + self.fnm = os.path.join(here, 'id.cfg') + try: + d = eval(open(self.fnm, 'rU').read()+'\n') + except: + d = { + 'systemID': 0xaaaab, + 'highID': 0 + } + win32api.MessageBox(0, "NextID.__init__ complete", "NextID.py") + def getNextID(self): + global d + d['highID'] = d['highID'] + 1 + open(self.fnm, 'w').write(repr(d)) + return '%(systemID)-0.5x%(highID)-0.7x' % d + +def RegisterNextID(): + from win32com.server import register + register.UseCommandLine(NextID) + +def UnRegisterNextID(): + from win32com.server import register + register.UnregisterServer(NextID._reg_clsid_, NextID._reg_progid_) + +if __name__ == '__main__': + import sys + if "/unreg" in sys.argv: + UnRegisterNextID() + elif "/register" in sys.argv: + RegisterNextID() + else: + print "running as server" + import win32com.server.localserver + win32com.server.localserver.main() + raw_input("Press any key...") diff --git a/pyinstaller/e2etests/win32/readme.txt b/pyinstaller/e2etests/win32/readme.txt new file mode 100644 index 0000000..144069b --- /dev/null +++ b/pyinstaller/e2etests/win32/readme.txt @@ -0,0 +1,23 @@ +NextID.py + + ../../MakeCOMServer.py [options] NextID.py + ../../Build.py DriveNextID.spec + distdriveNextID\driveNextID --register + testNextID.vbs + python + >>> import win32com.client + >>> import pythoncom + >>> o = win32com.client.Dispatch('MEInc.NextID', clsctx = pythoncom.CLSCTX_LOCAL_SERVER) + >>> o.getNextID() + 'aaaab0000003' + >>> ^Z + distdriveNextID\driveNextID --unregister + +the others: + + ../../Makespec [options]