Path: blob/master/venv/Lib/site-packages/setuptools/command/bdist_egg.py
811 views
"""setuptools.command.bdist_egg12Build .egg distributions"""34from distutils.errors import DistutilsSetupError5from distutils.dir_util import remove_tree, mkpath6from distutils import log7from types import CodeType8import sys9import os10import re11import textwrap12import marshal13import warnings1415from setuptools.extern import six1617from pkg_resources import get_build_platform, Distribution, ensure_directory18from pkg_resources import EntryPoint19from setuptools.extension import Library20from setuptools import Command, SetuptoolsDeprecationWarning2122try:23# Python 2.7 or >=3.224from sysconfig import get_path, get_python_version2526def _get_purelib():27return get_path("purelib")28except ImportError:29from distutils.sysconfig import get_python_lib, get_python_version3031def _get_purelib():32return get_python_lib(False)333435def strip_module(filename):36if '.' in filename:37filename = os.path.splitext(filename)[0]38if filename.endswith('module'):39filename = filename[:-6]40return filename414243def sorted_walk(dir):44"""Do os.walk in a reproducible way,45independent of indeterministic filesystem readdir order46"""47for base, dirs, files in os.walk(dir):48dirs.sort()49files.sort()50yield base, dirs, files515253def write_stub(resource, pyfile):54_stub_template = textwrap.dedent("""55def __bootstrap__():56global __bootstrap__, __loader__, __file__57import sys, pkg_resources58from importlib.machinery import ExtensionFileLoader59__file__ = pkg_resources.resource_filename(__name__, %r)60__loader__ = None; del __bootstrap__, __loader__61ExtensionFileLoader(__name__,__file__).exec_module()62__bootstrap__()63""").lstrip()64with open(pyfile, 'w') as f:65f.write(_stub_template % resource)666768class bdist_egg(Command):69description = "create an \"egg\" distribution"7071user_options = [72('bdist-dir=', 'b',73"temporary directory for creating the distribution"),74('plat-name=', 'p', "platform name to embed in generated filenames "75"(default: %s)" % get_build_platform()),76('exclude-source-files', None,77"remove all .py files from the generated egg"),78('keep-temp', 'k',79"keep the pseudo-installation tree around after " +80"creating the distribution archive"),81('dist-dir=', 'd',82"directory to put final built distributions in"),83('skip-build', None,84"skip rebuilding everything (for testing/debugging)"),85]8687boolean_options = [88'keep-temp', 'skip-build', 'exclude-source-files'89]9091def initialize_options(self):92self.bdist_dir = None93self.plat_name = None94self.keep_temp = 095self.dist_dir = None96self.skip_build = 097self.egg_output = None98self.exclude_source_files = None99100def finalize_options(self):101ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info")102self.egg_info = ei_cmd.egg_info103104if self.bdist_dir is None:105bdist_base = self.get_finalized_command('bdist').bdist_base106self.bdist_dir = os.path.join(bdist_base, 'egg')107108if self.plat_name is None:109self.plat_name = get_build_platform()110111self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))112113if self.egg_output is None:114115# Compute filename of the output egg116basename = Distribution(117None, None, ei_cmd.egg_name, ei_cmd.egg_version,118get_python_version(),119self.distribution.has_ext_modules() and self.plat_name120).egg_name()121122self.egg_output = os.path.join(self.dist_dir, basename + '.egg')123124def do_install_data(self):125# Hack for packages that install data to install's --install-lib126self.get_finalized_command('install').install_lib = self.bdist_dir127128site_packages = os.path.normcase(os.path.realpath(_get_purelib()))129old, self.distribution.data_files = self.distribution.data_files, []130131for item in old:132if isinstance(item, tuple) and len(item) == 2:133if os.path.isabs(item[0]):134realpath = os.path.realpath(item[0])135normalized = os.path.normcase(realpath)136if normalized == site_packages or normalized.startswith(137site_packages + os.sep138):139item = realpath[len(site_packages) + 1:], item[1]140# XXX else: raise ???141self.distribution.data_files.append(item)142143try:144log.info("installing package data to %s", self.bdist_dir)145self.call_command('install_data', force=0, root=None)146finally:147self.distribution.data_files = old148149def get_outputs(self):150return [self.egg_output]151152def call_command(self, cmdname, **kw):153"""Invoke reinitialized command `cmdname` with keyword args"""154for dirname in INSTALL_DIRECTORY_ATTRS:155kw.setdefault(dirname, self.bdist_dir)156kw.setdefault('skip_build', self.skip_build)157kw.setdefault('dry_run', self.dry_run)158cmd = self.reinitialize_command(cmdname, **kw)159self.run_command(cmdname)160return cmd161162def run(self):163# Generate metadata first164self.run_command("egg_info")165# We run install_lib before install_data, because some data hacks166# pull their data path from the install_lib command.167log.info("installing library code to %s", self.bdist_dir)168instcmd = self.get_finalized_command('install')169old_root = instcmd.root170instcmd.root = None171if self.distribution.has_c_libraries() and not self.skip_build:172self.run_command('build_clib')173cmd = self.call_command('install_lib', warn_dir=0)174instcmd.root = old_root175176all_outputs, ext_outputs = self.get_ext_outputs()177self.stubs = []178to_compile = []179for (p, ext_name) in enumerate(ext_outputs):180filename, ext = os.path.splitext(ext_name)181pyfile = os.path.join(self.bdist_dir, strip_module(filename) +182'.py')183self.stubs.append(pyfile)184log.info("creating stub loader for %s", ext_name)185if not self.dry_run:186write_stub(os.path.basename(ext_name), pyfile)187to_compile.append(pyfile)188ext_outputs[p] = ext_name.replace(os.sep, '/')189190if to_compile:191cmd.byte_compile(to_compile)192if self.distribution.data_files:193self.do_install_data()194195# Make the EGG-INFO directory196archive_root = self.bdist_dir197egg_info = os.path.join(archive_root, 'EGG-INFO')198self.mkpath(egg_info)199if self.distribution.scripts:200script_dir = os.path.join(egg_info, 'scripts')201log.info("installing scripts to %s", script_dir)202self.call_command('install_scripts', install_dir=script_dir,203no_ep=1)204205self.copy_metadata_to(egg_info)206native_libs = os.path.join(egg_info, "native_libs.txt")207if all_outputs:208log.info("writing %s", native_libs)209if not self.dry_run:210ensure_directory(native_libs)211libs_file = open(native_libs, 'wt')212libs_file.write('\n'.join(all_outputs))213libs_file.write('\n')214libs_file.close()215elif os.path.isfile(native_libs):216log.info("removing %s", native_libs)217if not self.dry_run:218os.unlink(native_libs)219220write_safety_flag(221os.path.join(archive_root, 'EGG-INFO'), self.zip_safe()222)223224if os.path.exists(os.path.join(self.egg_info, 'depends.txt')):225log.warn(226"WARNING: 'depends.txt' will not be used by setuptools 0.6!\n"227"Use the install_requires/extras_require setup() args instead."228)229230if self.exclude_source_files:231self.zap_pyfiles()232233# Make the archive234make_zipfile(self.egg_output, archive_root, verbose=self.verbose,235dry_run=self.dry_run, mode=self.gen_header())236if not self.keep_temp:237remove_tree(self.bdist_dir, dry_run=self.dry_run)238239# Add to 'Distribution.dist_files' so that the "upload" command works240getattr(self.distribution, 'dist_files', []).append(241('bdist_egg', get_python_version(), self.egg_output))242243def zap_pyfiles(self):244log.info("Removing .py files from temporary directory")245for base, dirs, files in walk_egg(self.bdist_dir):246for name in files:247path = os.path.join(base, name)248249if name.endswith('.py'):250log.debug("Deleting %s", path)251os.unlink(path)252253if base.endswith('__pycache__'):254path_old = path255256pattern = r'(?P<name>.+)\.(?P<magic>[^.]+)\.pyc'257m = re.match(pattern, name)258path_new = os.path.join(259base, os.pardir, m.group('name') + '.pyc')260log.info(261"Renaming file from [%s] to [%s]"262% (path_old, path_new))263try:264os.remove(path_new)265except OSError:266pass267os.rename(path_old, path_new)268269def zip_safe(self):270safe = getattr(self.distribution, 'zip_safe', None)271if safe is not None:272return safe273log.warn("zip_safe flag not set; analyzing archive contents...")274return analyze_egg(self.bdist_dir, self.stubs)275276def gen_header(self):277epm = EntryPoint.parse_map(self.distribution.entry_points or '')278ep = epm.get('setuptools.installation', {}).get('eggsecutable')279if ep is None:280return 'w' # not an eggsecutable, do it the usual way.281282warnings.warn(283"Eggsecutables are deprecated and will be removed in a future "284"version.",285SetuptoolsDeprecationWarning286)287288if not ep.attrs or ep.extras:289raise DistutilsSetupError(290"eggsecutable entry point (%r) cannot have 'extras' "291"or refer to a module" % (ep,)292)293294pyver = '{}.{}'.format(*sys.version_info)295pkg = ep.module_name296full = '.'.join(ep.attrs)297base = ep.attrs[0]298basename = os.path.basename(self.egg_output)299300header = (301"#!/bin/sh\n"302'if [ `basename $0` = "%(basename)s" ]\n'303'then exec python%(pyver)s -c "'304"import sys, os; sys.path.insert(0, os.path.abspath('$0')); "305"from %(pkg)s import %(base)s; sys.exit(%(full)s())"306'" "$@"\n'307'else\n'308' echo $0 is not the correct name for this egg file.\n'309' echo Please rename it back to %(basename)s and try again.\n'310' exec false\n'311'fi\n'312) % locals()313314if not self.dry_run:315mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)316f = open(self.egg_output, 'w')317f.write(header)318f.close()319return 'a'320321def copy_metadata_to(self, target_dir):322"Copy metadata (egg info) to the target_dir"323# normalize the path (so that a forward-slash in egg_info will324# match using startswith below)325norm_egg_info = os.path.normpath(self.egg_info)326prefix = os.path.join(norm_egg_info, '')327for path in self.ei_cmd.filelist.files:328if path.startswith(prefix):329target = os.path.join(target_dir, path[len(prefix):])330ensure_directory(target)331self.copy_file(path, target)332333def get_ext_outputs(self):334"""Get a list of relative paths to C extensions in the output distro"""335336all_outputs = []337ext_outputs = []338339paths = {self.bdist_dir: ''}340for base, dirs, files in sorted_walk(self.bdist_dir):341for filename in files:342if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS:343all_outputs.append(paths[base] + filename)344for filename in dirs:345paths[os.path.join(base, filename)] = (paths[base] +346filename + '/')347348if self.distribution.has_ext_modules():349build_cmd = self.get_finalized_command('build_ext')350for ext in build_cmd.extensions:351if isinstance(ext, Library):352continue353fullname = build_cmd.get_ext_fullname(ext.name)354filename = build_cmd.get_ext_filename(fullname)355if not os.path.basename(filename).startswith('dl-'):356if os.path.exists(os.path.join(self.bdist_dir, filename)):357ext_outputs.append(filename)358359return all_outputs, ext_outputs360361362NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split())363364365def walk_egg(egg_dir):366"""Walk an unpacked egg's contents, skipping the metadata directory"""367walker = sorted_walk(egg_dir)368base, dirs, files = next(walker)369if 'EGG-INFO' in dirs:370dirs.remove('EGG-INFO')371yield base, dirs, files372for bdf in walker:373yield bdf374375376def analyze_egg(egg_dir, stubs):377# check for existing flag in EGG-INFO378for flag, fn in safety_flags.items():379if os.path.exists(os.path.join(egg_dir, 'EGG-INFO', fn)):380return flag381if not can_scan():382return False383safe = True384for base, dirs, files in walk_egg(egg_dir):385for name in files:386if name.endswith('.py') or name.endswith('.pyw'):387continue388elif name.endswith('.pyc') or name.endswith('.pyo'):389# always scan, even if we already know we're not safe390safe = scan_module(egg_dir, base, name, stubs) and safe391return safe392393394def write_safety_flag(egg_dir, safe):395# Write or remove zip safety flag file(s)396for flag, fn in safety_flags.items():397fn = os.path.join(egg_dir, fn)398if os.path.exists(fn):399if safe is None or bool(safe) != flag:400os.unlink(fn)401elif safe is not None and bool(safe) == flag:402f = open(fn, 'wt')403f.write('\n')404f.close()405406407safety_flags = {408True: 'zip-safe',409False: 'not-zip-safe',410}411412413def scan_module(egg_dir, base, name, stubs):414"""Check whether module possibly uses unsafe-for-zipfile stuff"""415416filename = os.path.join(base, name)417if filename[:-1] in stubs:418return True # Extension module419pkg = base[len(egg_dir) + 1:].replace(os.sep, '.')420module = pkg + (pkg and '.' or '') + os.path.splitext(name)[0]421if six.PY2:422skip = 8 # skip magic & date423elif sys.version_info < (3, 7):424skip = 12 # skip magic & date & file size425else:426skip = 16 # skip magic & reserved? & date & file size427f = open(filename, 'rb')428f.read(skip)429code = marshal.load(f)430f.close()431safe = True432symbols = dict.fromkeys(iter_symbols(code))433for bad in ['__file__', '__path__']:434if bad in symbols:435log.warn("%s: module references %s", module, bad)436safe = False437if 'inspect' in symbols:438for bad in [439'getsource', 'getabsfile', 'getsourcefile', 'getfile'440'getsourcelines', 'findsource', 'getcomments', 'getframeinfo',441'getinnerframes', 'getouterframes', 'stack', 'trace'442]:443if bad in symbols:444log.warn("%s: module MAY be using inspect.%s", module, bad)445safe = False446return safe447448449def iter_symbols(code):450"""Yield names and strings used by `code` and its nested code objects"""451for name in code.co_names:452yield name453for const in code.co_consts:454if isinstance(const, six.string_types):455yield const456elif isinstance(const, CodeType):457for name in iter_symbols(const):458yield name459460461def can_scan():462if not sys.platform.startswith('java') and sys.platform != 'cli':463# CPython, PyPy, etc.464return True465log.warn("Unable to analyze compiled code on this platform.")466log.warn("Please ask the author to include a 'zip_safe'"467" setting (either True or False) in the package's setup.py")468469470# Attribute names of options for commands that might need to be convinced to471# install to the egg build directory472473INSTALL_DIRECTORY_ATTRS = [474'install_lib', 'install_dir', 'install_data', 'install_base'475]476477478def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=True,479mode='w'):480"""Create a zip file from all the files under 'base_dir'. The output481zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"482Python module (if available) or the InfoZIP "zip" utility (if installed483and found on the default search path). If neither tool is available,484raises DistutilsExecError. Returns the name of the output zip file.485"""486import zipfile487488mkpath(os.path.dirname(zip_filename), dry_run=dry_run)489log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)490491def visit(z, dirname, names):492for name in names:493path = os.path.normpath(os.path.join(dirname, name))494if os.path.isfile(path):495p = path[len(base_dir) + 1:]496if not dry_run:497z.write(path, p)498log.debug("adding '%s'", p)499500compression = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED501if not dry_run:502z = zipfile.ZipFile(zip_filename, mode, compression=compression)503for dirname, dirs, files in sorted_walk(base_dir):504visit(z, dirname, files)505z.close()506else:507for dirname, dirs, files in sorted_walk(base_dir):508visit(None, dirname, files)509return zip_filename510511512