| #!python |
| """\ |
| Easy Install |
| ------------ |
| |
| A tool for doing automatic download/extract/build of distutils-based Python |
| packages. For detailed documentation, see the accompanying EasyInstall.txt |
| file, or visit the `EasyInstall home page`__. |
| |
| __ http://peak.telecommunity.com/DevCenter/EasyInstall |
| """ |
| import sys, os.path, zipimport, shutil, tempfile, zipfile, re, stat, random |
| from glob import glob |
| from setuptools import Command |
| from setuptools.sandbox import run_setup |
| from distutils import log, dir_util |
| from distutils.sysconfig import get_python_lib |
| from distutils.errors import DistutilsArgError, DistutilsOptionError, \ |
| DistutilsError |
| from setuptools.archive_util import unpack_archive |
| from setuptools.package_index import PackageIndex, parse_bdist_wininst |
| from setuptools.package_index import URL_SCHEME |
| from setuptools.command import bdist_egg, egg_info |
| from pkg_resources import * |
| sys_executable = os.path.normpath(sys.executable) |
| |
| __all__ = [ |
| 'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg', |
| 'main', 'get_exe_prefixes', |
| ] |
| |
| def samefile(p1,p2): |
| if hasattr(os.path,'samefile') and ( |
| os.path.exists(p1) and os.path.exists(p2) |
| ): |
| return os.path.samefile(p1,p2) |
| return ( |
| os.path.normpath(os.path.normcase(p1)) == |
| os.path.normpath(os.path.normcase(p2)) |
| ) |
| |
| class easy_install(Command): |
| """Manage a download/build/install process""" |
| description = "Find/get/install Python packages" |
| command_consumes_arguments = True |
| |
| user_options = [ |
| ('prefix=', None, "installation prefix"), |
| ("zip-ok", "z", "install package as a zipfile"), |
| ("multi-version", "m", "make apps have to require() a version"), |
| ("upgrade", "U", "force upgrade (searches PyPI for latest versions)"), |
| ("install-dir=", "d", "install package to DIR"), |
| ("script-dir=", "s", "install scripts to DIR"), |
| ("exclude-scripts", "x", "Don't install scripts"), |
| ("always-copy", "a", "Copy all needed packages to install dir"), |
| ("index-url=", "i", "base URL of Python Package Index"), |
| ("find-links=", "f", "additional URL(s) to search for packages"), |
| ("delete-conflicting", "D", "no longer needed; don't use this"), |
| ("ignore-conflicts-at-my-risk", None, |
| "no longer needed; don't use this"), |
| ("build-directory=", "b", |
| "download/extract/build in DIR; keep the results"), |
| ('optimize=', 'O', |
| "also compile with optimization: -O1 for \"python -O\", " |
| "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), |
| ('record=', None, |
| "filename in which to record list of installed files"), |
| ('always-unzip', 'Z', "don't install as a zipfile, no matter what"), |
| ('site-dirs=','S',"list of directories where .pth files work"), |
| ('editable', 'e', "Install specified packages in editable form"), |
| ('no-deps', 'N', "don't install dependencies"), |
| ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), |
| ('local-snapshots-ok', 'l', "allow building eggs from local checkouts"), |
| ] |
| boolean_options = [ |
| 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', |
| 'delete-conflicting', 'ignore-conflicts-at-my-risk', 'editable', |
| 'no-deps', 'local-snapshots-ok', |
| ] |
| negative_opt = {'always-unzip': 'zip-ok'} |
| create_index = PackageIndex |
| |
| def initialize_options(self): |
| self.zip_ok = self.local_snapshots_ok = None |
| self.install_dir = self.script_dir = self.exclude_scripts = None |
| self.index_url = None |
| self.find_links = None |
| self.build_directory = None |
| self.args = None |
| self.optimize = self.record = None |
| self.upgrade = self.always_copy = self.multi_version = None |
| self.editable = self.no_deps = self.allow_hosts = None |
| self.root = self.prefix = self.no_report = None |
| |
| # Options not specifiable via command line |
| self.package_index = None |
| self.pth_file = self.always_copy_from = None |
| self.delete_conflicting = None |
| self.ignore_conflicts_at_my_risk = None |
| self.site_dirs = None |
| self.installed_projects = {} |
| self.sitepy_installed = False |
| # Always read easy_install options, even if we are subclassed, or have |
| # an independent instance created. This ensures that defaults will |
| # always come from the standard configuration file(s)' "easy_install" |
| # section, even if this is a "develop" or "install" command, or some |
| # other embedding. |
| self._dry_run = None |
| self.verbose = self.distribution.verbose |
| self.distribution._set_command_options( |
| self, self.distribution.get_option_dict('easy_install') |
| ) |
| |
| def delete_blockers(self, blockers): |
| for filename in blockers: |
| if os.path.exists(filename) or os.path.islink(filename): |
| log.info("Deleting %s", filename) |
| if not self.dry_run: |
| if os.path.isdir(filename) and not os.path.islink(filename): |
| rmtree(filename) |
| else: |
| os.unlink(filename) |
| |
| def finalize_options(self): |
| self._expand('install_dir','script_dir','build_directory','site_dirs') |
| # If a non-default installation directory was specified, default the |
| # script directory to match it. |
| if self.script_dir is None: |
| self.script_dir = self.install_dir |
| |
| # Let install_dir get set by install_lib command, which in turn |
| # gets its info from the install command, and takes into account |
| # --prefix and --home and all that other crud. |
| self.set_undefined_options('install_lib', |
| ('install_dir','install_dir') |
| ) |
| # Likewise, set default script_dir from 'install_scripts.install_dir' |
| self.set_undefined_options('install_scripts', |
| ('install_dir', 'script_dir') |
| ) |
| # default --record from the install command |
| self.set_undefined_options('install', ('record', 'record')) |
| normpath = map(normalize_path, sys.path) |
| self.all_site_dirs = get_site_dirs() |
| if self.site_dirs is not None: |
| site_dirs = [ |
| os.path.expanduser(s.strip()) for s in self.site_dirs.split(',') |
| ] |
| for d in site_dirs: |
| if not os.path.isdir(d): |
| log.warn("%s (in --site-dirs) does not exist", d) |
| elif normalize_path(d) not in normpath: |
| raise DistutilsOptionError( |
| d+" (in --site-dirs) is not on sys.path" |
| ) |
| else: |
| self.all_site_dirs.append(normalize_path(d)) |
| if not self.editable: self.check_site_dir() |
| self.index_url = self.index_url or "http://pypi.python.org/simple" |
| self.shadow_path = self.all_site_dirs[:] |
| for path_item in self.install_dir, normalize_path(self.script_dir): |
| if path_item not in self.shadow_path: |
| self.shadow_path.insert(0, path_item) |
| |
| if self.allow_hosts is not None: |
| hosts = [s.strip() for s in self.allow_hosts.split(',')] |
| else: |
| hosts = ['*'] |
| if self.package_index is None: |
| self.package_index = self.create_index( |
| self.index_url, search_path = self.shadow_path, hosts=hosts, |
| ) |
| self.local_index = Environment(self.shadow_path+sys.path) |
| |
| if self.find_links is not None: |
| if isinstance(self.find_links, basestring): |
| self.find_links = self.find_links.split() |
| else: |
| self.find_links = [] |
| if self.local_snapshots_ok: |
| self.package_index.scan_egg_links(self.shadow_path+sys.path) |
| self.package_index.add_find_links(self.find_links) |
| self.set_undefined_options('install_lib', ('optimize','optimize')) |
| if not isinstance(self.optimize,int): |
| try: |
| self.optimize = int(self.optimize) |
| if not (0 <= self.optimize <= 2): raise ValueError |
| except ValueError: |
| raise DistutilsOptionError("--optimize must be 0, 1, or 2") |
| |
| if self.delete_conflicting and self.ignore_conflicts_at_my_risk: |
| raise DistutilsOptionError( |
| "Can't use both --delete-conflicting and " |
| "--ignore-conflicts-at-my-risk at the same time" |
| ) |
| if self.editable and not self.build_directory: |
| raise DistutilsArgError( |
| "Must specify a build directory (-b) when using --editable" |
| ) |
| if not self.args: |
| raise DistutilsArgError( |
| "No urls, filenames, or requirements specified (see --help)") |
| |
| self.outputs = [] |
| |
| def run(self): |
| if self.verbose!=self.distribution.verbose: |
| log.set_verbosity(self.verbose) |
| try: |
| for spec in self.args: |
| self.easy_install(spec, not self.no_deps) |
| if self.record: |
| outputs = self.outputs |
| if self.root: # strip any package prefix |
| root_len = len(self.root) |
| for counter in xrange(len(outputs)): |
| outputs[counter] = outputs[counter][root_len:] |
| from distutils import file_util |
| self.execute( |
| file_util.write_file, (self.record, outputs), |
| "writing list of installed files to '%s'" % |
| self.record |
| ) |
| self.warn_deprecated_options() |
| finally: |
| log.set_verbosity(self.distribution.verbose) |
| |
| def pseudo_tempname(self): |
| """Return a pseudo-tempname base in the install directory. |
| This code is intentionally naive; if a malicious party can write to |
| the target directory you're already in deep doodoo. |
| """ |
| try: |
| pid = os.getpid() |
| except: |
| pid = random.randint(0,sys.maxint) |
| return os.path.join(self.install_dir, "test-easy-install-%s" % pid) |
| |
| def warn_deprecated_options(self): |
| if self.delete_conflicting or self.ignore_conflicts_at_my_risk: |
| log.warn( |
| "Note: The -D, --delete-conflicting and" |
| " --ignore-conflicts-at-my-risk no longer have any purpose" |
| " and should not be used." |
| ) |
| |
| def check_site_dir(self): |
| """Verify that self.install_dir is .pth-capable dir, if needed""" |
| instdir = normalize_path(self.install_dir) |
| pth_file = os.path.join(instdir,'easy-install.pth') |
| |
| # Is it a configured, PYTHONPATH, implicit, or explicit site dir? |
| is_site_dir = instdir in self.all_site_dirs |
| |
| if not is_site_dir and not self.multi_version: |
| # No? Then directly test whether it does .pth file processing |
| is_site_dir = self.check_pth_processing() |
| else: |
| # make sure we can write to target dir |
| testfile = self.pseudo_tempname()+'.write-test' |
| test_exists = os.path.exists(testfile) |
| try: |
| if test_exists: os.unlink(testfile) |
| open(testfile,'w').close() |
| os.unlink(testfile) |
| except (OSError,IOError): |
| self.cant_write_to_target() |
| |
| if not is_site_dir and not self.multi_version: |
| # Can't install non-multi to non-site dir |
| raise DistutilsError(self.no_default_version_msg()) |
| |
| if is_site_dir: |
| if self.pth_file is None: |
| self.pth_file = PthDistributions(pth_file, self.all_site_dirs) |
| else: |
| self.pth_file = None |
| |
| PYTHONPATH = os.environ.get('PYTHONPATH','').split(os.pathsep) |
| if instdir not in map(normalize_path, filter(None,PYTHONPATH)): |
| # only PYTHONPATH dirs need a site.py, so pretend it's there |
| self.sitepy_installed = True |
| elif self.multi_version and not os.path.exists(pth_file): |
| self.sitepy_installed = True # don't need site.py in this case |
| self.pth_file = None # and don't create a .pth file |
| self.install_dir = instdir |
| |
| def cant_write_to_target(self): |
| msg = """can't create or remove files in install directory |
| |
| The following error occurred while trying to add or remove files in the |
| installation directory: |
| |
| %s |
| |
| The installation directory you specified (via --install-dir, --prefix, or |
| the distutils default setting) was: |
| |
| %s |
| """ % (sys.exc_info()[1], self.install_dir,) |
| |
| if not os.path.exists(self.install_dir): |
| msg += """ |
| This directory does not currently exist. Please create it and try again, or |
| choose a different installation directory (using the -d or --install-dir |
| option). |
| """ |
| else: |
| msg += """ |
| Perhaps your account does not have write access to this directory? If the |
| installation directory is a system-owned directory, you may need to sign in |
| as the administrator or "root" account. If you do not have administrative |
| access to this machine, you may wish to choose a different installation |
| directory, preferably one that is listed in your PYTHONPATH environment |
| variable. |
| |
| For information on other options, you may wish to consult the |
| documentation at: |
| |
| http://peak.telecommunity.com/EasyInstall.html |
| |
| Please make the appropriate changes for your system and try again. |
| """ |
| raise DistutilsError(msg) |
| |
| |
| |
| |
| def check_pth_processing(self): |
| """Empirically verify whether .pth files are supported in inst. dir""" |
| instdir = self.install_dir |
| log.info("Checking .pth file support in %s", instdir) |
| pth_file = self.pseudo_tempname()+".pth" |
| ok_file = pth_file+'.ok' |
| ok_exists = os.path.exists(ok_file) |
| try: |
| if ok_exists: os.unlink(ok_file) |
| f = open(pth_file,'w') |
| except (OSError,IOError): |
| self.cant_write_to_target() |
| else: |
| try: |
| f.write("import os;open(%r,'w').write('OK')\n" % (ok_file,)) |
| f.close(); f=None |
| executable = sys.executable |
| if os.name=='nt': |
| dirname,basename = os.path.split(executable) |
| alt = os.path.join(dirname,'pythonw.exe') |
| if basename.lower()=='python.exe' and os.path.exists(alt): |
| # use pythonw.exe to avoid opening a console window |
| executable = alt |
| |
| from distutils.spawn import spawn |
| spawn([executable,'-E','-c','pass'],0) |
| |
| if os.path.exists(ok_file): |
| log.info( |
| "TEST PASSED: %s appears to support .pth files", |
| instdir |
| ) |
| return True |
| finally: |
| if f: f.close() |
| if os.path.exists(ok_file): os.unlink(ok_file) |
| if os.path.exists(pth_file): os.unlink(pth_file) |
| if not self.multi_version: |
| log.warn("TEST FAILED: %s does NOT support .pth files", instdir) |
| return False |
| |
| def install_egg_scripts(self, dist): |
| """Write all the scripts for `dist`, unless scripts are excluded""" |
| if not self.exclude_scripts and dist.metadata_isdir('scripts'): |
| for script_name in dist.metadata_listdir('scripts'): |
| self.install_script( |
| dist, script_name, |
| dist.get_metadata('scripts/'+script_name) |
| ) |
| self.install_wrapper_scripts(dist) |
| |
| def add_output(self, path): |
| if os.path.isdir(path): |
| for base, dirs, files in os.walk(path): |
| for filename in files: |
| self.outputs.append(os.path.join(base,filename)) |
| else: |
| self.outputs.append(path) |
| |
| def not_editable(self, spec): |
| if self.editable: |
| raise DistutilsArgError( |
| "Invalid argument %r: you can't use filenames or URLs " |
| "with --editable (except via the --find-links option)." |
| % (spec,) |
| ) |
| |
| def check_editable(self,spec): |
| if not self.editable: |
| return |
| |
| if os.path.exists(os.path.join(self.build_directory, spec.key)): |
| raise DistutilsArgError( |
| "%r already exists in %s; can't do a checkout there" % |
| (spec.key, self.build_directory) |
| ) |
| |
| |
| |
| |
| |
| |
| def easy_install(self, spec, deps=False): |
| tmpdir = tempfile.mkdtemp(prefix="easy_install-") |
| download = None |
| if not self.editable: self.install_site_py() |
| |
| try: |
| if not isinstance(spec,Requirement): |
| if URL_SCHEME(spec): |
| # It's a url, download it to tmpdir and process |
| self.not_editable(spec) |
| download = self.package_index.download(spec, tmpdir) |
| return self.install_item(None, download, tmpdir, deps, True) |
| |
| elif os.path.exists(spec): |
| # Existing file or directory, just process it directly |
| self.not_editable(spec) |
| return self.install_item(None, spec, tmpdir, deps, True) |
| else: |
| spec = parse_requirement_arg(spec) |
| |
| self.check_editable(spec) |
| dist = self.package_index.fetch_distribution( |
| spec, tmpdir, self.upgrade, self.editable, not self.always_copy, |
| self.local_index |
| ) |
| if dist is None: |
| msg = "Could not find suitable distribution for %r" % spec |
| if self.always_copy: |
| msg+=" (--always-copy skips system and development eggs)" |
| raise DistutilsError(msg) |
| elif dist.precedence==DEVELOP_DIST: |
| # .egg-info dists don't need installing, just process deps |
| self.process_distribution(spec, dist, deps, "Using") |
| return dist |
| else: |
| return self.install_item(spec, dist.location, tmpdir, deps) |
| |
| finally: |
| if os.path.exists(tmpdir): |
| rmtree(tmpdir) |
| |
| def install_item(self, spec, download, tmpdir, deps, install_needed=False): |
| |
| # Installation is also needed if file in tmpdir or is not an egg |
| install_needed = install_needed or self.always_copy |
| install_needed = install_needed or os.path.dirname(download) == tmpdir |
| install_needed = install_needed or not download.endswith('.egg') |
| install_needed = install_needed or ( |
| self.always_copy_from is not None and |
| os.path.dirname(normalize_path(download)) == |
| normalize_path(self.always_copy_from) |
| ) |
| |
| if spec and not install_needed: |
| # at this point, we know it's a local .egg, we just don't know if |
| # it's already installed. |
| for dist in self.local_index[spec.project_name]: |
| if dist.location==download: |
| break |
| else: |
| install_needed = True # it's not in the local index |
| |
| log.info("Processing %s", os.path.basename(download)) |
| |
| if install_needed: |
| dists = self.install_eggs(spec, download, tmpdir) |
| for dist in dists: |
| self.process_distribution(spec, dist, deps) |
| else: |
| dists = [self.check_conflicts(self.egg_distribution(download))] |
| self.process_distribution(spec, dists[0], deps, "Using") |
| |
| if spec is not None: |
| for dist in dists: |
| if dist in spec: |
| return dist |
| |
| |
| |
| |
| |
| |
| def process_distribution(self, requirement, dist, deps=True, *info): |
| self.update_pth(dist) |
| self.package_index.add(dist) |
| self.local_index.add(dist) |
| self.install_egg_scripts(dist) |
| self.installed_projects[dist.key] = dist |
| log.info(self.installation_report(requirement, dist, *info)) |
| if dist.has_metadata('dependency_links.txt'): |
| self.package_index.add_find_links( |
| dist.get_metadata_lines('dependency_links.txt') |
| ) |
| if not deps and not self.always_copy: |
| return |
| elif requirement is not None and dist.key != requirement.key: |
| log.warn("Skipping dependencies for %s", dist) |
| return # XXX this is not the distribution we were looking for |
| elif requirement is None or dist not in requirement: |
| # if we wound up with a different version, resolve what we've got |
| distreq = dist.as_requirement() |
| requirement = requirement or distreq |
| requirement = Requirement( |
| distreq.project_name, distreq.specs, requirement.extras |
| ) |
| log.info("Processing dependencies for %s", requirement) |
| try: |
| distros = WorkingSet([]).resolve( |
| [requirement], self.local_index, self.easy_install |
| ) |
| except DistributionNotFound, e: |
| raise DistutilsError( |
| "Could not find required distribution %s" % e.args |
| ) |
| except VersionConflict, e: |
| raise DistutilsError( |
| "Installed distribution %s conflicts with requirement %s" |
| % e.args |
| ) |
| if self.always_copy or self.always_copy_from: |
| # Force all the relevant distros to be copied or activated |
| for dist in distros: |
| if dist.key not in self.installed_projects: |
| self.easy_install(dist.as_requirement()) |
| log.info("Finished processing dependencies for %s", requirement) |
| |
| def should_unzip(self, dist): |
| if self.zip_ok is not None: |
| return not self.zip_ok |
| if dist.has_metadata('not-zip-safe'): |
| return True |
| if not dist.has_metadata('zip-safe'): |
| return True |
| return False |
| |
| def maybe_move(self, spec, dist_filename, setup_base): |
| dst = os.path.join(self.build_directory, spec.key) |
| if os.path.exists(dst): |
| log.warn( |
| "%r already exists in %s; build directory %s will not be kept", |
| spec.key, self.build_directory, setup_base |
| ) |
| return setup_base |
| if os.path.isdir(dist_filename): |
| setup_base = dist_filename |
| else: |
| if os.path.dirname(dist_filename)==setup_base: |
| os.unlink(dist_filename) # get it out of the tmp dir |
| contents = os.listdir(setup_base) |
| if len(contents)==1: |
| dist_filename = os.path.join(setup_base,contents[0]) |
| if os.path.isdir(dist_filename): |
| # if the only thing there is a directory, move it instead |
| setup_base = dist_filename |
| ensure_directory(dst); shutil.move(setup_base, dst) |
| return dst |
| |
| def install_wrapper_scripts(self, dist): |
| if not self.exclude_scripts: |
| for args in get_script_args(dist): |
| self.write_script(*args) |
| |
| |
| |
| def install_script(self, dist, script_name, script_text, dev_path=None): |
| """Generate a legacy script wrapper and install it""" |
| spec = str(dist.as_requirement()) |
| is_script = is_python_script(script_text, script_name) |
| |
| if is_script and dev_path: |
| script_text = get_script_header(script_text) + ( |
| "# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r\n" |
| "__requires__ = %(spec)r\n" |
| "from pkg_resources import require; require(%(spec)r)\n" |
| "del require\n" |
| "__file__ = %(dev_path)r\n" |
| "execfile(__file__)\n" |
| ) % locals() |
| elif is_script: |
| script_text = get_script_header(script_text) + ( |
| "# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r\n" |
| "__requires__ = %(spec)r\n" |
| "import pkg_resources\n" |
| "pkg_resources.run_script(%(spec)r, %(script_name)r)\n" |
| ) % locals() |
| self.write_script(script_name, script_text, 'b') |
| |
| def write_script(self, script_name, contents, mode="t", blockers=()): |
| """Write an executable file to the scripts directory""" |
| self.delete_blockers( # clean up old .py/.pyw w/o a script |
| [os.path.join(self.script_dir,x) for x in blockers]) |
| log.info("Installing %s script to %s", script_name, self.script_dir) |
| target = os.path.join(self.script_dir, script_name) |
| self.add_output(target) |
| |
| if not self.dry_run: |
| ensure_directory(target) |
| f = open(target,"w"+mode) |
| f.write(contents) |
| f.close() |
| chmod(target,0755) |
| |
| |
| |
| |
| def install_eggs(self, spec, dist_filename, tmpdir): |
| # .egg dirs or files are already built, so just return them |
| if dist_filename.lower().endswith('.egg'): |
| return [self.install_egg(dist_filename, tmpdir)] |
| elif dist_filename.lower().endswith('.exe'): |
| return [self.install_exe(dist_filename, tmpdir)] |
| |
| # Anything else, try to extract and build |
| setup_base = tmpdir |
| if os.path.isfile(dist_filename) and not dist_filename.endswith('.py'): |
| unpack_archive(dist_filename, tmpdir, self.unpack_progress) |
| elif os.path.isdir(dist_filename): |
| setup_base = os.path.abspath(dist_filename) |
| |
| if (setup_base.startswith(tmpdir) # something we downloaded |
| and self.build_directory and spec is not None |
| ): |
| setup_base = self.maybe_move(spec, dist_filename, setup_base) |
| |
| # Find the setup.py file |
| setup_script = os.path.join(setup_base, 'setup.py') |
| |
| if not os.path.exists(setup_script): |
| setups = glob(os.path.join(setup_base, '*', 'setup.py')) |
| if not setups: |
| raise DistutilsError( |
| "Couldn't find a setup script in %s" % os.path.abspath(dist_filename) |
| ) |
| if len(setups)>1: |
| raise DistutilsError( |
| "Multiple setup scripts in %s" % os.path.abspath(dist_filename) |
| ) |
| setup_script = setups[0] |
| |
| # Now run it, and return the result |
| if self.editable: |
| log.info(self.report_editable(spec, setup_script)) |
| return [] |
| else: |
| return self.build_and_install(setup_script, setup_base) |
| |
| def egg_distribution(self, egg_path): |
| if os.path.isdir(egg_path): |
| metadata = PathMetadata(egg_path,os.path.join(egg_path,'EGG-INFO')) |
| else: |
| metadata = EggMetadata(zipimport.zipimporter(egg_path)) |
| return Distribution.from_filename(egg_path,metadata=metadata) |
| |
| def install_egg(self, egg_path, tmpdir): |
| destination = os.path.join(self.install_dir,os.path.basename(egg_path)) |
| destination = os.path.abspath(destination) |
| if not self.dry_run: |
| ensure_directory(destination) |
| |
| dist = self.egg_distribution(egg_path) |
| self.check_conflicts(dist) |
| if not samefile(egg_path, destination): |
| if os.path.isdir(destination) and not os.path.islink(destination): |
| dir_util.remove_tree(destination, dry_run=self.dry_run) |
| elif os.path.exists(destination): |
| self.execute(os.unlink,(destination,),"Removing "+destination) |
| uncache_zipdir(destination) |
| if os.path.isdir(egg_path): |
| if egg_path.startswith(tmpdir): |
| f,m = shutil.move, "Moving" |
| else: |
| f,m = shutil.copytree, "Copying" |
| elif self.should_unzip(dist): |
| self.mkpath(destination) |
| f,m = self.unpack_and_compile, "Extracting" |
| elif egg_path.startswith(tmpdir): |
| f,m = shutil.move, "Moving" |
| else: |
| f,m = shutil.copy2, "Copying" |
| |
| self.execute(f, (egg_path, destination), |
| (m+" %s to %s") % |
| (os.path.basename(egg_path),os.path.dirname(destination))) |
| |
| self.add_output(destination) |
| return self.egg_distribution(destination) |
| |
| def install_exe(self, dist_filename, tmpdir): |
| # See if it's valid, get data |
| cfg = extract_wininst_cfg(dist_filename) |
| if cfg is None: |
| raise DistutilsError( |
| "%s is not a valid distutils Windows .exe" % dist_filename |
| ) |
| # Create a dummy distribution object until we build the real distro |
| dist = Distribution(None, |
| project_name=cfg.get('metadata','name'), |
| version=cfg.get('metadata','version'), platform="win32" |
| ) |
| |
| # Convert the .exe to an unpacked egg |
| egg_path = dist.location = os.path.join(tmpdir, dist.egg_name()+'.egg') |
| egg_tmp = egg_path+'.tmp' |
| egg_info = os.path.join(egg_tmp, 'EGG-INFO') |
| pkg_inf = os.path.join(egg_info, 'PKG-INFO') |
| ensure_directory(pkg_inf) # make sure EGG-INFO dir exists |
| dist._provider = PathMetadata(egg_tmp, egg_info) # XXX |
| self.exe_to_egg(dist_filename, egg_tmp) |
| |
| # Write EGG-INFO/PKG-INFO |
| if not os.path.exists(pkg_inf): |
| f = open(pkg_inf,'w') |
| f.write('Metadata-Version: 1.0\n') |
| for k,v in cfg.items('metadata'): |
| if k!='target_version': |
| f.write('%s: %s\n' % (k.replace('_','-').title(), v)) |
| f.close() |
| script_dir = os.path.join(egg_info,'scripts') |
| self.delete_blockers( # delete entry-point scripts to avoid duping |
| [os.path.join(script_dir,args[0]) for args in get_script_args(dist)] |
| ) |
| # Build .egg file from tmpdir |
| bdist_egg.make_zipfile( |
| egg_path, egg_tmp, verbose=self.verbose, dry_run=self.dry_run |
| ) |
| # install the .egg |
| return self.install_egg(egg_path, tmpdir) |
| |
| def exe_to_egg(self, dist_filename, egg_tmp): |
| """Extract a bdist_wininst to the directories an egg would use""" |
| # Check for .pth file and set up prefix translations |
| prefixes = get_exe_prefixes(dist_filename) |
| to_compile = [] |
| native_libs = [] |
| top_level = {} |
| def process(src,dst): |
| s = src.lower() |
| for old,new in prefixes: |
| if s.startswith(old): |
| src = new+src[len(old):] |
| parts = src.split('/') |
| dst = os.path.join(egg_tmp, *parts) |
| dl = dst.lower() |
| if dl.endswith('.pyd') or dl.endswith('.dll'): |
| parts[-1] = bdist_egg.strip_module(parts[-1]) |
| top_level[os.path.splitext(parts[0])[0]] = 1 |
| native_libs.append(src) |
| elif dl.endswith('.py') and old!='SCRIPTS/': |
| top_level[os.path.splitext(parts[0])[0]] = 1 |
| to_compile.append(dst) |
| return dst |
| if not src.endswith('.pth'): |
| log.warn("WARNING: can't process %s", src) |
| return None |
| # extract, tracking .pyd/.dll->native_libs and .py -> to_compile |
| unpack_archive(dist_filename, egg_tmp, process) |
| stubs = [] |
| for res in native_libs: |
| if res.lower().endswith('.pyd'): # create stubs for .pyd's |
| parts = res.split('/') |
| resource = parts[-1] |
| parts[-1] = bdist_egg.strip_module(parts[-1])+'.py' |
| pyfile = os.path.join(egg_tmp, *parts) |
| to_compile.append(pyfile); stubs.append(pyfile) |
| bdist_egg.write_stub(resource, pyfile) |
| self.byte_compile(to_compile) # compile .py's |
| bdist_egg.write_safety_flag(os.path.join(egg_tmp,'EGG-INFO'), |
| bdist_egg.analyze_egg(egg_tmp, stubs)) # write zip-safety flag |
| |
| for name in 'top_level','native_libs': |
| if locals()[name]: |
| txt = os.path.join(egg_tmp, 'EGG-INFO', name+'.txt') |
| if not os.path.exists(txt): |
| open(txt,'w').write('\n'.join(locals()[name])+'\n') |
| |
| def check_conflicts(self, dist): |
| """Verify that there are no conflicting "old-style" packages""" |
| |
| return dist # XXX temporarily disable until new strategy is stable |
| from imp import find_module, get_suffixes |
| from glob import glob |
| |
| blockers = [] |
| names = dict.fromkeys(dist._get_metadata('top_level.txt')) # XXX private attr |
| |
| exts = {'.pyc':1, '.pyo':1} # get_suffixes() might leave one out |
| for ext,mode,typ in get_suffixes(): |
| exts[ext] = 1 |
| |
| for path,files in expand_paths([self.install_dir]+self.all_site_dirs): |
| for filename in files: |
| base,ext = os.path.splitext(filename) |
| if base in names: |
| if not ext: |
| # no extension, check for package |
| try: |
| f, filename, descr = find_module(base, [path]) |
| except ImportError: |
| continue |
| else: |
| if f: f.close() |
| if filename not in blockers: |
| blockers.append(filename) |
| elif ext in exts and base!='site': # XXX ugh |
| blockers.append(os.path.join(path,filename)) |
| if blockers: |
| self.found_conflicts(dist, blockers) |
| |
| return dist |
| |
| def found_conflicts(self, dist, blockers): |
| if self.delete_conflicting: |
| log.warn("Attempting to delete conflicting packages:") |
| return self.delete_blockers(blockers) |
| |
| msg = """\ |
| ------------------------------------------------------------------------- |
| CONFLICT WARNING: |
| |
| The following modules or packages have the same names as modules or |
| packages being installed, and will be *before* the installed packages in |
| Python's search path. You MUST remove all of the relevant files and |
| directories before you will be able to use the package(s) you are |
| installing: |
| |
| %s |
| |
| """ % '\n '.join(blockers) |
| |
| if self.ignore_conflicts_at_my_risk: |
| msg += """\ |
| (Note: you can run EasyInstall on '%s' with the |
| --delete-conflicting option to attempt deletion of the above files |
| and/or directories.) |
| """ % dist.project_name |
| else: |
| msg += """\ |
| Note: you can attempt this installation again with EasyInstall, and use |
| either the --delete-conflicting (-D) option or the |
| --ignore-conflicts-at-my-risk option, to either delete the above files |
| and directories, or to ignore the conflicts, respectively. Note that if |
| you ignore the conflicts, the installed package(s) may not work. |
| """ |
| msg += """\ |
| ------------------------------------------------------------------------- |
| """ |
| sys.stderr.write(msg) |
| sys.stderr.flush() |
| if not self.ignore_conflicts_at_my_risk: |
| raise DistutilsError("Installation aborted due to conflicts") |
| |
| def installation_report(self, req, dist, what="Installed"): |
| """Helpful installation message for display to package users""" |
| msg = "\n%(what)s %(eggloc)s%(extras)s" |
| if self.multi_version and not self.no_report: |
| msg += """ |
| |
| Because this distribution was installed --multi-version, before you can |
| import modules from this package in an application, you will need to |
| 'import pkg_resources' and then use a 'require()' call similar to one of |
| these examples, in order to select the desired version: |
| |
| pkg_resources.require("%(name)s") # latest installed version |
| pkg_resources.require("%(name)s==%(version)s") # this exact version |
| pkg_resources.require("%(name)s>=%(version)s") # this version or higher |
| """ |
| if self.install_dir not in map(normalize_path,sys.path): |
| msg += """ |
| |
| Note also that the installation directory must be on sys.path at runtime for |
| this to work. (e.g. by being the application's script directory, by being on |
| PYTHONPATH, or by being added to sys.path by your code.) |
| """ |
| eggloc = dist.location |
| name = dist.project_name |
| version = dist.version |
| extras = '' # TODO: self.report_extras(req, dist) |
| return msg % locals() |
| |
| def report_editable(self, spec, setup_script): |
| dirname = os.path.dirname(setup_script) |
| python = sys.executable |
| return """\nExtracted editable version of %(spec)s to %(dirname)s |
| |
| If it uses setuptools in its setup script, you can activate it in |
| "development" mode by going to that directory and running:: |
| |
| %(python)s setup.py develop |
| |
| See the setuptools documentation for the "develop" command for more info. |
| """ % locals() |
| |
| def run_setup(self, setup_script, setup_base, args): |
| sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg) |
| sys.modules.setdefault('distutils.command.egg_info', egg_info) |
| |
| args = list(args) |
| if self.verbose>2: |
| v = 'v' * (self.verbose - 1) |
| args.insert(0,'-'+v) |
| elif self.verbose<2: |
| args.insert(0,'-q') |
| if self.dry_run: |
| args.insert(0,'-n') |
| log.info( |
| "Running %s %s", setup_script[len(setup_base)+1:], ' '.join(args) |
| ) |
| try: |
| run_setup(setup_script, args) |
| except SystemExit, v: |
| raise DistutilsError("Setup script exited with %s" % (v.args[0],)) |
| |
| def build_and_install(self, setup_script, setup_base): |
| args = ['bdist_egg', '--dist-dir'] |
| dist_dir = tempfile.mkdtemp( |
| prefix='egg-dist-tmp-', dir=os.path.dirname(setup_script) |
| ) |
| try: |
| args.append(dist_dir) |
| self.run_setup(setup_script, setup_base, args) |
| all_eggs = Environment([dist_dir]) |
| eggs = [] |
| for key in all_eggs: |
| for dist in all_eggs[key]: |
| eggs.append(self.install_egg(dist.location, setup_base)) |
| if not eggs and not self.dry_run: |
| log.warn("No eggs found in %s (setup script problem?)", |
| dist_dir) |
| return eggs |
| finally: |
| rmtree(dist_dir) |
| log.set_verbosity(self.verbose) # restore our log verbosity |
| |
| def update_pth(self,dist): |
| if self.pth_file is None: |
| return |
| |
| for d in self.pth_file[dist.key]: # drop old entries |
| if self.multi_version or d.location != dist.location: |
| log.info("Removing %s from easy-install.pth file", d) |
| self.pth_file.remove(d) |
| if d.location in self.shadow_path: |
| self.shadow_path.remove(d.location) |
| |
| if not self.multi_version: |
| if dist.location in self.pth_file.paths: |
| log.info( |
| "%s is already the active version in easy-install.pth", |
| dist |
| ) |
| else: |
| log.info("Adding %s to easy-install.pth file", dist) |
| self.pth_file.add(dist) # add new entry |
| if dist.location not in self.shadow_path: |
| self.shadow_path.append(dist.location) |
| |
| if not self.dry_run: |
| |
| self.pth_file.save() |
| |
| if dist.key=='setuptools': |
| # Ensure that setuptools itself never becomes unavailable! |
| # XXX should this check for latest version? |
| filename = os.path.join(self.install_dir,'setuptools.pth') |
| if os.path.islink(filename): os.unlink(filename) |
| f = open(filename, 'wt') |
| f.write(self.pth_file.make_relative(dist.location)+'\n') |
| f.close() |
| |
| def unpack_progress(self, src, dst): |
| # Progress filter for unpacking |
| log.debug("Unpacking %s to %s", src, dst) |
| return dst # only unpack-and-compile skips files for dry run |
| |
| def unpack_and_compile(self, egg_path, destination): |
| to_compile = []; to_chmod = [] |
| |
| def pf(src,dst): |
| if dst.endswith('.py') and not src.startswith('EGG-INFO/'): |
| to_compile.append(dst) |
| elif dst.endswith('.dll') or dst.endswith('.so'): |
| to_chmod.append(dst) |
| self.unpack_progress(src,dst) |
| return not self.dry_run and dst or None |
| |
| unpack_archive(egg_path, destination, pf) |
| self.byte_compile(to_compile) |
| if not self.dry_run: |
| for f in to_chmod: |
| mode = ((os.stat(f)[stat.ST_MODE]) | 0555) & 07755 |
| chmod(f, mode) |
| |
| def byte_compile(self, to_compile): |
| from distutils.util import byte_compile |
| try: |
| # try to make the byte compile messages quieter |
| log.set_verbosity(self.verbose - 1) |
| |
| byte_compile(to_compile, optimize=0, force=1, dry_run=self.dry_run) |
| if self.optimize: |
| byte_compile( |
| to_compile, optimize=self.optimize, force=1, |
| dry_run=self.dry_run |
| ) |
| finally: |
| log.set_verbosity(self.verbose) # restore original verbosity |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| def no_default_version_msg(self): |
| return """bad install directory or PYTHONPATH |
| |
| You are attempting to install a package to a directory that is not |
| on PYTHONPATH and which Python does not read ".pth" files from. The |
| installation directory you specified (via --install-dir, --prefix, or |
| the distutils default setting) was: |
| |
| %s |
| |
| and your PYTHONPATH environment variable currently contains: |
| |
| %r |
| |
| Here are some of your options for correcting the problem: |
| |
| * You can choose a different installation directory, i.e., one that is |
| on PYTHONPATH or supports .pth files |
| |
| * You can add the installation directory to the PYTHONPATH environment |
| variable. (It must then also be on PYTHONPATH whenever you run |
| Python and want to use the package(s) you are installing.) |
| |
| * You can set up the installation directory to support ".pth" files by |
| using one of the approaches described here: |
| |
| http://peak.telecommunity.com/EasyInstall.html#custom-installation-locations |
| |
| Please make the appropriate changes for your system and try again.""" % ( |
| self.install_dir, os.environ.get('PYTHONPATH','') |
| ) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| def install_site_py(self): |
| """Make sure there's a site.py in the target dir, if needed""" |
| |
| if self.sitepy_installed: |
| return # already did it, or don't need to |
| |
| sitepy = os.path.join(self.install_dir, "site.py") |
| source = resource_string(Requirement.parse("setuptools"), "site.py") |
| current = "" |
| |
| if os.path.exists(sitepy): |
| log.debug("Checking existing site.py in %s", self.install_dir) |
| current = open(sitepy,'rb').read() |
| if not current.startswith('def __boot():'): |
| raise DistutilsError( |
| "%s is not a setuptools-generated site.py; please" |
| " remove it." % sitepy |
| ) |
| |
| if current != source: |
| log.info("Creating %s", sitepy) |
| if not self.dry_run: |
| ensure_directory(sitepy) |
| f = open(sitepy,'wb') |
| f.write(source) |
| f.close() |
| self.byte_compile([sitepy]) |
| |
| self.sitepy_installed = True |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| INSTALL_SCHEMES = dict( |
| posix = dict( |
| install_dir = '$base/lib/python$py_version_short/site-packages', |
| script_dir = '$base/bin', |
| ), |
| ) |
| |
| DEFAULT_SCHEME = dict( |
| install_dir = '$base/Lib/site-packages', |
| script_dir = '$base/Scripts', |
| ) |
| |
| def _expand(self, *attrs): |
| config_vars = self.get_finalized_command('install').config_vars |
| |
| if self.prefix: |
| # Set default install_dir/scripts from --prefix |
| config_vars = config_vars.copy() |
| config_vars['base'] = self.prefix |
| scheme = self.INSTALL_SCHEMES.get(os.name,self.DEFAULT_SCHEME) |
| for attr,val in scheme.items(): |
| if getattr(self,attr,None) is None: |
| setattr(self,attr,val) |
| |
| from distutils.util import subst_vars |
| for attr in attrs: |
| val = getattr(self, attr) |
| if val is not None: |
| val = subst_vars(val, config_vars) |
| if os.name == 'posix': |
| val = os.path.expanduser(val) |
| setattr(self, attr, val) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| def get_site_dirs(): |
| # return a list of 'site' dirs |
| sitedirs = filter(None,os.environ.get('PYTHONPATH','').split(os.pathsep)) |
| prefixes = [sys.prefix] |
| if sys.exec_prefix != sys.prefix: |
| prefixes.append(sys.exec_prefix) |
| for prefix in prefixes: |
| if prefix: |
| if sys.platform in ('os2emx', 'riscos'): |
| sitedirs.append(os.path.join(prefix, "Lib", "site-packages")) |
| elif os.sep == '/': |
| sitedirs.extend([os.path.join(prefix, |
| "lib", |
| "python" + sys.version[:3], |
| "site-packages"), |
| os.path.join(prefix, "lib", "site-python")]) |
| else: |
| sitedirs.extend( |
| [prefix, os.path.join(prefix, "lib", "site-packages")] |
| ) |
| if sys.platform == 'darwin': |
| # for framework builds *only* we add the standard Apple |
| # locations. Currently only per-user, but /Library and |
| # /Network/Library could be added too |
| if 'Python.framework' in prefix: |
| home = os.environ.get('HOME') |
| if home: |
| sitedirs.append( |
| os.path.join(home, |
| 'Library', |
| 'Python', |
| sys.version[:3], |
| 'site-packages')) |
| for plat_specific in (0,1): |
| site_lib = get_python_lib(plat_specific) |
| if site_lib not in sitedirs: sitedirs.append(site_lib) |
| |
| sitedirs = map(normalize_path, sitedirs) |
| return sitedirs |
| |
| |
| def expand_paths(inputs): |
| """Yield sys.path directories that might contain "old-style" packages""" |
| |
| seen = {} |
| |
| for dirname in inputs: |
| dirname = normalize_path(dirname) |
| if dirname in seen: |
| continue |
| |
| seen[dirname] = 1 |
| if not os.path.isdir(dirname): |
| continue |
| |
| files = os.listdir(dirname) |
| yield dirname, files |
| |
| for name in files: |
| if not name.endswith('.pth'): |
| # We only care about the .pth files |
| continue |
| if name in ('easy-install.pth','setuptools.pth'): |
| # Ignore .pth files that we control |
| continue |
| |
| # Read the .pth file |
| f = open(os.path.join(dirname,name)) |
| lines = list(yield_lines(f)) |
| f.close() |
| |
| # Yield existing non-dupe, non-import directory lines from it |
| for line in lines: |
| if not line.startswith("import"): |
| line = normalize_path(line.rstrip()) |
| if line not in seen: |
| seen[line] = 1 |
| if not os.path.isdir(line): |
| continue |
| yield line, os.listdir(line) |
| |
| |
| def extract_wininst_cfg(dist_filename): |
| """Extract configuration data from a bdist_wininst .exe |
| |
| Returns a ConfigParser.RawConfigParser, or None |
| """ |
| f = open(dist_filename,'rb') |
| try: |
| endrec = zipfile._EndRecData(f) |
| if endrec is None: |
| return None |
| |
| prepended = (endrec[9] - endrec[5]) - endrec[6] |
| if prepended < 12: # no wininst data here |
| return None |
| f.seek(prepended-12) |
| |
| import struct, StringIO, ConfigParser |
| tag, cfglen, bmlen = struct.unpack("<iii",f.read(12)) |
| if tag not in (0x1234567A, 0x1234567B): |
| return None # not a valid tag |
| |
| f.seek(prepended-(12+cfglen)) |
| cfg = ConfigParser.RawConfigParser({'version':'','target_version':''}) |
| try: |
| cfg.readfp(StringIO.StringIO(f.read(cfglen).split(chr(0),1)[0])) |
| except ConfigParser.Error: |
| return None |
| if not cfg.has_section('metadata') or not cfg.has_section('Setup'): |
| return None |
| return cfg |
| |
| finally: |
| f.close() |
| |
| |
| |
| |
| |
| |
| |
| |
| def get_exe_prefixes(exe_filename): |
| """Get exe->egg path translations for a given .exe file""" |
| |
| prefixes = [ |
| ('PURELIB/', ''), ('PLATLIB/pywin32_system32', ''), |
| ('PLATLIB/', ''), |
| ('SCRIPTS/', 'EGG-INFO/scripts/') |
| ] |
| z = zipfile.ZipFile(exe_filename) |
| try: |
| for info in z.infolist(): |
| name = info.filename |
| parts = name.split('/') |
| if len(parts)==3 and parts[2]=='PKG-INFO': |
| if parts[1].endswith('.egg-info'): |
| prefixes.insert(0,('/'.join(parts[:2]), 'EGG-INFO/')) |
| break |
| if len(parts)!=2 or not name.endswith('.pth'): |
| continue |
| if name.endswith('-nspkg.pth'): |
| continue |
| if parts[0].upper() in ('PURELIB','PLATLIB'): |
| for pth in yield_lines(z.read(name)): |
| pth = pth.strip().replace('\\','/') |
| if not pth.startswith('import'): |
| prefixes.append((('%s/%s/' % (parts[0],pth)), '')) |
| finally: |
| z.close() |
| prefixes = [(x.lower(),y) for x, y in prefixes] |
| prefixes.sort(); prefixes.reverse() |
| return prefixes |
| |
| |
| def parse_requirement_arg(spec): |
| try: |
| return Requirement.parse(spec) |
| except ValueError: |
| raise DistutilsError( |
| "Not a URL, existing file, or requirement spec: %r" % (spec,) |
| ) |
| |
| class PthDistributions(Environment): |
| """A .pth file with Distribution paths in it""" |
| |
| dirty = False |
| |
| def __init__(self, filename, sitedirs=()): |
| self.filename = filename; self.sitedirs=map(normalize_path, sitedirs) |
| self.basedir = normalize_path(os.path.dirname(self.filename)) |
| self._load(); Environment.__init__(self, [], None, None) |
| for path in yield_lines(self.paths): |
| map(self.add, find_distributions(path, True)) |
| |
| def _load(self): |
| self.paths = [] |
| saw_import = False |
| seen = dict.fromkeys(self.sitedirs) |
| if os.path.isfile(self.filename): |
| for line in open(self.filename,'rt'): |
| if line.startswith('import'): |
| saw_import = True |
| continue |
| path = line.rstrip() |
| self.paths.append(path) |
| if not path.strip() or path.strip().startswith('#'): |
| continue |
| # skip non-existent paths, in case somebody deleted a package |
| # manually, and duplicate paths as well |
| path = self.paths[-1] = normalize_path( |
| os.path.join(self.basedir,path) |
| ) |
| if not os.path.exists(path) or path in seen: |
| self.paths.pop() # skip it |
| self.dirty = True # we cleaned up, so we're dirty now :) |
| continue |
| seen[path] = 1 |
| |
| if self.paths and not saw_import: |
| self.dirty = True # ensure anything we touch has import wrappers |
| while self.paths and not self.paths[-1].strip(): |
| self.paths.pop() |
| |
| def save(self): |
| """Write changed .pth file back to disk""" |
| if not self.dirty: |
| return |
| |
| data = '\n'.join(map(self.make_relative,self.paths)) |
| if data: |
| log.debug("Saving %s", self.filename) |
| data = ( |
| "import sys; sys.__plen = len(sys.path)\n" |
| "%s\n" |
| "import sys; new=sys.path[sys.__plen:];" |
| " del sys.path[sys.__plen:];" |
| " p=getattr(sys,'__egginsert',0); sys.path[p:p]=new;" |
| " sys.__egginsert = p+len(new)\n" |
| ) % data |
| |
| if os.path.islink(self.filename): |
| os.unlink(self.filename) |
| f = open(self.filename,'wb') |
| f.write(data); f.close() |
| |
| elif os.path.exists(self.filename): |
| log.debug("Deleting empty %s", self.filename) |
| os.unlink(self.filename) |
| |
| self.dirty = False |
| |
| def add(self,dist): |
| """Add `dist` to the distribution map""" |
| if dist.location not in self.paths and dist.location not in self.sitedirs: |
| self.paths.append(dist.location); self.dirty = True |
| Environment.add(self,dist) |
| |
| def remove(self,dist): |
| """Remove `dist` from the distribution map""" |
| while dist.location in self.paths: |
| self.paths.remove(dist.location); self.dirty = True |
| Environment.remove(self,dist) |
| |
| |
| def make_relative(self,path): |
| npath, last = os.path.split(normalize_path(path)) |
| baselen = len(self.basedir) |
| parts = [last] |
| sep = os.altsep=='/' and '/' or os.sep |
| while len(npath)>=baselen: |
| if npath==self.basedir: |
| parts.append(os.curdir) |
| parts.reverse() |
| return sep.join(parts) |
| npath, last = os.path.split(npath) |
| parts.append(last) |
| else: |
| return path |
| |
| def get_script_header(script_text, executable=sys_executable, wininst=False): |
| """Create a #! line, getting options (if any) from script_text""" |
| from distutils.command.build_scripts import first_line_re |
| first = (script_text+'\n').splitlines()[0] |
| match = first_line_re.match(first) |
| options = '' |
| if match: |
| options = match.group(1) or '' |
| if options: options = ' '+options |
| if wininst: |
| executable = "python.exe" |
| else: |
| executable = nt_quote_arg(executable) |
| hdr = "#!%(executable)s%(options)s\n" % locals() |
| if unicode(hdr,'ascii','ignore').encode('ascii') != hdr: |
| # Non-ascii path to sys.executable, use -x to prevent warnings |
| if options: |
| if options.strip().startswith('-'): |
| options = ' -x'+options.strip()[1:] |
| # else: punt, we can't do it, let the warning happen anyway |
| else: |
| options = ' -x' |
| executable = fix_jython_executable(executable, options) |
| hdr = "#!%(executable)s%(options)s\n" % locals() |
| return hdr |
| |
| def auto_chmod(func, arg, exc): |
| if func is os.remove and os.name=='nt': |
| chmod(arg, stat.S_IWRITE) |
| return func(arg) |
| exc = sys.exc_info() |
| raise exc[0], (exc[1][0], exc[1][1] + (" %s %s" % (func,arg))) |
| |
| def uncache_zipdir(path): |
| """Ensure that the importer caches dont have stale info for `path`""" |
| from zipimport import _zip_directory_cache as zdc |
| _uncache(path, zdc) |
| _uncache(path, sys.path_importer_cache) |
| |
| def _uncache(path, cache): |
| if path in cache: |
| del cache[path] |
| else: |
| path = normalize_path(path) |
| for p in cache: |
| if normalize_path(p)==path: |
| del cache[p] |
| return |
| |
| def is_python(text, filename='<string>'): |
| "Is this string a valid Python script?" |
| try: |
| compile(text, filename, 'exec') |
| except (SyntaxError, TypeError): |
| return False |
| else: |
| return True |
| |
| def is_sh(executable): |
| """Determine if the specified executable is a .sh (contains a #! line)""" |
| try: |
| fp = open(executable) |
| magic = fp.read(2) |
| fp.close() |
| except (OSError,IOError): return executable |
| return magic == '#!' |
| |
| def nt_quote_arg(arg): |
| """Quote a command line argument according to Windows parsing rules""" |
| |
| result = [] |
| needquote = False |
| nb = 0 |
| |
| needquote = (" " in arg) or ("\t" in arg) |
| if needquote: |
| result.append('"') |
| |
| for c in arg: |
| if c == '\\': |
| nb += 1 |
| elif c == '"': |
| # double preceding backslashes, then add a \" |
| result.append('\\' * (nb*2) + '\\"') |
| nb = 0 |
| else: |
| if nb: |
| result.append('\\' * nb) |
| nb = 0 |
| result.append(c) |
| |
| if nb: |
| result.append('\\' * nb) |
| |
| if needquote: |
| result.append('\\' * nb) # double the trailing backslashes |
| result.append('"') |
| |
| return ''.join(result) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| def is_python_script(script_text, filename): |
| """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. |
| """ |
| if filename.endswith('.py') or filename.endswith('.pyw'): |
| return True # extension says it's Python |
| if is_python(script_text, filename): |
| return True # it's syntactically valid Python |
| if script_text.startswith('#!'): |
| # It begins with a '#!' line, so check if 'python' is in it somewhere |
| return 'python' in script_text.splitlines()[0].lower() |
| |
| return False # Not any Python I can recognize |
| |
| try: |
| from os import chmod as _chmod |
| except ImportError: |
| # Jython compatibility |
| def _chmod(*args): pass |
| |
| def chmod(path, mode): |
| log.debug("changing mode of %s to %o", path, mode) |
| try: |
| _chmod(path, mode) |
| except os.error, e: |
| log.debug("chmod failed: %s", e) |
| |
| def fix_jython_executable(executable, options): |
| if sys.platform.startswith('java') and is_sh(executable): |
| # Workaround Jython's sys.executable being a .sh (an invalid |
| # shebang line interpreter) |
| if options: |
| # Can't apply the workaround, leave it broken |
| log.warn("WARNING: Unable to adapt shebang line for Jython," |
| " the following script is NOT executable\n" |
| " see http://bugs.jython.org/issue1112 for" |
| " more information.") |
| else: |
| return '/usr/bin/env %s' % executable |
| return executable |
| |
| |
| def get_script_args(dist, executable=sys_executable, wininst=False): |
| """Yield write_script() argument tuples for a distribution's entrypoints""" |
| spec = str(dist.as_requirement()) |
| header = get_script_header("", executable, wininst) |
| for group in 'console_scripts', 'gui_scripts': |
| for name,ep in dist.get_entry_map(group).items(): |
| script_text = ( |
| "# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r\n" |
| "__requires__ = %(spec)r\n" |
| "import sys\n" |
| "from pkg_resources import load_entry_point\n" |
| "\n" |
| "sys.exit(\n" |
| " load_entry_point(%(spec)r, %(group)r, %(name)r)()\n" |
| ")\n" |
| ) % locals() |
| if sys.platform=='win32' or wininst: |
| # On Windows/wininst, add a .py extension and an .exe launcher |
| if group=='gui_scripts': |
| ext, launcher = '-script.pyw', 'gui.exe' |
| old = ['.pyw'] |
| new_header = re.sub('(?i)python.exe','pythonw.exe',header) |
| else: |
| ext, launcher = '-script.py', 'cli.exe' |
| old = ['.py','.pyc','.pyo'] |
| new_header = re.sub('(?i)pythonw.exe','python.exe',header) |
| |
| if os.path.exists(new_header[2:-1]) or sys.platform!='win32': |
| hdr = new_header |
| else: |
| hdr = header |
| yield (name+ext, hdr+script_text, 't', [name+x for x in old]) |
| yield ( |
| name+'.exe', resource_string('setuptools', launcher), |
| 'b') # write in binary mode |
| yield (name+'.exe.manifest', _launcher_manifest % (name,), 't') |
| else: |
| # On other platforms, we assume the right thing to do is to |
| # just write the stub with no extension. |
| yield (name, header+script_text) |
| |
| _launcher_manifest = """ |
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> |
| <assemblyIdentity version="1.0.0.0" |
| processorArchitecture="X86" |
| name="%s.exe" |
| type="win32"/> |
| |
| <!-- Identify the application security requirements. --> |
| <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> |
| <security> |
| <requestedPrivileges> |
| <requestedExecutionLevel level="asInvoker" uiAccess="false"/> |
| </requestedPrivileges> |
| </security> |
| </trustInfo> |
| </assembly>""" |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| def rmtree(path, ignore_errors=False, onerror=auto_chmod): |
| """Recursively delete a directory tree. |
| |
| This code is taken from the Python 2.4 version of 'shutil', because |
| the 2.3 version doesn't really work right. |
| """ |
| if ignore_errors: |
| def onerror(*args): |
| pass |
| elif onerror is None: |
| def onerror(*args): |
| raise |
| names = [] |
| try: |
| names = os.listdir(path) |
| except os.error, err: |
| onerror(os.listdir, path, sys.exc_info()) |
| for name in names: |
| fullname = os.path.join(path, name) |
| try: |
| mode = os.lstat(fullname).st_mode |
| except os.error: |
| mode = 0 |
| if stat.S_ISDIR(mode): |
| rmtree(fullname, ignore_errors, onerror) |
| else: |
| try: |
| os.remove(fullname) |
| except os.error, err: |
| onerror(os.remove, fullname, sys.exc_info()) |
| try: |
| os.rmdir(path) |
| except os.error: |
| onerror(os.rmdir, path, sys.exc_info()) |
| |
| def bootstrap(): |
| # This function is called when setuptools*.egg is run using /bin/sh |
| import setuptools; argv0 = os.path.dirname(setuptools.__path__[0]) |
| sys.argv[0] = argv0; sys.argv.append(argv0); main() |
| |
| |
| def main(argv=None, **kw): |
| from setuptools import setup |
| from setuptools.dist import Distribution |
| import distutils.core |
| |
| USAGE = """\ |
| usage: %(script)s [options] requirement_or_url ... |
| or: %(script)s --help |
| """ |
| |
| def gen_usage (script_name): |
| script = os.path.basename(script_name) |
| return USAGE % vars() |
| |
| def with_ei_usage(f): |
| old_gen_usage = distutils.core.gen_usage |
| try: |
| distutils.core.gen_usage = gen_usage |
| return f() |
| finally: |
| distutils.core.gen_usage = old_gen_usage |
| |
| class DistributionWithoutHelpCommands(Distribution): |
| common_usage = "" |
| def _show_help(self,*args,**kw): |
| with_ei_usage(lambda: Distribution._show_help(self,*args,**kw)) |
| |
| if argv is None: |
| argv = sys.argv[1:] |
| |
| with_ei_usage(lambda: |
| setup( |
| script_args = ['-q','easy_install', '-v']+argv, |
| script_name = sys.argv[0] or 'easy_install', |
| distclass=DistributionWithoutHelpCommands, **kw |
| ) |
| ) |
| |
| |
| |
| |