예제 #1
0
def write(name,
          version,
          doc,
          entry_map,
          src_files,
          distributions,
          modules,
          dst_dir,
          logger,
          observer=None,
          compress=True):
    """
    Write egg in the manner of :mod:`setuptools`, with some differences:

    - Writes directly to the zip file, avoiding some intermediate copies.
    - Doesn't compile any Python modules.

    name: string
        Must be an alphanumeric string.

    version: string
        Must be an alphanumeric string.

    doc: string
        Used for the `Summary` and `Description` entries in the egg's metadata.

    entry_map: dict
        A :mod:`pkg_resources` :class:`EntryPoint` map: a dictionary mapping
        group names to dictionaries mapping entry point names to
        :class:`EntryPoint` objects.

    src_files: list
        List of non-Python files to include.

    distributions: list
        List of Distributions this egg depends on. It is used for the `Requires`
        entry in the egg's metadata.

    modules: list
        List of module names not found in a distribution that this egg depends
        on. It is used for the `Requires` entry in the egg's metadata and is
        also recorded in the 'openmdao_orphans.txt' resource.

    dst_dir: string
        The directory to write the egg to.

    logger: Logger
        Used for recording progress, etc.

    observer: callable
        Will be called via an :class:`EggObserver` intermediary.

    Returns the egg's filename.
    """
    observer = eggobserver.EggObserver(observer, logger)

    egg_name = egg_filename(name, version)
    egg_path = os.path.join(dst_dir, egg_name)

    distributions = sorted(distributions, key=lambda dist: dist.project_name)
    modules = sorted(modules)

    sources = []
    files = []
    size = 0  # Approximate (uncompressed) size. Used to set allowZip64 flag.

    # Collect src_files.
    for path in src_files:
        path = os.path.join(name, path)
        files.append(path)
        size += os.path.getsize(path)

    # Collect Python modules.
    for dirpath, dirnames, filenames in os.walk('.', followlinks=True):
        dirs = copy.copy(dirnames)
        for path in dirs:
            if not os.path.exists(os.path.join(dirpath, path, '__init__.py')):
                dirnames.remove(path)
        for path in filenames:
            if path.endswith('.py'):
                path = os.path.join(dirpath[2:], path)  # Skip leading './'
                files.append(path)
                size += os.path.getsize(path)
                sources.append(path)

    # Package info -> EGG-INFO/PKG-INFO
    pkg_info = []
    pkg_info.append('Metadata-Version: 1.1')
    pkg_info.append('Name: %s' % pkg_resources.safe_name(name))
    pkg_info.append('Version: %s' % pkg_resources.safe_version(version))
    pkg_info.append('Summary: %s' % doc.strip().split('\n')[0])
    pkg_info.append('Description: %s' % doc.strip())
    pkg_info.append('Author-email: UNKNOWN')
    pkg_info.append('License: UNKNOWN')
    pkg_info.append('Platform: UNKNOWN')
    for dist in distributions:
        pkg_info.append('Requires: %s (%s)' %
                        (dist.project_name, dist.version))
    for module in modules:
        pkg_info.append('Requires: %s' % module)
    pkg_info = '\n'.join(pkg_info) + '\n'
    sources.append(name + '.egg-info/PKG-INFO')
    size += len(pkg_info)

    # Dependency links -> EGG-INFO/dependency_links.txt
    dependency_links = '\n'
    sources.append(name + '.egg-info/dependency_links.txt')
    size += len(dependency_links)

    # Entry points -> EGG-INFO/entry_points.txt
    entry_points = []
    for entry_group in sorted(entry_map.keys()):
        entry_points.append('[%s]' % entry_group)
        for entry_name in sorted(entry_map[entry_group].keys()):
            entry_points.append('%s' % entry_map[entry_group][entry_name])
        entry_points.append('')
    entry_points = '\n'.join(entry_points) + '\n'
    sources.append(name + '.egg-info/entry_points.txt')
    size += len(entry_points)

    # Unsafe -> EGG-INFO/not-zip-safe
    not_zip_safe = '\n'
    sources.append(name + '.egg-info/not-zip-safe')
    size += len(not_zip_safe)

    # Requirements -> EGG-INFO/requires.txt
    requirements = [str(dist.as_requirement()) for dist in distributions]
    requirements = '\n'.join(requirements) + '\n'
    sources.append(name + '.egg-info/requires.txt')
    size += len(requirements)

    # Modules not part of a distribution -> EGG-INFO/openmdao_orphans.txt
    orphans = '\n'.join(modules) + '\n'
    sources.append(name + '.egg-info/openmdao_orphans.txt')
    size += len(orphans)

    # Top-level names -> EGG-INFO/top_level.txt
    top_level = '%s\n' % name
    sources.append(name + '.egg-info/top_level.txt')
    size += len(top_level)

    # Manifest -> EGG-INFO/SOURCES.txt
    sources.append(name + '.egg-info/SOURCES.txt')
    sources = '\n'.join(sorted(sources)) + '\n'
    size += len(sources)

    # Open zipfile.
    logger.debug('Creating %s', egg_path)
    zip64 = size > zipfile.ZIP64_LIMIT
    compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED
    egg = zipfile.ZipFile(egg_path, 'w', compression, zip64)

    stats = {
        'completed_files': 0.,
        'total_files': float(8 + len(files)),
        'completed_bytes': 0.,
        'total_bytes': float(size)
    }

    # Write egg info.
    _write_info(egg, 'PKG-INFO', pkg_info, observer, stats)
    _write_info(egg, 'dependency_links.txt', dependency_links, observer, stats)
    _write_info(egg, 'entry_points.txt', entry_points, observer, stats)
    _write_info(egg, 'not-zip-safe', not_zip_safe, observer, stats)
    _write_info(egg, 'requires.txt', requirements, observer, stats)
    _write_info(egg, 'openmdao_orphans.txt', orphans, observer, stats)
    _write_info(egg, 'top_level.txt', top_level, observer, stats)
    _write_info(egg, 'SOURCES.txt', sources, observer, stats)

    # Write collected files.
    for path in sorted(files):
        _write_file(egg, path, observer, stats)

    observer.complete(egg_name)

    egg.close()
    if os.path.getsize(egg_path) > zipfile.ZIP64_LIMIT:
        logger.warning('Egg zipfile requires Zip64 support to unzip.')
    return egg_name
