[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/sofibox/winpython/master/winpython/disthelpers.py [Back]  [Original]

# -*- coding: utf-8 -*-
#
# Copyright  2009-2011 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# (see guidata/__init__.py for details)

# pylint: disable=W0613

"""
disthelpers
-----------

The ``guidata.disthelpers`` module provides helper functions for Python 
package distribution on Microsoft Windows platforms with ``py2exe`` or on 
all platforms thanks to ``cx_Freeze``.
"""

import sys
import os
from pathlib import Path
import shutil
import traceback
import atexit
import imp
from subprocess import Popen, PIPE
import warnings
from distutils.version import LooseVersion, StrictVersion

# ==============================================================================
# Module, scripts, programs
# ==============================================================================
def get_module_path(modname):
    """Return module *modname* base path"""
    module = sys.modules.get(modname, __import__(modname))
    return str(Path(module.__file__).parent.resolve())


# ==============================================================================
# Dependency management
# ==============================================================================
def get_changeset(path, rev=None):
    """Return Mercurial repository *path* revision number"""
    args = ['hg', 'parent']
    if rev is not None:
        args += ['--rev', str(rev)]
    process = Popen(
        args, stdout=PIPE, stderr=PIPE, cwd=path, shell=True
    )
    try:
        return (
            process.stdout.read().splitlines()[0].split()[1]
        )
    except IndexError:
        raise RuntimeError(process.stderr.read())


def prepend_module_to_path(module_path):
    """
    Prepend to sys.path module located in *module_path*
    Return string with module infos: name, revision, changeset
    
    Use this function:
    1) In your application to import local frozen copies of internal libraries
    2) In your py2exe distributed package to add a text file containing the returned string
    """
    if not Path(module_path).is_dir():
        # Assuming py2exe distribution
        return
    sys.path.insert(0, str(Path(module_path).resolve()))
    changeset = get_changeset(module_path)
    name = Path(module_path).name
    prefix = "Prepending module to sys.path"
    message = prefix + (
        f"{name} [revision {changeset}]"
    ).rjust(80 - len(prefix), ".")
    print(message, file=sys.stderr)
    if name in sys.modules:
        sys.modules.pop(name)
        nbsp = 0
        for modname in sys.modules.keys():
            if modname.startswith(name + '.'):
                sys.modules.pop(modname)
                nbsp += 1
        warning = f'(removed {name} from sys.modules'
        if nbsp:
            warning += f' and {nbsp} subpackages'
        warning += ')'
        print(warning.rjust(80), file=sys.stderr)
    return message


def prepend_modules_to_path(module_base_path):
    """Prepend to sys.path all modules located in *module_base_path*"""
    if not Path(module_base_path).is_dir():
        # Assuming py2exe distribution
        return
    fnames = [
        str(Path(module_base_path) / name)
        for name in os.listdir(module_base_path)
    ]
    messages = [
        prepend_module_to_path(dirname)
        for dirname in fnames
        if Path(dirname).is_dir()
    ]
    return os.linesep.join(messages)


# ==============================================================================
# Distribution helpers
# ==============================================================================
def _remove_later(fname):
    """Try to remove file later (at exit)"""

    def try_to_remove(fname):
        if Path(fname).exists():
            os.remove(fname)

    atexit.register(try_to_remove, str(Path(fname).resolve()))


def to_include_files(data_files):
    """Convert data_files list to include_files list
    
    data_files:
      * this is the ``py2exe`` data files format
      * list of tuples (dest_dirname, (src_fname1, src_fname2, ...))
    
    include_files:
      * this is the ``cx_Freeze`` data files format
      * list of tuples ((src_fname1, dst_fname1),
                        (src_fname2, dst_fname2), ...))
    """
    include_files = []
    for dest_dir, fnames in data_files:
        for source_fname in fnames:
            dest_fname = str(Path(dest_dir) / 
                Path(source_fname).name)
            include_files.append((source_fname, dest_fname))
    return include_files


def strip_version(version):
    """Return version number with digits only
    (Windows does not support strings in version numbers)"""
    return (
        version.split('beta')[0]
        .split('alpha')[0]
        .split('rc')[0]
        .split('dev')[0]
    )


def remove_dir(dirname):
    """Remove directory *dirname* and all its contents
    Print details about the operation (progress, success/failure)"""
    print(f"Removing directory '{dirname}'...", end=' ')
    try:
        shutil.rmtree(dirname, ignore_errors=True)
        print("OK")
    except Exception:
        print("Failed!")
        traceback.print_exc()


class Distribution(object):
    """Distribution object
    
    Help creating an executable using ``py2exe`` or ``cx_Freeze``
    """

    DEFAULT_EXCLUDES = [
        'Tkconstants',
        'Tkinter',
        'tcl',
        'tk',
        'wx',
        '_imagingtk',
        'curses',
        'PIL._imagingtk',
        'ImageTk',
        'PIL.ImageTk',
        'FixTk',
        'bsddb',
        'email',
        'pywin.debugger',
        'pywin.debugger.dbgcon',
        'matplotlib',
    ]
    DEFAULT_INCLUDES = []
    DEFAULT_BIN_EXCLUDES = [
        'MSVCP100.dll',
        'MSVCP90.dll',
        'w9xpopen.exe',
        'MSVCP80.dll',
        'MSVCR80.dll',
    ]
    DEFAULT_BIN_INCLUDES = []
    DEFAULT_BIN_PATH_INCLUDES = []
    DEFAULT_BIN_PATH_EXCLUDES = []

    def __init__(self):
        self.name = None
        self.version = None
        self.description = None
        self.target_name = None
        self._target_dir = None
        self.icon = None
        self.data_files = []
        self.includes = self.DEFAULT_INCLUDES
        self.excludes = self.DEFAULT_EXCLUDES
        self.bin_includes = self.DEFAULT_BIN_INCLUDES
        self.bin_excludes = self.DEFAULT_BIN_EXCLUDES
        self.bin_path_includes = (
            self.DEFAULT_BIN_PATH_INCLUDES
        )
        self.bin_path_excludes = (
            self.DEFAULT_BIN_PATH_EXCLUDES
        )
        self.msvc = os.name == 'nt'
        self._py2exe_is_loaded = False
        self._pyqt4_added = False
        self._pyside_added = False
        # Attributes relative to cx_Freeze:
        self.executables = []

    @property
    def target_dir(self):
        """Return target directory (default: 'dist')"""
        dirname = self._target_dir
        if dirname is None:
            return 'dist'
        else:
            return dirname

    @target_dir.setter  # analysis:ignore
    def target_dir(self, value):
        self._target_dir = value

    def setup(
        self,
        name,
        version,
        description,
        script,
        target_name=None,
        target_dir=None,
        icon=None,
        data_files=None,
        includes=None,
        excludes=None,
        bin_includes=None,
        bin_excludes=None,
        bin_path_includes=None,
        bin_path_excludes=None,
        msvc=None,
    ):
        """Setup distribution object
        
        Notes:
          * bin_path_excludes is specific to cx_Freeze (ignored if it's None)
          * if msvc is None, it's set to True by default on Windows 
            platforms, False on non-Windows platforms
        """
        self.name = name
        self.version = (
            strip_version(version)
            if os.name == 'nt'
            else version
        )
        self.description = description
        assert Path(script).is_file()
        self.script = script
        self.target_name = target_name
        self.target_dir = target_dir
        self.icon = icon
        if data_files is not None:
            self.data_files += data_files
        if includes is not None:
            self.includes += includes
        if excludes is not None:
            self.excludes += excludes
        if bin_includes is not None:
            self.bin_includes += bin_includes
        if bin_excludes is not None:
            self.bin_excludes += bin_excludes
        if bin_path_includes is not None:
            self.bin_path_includes += bin_path_includes
        if bin_path_excludes is not None:
            self.bin_path_excludes += bin_path_excludes
        if msvc is not None:
            self.msvc = msvc
        if self.msvc:
            try:
                pass  # manage via msvc_runtime wheel (or give up anyway)
                #  self.data_files += create_msvc_data_files()
            except IOError:
                print(
                    "Setting the msvc option to False "
                    "will avoid this error",
                    file=sys.stderr,
                )
                raise
        # cx_Freeze:
        self.add_executable(
            self.script, self.target_name, icon=self.icon
        )

    def add_text_data_file(self, filename, contents):
        """Create temporary data file *filename* with *contents*
        and add it to *data_files*"""
        open(filename, 'wb').write(contents)
        self.data_files += [("", (filename,))]
        _remove_later(filename)

    def add_data_file(self, filename, destdir=''):
        self.data_files += [(destdir, (filename,))]

    # ------ Adding packages
    def add_pyqt4(self):
        """Include module PyQt4 to the distribution"""
        if self._pyqt4_added:
            return
        self._pyqt4_added = True

        self.includes += [
            'sip',
            'PyQt4.Qt',
            'PyQt4.QtSvg',
            'PyQt4.QtNetwork',
        ]

        import PyQt4

        pyqt_path = str(Path(PyQt4.__file__).parent)

        # Configuring PyQt4
        conf = os.linesep.join(
            ["[Paths]", "Prefix = .", "Binaries = ."]
        )
        self.add_text_data_file('qt.conf', conf)

        # Including plugins (.svg icons support, QtDesigner support, ...)
        if self.msvc:
            vc90man = "Microsoft.VC90.CRT.manifest"
            pyqt_tmp = 'pyqt_tmp'
            if Path(pyqt_tmp).is_dir():
                shutil.rmtree(pyqt_tmp)
            os.mkdir(pyqt_tmp)
            vc90man_pyqt = str(Path(pyqt_tmp) / vc90man)
            man = (
                open(vc90man, "r")
                .read()
                .replace(
                    '

Web Proxy Viewer  |  New URL  |  Original Page