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) # 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) # Fixup objects, classes, & sys.modules for __main__ imports. fixup = _fix_objects(objs, observer) # Verify that the fixups are retained. _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 corresponding to __main__ is local if it was used. # (Test script embedded in egg is an example of how this might occur) fixup_objects, fixup_classes, fixup_modules = fixup if fixup_objects: #pragma no cover # Something references __main__. main_mod = sys.modules['__main__'].__file__ local_modules.add(os.path.abspath(main_mod)) logger.debug(' py_dir: %s', py_dir) logger.debug(' src_dir: %s', src_dir) logger.debug(' local_modules:') for module in sorted(local_modules): mod = module if mod.startswith(py_dir): mod = mod[len(py_dir)+1:] logger.debug(' %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) _restore_objects(fixup) return (egg_name, required_distributions, orphan_modules)
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)