summaryrefslogtreecommitdiff
path: root/easy_install.py
diff options
context:
space:
mode:
authorpje <pje@6015fed2-1504-0410-9fe1-9d1591cc4771>2005-07-06 02:10:48 +0000
committerpje <pje@6015fed2-1504-0410-9fe1-9d1591cc4771>2005-07-06 02:10:48 +0000
commit3e18e88af8a190c746b52b1147bcb93b9b291b9c (patch)
tree1c7fa4ed785bcf70d5ea350280017ce93e0c7ae6 /easy_install.py
parent0e8fb6bac55e39187b5c65881b7ccf7a655a3a29 (diff)
downloadpython-setuptools-3e18e88af8a190c746b52b1147bcb93b9b291b9c.tar.gz
Made ``easy_install`` a standard ``setuptools`` command, moving it from
the ``easy_install`` module to ``setuptools.command.easy_install``. Note that if you were importing or extending it, you must now change your imports accordingly. ``easy_install.py`` is still installed as a script, but not as a module. git-svn-id: http://svn.python.org/projects/sandbox/trunk/setuptools@41079 6015fed2-1504-0410-9fe1-9d1591cc4771
Diffstat (limited to 'easy_install.py')
-rwxr-xr-xeasy_install.py805
1 files changed, 2 insertions, 803 deletions
diff --git a/easy_install.py b/easy_install.py
index 9ce3a0a..4b204c4 100755
--- a/easy_install.py
+++ b/easy_install.py
@@ -11,810 +11,9 @@ file, or visit the `EasyInstall home page`__.
__ http://peak.telecommunity.com/DevCenter/EasyInstall
"""
-import sys, os.path, zipimport, shutil, tempfile, zipfile
-
-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
-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
-from pkg_resources import *
-
-__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 = [
- ("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"),
- ("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"),
- ]
- boolean_options = [
- 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy'
- ]
- create_index = PackageIndex
-
- def initialize_options(self):
- self.zip_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
- # Options not specifiable via command line
- self.package_index = None
- self.pth_file = None
-
- def finalize_options(self):
- # 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'))
-
- site_packages = get_python_lib()
- instdir = self.install_dir
-
- if instdir is None or samefile(site_packages,instdir):
- instdir = site_packages
- if self.pth_file is None:
- self.pth_file = PthDistributions(
- os.path.join(instdir,'easy-install.pth')
- )
- self.install_dir = instdir
-
- elif self.multi_version is None:
- self.multi_version = True
-
- elif not self.multi_version:
- # explicit false set from Python code; raise an error
- raise DistutilsArgError(
- "Can't do single-version installs outside site-packages"
- )
-
- self.index_url = self.index_url or "http://www.python.org/pypi"
-
- self.shadow_path = sys.path[:]
-
- for path_item in self.install_dir, self.script_dir:
- if path_item not in self.shadow_path:
- self.shadow_path.insert(0, self.install_dir)
-
- if self.package_index is None:
- self.package_index = self.create_index(
- self.index_url, search_path = self.shadow_path
- )
-
- self.local_index = AvailableDistributions(self.shadow_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 = []
-
- 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 not self.args:
- raise DistutilsArgError(
- "No urls, filenames, or requirements specified (see --help)")
-
- elif len(self.args)>1 and self.build_directory is not None:
- raise DistutilsArgError(
- "Build directory can only be set when using one URL"
- )
-
- self.outputs = []
-
-
-
- def alloc_tmp(self):
- if self.build_directory is None:
- return tempfile.mkdtemp(prefix="easy_install-")
- tmpdir = os.path.realpath(self.build_directory)
- if not os.path.isdir(tmpdir):
- os.makedirs(tmpdir)
- return tmpdir
-
-
- def run(self):
- if self.verbose<>self.distribution.verbose:
- log.set_verbosity(self.verbose)
- try:
- for link in self.find_links:
- self.package_index.scan_url(link)
- for spec in self.args:
- self.easy_install(spec)
- if self.record:
- from distutils import file_util
- self.execute(
- file_util.write_file, (self.record, self.outputs),
- "writing list of installed files to '%s'" %
- self.record
- )
- finally:
- log.set_verbosity(self.distribution.verbose)
-
-
- 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 easy_install(self, spec):
- tmpdir = self.alloc_tmp()
- download = None
-
- try:
- if not isinstance(spec,Requirement):
- if URL_SCHEME(spec):
- # It's a url, download it to tmpdir and process
- download = self.package_index.download(spec, tmpdir)
- return self.install_item(None, download, tmpdir, True)
-
- elif os.path.exists(spec):
- # Existing file or directory, just process it directly
- return self.install_item(None, spec, tmpdir, True)
- else:
- try:
- spec = Requirement.parse(spec)
- except ValueError:
- raise RuntimeError(
- "Not a URL, existing file, or requirement spec: %r"
- % (spec,)
- )
-
- if isinstance(spec, Requirement):
- download = self.package_index.fetch(spec, tmpdir, self.upgrade)
- else:
- spec = None
-
- if download is None:
- raise RuntimeError(
- "Could not find distribution for %r" % spec
- )
-
- return self.install_item(spec, download, tmpdir)
-
- finally:
- if self.build_directory is None:
- shutil.rmtree(tmpdir)
-
-
-
- def install_item(self, spec, download, tmpdir, install_needed=False):
- # Installation is also needed if file in tmpdir or is not an egg
- install_needed = install_needed or os.path.dirname(download) == tmpdir
- install_needed = install_needed or not download.endswith('.egg')
- log.info("Processing %s", os.path.basename(download))
- if install_needed or self.always_copy:
- dists = self.install_eggs(download, self.zip_ok, tmpdir)
- for dist in dists:
- self.process_distribution(spec, dist)
- else:
- dists = [self.egg_distribution(download)]
- self.process_distribution(spec, dists[0], "Using")
- if spec is not None:
- for dist in dists:
- if dist in spec:
- return dist
-
- def process_distribution(self, requirement, dist, *info):
- self.update_pth(dist)
- self.package_index.add(dist)
- self.local_index.add(dist)
- self.install_egg_scripts(dist)
- log.warn(self.installation_report(dist, *info))
- if requirement is None:
- requirement = Requirement.parse('%s==%s'%(dist.name,dist.version))
- if dist in requirement:
- log.info("Processing dependencies for %s", requirement)
- try:
- self.local_index.resolve(
- [requirement], self.shadow_path, self.easy_install
- )
- except DistributionNotFound, e:
- raise RuntimeError(
- "Could not find required distribution %s" % e.args
- )
- except VersionConflict, e:
- raise RuntimeError(
- "Installed distribution %s conflicts with requirement %s"
- % e.args
- )
-
- def install_egg_scripts(self, dist):
- metadata = dist.metadata
- if self.exclude_scripts or not metadata.metadata_isdir('scripts'):
- return
-
- from distutils.command.build_scripts import first_line_re
-
- for script_name in metadata.metadata_listdir('scripts'):
- target = os.path.join(self.script_dir, script_name)
-
- log.info("Installing %s script to %s", script_name,self.script_dir)
-
- script_text = metadata.get_metadata('scripts/'+script_name)
- script_text = script_text.replace('\r','\n')
- first, rest = script_text.split('\n',1)
-
- match = first_line_re.match(first)
- options = ''
- if match:
- options = match.group(1) or ''
- if options:
- options = ' '+options
-
- spec = '%s==%s' % (dist.name,dist.version)
-
- script_text = '\n'.join([
- "#!%s%s" % (os.path.normpath(sys.executable),options),
- "# EASY-INSTALL-SCRIPT: %r,%r" % (spec, script_name),
- "import pkg_resources",
- "pkg_resources.run_main(%r, %r)" % (spec, script_name)
- ])
- if not self.dry_run:
- f = open(target,"w")
- f.write(script_text)
- f.close()
- try:
- os.chmod(target,0755)
- except (AttributeError, os.error):
- pass
-
-
- def install_eggs(self, dist_filename, zip_ok, tmpdir):
- # .egg dirs or files are already built, so just return them
- if dist_filename.lower().endswith('.egg'):
- return [self.install_egg(dist_filename, True, tmpdir)]
-
- if dist_filename.lower().endswith('.exe'):
- return [self.install_exe(dist_filename, tmpdir)]
-
- # Anything else, try to extract and build
- if os.path.isfile(dist_filename):
- unpack_archive(dist_filename, tmpdir, self.unpack_progress)
-
- # Find the setup.py file
- from glob import glob
- setup_script = os.path.join(tmpdir, 'setup.py')
- if not os.path.exists(setup_script):
- setups = glob(os.path.join(tmpdir, '*', 'setup.py'))
- if not setups:
- raise RuntimeError(
- "Couldn't find a setup script in %s" % dist_filename
- )
- if len(setups)>1:
- raise RuntimeError(
- "Multiple setup scripts in %s" % dist_filename
- )
- setup_script = setups[0]
-
- self.build_egg(tmpdir, setup_script)
- dist_dir = os.path.join(os.path.dirname(setup_script),'dist')
-
- eggs = []
- for egg in glob(os.path.join(dist_dir,'*.egg')):
- eggs.append(self.install_egg(egg, zip_ok, tmpdir))
-
- if not eggs and not self.dry_run:
- log.warn("No eggs found in %s (setup script problem?)", dist_dir)
-
- return eggs
-
-
-
- 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, zip_ok, 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)
-
- if not samefile(egg_path, destination):
- if os.path.isdir(destination):
- dir_util.remove_tree(destination, dry_run=self.dry_run)
- elif os.path.isfile(destination):
- self.execute(os.unlink,(destination,),"Removing "+destination)
-
- if os.path.isdir(egg_path):
- if egg_path.startswith(tmpdir):
- f,m = shutil.move, "Moving"
- else:
- f,m = shutil.copytree, "Copying"
- elif zip_ok:
- if egg_path.startswith(tmpdir):
- f,m = shutil.move, "Moving"
- else:
- f,m = shutil.copy2, "Copying"
- else:
- self.mkpath(destination)
- f,m = self.unpack_and_compile, "Extracting"
-
- 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 RuntimeError(
- "%s is not a valid distutils Windows .exe" % dist_filename
- )
-
- # Create a dummy distribution object until we build the real distro
- dist = Distribution(None,
- name=cfg.get('metadata','name'),
- version=cfg.get('metadata','version'),
- platform="win32"
- )
-
- # Convert the .exe to an unpacked egg
- egg_path = dist.path = os.path.join(tmpdir, dist.egg_name()+'.egg')
- egg_tmp = egg_path+'.tmp'
- self.exe_to_egg(dist_filename, egg_tmp)
-
- # Write EGG-INFO/PKG-INFO
- pkg_inf = os.path.join(egg_tmp, 'EGG-INFO', 'PKG-INFO')
- 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()
-
- # 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, self.zip_ok, 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 = []
-
- def process(src,dst):
- for old,new in prefixes:
- if src.startswith(old):
- src = new+src[len(old):]
- dst = os.path.join(egg_tmp, *src.split('/'))
- dl = dst.lower()
- if dl.endswith('.pyd') or dl.endswith('.dll'):
- native_libs.append(src)
- elif dl.endswith('.py') and old!='SCRIPTS/':
- 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)
-
- for res in native_libs:
- if res.lower().endswith('.pyd'): # create stubs for .pyd's
- parts = res.split('/')
- resource, parts[-1] = parts[-1], parts[-1][:-1]
- pyfile = os.path.join(egg_tmp, *parts)
- to_compile.append(pyfile)
- bdist_egg.write_stub(resource, pyfile)
-
- self.byte_compile(to_compile) # compile .py's
-
- if native_libs: # write EGG-INFO/native_libs.txt
- nl_txt = os.path.join(egg_tmp, 'EGG-INFO', 'native_libs.txt')
- ensure_directory(nl_txt)
- open(nl_txt,'w').write('\n'.join(native_libs)+'\n')
-
- def installation_report(self, dist, what="Installed"):
- """Helpful installation message for display to package users"""
-
- msg = "\n%(what)s %(eggloc)s"
- if self.multi_version:
- msg += """
-
-Because this distribution was installed --multi-version or --install-dir,
-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 not samefile(get_python_lib(),self.install_dir):
- 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.path
- name = dist.name
- version = dist.version
- return msg % locals()
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- def build_egg(self, tmpdir, setup_script):
- sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)
-
- args = ['bdist_egg']
- 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(tmpdir)+1:], ' '.join(args))
- try:
- try:
- run_setup(setup_script, args)
- except SystemExit, v:
- raise RuntimeError(
- "Setup script exited with %s" % (v.args[0],)
- )
- finally:
- 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.get(dist.key,()): # drop old entries
- if self.multi_version or d.path != dist.path:
- log.info("Removing %s from easy-install.pth file", d)
- self.pth_file.remove(d)
- if d.path in self.shadow_path:
- self.shadow_path.remove(d.path)
-
- if not self.multi_version:
- if dist.path 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.path not in self.shadow_path:
- self.shadow_path.append(dist.path)
-
- self.pth_file.save()
-
- if dist.name=='setuptools':
- # Ensure that setuptools itself never becomes unavailable!
- f = open(os.path.join(self.install_dir,'setuptools.pth'), 'w')
- f.write(dist.path+'\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 = []
-
- def pf(src,dst):
- if dst.endswith('.py'):
- to_compile.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)
-
-
- 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 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<>0x1234567A:
- return None # not a valid tag
-
- f.seek(prepended-(12+cfglen+bmlen))
- cfg = ConfigParser.RawConfigParser({'version':'','target_version':''})
- try:
- cfg.readfp(StringIO.StringIO(f.read(cfglen)))
- 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/', ''),
- ('SCRIPTS/', 'EGG-INFO/scripts/')
- ]
- z = zipfile.ZipFile(exe_filename)
- try:
- for info in z.infolist():
- name = info.filename
- if not name.endswith('.pth'):
- continue
- parts = name.split('/')
- if len(parts)<>2:
- continue
- if parts[0] in ('PURELIB','PLATLIB'):
- pth = z.read(name).strip()
- prefixes[0] = ('PURELIB/%s/' % pth), ''
- prefixes[1] = ('PLATLIB/%s/' % pth), ''
- break
- finally:
- z.close()
-
- return prefixes
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-class PthDistributions(AvailableDistributions):
- """A .pth file with Distribution paths in it"""
-
- dirty = False
-
- def __init__(self, filename):
- self.filename = filename; self._load()
- AvailableDistributions.__init__(
- self, list(yield_lines(self.paths)), None, None
- )
-
- def _load(self):
- self.paths = []
- if os.path.isfile(self.filename):
- self.paths = [line.rstrip() for line in open(self.filename,'rt')]
- while self.paths and not self.paths[-1].strip(): self.paths.pop()
-
- def save(self):
- """Write changed .pth file back to disk"""
- if self.dirty:
- data = '\n'.join(self.paths+[''])
- f = open(self.filename,'wt')
- f.write(data)
- f.close()
- self.dirty = False
-
- def add(self,dist):
- """Add `dist` to the distribution map"""
- if dist.path not in self.paths:
- self.paths.append(dist.path); self.dirty = True
- AvailableDistributions.add(self,dist)
-
- def remove(self,dist):
- """Remove `dist` from the distribution map"""
- while dist.path in self.paths:
- self.paths.remove(dist.path); self.dirty = True
- AvailableDistributions.remove(self,dist)
-
-
-
-
-def main(argv, cmds={'easy_install':easy_install}):
- from setuptools import setup
- try:
- setup(cmdclass = cmds, script_args = ['-q','easy_install', '-v']+argv)
- except RuntimeError, v:
- print >>sys.stderr,"error:",v
- sys.exit(1)
-
+import sys
+from setuptools.command.easy_install import *
if __name__ == '__main__':
main(sys.argv[1:])
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-