예제 #2
0
def save_to_egg(entry_pts,
                version=None,
                py_dir=None,
                src_dir=None,
                src_files=None,
                dst_dir=None,
                logger=None,
                observer=None,
                need_requirements=True):
    """
    Save state and other files to an egg. Analyzes the objects saved for
    distribution dependencies.  Modules not found in any distribution are
    recorded in an ``egg-info/openmdao_orphans.txt`` file.  Also creates and
    saves loader scripts for each entry point.

    entry_pts: list
        List of ``(obj, obj_name, obj_group)`` tuples.
        The first of these specifies the root object and package name.

    version: string
        Defaults to a timestamp of the form 'YYYY.MM.DD.HH.mm'.

    py_dir: string
        The (root) directory for local Python files.
        It defaults to the current directory.

    src_dir: string
        The root of all (relative) `src_files`.

    dst_dir: string
        The directory to write the egg in.

    observer: callable
        Will be called via an :class:`EggObserver` intermediary.

    need_requirements: bool
        If True, distributions required by the egg will be determined.
        This can be set False if the egg is just being used for distribution
        within the local host.

    Returns ``(egg_filename, required_distributions, orphan_modules)``.
    """
    root, name, group = entry_pts[0]
    logger = logger or NullLogger()
    observer = eggobserver.EggObserver(observer, logger)

    orig_dir = os.getcwd()

    if not py_dir:
        py_dir = orig_dir
    else:
        py_dir = os.path.realpath(py_dir)
    if sys.platform == 'win32':  # pragma no cover
        py_dir = py_dir.lower()

    if src_dir:
        src_dir = os.path.realpath(src_dir)
        if sys.platform == 'win32':  # pragma no cover
            src_dir = src_dir.lower()

    src_files = src_files or set()

    if not version:
        now = datetime.datetime.now()  # Could consider using utcnow().
        version = '%d.%02d.%02d.%02d.%02d' % \
                  (now.year, now.month, now.day, now.hour, now.minute)

    dst_dir = dst_dir or orig_dir
    if not os.access(dst_dir, os.W_OK):
        msg = "Can't save to '%s', no write permission" % dst_dir
        observer.exception(msg)
        raise IOError(msg)

    egg_name = eggwriter.egg_filename(name, version)
    logger.debug('Saving to %s in %s...', egg_name, orig_dir)

    # Clone __main__ as a 'real' module and have classes reference that.
    _fix_main(logger)

    # Get a list of all objects we'll be saving.
    objs = _get_objects(root, logger)

    # Check that each object can be pickled.
    _check_objects(objs, logger, observer)

    # Verify that no __main__ references are still around.
    _verify_objects(root, logger, observer)

    tmp_dir = None
    try:
        if need_requirements:
            # Determine distributions and local modules required.
            required_distributions, local_modules, orphan_modules = \
                _get_distributions(objs, py_dir, logger, observer)
        else:
            required_distributions = set()
            local_modules = set()
            orphan_modules = set()
            # Collect Python modules.
            for dirpath, dirnames, filenames in os.walk(py_dir):
                dirs = copy.copy(dirnames)
                for path in dirs:
                    if not os.path.exists(
                            os.path.join(dirpath, path, '__init__.py')):
                        dirnames.remove(path)
                for path in filenames:
                    if path.endswith('.py'):
                        local_modules.add(os.path.join(dirpath, path))

        # Ensure module defining root is local. Saving a root which is defined
        # in a package can be hidden if the package was installed.
        root_mod = root.__class__.__module__
        root_mod = sys.modules[root_mod].__file__
        if root_mod.endswith('.pyc') or root_mod.endswith('.pyo'):
            root_mod = root_mod[:-1]
        local_modules.add(os.path.abspath(root_mod))

        logger.log(LOG_DEBUG2, '    py_dir: %s', py_dir)
        logger.log(LOG_DEBUG2, '    src_dir: %s', src_dir)
        logger.log(LOG_DEBUG2, '    local_modules:')
        for module in sorted(local_modules):
            mod = module
            if mod.startswith(py_dir):
                mod = mod[len(py_dir) + 1:]
            logger.log(LOG_DEBUG2, '        %s', mod)

        # Move to scratch area.
        tmp_dir = tempfile.mkdtemp(prefix='Egg_', dir=tmp_dir)
        os.chdir(tmp_dir)
        os.mkdir(name)
        cleanup_files = []

        try:
            # Copy external files from src_dir.
            if src_dir:
                for path in src_files:
                    subdir = os.path.dirname(path)
                    if subdir:
                        subdir = os.path.join(name, subdir)
                        if not os.path.exists(subdir):
                            os.makedirs(subdir)
                    src = os.path.join(src_dir, path)
                    dst = os.path.join(name, path)
                    if sys.platform == 'win32':  # pragma no cover
                        shutil.copy2(src, dst)
                    else:
                        os.symlink(src, dst)

            # Copy local modules from py_dir.
            for path in local_modules:
                if not os.path.exists(
                        os.path.join(name, os.path.basename(path))):
                    if not os.path.isabs(path):
                        path = os.path.join(py_dir, path)
                    shutil.copy(path, name)

            # For each entry point...
            entry_info = []
            for obj, obj_name, obj_group in entry_pts:
                clean_name = obj_name
                if clean_name.startswith(name + '.'):
                    clean_name = clean_name[len(name) + 1:]
                clean_name = clean_name.replace('.', '_')

                # Save state of object hierarchy.
                state_name, state_path = \
                    _write_state_file(name, obj, clean_name, logger, observer)
                src_files.add(state_name)
                cleanup_files.append(state_path)

                # Create loader script.
                loader = '%s_loader' % clean_name
                loader_path = os.path.join(name, loader + '.py')
                cleanup_files.append(loader_path)
                _write_loader_script(loader_path, state_name, name,
                                     obj is root)

                entry_info.append((obj_group, obj_name, loader))

            # If needed, make an empty __init__.py
            init_path = os.path.join(name, '__init__.py')
            if not os.path.exists(init_path):
                cleanup_files.append(init_path)
                out = open(init_path, 'w')
                out.close()

            # Save everything to an egg.
            doc = root.__doc__ or ''
            entry_map = _create_entry_map(entry_info)
            orphans = [mod for mod, path in orphan_modules]
            eggwriter.write(name, version, doc, entry_map, src_files,
                            required_distributions, orphans, dst_dir, logger,
                            observer.observer)
        finally:
            for path in cleanup_files:
                if os.path.exists(path):
                    os.remove(path)
    finally:
        os.chdir(orig_dir)
        if tmp_dir:
            shutil.rmtree(tmp_dir, onerror=onerror)

    return (egg_name, required_distributions, orphan_modules)