summaryrefslogtreecommitdiff
path: root/setuptools/command
diff options
context:
space:
mode:
authorJason R. Coombs <jaraco@jaraco.com>2014-02-11 22:55:49 -0500
committerJason R. Coombs <jaraco@jaraco.com>2014-02-11 22:55:49 -0500
commitd4da24296de6dbe7fb4a59fdf4f075bebbd29489 (patch)
tree8180e49ebdefb2b9364aa047e3a12698c015206f /setuptools/command
parentc26f710208962f89e384775789e63decf55d1cfd (diff)
parent78ff89adc622c57283498fa408d944c6f9b29a30 (diff)
downloadpython-setuptools-bitbucket-d4da24296de6dbe7fb4a59fdf4f075bebbd29489.tar.gz
Merge backout of namespace package __init__ module generation; ref #148.3.0b1
Diffstat (limited to 'setuptools/command')
-rw-r--r--setuptools/command/__init__.py9
-rwxr-xr-xsetuptools/command/alias.py30
-rw-r--r--setuptools/command/bdist_egg.py297
-rwxr-xr-xsetuptools/command/bdist_rpm.py82
-rwxr-xr-xsetuptools/command/bdist_wininst.py82
-rw-r--r--setuptools/command/build_ext.py289
-rw-r--r--setuptools/command/build_py.py181
-rwxr-xr-xsetuptools/command/develop.py136
-rwxr-xr-xsetuptools/command/easy_install.py1599
-rwxr-xr-xsetuptools/command/egg_info.py328
-rw-r--r--setuptools/command/install.py89
-rwxr-xr-xsetuptools/command/install_egg_info.py125
-rw-r--r--setuptools/command/install_lib.py76
-rwxr-xr-xsetuptools/command/install_scripts.py54
-rw-r--r--setuptools/command/launcher manifest.xml15
-rwxr-xr-xsetuptools/command/register.py10
-rwxr-xr-xsetuptools/command/rotate.py32
-rwxr-xr-xsetuptools/command/saveopts.py3
-rwxr-xr-xsetuptools/command/sdist.py303
-rwxr-xr-xsetuptools/command/setopt.py55
-rw-r--r--setuptools/command/test.py149
-rwxr-xr-xsetuptools/command/upload.py172
-rw-r--r--setuptools/command/upload_docs.py193
23 files changed, 3230 insertions, 1079 deletions
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
index 720b7a3f..29c9d75a 100644
--- a/setuptools/command/__init__.py
+++ b/setuptools/command/__init__.py
@@ -1,9 +1,12 @@
__all__ = [
- 'alias', 'bdist_egg', 'build_ext', 'build_py', 'develop',
+ 'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
- 'sdist', 'setopt', 'test', 'upload',
+ 'sdist', 'setopt', 'test', 'install_egg_info', 'install_scripts',
+ 'register', 'bdist_wininst', 'upload_docs',
]
+from setuptools.command import install_scripts
+import sys
from distutils.command.bdist import bdist
@@ -11,4 +14,4 @@ if 'egg' not in bdist.format_commands:
bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
bdist.format_commands.append('egg')
-del bdist
+del bdist, sys
diff --git a/setuptools/command/alias.py b/setuptools/command/alias.py
index f5368b29..05c0766b 100755
--- a/setuptools/command/alias.py
+++ b/setuptools/command/alias.py
@@ -1,27 +1,24 @@
-import distutils, os
-from setuptools import Command
-from distutils.util import convert_path
-from distutils import log
-from distutils.errors import *
+from distutils.errors import DistutilsOptionError
+
from setuptools.command.setopt import edit_config, option_base, config_file
def shquote(arg):
"""Quote an argument for later parsing by shlex.split()"""
for c in '"', "'", "\\", "#":
if c in arg: return repr(arg)
- if arg.split()<>[arg]:
+ if arg.split() != [arg]:
return repr(arg)
- return arg
+ return arg
class alias(option_base):
"""Define a shortcut that invokes one or more commands"""
-
+
description = "define a shortcut to invoke one or more commands"
command_consumes_arguments = True
user_options = [
- ('remove', 'r', 'remove (unset) the alias'),
+ ('remove', 'r', 'remove (unset) the alias'),
] + option_base.user_options
boolean_options = option_base.boolean_options + ['remove']
@@ -33,7 +30,7 @@ class alias(option_base):
def finalize_options(self):
option_base.finalize_options(self)
- if self.remove and len(self.args)<>1:
+ if self.remove and len(self.args) != 1:
raise DistutilsOptionError(
"Must specify exactly one argument (the alias name) when "
"using --remove"
@@ -43,10 +40,10 @@ class alias(option_base):
aliases = self.distribution.get_option_dict('aliases')
if not self.args:
- print "Command Aliases"
- print "---------------"
+ print("Command Aliases")
+ print("---------------")
for alias in aliases:
- print "setup.py alias", format_alias(alias, aliases)
+ print("setup.py alias", format_alias(alias, aliases))
return
elif len(self.args)==1:
@@ -54,10 +51,10 @@ class alias(option_base):
if self.remove:
command = None
elif alias in aliases:
- print "setup.py alias", format_alias(alias, aliases)
+ print("setup.py alias", format_alias(alias, aliases))
return
else:
- print "No alias definition found for %r" % alias
+ print("No alias definition found for %r" % alias)
return
else:
alias = self.args[0]
@@ -77,6 +74,3 @@ def format_alias(name, aliases):
else:
source = '--filename=%r' % source
return source+name+' '+command
-
-
-
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 0a9d9a0c..8f4d44c3 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -3,13 +3,33 @@
Build .egg distributions"""
# This module should be kept compatible with Python 2.3
-import os, marshal
+import sys, os, marshal
from setuptools import Command
from distutils.dir_util import remove_tree, mkpath
-from distutils.sysconfig import get_python_version, get_python_lib
+try:
+ # Python 2.7 or >=3.2
+ from sysconfig import get_path, get_python_version
+ def _get_purelib():
+ return get_path("purelib")
+except ImportError:
+ from distutils.sysconfig import get_python_lib, get_python_version
+ def _get_purelib():
+ return get_python_lib(False)
+
from distutils import log
-from pkg_resources import get_platform, Distribution
+from distutils.errors import DistutilsSetupError
+from pkg_resources import get_build_platform, Distribution, ensure_directory
+from pkg_resources import EntryPoint
from types import CodeType
+from setuptools.compat import basestring, next
+from setuptools.extension import Library
+
+def strip_module(filename):
+ if '.' in filename:
+ filename = os.path.splitext(filename)[0]
+ if filename.endswith('module'):
+ filename = filename[:-6]
+ return filename
def write_stub(resource, pyfile):
f = open(pyfile,'w')
@@ -19,7 +39,7 @@ def write_stub(resource, pyfile):
" import sys, pkg_resources, imp",
" __file__ = pkg_resources.resource_filename(__name__,%r)"
% resource,
- " del __bootstrap__, __loader__",
+ " __loader__ = None; del __bootstrap__, __loader__",
" imp.load_dynamic(__name__,__file__)",
"__bootstrap__()",
"" # terminal \n
@@ -29,16 +49,6 @@ def write_stub(resource, pyfile):
-
-
-
-
-
-
-
-
-
-
class bdist_egg(Command):
description = "create an \"egg\" distribution"
@@ -48,7 +58,7 @@ class bdist_egg(Command):
"temporary directory for creating the distribution"),
('plat-name=', 'p',
"platform name to embed in generated filenames "
- "(default: %s)" % get_platform()),
+ "(default: %s)" % get_build_platform()),
('exclude-source-files', None,
"remove all .py files from the generated egg"),
('keep-temp', 'k',
@@ -91,7 +101,7 @@ class bdist_egg(Command):
def finalize_options(self):
- ei_cmd = self.get_finalized_command("egg_info")
+ ei_cmd = self.ei_cmd = self.get_finalized_command("egg_info")
self.egg_info = ei_cmd.egg_info
if self.bdist_dir is None:
@@ -99,7 +109,7 @@ class bdist_egg(Command):
self.bdist_dir = os.path.join(bdist_base, 'egg')
if self.plat_name is None:
- self.plat_name = get_platform()
+ self.plat_name = get_build_platform()
self.set_undefined_options('bdist',('dist_dir', 'dist_dir'))
@@ -125,7 +135,7 @@ class bdist_egg(Command):
# Hack for packages that install data to install's --install-lib
self.get_finalized_command('install').install_lib = self.bdist_dir
- site_packages = os.path.normcase(os.path.realpath(get_python_lib()))
+ site_packages = os.path.normcase(os.path.realpath(_get_purelib()))
old, self.distribution.data_files = self.distribution.data_files,[]
for item in old:
@@ -165,21 +175,22 @@ class bdist_egg(Command):
def run(self):
# Generate metadata first
self.run_command("egg_info")
-
# We run install_lib before install_data, because some data hacks
# pull their data path from the install_lib command.
-
log.info("installing library code to %s" % self.bdist_dir)
+ instcmd = self.get_finalized_command('install')
+ old_root = instcmd.root; instcmd.root = None
+ if self.distribution.has_c_libraries() and not self.skip_build:
+ self.run_command('build_clib')
cmd = self.call_command('install_lib', warn_dir=0)
+ instcmd.root = old_root
- ext_outputs = cmd._mutate_outputs(
- self.distribution.has_ext_modules(), 'build_ext', 'build_lib', ''
- )
+ all_outputs, ext_outputs = self.get_ext_outputs()
self.stubs = []
to_compile = []
for (p,ext_name) in enumerate(ext_outputs):
filename,ext = os.path.splitext(ext_name)
- pyfile = os.path.join(self.bdist_dir, filename + '.py')
+ pyfile = os.path.join(self.bdist_dir, strip_module(filename)+'.py')
self.stubs.append(pyfile)
log.info("creating stub loader for %s" % ext_name)
if not self.dry_run:
@@ -189,7 +200,6 @@ class bdist_egg(Command):
if to_compile:
cmd.byte_compile(to_compile)
-
if self.distribution.data_files:
self.do_install_data()
@@ -197,18 +207,19 @@ class bdist_egg(Command):
archive_root = self.bdist_dir
egg_info = os.path.join(archive_root,'EGG-INFO')
self.mkpath(egg_info)
-
if self.distribution.scripts:
script_dir = os.path.join(egg_info, 'scripts')
log.info("installing scripts to %s" % script_dir)
- self.call_command('install_scripts', install_dir=script_dir)
+ self.call_command('install_scripts',install_dir=script_dir,no_ep=1)
- native_libs = os.path.join(self.egg_info,"native_libs.txt")
- if ext_outputs:
+ self.copy_metadata_to(egg_info)
+ native_libs = os.path.join(egg_info, "native_libs.txt")
+ if all_outputs:
log.info("writing %s" % native_libs)
if not self.dry_run:
+ ensure_directory(native_libs)
libs_file = open(native_libs, 'wt')
- libs_file.write('\n'.join(ext_outputs))
+ libs_file.write('\n'.join(all_outputs))
libs_file.write('\n')
libs_file.close()
elif os.path.isfile(native_libs):
@@ -216,12 +227,9 @@ class bdist_egg(Command):
if not self.dry_run:
os.unlink(native_libs)
- for filename in os.listdir(self.egg_info):
- path = os.path.join(self.egg_info,filename)
- if os.path.isfile(path):
- self.copy_file(path,os.path.join(egg_info,filename))
-
- write_safety_flag(archive_root, self.zip_safe())
+ write_safety_flag(
+ os.path.join(archive_root,'EGG-INFO'), self.zip_safe()
+ )
if os.path.exists(os.path.join(self.egg_info,'depends.txt')):
log.warn(
@@ -231,10 +239,10 @@ class bdist_egg(Command):
if self.exclude_source_files:
self.zap_pyfiles()
-
+
# Make the archive
make_zipfile(self.egg_output, archive_root, verbose=self.verbose,
- dry_run=self.dry_run)
+ dry_run=self.dry_run, mode=self.gen_header())
if not self.keep_temp:
remove_tree(self.bdist_dir, dry_run=self.dry_run)
@@ -244,6 +252,7 @@ class bdist_egg(Command):
+
def zap_pyfiles(self):
log.info("Removing .py files from temporary directory")
for base,dirs,files in walk_egg(self.bdist_dir):
@@ -262,25 +271,88 @@ class bdist_egg(Command):
+ def gen_header(self):
+ epm = EntryPoint.parse_map(self.distribution.entry_points or '')
+ ep = epm.get('setuptools.installation',{}).get('eggsecutable')
+ if ep is None:
+ return 'w' # not an eggsecutable, do it the usual way.
+ if not ep.attrs or ep.extras:
+ raise DistutilsSetupError(
+ "eggsecutable entry point (%r) cannot have 'extras' "
+ "or refer to a module" % (ep,)
+ )
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ pyver = sys.version[:3]
+ pkg = ep.module_name
+ full = '.'.join(ep.attrs)
+ base = ep.attrs[0]
+ basename = os.path.basename(self.egg_output)
+
+ header = (
+ "#!/bin/sh\n"
+ 'if [ `basename $0` = "%(basename)s" ]\n'
+ 'then exec python%(pyver)s -c "'
+ "import sys, os; sys.path.insert(0, os.path.abspath('$0')); "
+ "from %(pkg)s import %(base)s; sys.exit(%(full)s())"
+ '" "$@"\n'
+ 'else\n'
+ ' echo $0 is not the correct name for this egg file.\n'
+ ' echo Please rename it back to %(basename)s and try again.\n'
+ ' exec false\n'
+ 'fi\n'
+
+ ) % locals()
+
+ if not self.dry_run:
+ mkpath(os.path.dirname(self.egg_output), dry_run=self.dry_run)
+ f = open(self.egg_output, 'w')
+ f.write(header)
+ f.close()
+ return 'a'
+
+
+ def copy_metadata_to(self, target_dir):
+ "Copy metadata (egg info) to the target_dir"
+ # normalize the path (so that a forward-slash in egg_info will
+ # match using startswith below)
+ norm_egg_info = os.path.normpath(self.egg_info)
+ prefix = os.path.join(norm_egg_info,'')
+ for path in self.ei_cmd.filelist.files:
+ if path.startswith(prefix):
+ target = os.path.join(target_dir, path[len(prefix):])
+ ensure_directory(target)
+ self.copy_file(path, target)
+
+ def get_ext_outputs(self):
+ """Get a list of relative paths to C extensions in the output distro"""
+
+ all_outputs = []
+ ext_outputs = []
+
+ paths = {self.bdist_dir:''}
+ for base, dirs, files in os.walk(self.bdist_dir):
+ for filename in files:
+ if os.path.splitext(filename)[1].lower() in NATIVE_EXTENSIONS:
+ all_outputs.append(paths[base]+filename)
+ for filename in dirs:
+ paths[os.path.join(base,filename)] = paths[base]+filename+'/'
+
+ if self.distribution.has_ext_modules():
+ build_cmd = self.get_finalized_command('build_ext')
+ for ext in build_cmd.extensions:
+ if isinstance(ext,Library):
+ continue
+ fullname = build_cmd.get_ext_fullname(ext.name)
+ filename = build_cmd.get_ext_filename(fullname)
+ if not os.path.basename(filename).startswith('dl-'):
+ if os.path.exists(os.path.join(self.bdist_dir,filename)):
+ ext_outputs.append(filename)
+
+ return all_outputs, ext_outputs
+
+
+NATIVE_EXTENSIONS = dict.fromkeys('.dll .so .dylib .pyd'.split())
@@ -288,7 +360,7 @@ class bdist_egg(Command):
def walk_egg(egg_dir):
"""Walk an unpacked egg's contents, skipping the metadata directory"""
walker = os.walk(egg_dir)
- base,dirs,files = walker.next()
+ base,dirs,files = next(walker)
if 'EGG-INFO' in dirs:
dirs.remove('EGG-INFO')
yield base,dirs,files
@@ -296,6 +368,11 @@ def walk_egg(egg_dir):
yield bdf
def analyze_egg(egg_dir, stubs):
+ # check for existing flag in EGG-INFO
+ for flag,fn in safety_flags.items():
+ if os.path.exists(os.path.join(egg_dir,'EGG-INFO',fn)):
+ return flag
+ if not can_scan(): return False
safe = True
for base, dirs, files in walk_egg(egg_dir):
for name in files:
@@ -304,27 +381,22 @@ def analyze_egg(egg_dir, stubs):
elif name.endswith('.pyc') or name.endswith('.pyo'):
# always scan, even if we already know we're not safe
safe = scan_module(egg_dir, base, name, stubs) and safe
- '''elif safe:
- log.warn(
- "Distribution contains data or extensions; assuming "
- "it's unsafe (set zip_safe=True in setup() to change"
- )
- safe = False # XXX'''
return safe
def write_safety_flag(egg_dir, safe):
- # Write a zip safety flag file
- flag = safe and 'zip-safe' or 'not-zip-safe'
- open(os.path.join(egg_dir,'EGG-INFO',flag),'w').close()
-
-
-
-
-
-
-
-
-
+ # Write or remove zip safety flag file(s)
+ for flag,fn in safety_flags.items():
+ fn = os.path.join(egg_dir, fn)
+ if os.path.exists(fn):
+ if safe is None or bool(safe) != flag:
+ os.unlink(fn)
+ elif safe is not None and bool(safe)==flag:
+ f=open(fn,'wt'); f.write('\n'); f.close()
+
+safety_flags = {
+ True: 'zip-safe',
+ False: 'not-zip-safe',
+}
def scan_module(egg_dir, base, name, stubs):
"""Check whether module possibly uses unsafe-for-zipfile stuff"""
@@ -334,8 +406,12 @@ def scan_module(egg_dir, base, name, stubs):
return True # Extension module
pkg = base[len(egg_dir)+1:].replace(os.sep,'.')
module = pkg+(pkg and '.' or '')+os.path.splitext(name)[0]
- f = open(filename,'rb'); f.read(8) # skip magic & date
- code = marshal.load(f); f.close()
+ if sys.version_info < (3, 3):
+ skip = 8 # skip magic & date
+ else:
+ skip = 12 # skip magic & date & file size
+ f = open(filename,'rb'); f.read(skip)
+ code = marshal.load(f); f.close()
safe = True
symbols = dict.fromkeys(iter_symbols(code))
for bad in ['__file__', '__path__']:
@@ -352,7 +428,7 @@ def scan_module(egg_dir, base, name, stubs):
log.warn("%s: module MAY be using inspect.%s", module, bad)
safe = False
if '__name__' in symbols and '__main__' in symbols and '.' not in module:
- if get_python_version()>="2.4":
+ if sys.version[:3]=="2.4": # -m works w/zipfiles in 2.5
log.warn("%s: top-level module may be 'python -m' script", module)
safe = False
return safe
@@ -367,6 +443,47 @@ def iter_symbols(code):
for name in iter_symbols(const):
yield name
+def can_scan():
+ if not sys.platform.startswith('java') and sys.platform != 'cli':
+ # CPython, PyPy, etc.
+ return True
+ log.warn("Unable to analyze compiled code on this platform.")
+ log.warn("Please ask the author to include a 'zip_safe'"
+ " setting (either True or False) in the package's setup.py")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# Attribute names of options for commands that might need to be convinced to
# install to the egg build directory
@@ -374,8 +491,9 @@ INSTALL_DIRECTORY_ATTRS = [
'install_lib', 'install_dir', 'install_data', 'install_base'
]
-
-def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0):
+def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None,
+ mode='w'
+):
"""Create a zip file from all the files under 'base_dir'. The output
zip file will be named 'base_dir' + ".zip". Uses either the "zipfile"
Python module (if available) or the InfoZIP "zip" utility (if installed
@@ -384,10 +502,9 @@ def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0):
"""
import zipfile
mkpath(os.path.dirname(zip_filename), dry_run=dry_run)
-
log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)
- def visit (z, dirname, names):
+ def visit(z, dirname, names):
for name in names:
path = os.path.normpath(os.path.join(dirname, name))
if os.path.isfile(path):
@@ -396,15 +513,17 @@ def make_zipfile (zip_filename, base_dir, verbose=0, dry_run=0):
z.write(path, p)
log.debug("adding '%s'" % p)
+ if compress is None:
+ compress = (sys.version>="2.4") # avoid 2.3 zipimport bug when 64 bits
+
+ compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)]
if not dry_run:
- z = zipfile.ZipFile(zip_filename, "w",
- compression=zipfile.ZIP_DEFLATED)
- os.path.walk(base_dir, visit, z)
+ z = zipfile.ZipFile(zip_filename, mode, compression=compression)
+ for dirname, dirs, files in os.walk(base_dir):
+ visit(z, dirname, files)
z.close()
else:
- os.path.walk(base_dir, visit, None)
-
+ for dirname, dirs, files in os.walk(base_dir):
+ visit(None, dirname, files)
return zip_filename
-
-
-
+#
diff --git a/setuptools/command/bdist_rpm.py b/setuptools/command/bdist_rpm.py
new file mode 100755
index 00000000..8c48da35
--- /dev/null
+++ b/setuptools/command/bdist_rpm.py
@@ -0,0 +1,82 @@
+# This is just a kludge so that bdist_rpm doesn't guess wrong about the
+# distribution name and version, if the egg_info command is going to alter
+# them, another kludge to allow you to build old-style non-egg RPMs, and
+# finally, a kludge to track .rpm files for uploading when run on Python <2.5.
+
+from distutils.command.bdist_rpm import bdist_rpm as _bdist_rpm
+import sys, os
+
+class bdist_rpm(_bdist_rpm):
+
+ def initialize_options(self):
+ _bdist_rpm.initialize_options(self)
+ self.no_egg = None
+
+ if sys.version<"2.5":
+ # Track for uploading any .rpm file(s) moved to self.dist_dir
+ def move_file(self, src, dst, level=1):
+ _bdist_rpm.move_file(self, src, dst, level)
+ if dst==self.dist_dir and src.endswith('.rpm'):
+ getattr(self.distribution,'dist_files',[]).append(
+ ('bdist_rpm',
+ src.endswith('.src.rpm') and 'any' or sys.version[:3],
+ os.path.join(dst, os.path.basename(src)))
+ )
+
+ def run(self):
+ self.run_command('egg_info') # ensure distro name is up-to-date
+ _bdist_rpm.run(self)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ def _make_spec_file(self):
+ version = self.distribution.get_version()
+ rpmversion = version.replace('-','_')
+ spec = _bdist_rpm._make_spec_file(self)
+ line23 = '%define version '+version
+ line24 = '%define version '+rpmversion
+ spec = [
+ line.replace(
+ "Source0: %{name}-%{version}.tar",
+ "Source0: %{name}-%{unmangled_version}.tar"
+ ).replace(
+ "setup.py install ",
+ "setup.py install --single-version-externally-managed "
+ ).replace(
+ "%setup",
+ "%setup -n %{name}-%{unmangled_version}"
+ ).replace(line23,line24)
+ for line in spec
+ ]
+ spec.insert(spec.index(line24)+1, "%define unmangled_version "+version)
+ return spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setuptools/command/bdist_wininst.py b/setuptools/command/bdist_wininst.py
new file mode 100755
index 00000000..e8521f83
--- /dev/null
+++ b/setuptools/command/bdist_wininst.py
@@ -0,0 +1,82 @@
+from distutils.command.bdist_wininst import bdist_wininst as _bdist_wininst
+import os, sys
+
+class bdist_wininst(_bdist_wininst):
+ _good_upload = _bad_upload = None
+
+ def create_exe(self, arcname, fullname, bitmap=None):
+ _bdist_wininst.create_exe(self, arcname, fullname, bitmap)
+ installer_name = self.get_installer_filename(fullname)
+ if self.target_version:
+ pyversion = self.target_version
+ # fix 2.5+ bdist_wininst ignoring --target-version spec
+ self._bad_upload = ('bdist_wininst', 'any', installer_name)
+ else:
+ pyversion = 'any'
+ self._good_upload = ('bdist_wininst', pyversion, installer_name)
+
+ def _fix_upload_names(self):
+ good, bad = self._good_upload, self._bad_upload
+ dist_files = getattr(self.distribution, 'dist_files', [])
+ if bad in dist_files:
+ dist_files.remove(bad)
+ if good not in dist_files:
+ dist_files.append(good)
+
+ def reinitialize_command (self, command, reinit_subcommands=0):
+ cmd = self.distribution.reinitialize_command(
+ command, reinit_subcommands)
+ if command in ('install', 'install_lib'):
+ cmd.install_lib = None # work around distutils bug
+ return cmd
+
+ def run(self):
+ self._is_running = True
+ try:
+ _bdist_wininst.run(self)
+ self._fix_upload_names()
+ finally:
+ self._is_running = False
+
+
+ if not hasattr(_bdist_wininst, 'get_installer_filename'):
+ def get_installer_filename(self, fullname):
+ # Factored out to allow overriding in subclasses
+ if self.target_version:
+ # if we create an installer for a specific python version,
+ # it's better to include this in the name
+ installer_name = os.path.join(self.dist_dir,
+ "%s.win32-py%s.exe" %
+ (fullname, self.target_version))
+ else:
+ installer_name = os.path.join(self.dist_dir,
+ "%s.win32.exe" % fullname)
+ return installer_name
+ # get_installer_filename()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/setuptools/command/build_ext.py b/setuptools/command/build_ext.py
index 35a9e63e..e08131d7 100644
--- a/setuptools/command/build_ext.py
+++ b/setuptools/command/build_ext.py
@@ -1,6 +1,289 @@
-# Attempt to use Pyrex for building extensions, if available
+from distutils.command.build_ext import build_ext as _du_build_ext
+try:
+ # Attempt to use Pyrex for building extensions, if available
+ from Pyrex.Distutils.build_ext import build_ext as _build_ext
+except ImportError:
+ _build_ext = _du_build_ext
+import os
+import sys
+from distutils.file_util import copy_file
+from setuptools.extension import Library
+from distutils.ccompiler import new_compiler
+from distutils.sysconfig import customize_compiler
try:
- from Pyrex.Distutils.build_ext import build_ext
+ # Python 2.7 or >=3.2
+ from sysconfig import _CONFIG_VARS
except ImportError:
- from distutils.command.build_ext import build_ext
+ from distutils.sysconfig import get_config_var
+ get_config_var("LDSHARED") # make sure _config_vars is initialized
+ del get_config_var
+ from distutils.sysconfig import _config_vars as _CONFIG_VARS
+from distutils import log
+from distutils.errors import DistutilsError
+
+have_rtld = False
+use_stubs = False
+libtype = 'shared'
+
+if sys.platform == "darwin":
+ use_stubs = True
+elif os.name != 'nt':
+ try:
+ from dl import RTLD_NOW
+ have_rtld = True
+ use_stubs = True
+ except ImportError:
+ pass
+
+def if_dl(s):
+ if have_rtld:
+ return s
+ return ''
+
+
+class build_ext(_build_ext):
+ def run(self):
+ """Build extensions in build directory, then copy if --inplace"""
+ old_inplace, self.inplace = self.inplace, 0
+ _build_ext.run(self)
+ self.inplace = old_inplace
+ if old_inplace:
+ self.copy_extensions_to_source()
+
+ def copy_extensions_to_source(self):
+ build_py = self.get_finalized_command('build_py')
+ for ext in self.extensions:
+ fullname = self.get_ext_fullname(ext.name)
+ filename = self.get_ext_filename(fullname)
+ modpath = fullname.split('.')
+ package = '.'.join(modpath[:-1])
+ package_dir = build_py.get_package_dir(package)
+ dest_filename = os.path.join(package_dir,os.path.basename(filename))
+ src_filename = os.path.join(self.build_lib,filename)
+
+ # Always copy, even if source is older than destination, to ensure
+ # that the right extensions for the current Python/platform are
+ # used.
+ copy_file(
+ src_filename, dest_filename, verbose=self.verbose,
+ dry_run=self.dry_run
+ )
+ if ext._needs_stub:
+ self.write_stub(package_dir or os.curdir, ext, True)
+
+ if _build_ext is not _du_build_ext and not hasattr(_build_ext,'pyrex_sources'):
+ # Workaround for problems using some Pyrex versions w/SWIG and/or 2.4
+ def swig_sources(self, sources, *otherargs):
+ # first do any Pyrex processing
+ sources = _build_ext.swig_sources(self, sources) or sources
+ # Then do any actual SWIG stuff on the remainder
+ return _du_build_ext.swig_sources(self, sources, *otherargs)
+
+ def get_ext_filename(self, fullname):
+ filename = _build_ext.get_ext_filename(self,fullname)
+ if fullname in self.ext_map:
+ ext = self.ext_map[fullname]
+ if isinstance(ext,Library):
+ fn, ext = os.path.splitext(filename)
+ return self.shlib_compiler.library_filename(fn,libtype)
+ elif use_stubs and ext._links_to_dynamic:
+ d,fn = os.path.split(filename)
+ return os.path.join(d,'dl-'+fn)
+ return filename
+
+ def initialize_options(self):
+ _build_ext.initialize_options(self)
+ self.shlib_compiler = None
+ self.shlibs = []
+ self.ext_map = {}
+
+ def finalize_options(self):
+ _build_ext.finalize_options(self)
+ self.extensions = self.extensions or []
+ self.check_extensions_list(self.extensions)
+ self.shlibs = [ext for ext in self.extensions
+ if isinstance(ext, Library)]
+ if self.shlibs:
+ self.setup_shlib_compiler()
+ for ext in self.extensions:
+ ext._full_name = self.get_ext_fullname(ext.name)
+ for ext in self.extensions:
+ fullname = ext._full_name
+ self.ext_map[fullname] = ext
+
+ # distutils 3.1 will also ask for module names
+ # XXX what to do with conflicts?
+ self.ext_map[fullname.split('.')[-1]] = ext
+
+ ltd = ext._links_to_dynamic = \
+ self.shlibs and self.links_to_dynamic(ext) or False
+ ext._needs_stub = ltd and use_stubs and not isinstance(ext,Library)
+ filename = ext._file_name = self.get_ext_filename(fullname)
+ libdir = os.path.dirname(os.path.join(self.build_lib,filename))
+ if ltd and libdir not in ext.library_dirs:
+ ext.library_dirs.append(libdir)
+ if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs:
+ ext.runtime_library_dirs.append(os.curdir)
+
+ def setup_shlib_compiler(self):
+ compiler = self.shlib_compiler = new_compiler(
+ compiler=self.compiler, dry_run=self.dry_run, force=self.force
+ )
+ if sys.platform == "darwin":
+ tmp = _CONFIG_VARS.copy()
+ try:
+ # XXX Help! I don't have any idea whether these are right...
+ _CONFIG_VARS['LDSHARED'] = "gcc -Wl,-x -dynamiclib -undefined dynamic_lookup"
+ _CONFIG_VARS['CCSHARED'] = " -dynamiclib"
+ _CONFIG_VARS['SO'] = ".dylib"
+ customize_compiler(compiler)
+ finally:
+ _CONFIG_VARS.clear()
+ _CONFIG_VARS.update(tmp)
+ else:
+ customize_compiler(compiler)
+
+ if self.include_dirs is not None:
+ compiler.set_include_dirs(self.include_dirs)
+ if self.define is not None:
+ # 'define' option is a list of (name,value) tuples
+ for (name,value) in self.define:
+ compiler.define_macro(name, value)
+ if self.undef is not None:
+ for macro in self.undef:
+ compiler.undefine_macro(macro)
+ if self.libraries is not None:
+ compiler.set_libraries(self.libraries)
+ if self.library_dirs is not None:
+ compiler.set_library_dirs(self.library_dirs)
+ if self.rpath is not None:
+ compiler.set_runtime_library_dirs(self.rpath)
+ if self.link_objects is not None:
+ compiler.set_link_objects(self.link_objects)
+
+ # hack so distutils' build_extension() builds a library instead
+ compiler.link_shared_object = link_shared_object.__get__(compiler)
+
+ def get_export_symbols(self, ext):
+ if isinstance(ext,Library):
+ return ext.export_symbols
+ return _build_ext.get_export_symbols(self,ext)
+
+ def build_extension(self, ext):
+ _compiler = self.compiler
+ try:
+ if isinstance(ext,Library):
+ self.compiler = self.shlib_compiler
+ _build_ext.build_extension(self,ext)
+ if ext._needs_stub:
+ self.write_stub(
+ self.get_finalized_command('build_py').build_lib, ext
+ )
+ finally:
+ self.compiler = _compiler
+
+ def links_to_dynamic(self, ext):
+ """Return true if 'ext' links to a dynamic lib in the same package"""
+ # XXX this should check to ensure the lib is actually being built
+ # XXX as dynamic, and not just using a locally-found version or a
+ # XXX static-compiled version
+ libnames = dict.fromkeys([lib._full_name for lib in self.shlibs])
+ pkg = '.'.join(ext._full_name.split('.')[:-1]+[''])
+ for libname in ext.libraries:
+ if pkg+libname in libnames: return True
+ return False
+
+ def get_outputs(self):
+ outputs = _build_ext.get_outputs(self)
+ optimize = self.get_finalized_command('build_py').optimize
+ for ext in self.extensions:
+ if ext._needs_stub:
+ base = os.path.join(self.build_lib, *ext._full_name.split('.'))
+ outputs.append(base+'.py')
+ outputs.append(base+'.pyc')
+ if optimize:
+ outputs.append(base+'.pyo')
+ return outputs
+
+ def write_stub(self, output_dir, ext, compile=False):
+ log.info("writing stub loader for %s to %s",ext._full_name, output_dir)
+ stub_file = os.path.join(output_dir, *ext._full_name.split('.'))+'.py'
+ if compile and os.path.exists(stub_file):
+ raise DistutilsError(stub_file+" already exists! Please delete.")
+ if not self.dry_run:
+ f = open(stub_file,'w')
+ f.write(
+ '\n'.join([
+ "def __bootstrap__():",
+ " global __bootstrap__, __file__, __loader__",
+ " import sys, os, pkg_resources, imp"+if_dl(", dl"),
+ " __file__ = pkg_resources.resource_filename(__name__,%r)"
+ % os.path.basename(ext._file_name),
+ " del __bootstrap__",
+ " if '__loader__' in globals():",
+ " del __loader__",
+ if_dl(" old_flags = sys.getdlopenflags()"),
+ " old_dir = os.getcwd()",
+ " try:",
+ " os.chdir(os.path.dirname(__file__))",
+ if_dl(" sys.setdlopenflags(dl.RTLD_NOW)"),
+ " imp.load_dynamic(__name__,__file__)",
+ " finally:",
+ if_dl(" sys.setdlopenflags(old_flags)"),
+ " os.chdir(old_dir)",
+ "__bootstrap__()",
+ "" # terminal \n
+ ])
+ )
+ f.close()
+ if compile:
+ from distutils.util import byte_compile
+ byte_compile([stub_file], optimize=0,
+ force=True, dry_run=self.dry_run)
+ optimize = self.get_finalized_command('install_lib').optimize
+ if optimize > 0:
+ byte_compile([stub_file], optimize=optimize,
+ force=True, dry_run=self.dry_run)
+ if os.path.exists(stub_file) and not self.dry_run:
+ os.unlink(stub_file)
+
+
+if use_stubs or os.name=='nt':
+ # Build shared libraries
+ #
+ def link_shared_object(self, objects, output_libname, output_dir=None,
+ libraries=None, library_dirs=None, runtime_library_dirs=None,
+ export_symbols=None, debug=0, extra_preargs=None,
+ extra_postargs=None, build_temp=None, target_lang=None):
+ self.link(
+ self.SHARED_LIBRARY, objects, output_libname,
+ output_dir, libraries, library_dirs, runtime_library_dirs,
+ export_symbols, debug, extra_preargs, extra_postargs,
+ build_temp, target_lang
+ )
+else:
+ # Build static libraries everywhere else
+ libtype = 'static'
+
+ def link_shared_object(self, objects, output_libname, output_dir=None,
+ libraries=None, library_dirs=None, runtime_library_dirs=None,
+ export_symbols=None, debug=0, extra_preargs=None,
+ extra_postargs=None, build_temp=None, target_lang=None):
+ # XXX we need to either disallow these attrs on Library instances,
+ # or warn/abort here if set, or something...
+ #libraries=None, library_dirs=None, runtime_library_dirs=None,
+ #export_symbols=None, extra_preargs=None, extra_postargs=None,
+ #build_temp=None
+
+ assert output_dir is None # distutils build_ext doesn't pass this
+ output_dir,filename = os.path.split(output_libname)
+ basename, ext = os.path.splitext(filename)
+ if self.library_filename("x").startswith('lib'):
+ # strip 'lib' prefix; this is kludgy if some platform uses
+ # a different prefix
+ basename = basename[3:]
+
+ self.create_static_lib(
+ objects, basename, output_dir, debug, target_lang
+ )
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
index 9db49080..1efabc02 100644
--- a/setuptools/command/build_py.py
+++ b/setuptools/command/build_py.py
@@ -1,11 +1,19 @@
-import os.path
-
+import os
+import sys
+import fnmatch
+import textwrap
from distutils.command.build_py import build_py as _build_py
from distutils.util import convert_path
from glob import glob
+try:
+ from setuptools.lib2to3_ex import Mixin2to3
+except ImportError:
+ class Mixin2to3:
+ def run_2to3(self, files, doctests=True):
+ "do nothing"
-class build_py(_build_py):
+class build_py(_build_py, Mixin2to3):
"""Enhanced 'build_py' command that includes data files with packages
The data files are specified via a 'package_data' argument to 'setup()'.
@@ -14,11 +22,13 @@ class build_py(_build_py):
Also, this version of the 'build_py' command allows you to specify both
'py_modules' and 'packages' in the same setup operation.
"""
-
def finalize_options(self):
_build_py.finalize_options(self)
self.package_data = self.distribution.package_data
- self.data_files = self.get_data_files()
+ self.exclude_package_data = self.distribution.exclude_package_data or {}
+ if 'data_files' in self.__dict__: del self.__dict__['data_files']
+ self.__updated_files = []
+ self.__doctests_2to3 = []
def run(self):
"""Build modules, packages, and copy data files to build directory"""
@@ -32,12 +42,29 @@ class build_py(_build_py):
self.build_packages()
self.build_package_data()
+ self.run_2to3(self.__updated_files, False)
+ self.run_2to3(self.__updated_files, True)
+ self.run_2to3(self.__doctests_2to3, True)
+
# Only compile actual .py files, using our base class' idea of what our
# output files are.
self.byte_compile(_build_py.get_outputs(self, include_bytecode=0))
- def get_data_files(self):
+ def __getattr__(self, attr):
+ if attr=='data_files': # lazily compute data files
+ self.data_files = files = self._get_data_files()
+ return files
+ return _build_py.__getattr__(self,attr)
+
+ def build_module(self, module, module_file, package):
+ outfile, copied = _build_py.build_module(self, module, module_file, package)
+ if copied:
+ self.__updated_files.append(outfile)
+ return outfile, copied
+
+ def _get_data_files(self):
"""Generate list of '(package,src_dir,build_dir,filenames)' tuples"""
+ self.analyze_manifest()
data = []
for package in self.packages or ():
# Locate package source directory
@@ -53,38 +80,142 @@ class build_py(_build_py):
filenames = [
file[plen:] for file in self.find_data_files(package, src_dir)
]
- data.append( (package, src_dir, build_dir, filenames) )
+ data.append((package, src_dir, build_dir, filenames))
return data
def find_data_files(self, package, src_dir):
"""Return filenames for package's data files in 'src_dir'"""
globs = (self.package_data.get('', [])
+ self.package_data.get(package, []))
- files = []
+ files = self.manifest_files.get(package, [])[:]
for pattern in globs:
# Each pattern has to be converted to a platform-specific path
files.extend(glob(os.path.join(src_dir, convert_path(pattern))))
- return files
+ return self.exclude_data_files(package, src_dir, files)
def build_package_data(self):
"""Copy data files into build directory"""
- lastdir = None
for package, src_dir, build_dir, filenames in self.data_files:
for filename in filenames:
target = os.path.join(build_dir, filename)
self.mkpath(os.path.dirname(target))
- self.copy_file(os.path.join(src_dir, filename), target)
-
- def get_outputs(self, include_bytecode=1):
- """Return complete list of files copied to the build directory
-
- This includes both '.py' files and data files, as well as '.pyc' and
- '.pyo' files if 'include_bytecode' is true. (This method is needed for
- the 'install_lib' command to do its job properly, and to generate a
- correct installation manifest.)
- """
- return _build_py.get_outputs(self, include_bytecode) + [
- os.path.join(build_dir, filename)
- for package, src_dir, build_dir,filenames in self.data_files
- for filename in filenames
- ]
+ srcfile = os.path.join(src_dir, filename)
+ outf, copied = self.copy_file(srcfile, target)
+ srcfile = os.path.abspath(srcfile)
+ if copied and srcfile in self.distribution.convert_2to3_doctests:
+ self.__doctests_2to3.append(outf)
+
+ def analyze_manifest(self):
+ self.manifest_files = mf = {}
+ if not self.distribution.include_package_data:
+ return
+ src_dirs = {}
+ for package in self.packages or ():
+ # Locate package source directory
+ src_dirs[assert_relative(self.get_package_dir(package))] = package
+
+ self.run_command('egg_info')
+ ei_cmd = self.get_finalized_command('egg_info')
+ for path in ei_cmd.filelist.files:
+ d,f = os.path.split(assert_relative(path))
+ prev = None
+ oldf = f
+ while d and d!=prev and d not in src_dirs:
+ prev = d
+ d, df = os.path.split(d)
+ f = os.path.join(df, f)
+ if d in src_dirs:
+ if path.endswith('.py') and f==oldf:
+ continue # it's a module, not data
+ mf.setdefault(src_dirs[d],[]).append(path)
+
+ def get_data_files(self): pass # kludge 2.4 for lazy computation
+
+ if sys.version<"2.4": # Python 2.4 already has this code
+ def get_outputs(self, include_bytecode=1):
+ """Return complete list of files copied to the build directory
+
+ This includes both '.py' files and data files, as well as '.pyc'
+ and '.pyo' files if 'include_bytecode' is true. (This method is
+ needed for the 'install_lib' command to do its job properly, and to
+ generate a correct installation manifest.)
+ """
+ return _build_py.get_outputs(self, include_bytecode) + [
+ os.path.join(build_dir, filename)
+ for package, src_dir, build_dir,filenames in self.data_files
+ for filename in filenames
+ ]
+
+ def check_package(self, package, package_dir):
+ """Check namespace packages' __init__ for declare_namespace"""
+ try:
+ return self.packages_checked[package]
+ except KeyError:
+ pass
+
+ init_py = _build_py.check_package(self, package, package_dir)
+ self.packages_checked[package] = init_py
+
+ if not init_py or not self.distribution.namespace_packages:
+ return init_py
+
+ for pkg in self.distribution.namespace_packages:
+ if pkg==package or pkg.startswith(package+'.'):
+ break
+ else:
+ return init_py
+
+ f = open(init_py,'rbU')
+ if 'declare_namespace'.encode() not in f.read():
+ from distutils.errors import DistutilsError
+ raise DistutilsError(
+ "Namespace package problem: %s is a namespace package, but its\n"
+ "__init__.py does not call declare_namespace()! Please fix it.\n"
+ '(See the setuptools manual under "Namespace Packages" for '
+ "details.)\n" % (package,)
+ )
+ f.close()
+ return init_py
+
+ def initialize_options(self):
+ self.packages_checked={}
+ _build_py.initialize_options(self)
+
+ def get_package_dir(self, package):
+ res = _build_py.get_package_dir(self, package)
+ if self.distribution.src_root is not None:
+ return os.path.join(self.distribution.src_root, res)
+ return res
+
+ def exclude_data_files(self, package, src_dir, files):
+ """Filter filenames for package's data files in 'src_dir'"""
+ globs = (self.exclude_package_data.get('', [])
+ + self.exclude_package_data.get(package, []))
+ bad = []
+ for pattern in globs:
+ bad.extend(
+ fnmatch.filter(
+ files, os.path.join(src_dir, convert_path(pattern))
+ )
+ )
+ bad = dict.fromkeys(bad)
+ seen = {}
+ return [
+ f for f in files if f not in bad
+ and f not in seen and seen.setdefault(f,1) # ditch dupes
+ ]
+
+
+def assert_relative(path):
+ if not os.path.isabs(path):
+ return path
+ from distutils.errors import DistutilsSetupError
+ msg = textwrap.dedent("""
+ Error: setup script specifies an absolute path:
+
+ %s
+
+ setup() arguments must *always* be /-separated paths relative to the
+ setup.py directory, *never* absolute paths.
+ """).lstrip() % path
+ raise DistutilsSetupError(msg)
diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py
index 1eb8bf6b..1d500040 100755
--- a/setuptools/command/develop.py
+++ b/setuptools/command/develop.py
@@ -1,90 +1,141 @@
from setuptools.command.easy_install import easy_install
-from distutils.util import convert_path
+from distutils.util import convert_path, subst_vars
from pkg_resources import Distribution, PathMetadata, normalize_path
from distutils import log
-import sys, os
+from distutils.errors import DistutilsError, DistutilsOptionError
+import os, sys, setuptools, glob
class develop(easy_install):
"""Set up package for development"""
description = "install package in 'development mode'"
- user_options = [
- ("install-dir=", "d", "link package from DIR"),
- ("script-dir=", "s", "create script wrappers in DIR"),
- ("multi-version", "m", "make apps have to require() a version"),
- ("exclude-scripts", "x", "Don't install scripts"),
- ("always-copy", "a", "Copy all needed dependencies to install dir"),
+ user_options = easy_install.user_options + [
("uninstall", "u", "Uninstall this source package"),
+ ("egg-path=", None, "Set the path to be used in the .egg-link file"),
]
- boolean_options = [
- 'multi-version', 'exclude-scripts', 'always-copy', 'uninstall'
- ]
+ boolean_options = easy_install.boolean_options + ['uninstall']
command_consumes_arguments = False # override base
- negative_opt = {}
-
def run(self):
if self.uninstall:
self.multi_version = True
self.uninstall_link()
else:
self.install_for_development()
+ self.warn_deprecated_options()
def initialize_options(self):
self.uninstall = None
+ self.egg_path = None
easy_install.initialize_options(self)
+ self.setup_path = None
+ self.always_copy_from = '.' # always copy eggs installed in curdir
def finalize_options(self):
ei = self.get_finalized_command("egg_info")
+ if ei.broken_egg_info:
+ raise DistutilsError(
+ "Please rename %r to %r before using 'develop'"
+ % (ei.egg_info, ei.broken_egg_info)
+ )
self.args = [ei.egg_name]
+
+
+
+
easy_install.finalize_options(self)
+ self.expand_basedirs()
+ self.expand_dirs()
+ # pick up setup-dir .egg files only: no .egg-info
+ self.package_index.scan(glob.glob('*.egg'))
+
self.egg_link = os.path.join(self.install_dir, ei.egg_name+'.egg-link')
self.egg_base = ei.egg_base
- self.egg_path = os.path.abspath(ei.egg_base)
+ if self.egg_path is None:
+ self.egg_path = os.path.abspath(ei.egg_base)
+
+ target = normalize_path(self.egg_base)
+ if normalize_path(os.path.join(self.install_dir, self.egg_path)) != target:
+ raise DistutilsOptionError(
+ "--egg-path must be a relative path from the install"
+ " directory to "+target
+ )
# Make a distribution for the package's source
self.dist = Distribution(
- normalize_path(self.egg_path),
- PathMetadata(self.egg_path, os.path.abspath(ei.egg_info)),
+ target,
+ PathMetadata(target, os.path.abspath(ei.egg_info)),
project_name = ei.egg_name
)
- def install_for_development(self):
- # Ensure metadata is up-to-date
- self.run_command('egg_info')
- ei = self.get_finalized_command("egg_info")
-
- # Build extensions in-place
- self.reinitialize_command('build_ext', inplace=1)
- self.run_command('build_ext')
+ p = self.egg_base.replace(os.sep,'/')
+ if p!= os.curdir:
+ p = '../' * (p.count('/')+1)
+ self.setup_path = p
+ p = normalize_path(os.path.join(self.install_dir, self.egg_path, p))
+ if p != normalize_path(os.curdir):
+ raise DistutilsOptionError(
+ "Can't get a consistent path to setup script from"
+ " installation directory", p, normalize_path(os.curdir))
+ def install_for_development(self):
+ if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False):
+ # If we run 2to3 we can not do this inplace:
+
+ # Ensure metadata is up-to-date
+ self.reinitialize_command('build_py', inplace=0)
+ self.run_command('build_py')
+ bpy_cmd = self.get_finalized_command("build_py")
+ build_path = normalize_path(bpy_cmd.build_lib)
+
+ # Build extensions
+ self.reinitialize_command('egg_info', egg_base=build_path)
+ self.run_command('egg_info')
+
+ self.reinitialize_command('build_ext', inplace=0)
+ self.run_command('build_ext')
+
+ # Fixup egg-link and easy-install.pth
+ ei_cmd = self.get_finalized_command("egg_info")
+ self.egg_path = build_path
+ self.dist.location = build_path
+ self.dist._provider = PathMetadata(build_path, ei_cmd.egg_info) # XXX
+ else:
+ # Without 2to3 inplace works fine:
+ self.run_command('egg_info')
+
+ # Build extensions in-place
+ self.reinitialize_command('build_ext', inplace=1)
+ self.run_command('build_ext')
+
+ self.install_site_py() # ensure that target dir is site-safe
+ if setuptools.bootstrap_install_from:
+ self.easy_install(setuptools.bootstrap_install_from)
+ setuptools.bootstrap_install_from = None
# create an .egg-link in the installation dir, pointing to our egg
log.info("Creating %s (link to %s)", self.egg_link, self.egg_base)
if not self.dry_run:
f = open(self.egg_link,"w")
- f.write(self.egg_path)
+ f.write(self.egg_path + "\n" + self.setup_path)
f.close()
-
# postprocess the installed distro, fixing up .pth, installing scripts,
# and handling requirements
- self.process_distribution(None, self.dist)
-
-
-
-
+ self.process_distribution(None, self.dist, not self.no_deps)
def uninstall_link(self):
if os.path.exists(self.egg_link):
log.info("Removing %s (link to %s)", self.egg_link, self.egg_base)
- contents = [line.rstrip() for line in file(self.egg_link)]
- if contents != [self.egg_path]:
+ egg_link_file = open(self.egg_link)
+ contents = [line.rstrip() for line in egg_link_file]
+ egg_link_file.close()
+ if contents not in ([self.egg_path], [self.egg_path, self.setup_path]):
log.warn("Link points to %s: uninstall aborted", contents)
return
if not self.dry_run:
@@ -92,15 +143,20 @@ class develop(easy_install):
if not self.dry_run:
self.update_pth(self.dist) # remove any .pth link to us
if self.distribution.scripts:
+ # XXX should also check for entry point scripts!
log.warn("Note: you must uninstall or replace scripts manually!")
-
def install_egg_scripts(self, dist):
if dist is not self.dist:
# Installing a dependency, so fall back to normal behavior
return easy_install.install_egg_scripts(self,dist)
# create wrapper scripts in the script dir, pointing to dist.scripts
+
+ # new-style...
+ self.install_wrapper_scripts(dist)
+
+ # ...and old-style
for script_name in self.distribution.scripts or []:
script_path = os.path.abspath(convert_path(script_name))
script_name = os.path.basename(script_path)
@@ -109,15 +165,3 @@ class develop(easy_install):
f.close()
self.install_script(dist, script_name, script_text, script_path)
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index e0a98b62..8e39ee80 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -1,5 +1,6 @@
-#!python
-"""\
+#!/usr/bin/env python
+
+"""
Easy Install
------------
@@ -7,46 +8,97 @@ 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
+__ https://pythonhosted.org/setuptools/easy_install.html
+
"""
-import sys, os.path, zipimport, shutil, tempfile, zipfile
+import sys
+import os
+import zipimport
+import shutil
+import tempfile
+import zipfile
+import re
+import stat
+import random
+import platform
+import textwrap
+import warnings
+import site
+import struct
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
+
+import pkg_resources
+from setuptools import Command, _dont_write_bytecode
+from setuptools.sandbox import run_setup
+from setuptools.py31compat import get_path, get_config_vars
+
+from distutils.util import get_platform
+from distutils.util import convert_path, subst_vars
from distutils.errors import DistutilsArgError, DistutilsOptionError, \
- DistutilsError
+ DistutilsError, DistutilsPlatformError
+from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS
+from setuptools.command import setopt
from setuptools.archive_util import unpack_archive
-from setuptools.package_index import PackageIndex, parse_bdist_wininst
+from setuptools.package_index import PackageIndex
from setuptools.package_index import URL_SCHEME
from setuptools.command import bdist_egg, egg_info
-from pkg_resources import *
+from setuptools.compat import (iteritems, maxsize, basestring, unicode,
+ reraise)
+from pkg_resources import (
+ yield_lines, normalize_path, resource_string, ensure_directory,
+ get_distribution, find_distributions, Environment, Requirement,
+ Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound,
+ VersionConflict, DEVELOP_DIST,
+)
+
+sys_executable = os.environ.get('__VENV_LAUNCHER__',
+ 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))
- )
+def is_64bit():
+ return struct.calcsize("P") == 8
+
+def samefile(p1, p2):
+ both_exist = os.path.exists(p1) and os.path.exists(p2)
+ use_samefile = hasattr(os.path, 'samefile') and both_exist
+ if use_samefile:
+ return os.path.samefile(p1, p2)
+ norm_p1 = os.path.normpath(os.path.normcase(p1))
+ norm_p2 = os.path.normpath(os.path.normcase(p2))
+ return norm_p1 == norm_p2
+
+if sys.version_info <= (3,):
+ def _to_ascii(s):
+ return s
+ def isascii(s):
+ try:
+ unicode(s, 'ascii')
+ return True
+ except UnicodeError:
+ return False
+else:
+ def _to_ascii(s):
+ return s.encode('ascii')
+ def isascii(s):
+ try:
+ s.encode('ascii')
+ return True
+ except UnicodeError:
+ return False
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)"),
@@ -56,32 +108,46 @@ class easy_install(Command):
("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", "delete old packages that get in the way"),
- ("ignore-conflicts-at-my-risk", None,
- "install even if old packages are in the way, even though it "
- "most likely means the new package won't work."),
("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]"),
+ "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"),
+ "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"),
+ ('version', None, "print version information and exit"),
+ ('no-find-links', None,
+ "Don't load find-links defined in packages being installed")
]
-
boolean_options = [
'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy',
- 'delete-conflicting', 'ignore-conflicts-at-my-risk', 'editable',
+ 'editable',
+ 'no-deps', 'local-snapshots-ok', 'version'
]
+ if site.ENABLE_USER_SITE:
+ help_msg = "install in user site-package '%s'" % site.USER_SITE
+ user_options.append(('user', None, help_msg))
+ boolean_options.append('user')
+
negative_opt = {'always-unzip': 'zip-ok'}
create_index = PackageIndex
def initialize_options(self):
- self.zip_ok = None
+ if site.ENABLE_USER_SITE:
+ whereami = os.path.abspath(__file__)
+ self.user = whereami.startswith(site.USER_SITE)
+ else:
+ self.user = 0
+
+ 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
@@ -89,44 +155,104 @@ class easy_install(Command):
self.args = None
self.optimize = self.record = None
self.upgrade = self.always_copy = self.multi_version = None
- self.editable = None
+ self.editable = self.no_deps = self.allow_hosts = None
+ self.root = self.prefix = self.no_report = None
+ self.version = None
+ self.install_purelib = None # for pure module distributions
+ self.install_platlib = None # non-pure (dists w/ extensions)
+ self.install_headers = None # for C/C++ headers
+ self.install_lib = None # set to either purelib or platlib
+ self.install_scripts = None
+ self.install_data = None
+ self.install_base = None
+ self.install_platbase = None
+ if site.ENABLE_USER_SITE:
+ self.install_userbase = site.USER_BASE
+ self.install_usersite = site.USER_SITE
+ else:
+ self.install_userbase = None
+ self.install_usersite = None
+ self.no_find_links = None
# Options not specifiable via command line
self.package_index = None
- self.pth_file = None
- self.delete_conflicting = None
- self.ignore_conflicts_at_my_risk = None
+ self.pth_file = self.always_copy_from = 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:
- log.info("Deleting %s", filename)
- if not self.dry_run:
- if os.path.isdir(filename):
- shutil.rmtree(filename)
- else:
- os.unlink(filename)
-
-
-
-
-
-
-
-
-
-
-
+ 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):
+ if self.version:
+ print('setuptools %s' % get_distribution('setuptools').version)
+ sys.exit()
+
+ py_version = sys.version.split()[0]
+ prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix')
+
+ self.config_vars = {
+ 'dist_name': self.distribution.get_name(),
+ 'dist_version': self.distribution.get_version(),
+ 'dist_fullname': self.distribution.get_fullname(),
+ 'py_version': py_version,
+ 'py_version_short': py_version[0:3],
+ 'py_version_nodot': py_version[0] + py_version[2],
+ 'sys_prefix': prefix,
+ 'prefix': prefix,
+ 'sys_exec_prefix': exec_prefix,
+ 'exec_prefix': exec_prefix,
+ # Only python 3.2+ has abiflags
+ 'abiflags': getattr(sys, 'abiflags', ''),
+ }
+
+ if site.ENABLE_USER_SITE:
+ self.config_vars['userbase'] = self.install_userbase
+ self.config_vars['usersite'] = self.install_usersite
+
+ # fix the install_dir if "--user" was used
+ #XXX: duplicate of the code in the setup command
+ if self.user and site.ENABLE_USER_SITE:
+ self.create_home_path()
+ if self.install_userbase is None:
+ raise DistutilsPlatformError(
+ "User base directory is not specified")
+ self.install_base = self.install_platbase = self.install_userbase
+ if os.name == 'posix':
+ self.select_scheme("unix_user")
+ else:
+ self.select_scheme(os.name + "_user")
+ self.expand_basedirs()
+ self.expand_dirs()
- 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
+ if self.no_find_links is None:
+ self.no_find_links = False
+
# 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.
@@ -137,8 +263,14 @@ class easy_install(Command):
self.set_undefined_options('install_scripts',
('install_dir', 'script_dir')
)
+
+ if self.user and self.install_purelib:
+ self.install_dir = self.install_purelib
+ self.script_dir = self.install_scripts
# default --record from the install command
self.set_undefined_options('install', ('record', 'record'))
+ # Should this be moved to the if statement below? It's not used
+ # elsewhere
normpath = map(normalize_path, sys.path)
self.all_site_dirs = get_site_dirs()
if self.site_dirs is not None:
@@ -154,41 +286,32 @@ class easy_install(Command):
)
else:
self.all_site_dirs.append(normalize_path(d))
-
- instdir = normalize_path(self.install_dir or self.all_site_dirs[-1])
- if instdir in self.all_site_dirs:
- if self.pth_file is None:
- self.pth_file = PthDistributions(
- os.path.join(instdir,'easy-install.pth')
- )
-
- 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-package' dirs"
- )
-
- self.install_dir = instdir
- self.index_url = self.index_url or "http://www.python.org/pypi"
+ if not self.editable: self.check_site_dir()
+ self.index_url = self.index_url or "https://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
+ self.index_url, search_path = self.shadow_path, hosts=hosts,
)
- self.local_index = Environment(self.shadow_path)
+ 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)
+ if not self.no_find_links:
+ self.package_index.add_find_links(self.find_links)
self.set_undefined_options('install_lib', ('optimize','optimize'))
if not isinstance(self.optimize,int):
try:
@@ -197,52 +320,214 @@ class easy_install(Command):
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 _expand_attrs(self, attrs):
+ for attr in attrs:
+ val = getattr(self, attr)
+ if val is not None:
+ if os.name == 'posix' or os.name == 'nt':
+ val = os.path.expanduser(val)
+ val = subst_vars(val, self.config_vars)
+ setattr(self, attr, val)
+
+ def expand_basedirs(self):
+ """Calls `os.path.expanduser` on install_base, install_platbase and
+ root."""
+ self._expand_attrs(['install_base', 'install_platbase', 'root'])
+
+ def expand_dirs(self):
+ """Calls `os.path.expanduser` on install dirs."""
+ self._expand_attrs(['install_purelib', 'install_platlib',
+ 'install_lib', 'install_headers',
+ 'install_scripts', 'install_data',])
def run(self):
- if self.verbose<>self.distribution.verbose:
+ 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, True)
+ 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 range(len(outputs)):
+ outputs[counter] = outputs[counter][root_len:]
from distutils import file_util
self.execute(
- file_util.write_file, (self.record, self.outputs),
+ 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, maxsize)
+ return os.path.join(self.install_dir, "test-easy-install-%s" % pid)
+ def warn_deprecated_options(self):
+ pass
+ 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, [_f for _f in PYTHONPATH if _f]):
+ # 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):
+ template = """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
+"""
+ msg = template % (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:
+
+ https://pythonhosted.org/setuptools/easy_install.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)
+ dirname = os.path.dirname(ok_file)
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+ f = open(pth_file,'w')
+ except (OSError,IOError):
+ self.cant_write_to_target()
+ else:
+ try:
+ f.write("import os; f = open(%r, 'w'); f.write('OK'); f.close()\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'):
+ if dist.metadata_isdir('scripts/' + script_name):
+ # The "script" is a directory, likely a Python 3
+ # __pycache__ directory, so skip it.
+ continue
+ 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):
@@ -270,24 +555,10 @@ class easy_install(Command):
(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):
@@ -305,41 +576,55 @@ class easy_install(Command):
spec = parse_requirement_arg(spec)
self.check_editable(spec)
- download = self.package_index.fetch(
- spec, tmpdir, self.upgrade, self.editable
+ dist = self.package_index.fetch_distribution(
+ spec, tmpdir, self.upgrade, self.editable, not self.always_copy,
+ self.local_index
)
-
- if download is None:
- raise DistutilsError(
- "Could not find distribution for %r" % spec
- )
-
- return self.install_item(spec, download, tmpdir, deps)
+ 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):
- shutil.rmtree(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 or self.always_copy:
+ 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))]
+ dists = [self.egg_distribution(download)]
self.process_distribution(spec, dists[0], deps, "Using")
if spec is not None:
@@ -347,25 +632,14 @@ class easy_install(Command):
if dist in spec:
return dist
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ def select_scheme(self, name):
+ """Sets the install directories by applying the install schemes."""
+ # it's the caller's problem if they supply a bad name!
+ scheme = INSTALL_SCHEMES[name]
+ for key in SCHEME_KEYS:
+ attrname = 'install_' + key
+ if getattr(self, attrname) is None:
+ setattr(self, attrname, scheme[key])
def process_distribution(self, requirement, dist, deps=True, *info):
self.update_pth(dist)
@@ -373,50 +647,46 @@ class easy_install(Command):
self.local_index.add(dist)
self.install_egg_scripts(dist)
self.installed_projects[dist.key] = dist
- log.warn(self.installation_report(dist, *info))
-
- if requirement is None:
- requirement = dist.as_requirement()
-
- if dist not in requirement:
- return
-
- if deps or self.always_copy:
- log.info("Processing dependencies for %s", requirement)
- else:
- return
-
- if self.always_copy:
- # Recursively install *all* dependencies
- for req in dist.requires(requirement.extras):
- if req.key not in self.installed_projects:
- self.easy_install(req)
+ log.info(self.installation_report(requirement, dist, *info))
+ if (dist.has_metadata('dependency_links.txt') and
+ not self.no_find_links):
+ 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:
- WorkingSet(self.shadow_path).resolve(
+ distros = WorkingSet([]).resolve(
[requirement], self.local_index, self.easy_install
)
- except DistributionNotFound, e:
+ except DistributionNotFound:
+ e = sys.exc_info()[1]
raise DistutilsError(
"Could not find required distribution %s" % e.args
)
- except VersionConflict, e:
+ except VersionConflict:
+ e = sys.exc_info()[1]
raise DistutilsError(
"Installed distribution %s conflicts with requirement %s"
% e.args
)
-
-
- def install_egg_scripts(self, dist):
- if self.exclude_scripts or not dist.metadata_isdir('scripts'):
- return
-
- for script_name in dist.metadata_listdir('scripts'):
- self.install_script(
- dist, script_name,
- dist.get_metadata('scripts/'+script_name).replace('\r','\n')
- )
+ 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:
@@ -430,10 +700,8 @@ class easy_install(Command):
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
- )
+ msg = "%r already exists in %s; build directory %s will not be kept"
+ log.warn(msg, spec.key, self.build_directory, setup_base)
return setup_base
if os.path.isdir(dist_filename):
setup_base = dist_filename
@@ -446,49 +714,61 @@ class easy_install(Command):
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)
+ 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):
- log.info("Installing %s script to %s", script_name,self.script_dir)
- target = os.path.join(self.script_dir, script_name)
- first, rest = script_text.split('\n',1)
- from distutils.command.build_scripts import first_line_re
- match = first_line_re.match(first)
- options = ''
- if match:
- options = match.group(1) or ''
- if options:
- options = ' '+options
+ """Generate a legacy script wrapper and install it"""
spec = str(dist.as_requirement())
- executable = os.path.normpath(sys.executable)
-
- if dev_path:
- script_text = (
- "#!%(executable)s%(options)s\n"
- "# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r\n"
- "from pkg_resources import require; require(%(spec)r)\n"
- "del require\n"
- "__file__ = %(dev_path)r\n"
- "execfile(__file__)\n"
- ) % locals()
- else:
- script_text = (
- "#!%(executable)s%(options)s\n"
- "# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r\n"
- "import pkg_resources\n"
- "pkg_resources.run_script(%(spec)r, %(script_name)r)\n"
- ) % locals()
+ is_script = is_python_script(script_text, script_name)
+
+ def get_template(filename):
+ """
+ There are a couple of template scripts in the package. This
+ function loads one of them and prepares it for use.
+
+ These templates use triple-quotes to escape variable
+ substitutions so the scripts get the 2to3 treatment when build
+ on Python 3. The templates cannot use triple-quotes naturally.
+ """
+ raw_bytes = resource_string('setuptools', template_name)
+ template_str = raw_bytes.decode('utf-8')
+ clean_template = template_str.replace('"""', '')
+ return clean_template
+
+ if is_script:
+ # See https://bitbucket.org/pypa/setuptools/issue/134 for info
+ # on script file naming and downstream issues with SVR4
+ template_name = 'script template.py'
+ if dev_path:
+ template_name = template_name.replace('.py', ' (dev).py')
+ script_text = (get_script_header(script_text) +
+ get_template(template_name) % locals())
+ self.write_script(script_name, _to_ascii(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)
+ mask = current_umask()
if not self.dry_run:
- f = open(target,"w")
- f.write(script_text)
+ ensure_directory(target)
+ if os.path.exists(target):
+ os.unlink(target)
+ f = open(target,"w"+mode)
+ f.write(contents)
f.close()
- try:
- os.chmod(target,0755)
- except (AttributeError, os.error):
- pass
-
+ chmod(target, 0x1FF-mask) # 0777
def install_eggs(self, spec, dist_filename, tmpdir):
# .egg dirs or files are already built, so just return them
@@ -499,14 +779,13 @@ class easy_install(Command):
# Anything else, try to extract and build
setup_base = tmpdir
- if os.path.isfile(dist_filename):
+ 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
- ):
+ and self.build_directory and spec is not None):
setup_base = self.maybe_move(spec, dist_filename, setup_base)
# Find the setup.py file
@@ -516,17 +795,17 @@ class easy_install(Command):
setups = glob(os.path.join(setup_base, '*', 'setup.py'))
if not setups:
raise DistutilsError(
- "Couldn't find a setup script in %s" % dist_filename
+ "Couldn't find a setup script in %s" % os.path.abspath(dist_filename)
)
if len(setups)>1:
raise DistutilsError(
- "Multiple setup scripts in %s" % dist_filename
+ "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.warn(self.report_editable(spec, setup_script))
+ log.info(self.report_editable(spec, setup_script))
return []
else:
return self.build_and_install(setup_script, setup_base)
@@ -545,13 +824,12 @@ class easy_install(Command):
ensure_directory(destination)
dist = self.egg_distribution(egg_path)
- self.check_conflicts(dist)
if not samefile(egg_path, destination):
- if os.path.isdir(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.isfile(destination):
+ 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"
@@ -579,40 +857,41 @@ class easy_install(Command):
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,
+ dist = Distribution(
+ None,
project_name=cfg.get('metadata','name'),
- version=cfg.get('metadata','version'),
- platform="win32"
+ version=cfg.get('metadata','version'), platform=get_platform(),
)
# Convert the .exe to an unpacked egg
egg_path = dist.location = os.path.join(tmpdir, dist.egg_name()+'.egg')
- egg_tmp = egg_path+'.tmp'
- pkg_inf = os.path.join(egg_tmp, 'EGG-INFO', 'PKG-INFO')
+ 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
- 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()
-
+ 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
+ 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
@@ -620,15 +899,16 @@ class easy_install(Command):
to_compile = []
native_libs = []
top_level = {}
-
def process(src,dst):
+ s = src.lower()
for old,new in prefixes:
- if src.startswith(old):
+ 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/':
@@ -638,122 +918,47 @@ class easy_install(Command):
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], parts[-1][:-1]
+ 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)
+ 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(egg_tmp,
+ 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')
- open(txt,'w').write('\n'.join(locals()[name])+'\n')
-
-
- def check_conflicts(self, dist):
- """Verify that there are no conflicting "old-style" packages"""
-
- from imp import find_module, get_suffixes
- from glob import glob
-
- blockers = []
- names = dict.fromkeys(dist._get_metadata('top_level.txt')) # XXX private attr
+ if not os.path.exists(txt):
+ f = open(txt,'w')
+ f.write('\n'.join(locals()[name])+'\n')
+ f.close()
- 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:
- 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, dist, what="Installed"):
+ def installation_report(self, req, dist, what="Installed"):
"""Helpful installation message for display to package users"""
-
- msg = "\n%(what)s %(eggloc)s"
- if self.multi_version:
+ 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 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:
+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 += """
+ 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
@@ -762,6 +967,7 @@ 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):
@@ -772,7 +978,7 @@ PYTHONPATH, or by being added to sys.path by your code.)
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
+ %(python)s setup.py develop
See the setuptools documentation for the "develop" command for more info.
""" % locals()
@@ -783,7 +989,7 @@ See the setuptools documentation for the "develop" command for more info.
args = list(args)
if self.verbose>2:
- v = 'v' * self.verbose - 1
+ v = 'v' * (self.verbose - 1)
args.insert(0,'-'+v)
elif self.verbose<2:
args.insert(0,'-q')
@@ -794,16 +1000,20 @@ See the setuptools documentation for the "develop" command for more info.
)
try:
run_setup(setup_script, args)
- except SystemExit, v:
+ except SystemExit:
+ v = sys.exc_info()[1]
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:
+ self._set_fetcher_options(os.path.dirname(setup_script))
args.append(dist_dir)
+
self.run_setup(setup_script, setup_base, args)
all_eggs = Environment([dist_dir])
eggs = []
@@ -815,10 +1025,33 @@ See the setuptools documentation for the "develop" command for more info.
dist_dir)
return eggs
finally:
- shutil.rmtree(dist_dir)
+ rmtree(dist_dir)
log.set_verbosity(self.verbose) # restore our log verbosity
- def update_pth(self,dist):
+ def _set_fetcher_options(self, base):
+ """
+ When easy_install is about to run bdist_egg on a source dist, that
+ source dist might have 'setup_requires' directives, requiring
+ additional fetching. Ensure the fetcher options given to easy_install
+ are available to that command as well.
+ """
+ # find the fetch options from easy_install and write them out
+ # to the setup.cfg file.
+ ei_opts = self.distribution.get_option_dict('easy_install').copy()
+ fetch_directives = (
+ 'find_links', 'site_dirs', 'index_url', 'optimize',
+ 'site_dirs', 'allow_hosts',
+ )
+ fetch_options = {}
+ for key, val in ei_opts.items():
+ if key not in fetch_directives: continue
+ fetch_options[key.replace('_', '-')] = val[1]
+ # create a settings dictionary suitable for `edit_config`
+ settings = dict(easy_install=fetch_options)
+ cfg_filename = os.path.join(base, 'setup.cfg')
+ setopt.edit_config(cfg_filename, settings)
+
+ def update_pth(self, dist):
if self.pth_file is None:
return
@@ -841,38 +1074,48 @@ See the setuptools documentation for the "develop" command for more info.
if dist.location not in self.shadow_path:
self.shadow_path.append(dist.location)
- self.pth_file.save()
+ if not self.dry_run:
- if dist.key=='setuptools':
- # Ensure that setuptools itself never becomes unavailable!
- # XXX should this check for latest version?
- f = open(os.path.join(self.install_dir,'setuptools.pth'), 'wt')
- f.write(dist.location+'\n')
- f.close()
+ 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):
+ 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]) | 0x16D) & 0xFED # 0555, 07755
+ chmod(f, mode)
def byte_compile(self, to_compile):
+ if _dont_write_bytecode:
+ self.warn('byte-compiling is disabled, skipping.')
+ return
+
from distutils.util import byte_compile
try:
# try to make the byte compile messages quieter
@@ -887,22 +1130,120 @@ See the setuptools documentation for the "develop" command for more info.
finally:
log.set_verbosity(self.verbose) # restore original verbosity
+ def no_default_version_msg(self):
+ template = """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:
+ https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations
+Please make the appropriate changes for your system and try again."""
+ return template % (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("setuptools", "site-patch.py")
+ current = ""
+ if os.path.exists(sitepy):
+ log.debug("Checking existing site.py in %s", self.install_dir)
+ f = open(sitepy,'rb')
+ current = f.read()
+ # we want str, not bytes
+ if sys.version_info >= (3,):
+ current = current.decode()
+
+ f.close()
+ 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
+
+ def create_home_path(self):
+ """Create directories under ~."""
+ if not self.user:
+ return
+ home = convert_path(os.path.expanduser("~"))
+ for name, path in iteritems(self.config_vars):
+ if path.startswith(home) and not os.path.isdir(path):
+ self.debug_print("os.makedirs('%s', 0700)" % path)
+ os.makedirs(path, 0x1C0) # 0700
+
+ 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, based on 'site' module's code to do this
- sitedirs = []
+ # return a list of 'site' dirs
+ sitedirs = [_f for _f in os.environ.get('PYTHONPATH',
+ '').split(os.pathsep) if _f]
prefixes = [sys.prefix]
if sys.exec_prefix != sys.prefix:
prefixes.append(sys.exec_prefix)
@@ -933,12 +1274,16 @@ def get_site_dirs():
'Python',
sys.version[:3],
'site-packages'))
+ lib_paths = get_path('purelib'), get_path('platlib')
+ for site_lib in lib_paths:
+ if site_lib not in sitedirs: sitedirs.append(site_lib)
- sitedirs = filter(os.path.isdir, sitedirs)
- sitedirs = map(normalize_path, sitedirs)
- return sitedirs or [normalize_path(get_python_lib())] # ensure at least one
+ if site.ENABLE_USER_SITE:
+ sitedirs.append(site.USER_SITE)
+ sitedirs = list(map(normalize_path, sitedirs))
+ return sitedirs
def expand_paths(inputs):
@@ -998,15 +1343,27 @@ def extract_wininst_cfg(dist_filename):
return None
f.seek(prepended-12)
- import struct, StringIO, ConfigParser
+ from setuptools.compat import StringIO, ConfigParser
+ import struct
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+bmlen))
+ f.seek(prepended-(12+cfglen))
cfg = ConfigParser.RawConfigParser({'version':'','target_version':''})
try:
- cfg.readfp(StringIO.StringIO(f.read(cfglen).split(chr(0),1)[0]))
+ part = f.read(cfglen)
+ # part is in bytes, but we need to read up to the first null
+ # byte.
+ if sys.version_info >= (2,6):
+ null_byte = bytes([0])
+ else:
+ null_byte = chr(0)
+ config = part.split(null_byte, 1)[0]
+ # Now the config is in bytes, but for RawConfigParser, it should
+ # be text, so decode it.
+ config = config.decode(sys.getfilesystemencoding())
+ cfg.readfp(StringIO(config))
except ConfigParser.Error:
return None
if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
@@ -1017,37 +1374,41 @@ def extract_wininst_cfg(dist_filename):
f.close()
-
-
-
-
-
-
def get_exe_prefixes(exe_filename):
"""Get exe->egg path translations for a given .exe file"""
prefixes = [
- ('PURELIB/', ''),
+ ('PURELIB/', ''), ('PLATLIB/pywin32_system32', ''),
('PLATLIB/', ''),
- ('SCRIPTS/', 'EGG-INFO/scripts/')
+ ('SCRIPTS/', 'EGG-INFO/scripts/'),
+ ('DATA/lib/site-packages', ''),
]
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:
+ 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] in ('PURELIB','PLATLIB'):
- pth = z.read(name).strip()
- prefixes[0] = ('PURELIB/%s/' % pth), ''
- prefixes[1] = ('PLATLIB/%s/' % pth), ''
- break
+ if parts[0].upper() in ('PURELIB','PLATLIB'):
+ contents = z.read(name)
+ if sys.version_info >= (3,):
+ contents = contents.decode()
+ for pth in yield_lines(contents):
+ 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
@@ -1059,90 +1420,498 @@ def parse_requirement_arg(spec):
"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):
- self.filename = filename; self._load()
- Environment.__init__(
- self, list(yield_lines(self.paths)), None, None
- )
+ def __init__(self, filename, sitedirs=()):
+ self.filename = filename
+ self.sitedirs = list(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):
+ list(map(self.add, find_distributions(path, True)))
def _load(self):
self.paths = []
- seen = {}
+ saw_import = False
+ seen = dict.fromkeys(self.sitedirs)
if os.path.isfile(self.filename):
- for line in open(self.filename,'rt'):
+ f = open(self.filename,'rt')
+ for line in f:
+ 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(path)
+ 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
+ f.close()
- while self.paths and not self.paths[-1].strip(): self.paths.pop()
+ 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 self.dirty:
+ if not self.dirty:
+ return
+
+ data = '\n'.join(map(self.make_relative,self.paths))
+ if data:
log.debug("Saving %s", self.filename)
- data = '\n'.join(self.paths+[''])
- f = open(self.filename,'wt'); f.write(data); f.close()
- self.dirty = False
+ 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,'wt')
+ 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):
+ def add(self, dist):
"""Add `dist` to the distribution map"""
- if dist.location not in self.paths:
- self.paths.append(dist.location); self.dirty = True
- Environment.add(self,dist)
-
- def remove(self,dist):
+ if (dist.location not in self.paths and (
+ dist.location not in self.sitedirs or
+ dist.location == os.getcwd() # account for '.' being in PYTHONPATH
+ )):
+ 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 main(argv, **kw):
- from setuptools import setup
- setup(script_args = ['-q','easy_install', '-v']+argv, **kw)
-
-
-
-
-
-
-
+ 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_line_re in Python >=3.1.4 and >=3.2.1 is a bytes pattern.
+ if not isinstance(first_line_re.pattern, str):
+ first_line_re = re.compile(first_line_re.pattern.decode())
+
+ 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 not isascii(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)
+ et, ev, _ = sys.exc_info()
+ reraise(et, (ev[0], ev[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 = sys.exc_info()[1]
+ log.debug("chmod failed: %s", e)
+
+def fix_jython_executable(executable, options):
+ if sys.platform.startswith('java') and is_sh(executable):
+ # Workaround for Jython is not needed on Linux systems.
+ import java
+ if java.lang.System.getProperty("os.name") == "Linux":
+ return 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
+class ScriptWriter(object):
+ """
+ Encapsulates behavior around writing entry point scripts for console and
+ gui apps.
+ """
+ template = textwrap.dedent("""
+ # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r
+ __requires__ = %(spec)r
+ import sys
+ from pkg_resources import load_entry_point
+ if __name__ == '__main__':
+ sys.exit(
+ load_entry_point(%(spec)r, %(group)r, %(name)r)()
+ )
+ """).lstrip()
+
+ @classmethod
+ def get_script_args(cls, dist, executable=sys_executable, wininst=False):
+ """
+ Yield write_script() argument tuples for a distribution's entrypoints
+ """
+ gen_class = cls.get_writer(wininst)
+ spec = str(dist.as_requirement())
+ header = get_script_header("", executable, wininst)
+ for type_ in 'console', 'gui':
+ group = type_ + '_scripts'
+ for name, ep in dist.get_entry_map(group).items():
+ script_text = gen_class.template % locals()
+ for res in gen_class._get_script_args(type_, name, header,
+ script_text):
+ yield res
+
+ @classmethod
+ def get_writer(cls, force_windows):
+ if force_windows or sys.platform=='win32':
+ return WindowsScriptWriter.get_writer()
+ return cls
+
+ @classmethod
+ def _get_script_args(cls, type_, name, header, script_text):
+ # Simply write the stub with no extension.
+ yield (name, header+script_text)
+
+
+class WindowsScriptWriter(ScriptWriter):
+ @classmethod
+ def get_writer(cls):
+ """
+ Get a script writer suitable for Windows
+ """
+ writer_lookup = dict(
+ executable=WindowsExecutableLauncherWriter,
+ natural=cls,
+ )
+ # for compatibility, use the executable launcher by default
+ launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable')
+ return writer_lookup[launcher]
+
+ @classmethod
+ def _get_script_args(cls, type_, name, header, script_text):
+ "For Windows, add a .py extension"
+ ext = dict(console='.pya', gui='.pyw')[type_]
+ if ext not in os.environ['PATHEXT'].lower().split(';'):
+ warnings.warn("%s not listed in PATHEXT; scripts will not be "
+ "recognized as executables." % ext, UserWarning)
+ old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe']
+ old.remove(ext)
+ header = cls._adjust_header(type_, header)
+ blockers = [name+x for x in old]
+ yield name+ext, header+script_text, 't', blockers
+
+ @staticmethod
+ def _adjust_header(type_, orig_header):
+ """
+ Make sure 'pythonw' is used for gui and and 'python' is used for
+ console (regardless of what sys.executable is).
+ """
+ pattern = 'pythonw.exe'
+ repl = 'python.exe'
+ if type_ == 'gui':
+ pattern, repl = repl, pattern
+ pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE)
+ new_header = pattern_ob.sub(string=orig_header, repl=repl)
+ clean_header = new_header[2:-1].strip('"')
+ if sys.platform == 'win32' and not os.path.exists(clean_header):
+ # the adjusted version doesn't exist, so return the original
+ return orig_header
+ return new_header
+
+
+class WindowsExecutableLauncherWriter(WindowsScriptWriter):
+ @classmethod
+ def _get_script_args(cls, type_, name, header, script_text):
+ """
+ For Windows, add a .py extension and an .exe launcher
+ """
+ if type_=='gui':
+ launcher_type = 'gui'
+ ext = '-script.pyw'
+ old = ['.pyw']
+ else:
+ launcher_type = 'cli'
+ ext = '-script.py'
+ old = ['.py','.pyc','.pyo']
+ hdr = cls._adjust_header(type_, header)
+ blockers = [name+x for x in old]
+ yield (name+ext, hdr+script_text, 't', blockers)
+ yield (
+ name+'.exe', get_win_launcher(launcher_type),
+ 'b' # write in binary mode
+ )
+ if not is_64bit():
+ # install a manifest for the launcher to prevent Windows
+ # from detecting it as an installer (which it will for
+ # launchers like easy_install.exe). Consider only
+ # adding a manifest for launchers detected as installers.
+ # See Distribute #143 for details.
+ m_name = name + '.exe.manifest'
+ yield (m_name, load_launcher_manifest(name), 't')
+
+# for backward-compatibility
+get_script_args = ScriptWriter.get_script_args
+
+def get_win_launcher(type):
+ """
+ Load the Windows launcher (executable) suitable for launching a script.
+ `type` should be either 'cli' or 'gui'
+ Returns the executable as a byte string.
+ """
+ launcher_fn = '%s.exe' % type
+ if platform.machine().lower()=='arm':
+ launcher_fn = launcher_fn.replace(".", "-arm.")
+ if is_64bit():
+ launcher_fn = launcher_fn.replace(".", "-64.")
+ else:
+ launcher_fn = launcher_fn.replace(".", "-32.")
+ return resource_string('setuptools', launcher_fn)
+
+def load_launcher_manifest(name):
+ manifest = pkg_resources.resource_string(__name__, 'launcher manifest.xml')
+ if sys.version_info[0] < 3:
+ return manifest % vars()
+ else:
+ return manifest.decode('utf-8') % vars()
+
+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:
+ 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:
+ onerror(os.remove, fullname, sys.exc_info())
+ try:
+ os.rmdir(path)
+ except os.error:
+ onerror(os.rmdir, path, sys.exc_info())
+
+def current_umask():
+ tmp = os.umask(0x12) # 022
+ os.umask(tmp)
+ return tmp
+
+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):
+ return USAGE % dict(
+ script=os.path.basename(script_name),
+ )
+ 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
+ )
+ )
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index c05601a4..5953aad4 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -2,16 +2,24 @@
Create a distribution's .egg-info directory and contents"""
-# This module should be kept compatible with Python 2.3
import os
+import re
+import sys
+
from setuptools import Command
-from distutils.errors import *
+import distutils.errors
from distutils import log
-from pkg_resources import parse_requirements, safe_name, \
- safe_version, yield_lines, EntryPoint, iter_entry_points
+from setuptools.command.sdist import sdist
+from setuptools.compat import basestring
+from setuptools import svn_utils
+from distutils.util import convert_path
+from distutils.filelist import FileList as _FileList
+from pkg_resources import (parse_requirements, safe_name, parse_version,
+ safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename)
+from setuptools.command.sdist import walk_revctrl
-class egg_info(Command):
+class egg_info(Command):
description = "create a distribution's .egg-info directory"
user_options = [
@@ -21,12 +29,16 @@ class egg_info(Command):
"Add subversion revision ID to version number"),
('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
('tag-build=', 'b', "Specify explicit tag to add to version number"),
+ ('no-svn-revision', 'R',
+ "Don't add subversion revision ID [default]"),
+ ('no-date', 'D', "Don't include date stamp [default]"),
]
- boolean_options = ['tag-date','tag-svn-revision']
+ boolean_options = ['tag-date', 'tag-svn-revision']
+ negative_opt = {'no-svn-revision': 'tag-svn-revision',
+ 'no-date': 'tag-date'}
-
- def initialize_options (self):
+ def initialize_options(self):
self.egg_name = None
self.egg_version = None
self.egg_base = None
@@ -34,13 +46,23 @@ class egg_info(Command):
self.tag_build = None
self.tag_svn_revision = 0
self.tag_date = 0
+ self.broken_egg_info = False
+ self.vtags = None
+
+ def save_version_info(self, filename):
+ from setuptools.command.setopt import edit_config
+ values = dict(
+ egg_info=dict(
+ tag_svn_revision=0,
+ tag_date=0,
+ tag_build=self.tags(),
+ )
+ )
+ edit_config(filename, values)
-
-
-
-
- def finalize_options (self):
+ def finalize_options(self):
self.egg_name = safe_name(self.distribution.get_name())
+ self.vtags = self.tags()
self.egg_version = self.tagged_version()
try:
@@ -48,7 +70,7 @@ class egg_info(Command):
parse_requirements('%s==%s' % (self.egg_name,self.egg_version))
)
except ValueError:
- raise DistutilsOptionError(
+ raise distutils.errors.DistutilsOptionError(
"Invalid distribution name or version syntax: %s-%s" %
(self.egg_name,self.egg_version)
)
@@ -58,41 +80,39 @@ class egg_info(Command):
self.egg_base = (dirs or {}).get('',os.curdir)
self.ensure_dirname('egg_base')
- self.egg_info = os.path.join(self.egg_base, self.egg_name+'.egg-info')
+ self.egg_info = to_filename(self.egg_name)+'.egg-info'
+ if self.egg_base != os.curdir:
+ self.egg_info = os.path.join(self.egg_base, self.egg_info)
+ if '-' in self.egg_name: self.check_broken_egg_info()
- # Set package version and name for the benefit of dumber commands
- # (e.g. sdist, bdist_wininst, etc.) We escape '-' so filenames will
- # be more machine-parseable.
+ # Set package version for the benefit of dumber commands
+ # (e.g. sdist, bdist_wininst, etc.)
#
- metadata = self.distribution.metadata
- metadata.version = self.egg_version.replace('-','_')
- metadata.name = self.egg_name.replace('-','_')
-
-
-
-
-
-
-
-
-
-
-
+ self.distribution.metadata.version = self.egg_version
+ # If we bootstrapped around the lack of a PKG-INFO, as might be the
+ # case in a fresh checkout, make sure that any special tags get added
+ # to the version info
+ #
+ pd = self.distribution._patched_dist
+ if pd is not None and pd.key==self.egg_name.lower():
+ pd._version = self.egg_version
+ pd._parsed_version = parse_version(self.egg_version)
+ self.distribution._patched_dist = None
- def write_or_delete_file(self, what, filename, data):
+ def write_or_delete_file(self, what, filename, data, force=False):
"""Write `data` to `filename` or delete if empty
If `data` is non-empty, this routine is the same as ``write_file()``.
If `data` is empty but not ``None``, this is the same as calling
``delete_file(filename)`. If `data` is ``None``, then this is a no-op
unless `filename` exists, in which case a warning is issued about the
- orphaned file.
+ orphaned file (if `force` is false), or deleted (if `force` is true).
"""
if data:
self.write_file(what, filename, data)
elif os.path.exists(filename):
- if data is None:
+ if data is None and not force:
log.warn(
"%s not set in setup(), but %s exists", what, filename
)
@@ -107,6 +127,8 @@ class egg_info(Command):
to the file.
"""
log.info("writing %s to %s", what, filename)
+ if sys.version_info >= (3,):
+ data = data.encode("utf-8")
if not self.dry_run:
f = open(filename, 'wb')
f.write(data)
@@ -118,56 +140,182 @@ class egg_info(Command):
if not self.dry_run:
os.unlink(filename)
-
-
+ def tagged_version(self):
+ version = self.distribution.get_version()
+ # egg_info may be called more than once for a distribution,
+ # in which case the version string already contains all tags.
+ if self.vtags and version.endswith(self.vtags):
+ return safe_version(version)
+ return safe_version(version + self.vtags)
def run(self):
- # Make the .egg-info directory, then write PKG-INFO and requires.txt
self.mkpath(self.egg_info)
installer = self.distribution.fetch_build_egg
for ep in iter_entry_points('egg_info.writers'):
writer = ep.load(installer=installer)
writer(self, ep.name, os.path.join(self.egg_info,ep.name))
- def tagged_version(self):
- version = self.distribution.get_version()
+ # Get rid of native_libs.txt if it was put there by older bdist_egg
+ nl = os.path.join(self.egg_info, "native_libs.txt")
+ if os.path.exists(nl):
+ self.delete_file(nl)
+
+ self.find_sources()
+
+ def tags(self):
+ version = ''
if self.tag_build:
version+=self.tag_build
- if self.tag_svn_revision and os.path.exists('.svn'):
- version += '-r%s' % self.get_svn_revision()
+ if self.tag_svn_revision and (
+ os.path.exists('.svn') or os.path.exists('PKG-INFO')
+ ): version += '-r%s' % self.get_svn_revision()
if self.tag_date:
import time
version += time.strftime("-%Y%m%d")
- return safe_version(version)
-
- def get_svn_revision(self):
- stdin, stdout = os.popen4("svn info -R"); stdin.close()
- result = stdout.read(); stdout.close()
- import re
- revisions = [
- int(match.group(1))
- for match in re.finditer(r'Last Changed Rev: (\d+)', result)
- ]
- if not revisions:
- raise DistutilsError("svn info error: %s" % result.strip())
- return str(max(revisions))
-
-
-
-
-
-
-
-
-
+ return version
+
+ @staticmethod
+ def get_svn_revision():
+ return str(svn_utils.SvnInfo.load(os.curdir).get_revision())
+
+ def find_sources(self):
+ """Generate SOURCES.txt manifest file"""
+ manifest_filename = os.path.join(self.egg_info,"SOURCES.txt")
+ mm = manifest_maker(self.distribution)
+ mm.manifest = manifest_filename
+ mm.run()
+ self.filelist = mm.filelist
+
+ def check_broken_egg_info(self):
+ bei = self.egg_name+'.egg-info'
+ if self.egg_base != os.curdir:
+ bei = os.path.join(self.egg_base, bei)
+ if os.path.exists(bei):
+ log.warn(
+ "-"*78+'\n'
+ "Note: Your current .egg-info directory has a '-' in its name;"
+ '\nthis will not work correctly with "setup.py develop".\n\n'
+ 'Please rename %s to %s to correct this problem.\n'+'-'*78,
+ bei, self.egg_info
+ )
+ self.broken_egg_info = self.egg_info
+ self.egg_info = bei # make it work for now
+
+class FileList(_FileList):
+ """File list that accepts only existing, platform-independent paths"""
+
+ def append(self, item):
+ if item.endswith('\r'): # Fix older sdists built on Windows
+ item = item[:-1]
+ path = convert_path(item)
+
+ if sys.version_info >= (3,):
+ try:
+ if os.path.exists(path) or os.path.exists(path.encode('utf-8')):
+ self.files.append(path)
+ except UnicodeEncodeError:
+ # Accept UTF-8 filenames even if LANG=C
+ if os.path.exists(path.encode('utf-8')):
+ self.files.append(path)
+ else:
+ log.warn("'%s' not %s encodable -- skipping", path,
+ sys.getfilesystemencoding())
+ else:
+ if os.path.exists(path):
+ self.files.append(path)
+
+
+class manifest_maker(sdist):
+
+ template = "MANIFEST.in"
+
+ def initialize_options(self):
+ self.use_defaults = 1
+ self.prune = 1
+ self.manifest_only = 1
+ self.force_manifest = 1
+
+ def finalize_options(self):
+ pass
+ def run(self):
+ self.filelist = FileList()
+ if not os.path.exists(self.manifest):
+ self.write_manifest() # it must exist so it'll get in the list
+ self.filelist.findall()
+ self.add_defaults()
+ if os.path.exists(self.template):
+ self.read_template()
+ self.prune_file_list()
+ self.filelist.sort()
+ self.filelist.remove_duplicates()
+ self.write_manifest()
+
+ def write_manifest(self):
+ """Write the file list in 'self.filelist' (presumably as filled in
+ by 'add_defaults()' and 'read_template()') to the manifest file
+ named by 'self.manifest'.
+ """
+ # The manifest must be UTF-8 encodable. See #303.
+ if sys.version_info >= (3,):
+ files = []
+ for file in self.filelist.files:
+ try:
+ file.encode("utf-8")
+ except UnicodeEncodeError:
+ log.warn("'%s' not UTF-8 encodable -- skipping" % file)
+ else:
+ files.append(file)
+ self.filelist.files = files
+
+ files = self.filelist.files
+ if os.sep!='/':
+ files = [f.replace(os.sep,'/') for f in files]
+ self.execute(write_file, (self.manifest, files),
+ "writing manifest file '%s'" % self.manifest)
+
+ def warn(self, msg): # suppress missing-file warnings from sdist
+ if not msg.startswith("standard file not found:"):
+ sdist.warn(self, msg)
+
+ def add_defaults(self):
+ sdist.add_defaults(self)
+ self.filelist.append(self.template)
+ self.filelist.append(self.manifest)
+ rcfiles = list(walk_revctrl())
+ if rcfiles:
+ self.filelist.extend(rcfiles)
+ elif os.path.exists(self.manifest):
+ self.read_manifest()
+ ei_cmd = self.get_finalized_command('egg_info')
+ self.filelist.include_pattern("*", prefix=ei_cmd.egg_info)
+
+ def prune_file_list(self):
+ build = self.get_finalized_command('build')
+ base_dir = self.distribution.get_fullname()
+ self.filelist.exclude_pattern(None, prefix=build.build_base)
+ self.filelist.exclude_pattern(None, prefix=base_dir)
+ sep = re.escape(os.sep)
+ self.filelist.exclude_pattern(sep+r'(RCS|CVS|\.svn)'+sep, is_regex=1)
+
+
+def write_file(filename, contents):
+ """Create a file with the specified name and write 'contents' (a
+ sequence of strings without line terminators) to it.
+ """
+ contents = "\n".join(contents)
+ if sys.version_info >= (3,):
+ contents = contents.encode("utf-8")
+ f = open(filename, "wb") # always write POSIX-style manifest
+ f.write(contents)
+ f.close()
def write_pkg_info(cmd, basename, filename):
log.info("writing %s", filename)
if not cmd.dry_run:
metadata = cmd.distribution.metadata
metadata.version, oldver = cmd.egg_version, metadata.version
- metadata.name, oldname = cmd.egg_name, metadata.name
+ metadata.name, oldname = cmd.egg_name, metadata.name
try:
# write unescaped data to PKG-INFO, so older pkg_resources
# can still parse it
@@ -175,6 +323,10 @@ def write_pkg_info(cmd, basename, filename):
finally:
metadata.name, metadata.version = oldname, oldver
+ safe = getattr(cmd.distribution,'zip_safe',None)
+ from setuptools.command import bdist_egg
+ bdist_egg.write_safety_flag(cmd.egg_info, safe)
+
def warn_depends_obsolete(cmd, basename, filename):
if os.path.exists(filename):
log.warn(
@@ -192,23 +344,23 @@ def write_requirements(cmd, basename, filename):
def write_toplevel_names(cmd, basename, filename):
pkgs = dict.fromkeys(
- [k.split('.',1)[0]
+ [
+ k.split('.',1)[0]
for k in cmd.distribution.iter_distribution_names()
]
)
cmd.write_file("top-level names", filename, '\n'.join(pkgs)+'\n')
+def overwrite_arg(cmd, basename, filename):
+ write_arg(cmd, basename, filename, True)
-
-
-
-def write_arg(cmd, basename, filename):
+def write_arg(cmd, basename, filename, force=False):
argname = os.path.splitext(basename)[0]
value = getattr(cmd.distribution, argname, None)
if value is not None:
value = '\n'.join(value)+'\n'
- cmd.write_or_delete_file(argname, filename, value)
+ cmd.write_or_delete_file(argname, filename, value, force)
def write_entries(cmd, basename, filename):
ep = cmd.distribution.entry_points
@@ -224,23 +376,17 @@ def write_entries(cmd, basename, filename):
data.append('[%s]\n%s\n\n' % (section,contents))
data = ''.join(data)
- cmd.write_or_delete_file('entry points', filename, data)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ cmd.write_or_delete_file('entry points', filename, data, True)
+
+def get_pkg_info_revision():
+ # See if we can get a -r### off of PKG-INFO, in case this is an sdist of
+ # a subversion revision
+ #
+ if os.path.exists('PKG-INFO'):
+ f = open('PKG-INFO','rU')
+ for line in f:
+ match = re.match(r"Version:.*-r(\d+)\s*$", line)
+ if match:
+ return int(match.group(1))
+ f.close()
+ return 0
diff --git a/setuptools/command/install.py b/setuptools/command/install.py
index f438dda6..459cd3cd 100644
--- a/setuptools/command/install.py
+++ b/setuptools/command/install.py
@@ -1,31 +1,90 @@
-import setuptools, sys
+import setuptools
+import sys
+import glob
from distutils.command.install import install as _install
+from distutils.errors import DistutilsArgError
class install(_install):
"""Use easy_install to install the package, w/dependencies"""
+ user_options = _install.user_options + [
+ ('old-and-unmanageable', None, "Try not to use this!"),
+ ('single-version-externally-managed', None,
+ "used by system package builders to create 'flat' eggs"),
+ ]
+ boolean_options = _install.boolean_options + [
+ 'old-and-unmanageable', 'single-version-externally-managed',
+ ]
+ new_commands = [
+ ('install_egg_info', lambda self: True),
+ ('install_scripts', lambda self: True),
+ ]
+ _nc = dict(new_commands)
+
+ def initialize_options(self):
+ _install.initialize_options(self)
+ self.old_and_unmanageable = None
+ self.single_version_externally_managed = None
+ self.no_compile = None # make DISTUTILS_DEBUG work right!
+
+ def finalize_options(self):
+ _install.finalize_options(self)
+ if self.root:
+ self.single_version_externally_managed = True
+ elif self.single_version_externally_managed:
+ if not self.root and not self.record:
+ raise DistutilsArgError(
+ "You must specify --record or --root when building system"
+ " packages"
+ )
+
def handle_extra_path(self):
- # We always ignore extra_path, because we always install eggs
- # (you can always use install_* commands directly if needed)
+ if self.root or self.single_version_externally_managed:
+ # explicit backward-compatibility mode, allow extra_path to work
+ return _install.handle_extra_path(self)
+
+ # Ignore extra_path when installing an egg (or being run by another
+ # command without --root or --single-version-externally-managed
self.path_file = None
self.extra_dirs = ''
def run(self):
- calling_module = sys._getframe(1).f_globals.get('__name__','')
- if calling_module != 'distutils.dist':
- # We're not being run from the command line, so use old-style
- # behavior. This is a bit kludgy, because a command might use
- # dist.run_command() to run 'install', but bdist_dumb and
- # bdist_wininst both call run directly at the moment.
- # When this is part of the distutils, the old install behavior
- # should probably be requested with a flag, or a different method.
+ # Explicit request for old-style install? Just do it
+ if self.old_and_unmanageable or self.single_version_externally_managed:
return _install.run(self)
- from setuptools.command.easy_install import easy_install
+ # Attempt to detect whether we were called from setup() or by another
+ # command. If we were called by setup(), our caller will be the
+ # 'run_command' method in 'distutils.dist', and *its* caller will be
+ # the 'run_commands' method. If we were called any other way, our
+ # immediate caller *might* be 'run_command', but it won't have been
+ # called by 'run_commands'. This is slightly kludgy, but seems to
+ # work.
+ #
+ caller = sys._getframe(2)
+ caller_module = caller.f_globals.get('__name__','')
+ caller_name = caller.f_code.co_name
+
+ if caller_module != 'distutils.dist' or caller_name!='run_commands':
+ # We weren't called from the command line or setup(), so we
+ # should run in backward-compatibility mode to support bdist_*
+ # commands.
+ _install.run(self)
+ else:
+ self.do_egg_install()
+
+ def do_egg_install(self):
+
+ easy_install = self.distribution.get_command_class('easy_install')
+
cmd = easy_install(
- self.distribution, args="x", ignore_conflicts_at_my_risk=1
+ self.distribution, args="x", root=self.root, record=self.record,
)
cmd.ensure_finalized() # finalize before bdist_egg munges install cmd
+ cmd.always_copy_from = '.' # make sure local-dir eggs get installed
+
+ # pick up setup-dir .egg files only: no .egg-info
+ cmd.package_index.scan(glob.glob('*.egg'))
self.run_command('bdist_egg')
args = [self.distribution.get_command_obj('bdist_egg').egg_output]
@@ -38,3 +97,7 @@ class install(_install):
cmd.run()
setuptools.bootstrap_install_from = None
+# XXX Python 3.1 doesn't see _nc if this is inside the class
+install.sub_commands = [
+ cmd for cmd in _install.sub_commands if cmd[0] not in install._nc
+ ] + install.new_commands
diff --git a/setuptools/command/install_egg_info.py b/setuptools/command/install_egg_info.py
new file mode 100755
index 00000000..f44b34b5
--- /dev/null
+++ b/setuptools/command/install_egg_info.py
@@ -0,0 +1,125 @@
+from setuptools import Command
+from setuptools.archive_util import unpack_archive
+from distutils import log, dir_util
+import os, shutil, pkg_resources
+
+class install_egg_info(Command):
+ """Install an .egg-info directory for the package"""
+
+ description = "Install an .egg-info directory for the package"
+
+ user_options = [
+ ('install-dir=', 'd', "directory to install to"),
+ ]
+
+ def initialize_options(self):
+ self.install_dir = None
+
+ def finalize_options(self):
+ self.set_undefined_options('install_lib',('install_dir','install_dir'))
+ ei_cmd = self.get_finalized_command("egg_info")
+ basename = pkg_resources.Distribution(
+ None, None, ei_cmd.egg_name, ei_cmd.egg_version
+ ).egg_name()+'.egg-info'
+ self.source = ei_cmd.egg_info
+ self.target = os.path.join(self.install_dir, basename)
+ self.outputs = [self.target]
+
+ def run(self):
+ self.run_command('egg_info')
+ target = self.target
+ if os.path.isdir(self.target) and not os.path.islink(self.target):
+ dir_util.remove_tree(self.target, dry_run=self.dry_run)
+ elif os.path.exists(self.target):
+ self.execute(os.unlink,(self.target,),"Removing "+self.target)
+ if not self.dry_run:
+ pkg_resources.ensure_directory(self.target)
+ self.execute(self.copytree, (),
+ "Copying %s to %s" % (self.source, self.target)
+ )
+ self.install_namespaces()
+
+ def get_outputs(self):
+ return self.outputs
+
+ def copytree(self):
+ # Copy the .egg-info tree to site-packages
+ def skimmer(src,dst):
+ # filter out source-control directories; note that 'src' is always
+ # a '/'-separated path, regardless of platform. 'dst' is a
+ # platform-specific path.
+ for skip in '.svn/','CVS/':
+ if src.startswith(skip) or '/'+skip in src:
+ return None
+ self.outputs.append(dst)
+ log.debug("Copying %s to %s", src, dst)
+ return dst
+ unpack_archive(self.source, self.target, skimmer)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ def install_namespaces(self):
+ nsp = self._get_all_ns_packages()
+ if not nsp: return
+ filename,ext = os.path.splitext(self.target)
+ filename += '-nspkg.pth'; self.outputs.append(filename)
+ log.info("Installing %s",filename)
+ if not self.dry_run:
+ f = open(filename,'wt')
+ for pkg in nsp:
+ # ensure pkg is not a unicode string under Python 2.7
+ pkg = str(pkg)
+ pth = tuple(pkg.split('.'))
+ trailer = '\n'
+ if '.' in pkg:
+ trailer = (
+ "; m and setattr(sys.modules[%r], %r, m)\n"
+ % ('.'.join(pth[:-1]), pth[-1])
+ )
+ f.write(
+ "import sys,types,os; "
+ "p = os.path.join(sys._getframe(1).f_locals['sitedir'], "
+ "*%(pth)r); "
+ "ie = os.path.exists(os.path.join(p,'__init__.py')); "
+ "m = not ie and "
+ "sys.modules.setdefault(%(pkg)r,types.ModuleType(%(pkg)r)); "
+ "mp = (m or []) and m.__dict__.setdefault('__path__',[]); "
+ "(p not in mp) and mp.append(p)%(trailer)s"
+ % locals()
+ )
+ f.close()
+
+ def _get_all_ns_packages(self):
+ nsp = {}
+ for pkg in self.distribution.namespace_packages or []:
+ pkg = pkg.split('.')
+ while pkg:
+ nsp['.'.join(pkg)] = 1
+ pkg.pop()
+ nsp=list(nsp)
+ nsp.sort() # set up shorter names first
+ return nsp
+
+
diff --git a/setuptools/command/install_lib.py b/setuptools/command/install_lib.py
index 63e2468c..c508cd33 100644
--- a/setuptools/command/install_lib.py
+++ b/setuptools/command/install_lib.py
@@ -1,16 +1,70 @@
from distutils.command.install_lib import install_lib as _install_lib
+import os
class install_lib(_install_lib):
"""Don't add compiled flags to filenames of non-Python files"""
- def _bytecode_filenames (self, py_filenames):
- bytecode_files = []
- for py_file in py_filenames:
- if not py_file.endswith('.py'):
- continue
- if self.compile:
- bytecode_files.append(py_file + "c")
- if self.optimize > 0:
- bytecode_files.append(py_file + "o")
-
- return bytecode_files
+ def run(self):
+ self.build()
+ outfiles = self.install()
+ if outfiles is not None:
+ # always compile, in case we have any extension stubs to deal with
+ self.byte_compile(outfiles)
+
+ def get_exclusions(self):
+ exclude = {}
+ nsp = self.distribution.namespace_packages
+
+ if (nsp and self.get_finalized_command('install')
+ .single_version_externally_managed
+ ):
+ for pkg in nsp:
+ parts = pkg.split('.')
+ while parts:
+ pkgdir = os.path.join(self.install_dir, *parts)
+ for f in '__init__.py', '__init__.pyc', '__init__.pyo':
+ exclude[os.path.join(pkgdir,f)] = 1
+ parts.pop()
+ return exclude
+
+ def copy_tree(
+ self, infile, outfile,
+ preserve_mode=1, preserve_times=1, preserve_symlinks=0, level=1
+ ):
+ assert preserve_mode and preserve_times and not preserve_symlinks
+ exclude = self.get_exclusions()
+
+ if not exclude:
+ return _install_lib.copy_tree(self, infile, outfile)
+
+ # Exclude namespace package __init__.py* files from the output
+
+ from setuptools.archive_util import unpack_directory
+ from distutils import log
+
+ outfiles = []
+
+ def pf(src, dst):
+ if dst in exclude:
+ log.warn("Skipping installation of %s (namespace package)",dst)
+ return False
+
+ log.info("copying %s -> %s", src, os.path.dirname(dst))
+ outfiles.append(dst)
+ return dst
+
+ unpack_directory(infile, outfile, pf)
+ return outfiles
+
+ def get_outputs(self):
+ outputs = _install_lib.get_outputs(self)
+ exclude = self.get_exclusions()
+ if exclude:
+ return [f for f in outputs if f not in exclude]
+ return outputs
+
+
+
+
+
+
diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py
new file mode 100755
index 00000000..105dabca
--- /dev/null
+++ b/setuptools/command/install_scripts.py
@@ -0,0 +1,54 @@
+from distutils.command.install_scripts import install_scripts \
+ as _install_scripts
+from pkg_resources import Distribution, PathMetadata, ensure_directory
+import os
+from distutils import log
+
+class install_scripts(_install_scripts):
+ """Do normal script install, plus any egg_info wrapper scripts"""
+
+ def initialize_options(self):
+ _install_scripts.initialize_options(self)
+ self.no_ep = False
+
+ def run(self):
+ from setuptools.command.easy_install import get_script_args
+ from setuptools.command.easy_install import sys_executable
+
+ self.run_command("egg_info")
+ if self.distribution.scripts:
+ _install_scripts.run(self) # run first to set up self.outfiles
+ else:
+ self.outfiles = []
+ if self.no_ep:
+ # don't install entry point scripts into .egg file!
+ return
+
+ ei_cmd = self.get_finalized_command("egg_info")
+ dist = Distribution(
+ ei_cmd.egg_base, PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info),
+ ei_cmd.egg_name, ei_cmd.egg_version,
+ )
+ bs_cmd = self.get_finalized_command('build_scripts')
+ executable = getattr(bs_cmd,'executable',sys_executable)
+ is_wininst = getattr(
+ self.get_finalized_command("bdist_wininst"), '_is_running', False
+ )
+ for args in get_script_args(dist, executable, is_wininst):
+ self.write_script(*args)
+
+ def write_script(self, script_name, contents, mode="t", *ignored):
+ """Write an executable file to the scripts directory"""
+ from setuptools.command.easy_install import chmod, current_umask
+ log.info("Installing %s script to %s", script_name, self.install_dir)
+ target = os.path.join(self.install_dir, script_name)
+ self.outfiles.append(target)
+
+ mask = current_umask()
+ if not self.dry_run:
+ ensure_directory(target)
+ f = open(target,"w"+mode)
+ f.write(contents)
+ f.close()
+ chmod(target, 0x1FF-mask) # 0777
+
diff --git a/setuptools/command/launcher manifest.xml b/setuptools/command/launcher manifest.xml
new file mode 100644
index 00000000..844d2264
--- /dev/null
+++ b/setuptools/command/launcher manifest.xml
@@ -0,0 +1,15 @@
+<?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="%(name)s"
+ 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>
diff --git a/setuptools/command/register.py b/setuptools/command/register.py
new file mode 100755
index 00000000..3b2e0859
--- /dev/null
+++ b/setuptools/command/register.py
@@ -0,0 +1,10 @@
+from distutils.command.register import register as _register
+
+class register(_register):
+ __doc__ = _register.__doc__
+
+ def run(self):
+ # Make sure that we are using valid current name/version info
+ self.run_command('egg_info')
+ _register.run(self)
+
diff --git a/setuptools/command/rotate.py b/setuptools/command/rotate.py
index 11b6eae8..c556aa17 100755
--- a/setuptools/command/rotate.py
+++ b/setuptools/command/rotate.py
@@ -1,8 +1,9 @@
-import distutils, os
+import os
from setuptools import Command
+from setuptools.compat import basestring
from distutils.util import convert_path
from distutils import log
-from distutils.errors import *
+from distutils.errors import DistutilsOptionError
class rotate(Command):
"""Delete older distributions"""
@@ -28,7 +29,7 @@ class rotate(Command):
"(e.g. '.zip' or '.egg')"
)
if self.keep is None:
- raise DistutilsOptionError("Must specify number of files to keep")
+ raise DistutilsOptionError("Must specify number of files to keep")
try:
self.keep = int(self.keep)
except ValueError:
@@ -55,28 +56,3 @@ class rotate(Command):
log.info("Deleting %s", f)
if not self.dry_run:
os.unlink(f)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/setuptools/command/saveopts.py b/setuptools/command/saveopts.py
index 1180a440..7209be4c 100755
--- a/setuptools/command/saveopts.py
+++ b/setuptools/command/saveopts.py
@@ -9,10 +9,9 @@ class saveopts(option_base):
def run(self):
dist = self.distribution
- commands = dist.command_options.keys()
settings = {}
- for cmd in commands:
+ for cmd in dist.command_options:
if cmd=='saveopts':
continue # don't save our own options!
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index cdbc5248..76e1c5f1 100755
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -1,123 +1,244 @@
+import os
+import re
+import sys
+from glob import glob
+
+import pkg_resources
from distutils.command.sdist import sdist as _sdist
from distutils.util import convert_path
-import os,re
-
-entities = [
- ("&lt;","<"), ("&gt;", ">"), ("&quot;", '"'), ("&apos;", "'"),
- ("&amp;", "&")
-]
-
-def unescape(data):
- for old,new in entities:
- data = data.replace(old,new)
- return data
-
-def re_finder(pattern, postproc=None):
- def find(dirname, filename):
- f = open(filename,'rU')
- data = f.read()
- f.close()
- for match in pattern.finditer(data):
- path = match.group(1)
- if postproc:
- path = postproc(path)
- yield joinpath(dirname,path)
- return find
-
-def joinpath(prefix,suffix):
- if not prefix:
- return suffix
- return os.path.join(prefix,suffix)
+from distutils import log
+from setuptools import svn_utils
+READMES = ('README', 'README.rst', 'README.txt')
+def walk_revctrl(dirname=''):
+ """Find all files under revision control"""
+ for ep in pkg_resources.iter_entry_points('setuptools.file_finders'):
+ for item in ep.load()(dirname):
+ yield item
+#TODO will need test case
+class re_finder(object):
+ """
+ Finder that locates files based on entries in a file matched by a
+ regular expression.
+ """
+ def __init__(self, path, pattern, postproc=lambda x: x):
+ self.pattern = pattern
+ self.postproc = postproc
+ self.entries_path = convert_path(path)
+ def _finder(self, dirname, filename):
+ f = open(filename,'rU')
+ try:
+ data = f.read()
+ finally:
+ f.close()
+ for match in self.pattern.finditer(data):
+ path = match.group(1)
+ # postproc was formerly used when the svn finder
+ # was an re_finder for calling unescape
+ path = self.postproc(path)
+ yield svn_utils.joinpath(dirname, path)
+ def find(self, dirname=''):
+ path = svn_utils.joinpath(dirname, self.entries_path)
+ if not os.path.isfile(path):
+ # entries file doesn't exist
+ return
+ for path in self._finder(dirname,path):
+ if os.path.isfile(path):
+ yield path
+ elif os.path.isdir(path):
+ for item in self.find(path):
+ yield item
+ __call__ = find
-def walk_revctrl(dirname='', memo=None):
- """Find all files under revision control"""
- if memo is None:
- memo = {}
- if dirname in memo:
- # Don't rescan a scanned directory
- return
- for path, finder in finders:
- path = joinpath(dirname,path)
- if os.path.isfile(path):
- for path in finder(dirname,path):
- if os.path.isfile(path):
- yield path
- elif os.path.isdir(path):
- for item in walk_revctrl(path, memo):
- yield item
-
-def externals_finder(dirname, filename):
- """Find any 'svn:externals' directories"""
- found = False
- f = open(filename,'rb')
- for line in iter(f.readline, ''): # can't use direct iter!
- parts = line.split()
- if len(parts)==2:
- kind,length = parts
- data = f.read(int(length))
- if kind=='K' and data=='svn:externals':
- found = True
- elif kind=='V' and found:
- f.close()
- break
- else:
- f.close()
- return
-
- for line in data.splitlines():
- parts = line.split()
- if parts:
- yield joinpath(dirname, parts[0])
+def _default_revctrl(dirname=''):
+ 'Primary svn_cvs entry point'
+ for finder in finders:
+ for item in finder(dirname):
+ yield item
finders = [
- (convert_path('CVS/Entries'),
- re_finder(re.compile(r"^\w?/([^/]+)/", re.M))),
- (convert_path('.svn/entries'),
- re_finder(
- re.compile(r'name="([^"]+)"(?![^>]+deleted="true")', re.I),
- unescape
- )
- ),
- (convert_path('.svn/dir-props'), externals_finder),
+ re_finder('CVS/Entries', re.compile(r"^\w?/([^/]+)/", re.M)),
+ svn_utils.svn_finder,
]
class sdist(_sdist):
"""Smart sdist that finds anything supported by revision control"""
+ user_options = [
+ ('formats=', None,
+ "formats for source distribution (comma-separated list)"),
+ ('keep-temp', 'k',
+ "keep the distribution tree around after creating " +
+ "archive file(s)"),
+ ('dist-dir=', 'd',
+ "directory to put the source distribution archive(s) in "
+ "[default: dist]"),
+ ]
+
+ negative_opt = {}
+
def run(self):
self.run_command('egg_info')
- _sdist.run(self)
+ ei_cmd = self.get_finalized_command('egg_info')
+ self.filelist = ei_cmd.filelist
+ self.filelist.append(os.path.join(ei_cmd.egg_info,'SOURCES.txt'))
+ self.check_readme()
+
+ # Run sub commands
+ for cmd_name in self.get_sub_commands():
+ self.run_command(cmd_name)
+
+ # Call check_metadata only if no 'check' command
+ # (distutils <= 2.6)
+ import distutils.command
+ if 'check' not in distutils.command.__all__:
+ self.check_metadata()
+
+ self.make_distribution()
+
dist_files = getattr(self.distribution,'dist_files',[])
for file in self.archive_files:
data = ('sdist', '', file)
if data not in dist_files:
dist_files.append(data)
- def finalize_options(self):
- _sdist.finalize_options(self)
- if not os.path.isfile(self.template):
- self.force_manifest = 1 # always regen if no template
+ def __read_template_hack(self):
+ # This grody hack closes the template file (MANIFEST.in) if an
+ # exception occurs during read_template.
+ # Doing so prevents an error when easy_install attempts to delete the
+ # file.
+ try:
+ _sdist.read_template(self)
+ except:
+ sys.exc_info()[2].tb_next.tb_frame.f_locals['template'].close()
+ raise
+ # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle
+ # has been fixed, so only override the method if we're using an earlier
+ # Python.
+ has_leaky_handle = (
+ sys.version_info < (2,7,2)
+ or (3,0) <= sys.version_info < (3,1,4)
+ or (3,2) <= sys.version_info < (3,2,1)
+ )
+ if has_leaky_handle:
+ read_template = __read_template_hack
def add_defaults(self):
- _sdist.add_defaults(self)
- self.filelist.extend(walk_revctrl())
-
-
-
-
-
-
-
-
+ standards = [READMES,
+ self.distribution.script_name]
+ for fn in standards:
+ if isinstance(fn, tuple):
+ alts = fn
+ got_it = 0
+ for fn in alts:
+ if os.path.exists(fn):
+ got_it = 1
+ self.filelist.append(fn)
+ break
+
+ if not got_it:
+ self.warn("standard file not found: should have one of " +
+ ', '.join(alts))
+ else:
+ if os.path.exists(fn):
+ self.filelist.append(fn)
+ else:
+ self.warn("standard file '%s' not found" % fn)
+
+ optional = ['test/test*.py', 'setup.cfg']
+ for pattern in optional:
+ files = list(filter(os.path.isfile, glob(pattern)))
+ if files:
+ self.filelist.extend(files)
+
+ # getting python files
+ if self.distribution.has_pure_modules():
+ build_py = self.get_finalized_command('build_py')
+ self.filelist.extend(build_py.get_source_files())
+ # This functionality is incompatible with include_package_data, and
+ # will in fact create an infinite recursion if include_package_data
+ # is True. Use of include_package_data will imply that
+ # distutils-style automatic handling of package_data is disabled
+ if not self.distribution.include_package_data:
+ for _, src_dir, _, filenames in build_py.data_files:
+ self.filelist.extend([os.path.join(src_dir, filename)
+ for filename in filenames])
+
+ if self.distribution.has_ext_modules():
+ build_ext = self.get_finalized_command('build_ext')
+ self.filelist.extend(build_ext.get_source_files())
+
+ if self.distribution.has_c_libraries():
+ build_clib = self.get_finalized_command('build_clib')
+ self.filelist.extend(build_clib.get_source_files())
+
+ if self.distribution.has_scripts():
+ build_scripts = self.get_finalized_command('build_scripts')
+ self.filelist.extend(build_scripts.get_source_files())
+
+ def check_readme(self):
+ for f in READMES:
+ if os.path.exists(f):
+ return
+ else:
+ self.warn(
+ "standard file not found: should have one of " +', '.join(READMES)
+ )
+
+ def make_release_tree(self, base_dir, files):
+ _sdist.make_release_tree(self, base_dir, files)
+
+ # Save any egg_info command line options used to create this sdist
+ dest = os.path.join(base_dir, 'setup.cfg')
+ if hasattr(os,'link') and os.path.exists(dest):
+ # unlink and re-copy, since it might be hard-linked, and
+ # we don't want to change the source version
+ os.unlink(dest)
+ self.copy_file('setup.cfg', dest)
+
+ self.get_finalized_command('egg_info').save_version_info(dest)
+
+ def _manifest_is_not_generated(self):
+ # check for special comment used in 2.7.1 and higher
+ if not os.path.isfile(self.manifest):
+ return False
+
+ fp = open(self.manifest, 'rbU')
+ try:
+ first_line = fp.readline()
+ finally:
+ fp.close()
+ return first_line != '# file GENERATED by distutils, do NOT edit\n'.encode()
+
+ def read_manifest(self):
+ """Read the manifest file (named by 'self.manifest') and use it to
+ fill in 'self.filelist', the list of files to include in the source
+ distribution.
+ """
+ log.info("reading manifest file '%s'", self.manifest)
+ manifest = open(self.manifest, 'rbU')
+ for line in manifest:
+ # The manifest must contain UTF-8. See #303.
+ if sys.version_info >= (3,):
+ try:
+ line = line.decode('UTF-8')
+ except UnicodeDecodeError:
+ log.warn("%r not UTF-8 decodable -- skipping" % line)
+ continue
+ # ignore comments and blank lines
+ line = line.strip()
+ if line.startswith('#') or not line:
+ continue
+ self.filelist.append(line)
+ manifest.close()
diff --git a/setuptools/command/setopt.py b/setuptools/command/setopt.py
index dbf3a94e..575653c8 100755
--- a/setuptools/command/setopt.py
+++ b/setuptools/command/setopt.py
@@ -1,8 +1,9 @@
-import distutils, os
+import os
+import distutils
from setuptools import Command
from distutils.util import convert_path
from distutils import log
-from distutils.errors import *
+from distutils.errors import DistutilsOptionError
__all__ = ['config_file', 'edit_config', 'option_base', 'setopt']
@@ -25,20 +26,6 @@ def config_file(kind="local"):
"config_file() type must be 'local', 'global', or 'user'", kind
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
def edit_config(filename, settings, dry_run=False):
"""Edit a configuration file to include `settings`
@@ -47,9 +34,9 @@ def edit_config(filename, settings, dry_run=False):
while a dictionary lists settings to be changed or deleted in that section.
A setting of ``None`` means to delete that setting.
"""
- from ConfigParser import RawConfigParser
+ from setuptools.compat import ConfigParser
log.debug("Reading configuration from %s", filename)
- opts = RawConfigParser()
+ opts = ConfigParser.RawConfigParser()
opts.read([filename])
for section, options in settings.items():
if options is None:
@@ -61,13 +48,14 @@ def edit_config(filename, settings, dry_run=False):
opts.add_section(section)
for option,value in options.items():
if value is None:
- log.debug("Deleting %s.%s from %s",
+ log.debug(
+ "Deleting %s.%s from %s",
section, option, filename
)
opts.remove_option(section,option)
if not opts.options(section):
log.info("Deleting empty [%s] section from %s",
- section, filename)
+ section, filename)
opts.remove_section(section)
else:
log.debug(
@@ -78,27 +66,28 @@ def edit_config(filename, settings, dry_run=False):
log.info("Writing %s", filename)
if not dry_run:
- f = open(filename,'w'); opts.write(f); f.close()
+ with open(filename, 'w') as f:
+ opts.write(f)
class option_base(Command):
"""Abstract base class for commands that mess with config files"""
-
+
user_options = [
('global-config', 'g',
- "save options to the site-wide distutils.cfg file"),
+ "save options to the site-wide distutils.cfg file"),
('user-config', 'u',
- "save options to the current user's pydistutils.cfg file"),
+ "save options to the current user's pydistutils.cfg file"),
('filename=', 'f',
- "configuration file to use (default=setup.cfg)"),
+ "configuration file to use (default=setup.cfg)"),
]
boolean_options = [
'global-config', 'user-config',
- ]
+ ]
def initialize_options(self):
self.global_config = None
- self.user_config = None
+ self.user_config = None
self.filename = None
def finalize_options(self):
@@ -116,9 +105,7 @@ class option_base(Command):
"Must specify only one configuration file option",
filenames
)
- self.filename, = filenames
-
-
+ self.filename, = filenames
class setopt(option_base):
@@ -130,7 +117,7 @@ class setopt(option_base):
('command=', 'c', 'command to set an option for'),
('option=', 'o', 'option to set'),
('set-value=', 's', 'value of the option'),
- ('remove', 'r', 'remove (unset) the value'),
+ ('remove', 'r', 'remove (unset) the value'),
] + option_base.user_options
boolean_options = option_base.boolean_options + ['remove']
@@ -156,9 +143,3 @@ class setopt(option_base):
},
self.dry_run
)
-
-
-
-
-
-
diff --git a/setuptools/command/test.py b/setuptools/command/test.py
index 200d255d..a9a0d1d7 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -1,7 +1,45 @@
from setuptools import Command
from distutils.errors import DistutilsOptionError
import sys
-from pkg_resources import *
+from pkg_resources import (resource_listdir, resource_exists,
+ normalize_path, working_set, _namespace_packages, add_activation_listener,
+ require, EntryPoint)
+from unittest import TestLoader
+
+class ScanningLoader(TestLoader):
+
+ def loadTestsFromModule(self, module):
+ """Return a suite of all tests cases contained in the given module
+
+ If the module is a package, load tests from all the modules in it.
+ If the module has an ``additional_tests`` function, call it and add
+ the return value to the tests.
+ """
+ tests = []
+ if module.__name__!='setuptools.tests.doctest': # ugh
+ tests.append(TestLoader.loadTestsFromModule(self,module))
+
+ if hasattr(module, "additional_tests"):
+ tests.append(module.additional_tests())
+
+ if hasattr(module, '__path__'):
+ for file in resource_listdir(module.__name__, ''):
+ if file.endswith('.py') and file!='__init__.py':
+ submodule = module.__name__+'.'+file[:-3]
+ else:
+ if resource_exists(
+ module.__name__, file+'/__init__.py'
+ ):
+ submodule = module.__name__+'.'+file
+ else:
+ continue
+ tests.append(self.loadTestsFromName(submodule))
+
+ if len(tests)!=1:
+ return self.suiteClass(tests)
+ else:
+ return tests[0] # don't create a nested suite for only one return
+
class test(Command):
@@ -15,12 +53,10 @@ class test(Command):
"Test suite to run (e.g. 'some_module.test_suite')"),
]
- test_suite = None
- test_module = None
-
def initialize_options(self):
- pass
-
+ self.test_suite = None
+ self.test_module = None
+ self.test_loader = None
def finalize_options(self):
@@ -38,14 +74,57 @@ class test(Command):
if self.verbose:
self.test_args.insert(0,'--verbose')
+ if self.test_loader is None:
+ self.test_loader = getattr(self.distribution,'test_loader',None)
+ if self.test_loader is None:
+ self.test_loader = "setuptools.command.test:ScanningLoader"
+
+ def with_project_on_sys_path(self, func):
+ if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False):
+ # If we run 2to3 we can not do this inplace:
+
+ # Ensure metadata is up-to-date
+ self.reinitialize_command('build_py', inplace=0)
+ self.run_command('build_py')
+ bpy_cmd = self.get_finalized_command("build_py")
+ build_path = normalize_path(bpy_cmd.build_lib)
+
+ # Build extensions
+ self.reinitialize_command('egg_info', egg_base=build_path)
+ self.run_command('egg_info')
+
+ self.reinitialize_command('build_ext', inplace=0)
+ self.run_command('build_ext')
+ else:
+ # Without 2to3 inplace works fine:
+ self.run_command('egg_info')
+
+ # Build extensions in-place
+ self.reinitialize_command('build_ext', inplace=1)
+ self.run_command('build_ext')
- def run(self):
- # Ensure metadata is up-to-date
- self.run_command('egg_info')
+ ei_cmd = self.get_finalized_command("egg_info")
+
+ old_path = sys.path[:]
+ old_modules = sys.modules.copy()
+
+ try:
+ sys.path.insert(0, normalize_path(ei_cmd.egg_base))
+ working_set.__init__()
+ add_activation_listener(lambda dist: dist.activate())
+ require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
+ func()
+ finally:
+ sys.path[:] = old_path
+ sys.modules.clear()
+ sys.modules.update(old_modules)
+ working_set.__init__()
- # Build extensions in-place
- self.reinitialize_command('build_ext', inplace=1)
- self.run_command('build_ext')
+ def run(self):
+ if self.distribution.install_requires:
+ self.distribution.fetch_build_eggs(self.distribution.install_requires)
+ if self.distribution.tests_require:
+ self.distribution.fetch_build_eggs(self.distribution.tests_require)
if self.test_suite:
cmd = ' '.join(self.test_args)
@@ -53,30 +132,30 @@ class test(Command):
self.announce('skipping "unittest %s" (dry run)' % cmd)
else:
self.announce('running "unittest %s"' % cmd)
- self.run_tests()
+ self.with_project_on_sys_path(self.run_tests)
def run_tests(self):
import unittest
- old_path = sys.path[:]
- ei_cmd = self.get_finalized_command("egg_info")
- path_item = normalize_path(ei_cmd.egg_base)
- metadata = PathMetadata(
- path_item, normalize_path(ei_cmd.egg_info)
- )
- dist = Distribution(path_item, metadata, project_name=ei_cmd.egg_name)
- working_set.add(dist)
- require(str(dist.as_requirement()))
- unittest.main(None, None, [unittest.__file__]+self.test_args)
-
-
-
-
-
-
-
-
-
-
-
-
+ # Purge modules under test from sys.modules. The test loader will
+ # re-import them from the build location. Required when 2to3 is used
+ # with namespace packages.
+ if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False):
+ module = self.test_args[-1].split('.')[0]
+ if module in _namespace_packages:
+ del_modules = []
+ if module in sys.modules:
+ del_modules.append(module)
+ module += '.'
+ for name in sys.modules:
+ if name.startswith(module):
+ del_modules.append(name)
+ list(map(sys.modules.__delitem__, del_modules))
+
+ loader_ep = EntryPoint.parse("x="+self.test_loader)
+ loader_class = loader_ep.load(require=False)
+ cks = loader_class()
+ unittest.main(
+ None, None, [unittest.__file__]+self.test_args,
+ testLoader = cks
+ )
diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py
deleted file mode 100755
index 4aa73cd1..00000000
--- a/setuptools/command/upload.py
+++ /dev/null
@@ -1,172 +0,0 @@
-"""distutils.command.upload
-
-Implements the Distutils 'upload' subcommand (upload package to PyPI)."""
-
-from distutils.errors import *
-from distutils.core import Command
-from distutils.spawn import spawn
-from distutils import log
-from md5 import md5
-import os
-import socket
-import platform
-import ConfigParser
-import httplib
-import base64
-import urlparse
-import cStringIO as StringIO
-
-class upload(Command):
-
- description = "upload binary package to PyPI"
-
- DEFAULT_REPOSITORY = 'http://www.python.org/pypi'
-
- user_options = [
- ('repository=', 'r',
- "url of repository [default: %s]" % DEFAULT_REPOSITORY),
- ('show-response', None,
- 'display full response text from server'),
- ('sign', 's',
- 'sign files to upload using gpg'),
- ]
- boolean_options = ['show-response', 'sign']
-
- def initialize_options(self):
- self.username = ''
- self.password = ''
- self.repository = ''
- self.show_response = 0
- self.sign = False
-
- def finalize_options(self):
- if os.environ.has_key('HOME'):
- rc = os.path.join(os.environ['HOME'], '.pypirc')
- if os.path.exists(rc):
- self.announce('Using PyPI login from %s' % rc)
- config = ConfigParser.ConfigParser({
- 'username':'',
- 'password':'',
- 'repository':''})
- config.read(rc)
- if not self.repository:
- self.repository = config.get('server-login', 'repository')
- if not self.username:
- self.username = config.get('server-login', 'username')
- if not self.password:
- self.password = config.get('server-login', 'password')
- if not self.repository:
- self.repository = self.DEFAULT_REPOSITORY
-
- def run(self):
- if not self.distribution.dist_files:
- raise DistutilsOptionError("No dist file created in earlier command")
- for command, pyversion, filename in self.distribution.dist_files:
- self.upload_file(command, pyversion, filename)
-
- def upload_file(self, command, pyversion, filename):
- # Sign if requested
- if self.sign:
- spawn(("gpg", "--detach-sign", "-a", filename),
- dry_run=self.dry_run)
-
- # Fill in the data
- content = open(filename,'rb').read()
- basename = os.path.basename(filename)
- if basename.endswith('.egg'):
- basename += '.zip'
- comment = ''
- if command=='bdist_egg':
- command='sdist'
- comment='Binary egg for use with setuptools'
- data = {
- ':action':'file_upload',
- 'protcol_version':'1',
- 'name':self.distribution.get_name(),
- 'version':self.distribution.get_version(),
- 'content':(basename,content),
- 'filetype':command,
- 'pyversion':pyversion,
- 'md5_digest':md5(content).hexdigest(),
- }
- if command == 'bdist_rpm':
- dist, version, id = platform.dist()
- if dist:
- comment = 'built for %s %s' % (dist, version)
- elif command == 'bdist_dumb':
- comment = 'built for %s' % platform.platform(terse=1)
- data['comment'] = comment
-
- if self.sign:
- data['gpg_signature'] = (os.path.basename(filename) + ".asc",
- open(filename+".asc").read())
-
- # set up the authentication
- auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
-
- # Build up the MIME payload for the POST data
- boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
- sep_boundary = '\n--' + boundary
- end_boundary = sep_boundary + '--'
- body = StringIO.StringIO()
- for key, value in data.items():
- # handle multiple entries for the same name
- if type(value) != type([]):
- value = [value]
- for value in value:
- if type(value) is tuple:
- fn = ';filename="%s"' % value[0]
- value = value[1]
- else:
- fn = ""
- value = str(value)
- body.write(sep_boundary)
- body.write('\nContent-Disposition: form-data; name="%s"'%key)
- body.write(fn)
- body.write("\n\n")
- body.write(value)
- if value and value[-1] == '\r':
- body.write('\n') # write an extra newline (lurve Macs)
- body.write(end_boundary)
- body.write("\n")
- body = body.getvalue()
-
- self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO)
-
- # build the Request
- # We can't use urllib2 since we need to send the Basic
- # auth right with the first request
- schema, netloc, url, params, query, fragments = \
- urlparse.urlparse(self.repository)
- assert not params and not query and not fragments
- if schema == 'http':
- http = httplib.HTTPConnection(netloc)
- elif schema == 'https':
- http = httplib.HTTPSConnection(netloc)
- else:
- raise AssertionError, "unsupported schema "+schema
-
- data = ''
- loglevel = log.INFO
- try:
- http.connect()
- http.putrequest("POST", url)
- http.putheader('Content-type',
- 'multipart/form-data; boundary=%s'%boundary)
- http.putheader('Content-length', str(len(body)))
- http.putheader('Authorization', auth)
- http.endheaders()
- http.send(body)
- except socket.error, e:
- self.announce(e.msg, log.ERROR)
- return
-
- r = http.getresponse()
- if r.status == 200:
- self.announce('Server response (%s): %s' % (r.status, r.reason),
- log.INFO)
- else:
- self.announce('Upload failed (%s): %s' % (r.status, r.reason),
- log.ERROR)
- if self.show_response:
- print '-'*75, r.read(), '-'*75
diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py
new file mode 100644
index 00000000..cad7a52d
--- /dev/null
+++ b/setuptools/command/upload_docs.py
@@ -0,0 +1,193 @@
+# -*- coding: utf-8 -*-
+"""upload_docs
+
+Implements a Distutils 'upload_docs' subcommand (upload documentation to
+PyPI's pythonhosted.org).
+"""
+
+import os
+import socket
+import zipfile
+import tempfile
+import sys
+import shutil
+
+from base64 import standard_b64encode
+from pkg_resources import iter_entry_points
+
+from distutils import log
+from distutils.errors import DistutilsOptionError
+from distutils.command.upload import upload
+
+from setuptools.compat import httplib, urlparse, unicode, iteritems, PY3
+
+errors = 'surrogateescape' if PY3 else 'strict'
+
+
+# This is not just a replacement for byte literals
+# but works as a general purpose encoder
+def b(s, encoding='utf-8'):
+ if isinstance(s, unicode):
+ return s.encode(encoding, errors)
+ return s
+
+
+class upload_docs(upload):
+
+ description = 'Upload documentation to PyPI'
+
+ user_options = [
+ ('repository=', 'r',
+ "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY),
+ ('show-response', None,
+ 'display full response text from server'),
+ ('upload-dir=', None, 'directory to upload'),
+ ]
+ boolean_options = upload.boolean_options
+
+ def has_sphinx(self):
+ if self.upload_dir is None:
+ for ep in iter_entry_points('distutils.commands', 'build_sphinx'):
+ return True
+
+ sub_commands = [('build_sphinx', has_sphinx)]
+
+ def initialize_options(self):
+ upload.initialize_options(self)
+ self.upload_dir = None
+ self.target_dir = None
+
+ def finalize_options(self):
+ upload.finalize_options(self)
+ if self.upload_dir is None:
+ if self.has_sphinx():
+ build_sphinx = self.get_finalized_command('build_sphinx')
+ self.target_dir = build_sphinx.builder_target_dir
+ else:
+ build = self.get_finalized_command('build')
+ self.target_dir = os.path.join(build.build_base, 'docs')
+ else:
+ self.ensure_dirname('upload_dir')
+ self.target_dir = self.upload_dir
+ self.announce('Using upload directory %s' % self.target_dir)
+
+ def create_zipfile(self, filename):
+ zip_file = zipfile.ZipFile(filename, "w")
+ try:
+ self.mkpath(self.target_dir) # just in case
+ for root, dirs, files in os.walk(self.target_dir):
+ if root == self.target_dir and not files:
+ raise DistutilsOptionError(
+ "no files found in upload directory '%s'"
+ % self.target_dir)
+ for name in files:
+ full = os.path.join(root, name)
+ relative = root[len(self.target_dir):].lstrip(os.path.sep)
+ dest = os.path.join(relative, name)
+ zip_file.write(full, dest)
+ finally:
+ zip_file.close()
+
+ def run(self):
+ # Run sub commands
+ for cmd_name in self.get_sub_commands():
+ self.run_command(cmd_name)
+
+ tmp_dir = tempfile.mkdtemp()
+ name = self.distribution.metadata.get_name()
+ zip_file = os.path.join(tmp_dir, "%s.zip" % name)
+ try:
+ self.create_zipfile(zip_file)
+ self.upload_file(zip_file)
+ finally:
+ shutil.rmtree(tmp_dir)
+
+ def upload_file(self, filename):
+ f = open(filename, 'rb')
+ content = f.read()
+ f.close()
+ meta = self.distribution.metadata
+ data = {
+ ':action': 'doc_upload',
+ 'name': meta.get_name(),
+ 'content': (os.path.basename(filename), content),
+ }
+ # set up the authentication
+ credentials = b(self.username + ':' + self.password)
+ credentials = standard_b64encode(credentials)
+ if PY3:
+ credentials = credentials.decode('ascii')
+ auth = "Basic " + credentials
+
+ # Build up the MIME payload for the POST data
+ boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+ sep_boundary = b('\n--') + b(boundary)
+ end_boundary = sep_boundary + b('--')
+ body = []
+ for key, values in iteritems(data):
+ title = '\nContent-Disposition: form-data; name="%s"' % key
+ # handle multiple entries for the same name
+ if not isinstance(values, list):
+ values = [values]
+ for value in values:
+ if type(value) is tuple:
+ title += '; filename="%s"' % value[0]
+ value = value[1]
+ else:
+ value = b(value)
+ body.append(sep_boundary)
+ body.append(b(title))
+ body.append(b("\n\n"))
+ body.append(value)
+ if value and value[-1:] == b('\r'):
+ body.append(b('\n')) # write an extra newline (lurve Macs)
+ body.append(end_boundary)
+ body.append(b("\n"))
+ body = b('').join(body)
+
+ self.announce("Submitting documentation to %s" % (self.repository),
+ log.INFO)
+
+ # build the Request
+ # We can't use urllib2 since we need to send the Basic
+ # auth right with the first request
+ schema, netloc, url, params, query, fragments = \
+ urlparse(self.repository)
+ assert not params and not query and not fragments
+ if schema == 'http':
+ conn = httplib.HTTPConnection(netloc)
+ elif schema == 'https':
+ conn = httplib.HTTPSConnection(netloc)
+ else:
+ raise AssertionError("unsupported schema "+schema)
+
+ data = ''
+ try:
+ conn.connect()
+ conn.putrequest("POST", url)
+ content_type = 'multipart/form-data; boundary=%s' % boundary
+ conn.putheader('Content-type', content_type)
+ conn.putheader('Content-length', str(len(body)))
+ conn.putheader('Authorization', auth)
+ conn.endheaders()
+ conn.send(body)
+ except socket.error:
+ e = sys.exc_info()[1]
+ self.announce(str(e), log.ERROR)
+ return
+
+ r = conn.getresponse()
+ if r.status == 200:
+ self.announce('Server response (%s): %s' % (r.status, r.reason),
+ log.INFO)
+ elif r.status == 301:
+ location = r.getheader('Location')
+ if location is None:
+ location = 'https://pythonhosted.org/%s/' % meta.get_name()
+ self.announce('Upload successful. Visit %s' % location,
+ log.INFO)
+ else:
+ self.announce('Upload failed (%s): %s' % (r.status, r.reason),
+ log.ERROR)
+ if self.show_response:
+ print('-'*75, r.read(), '-'*75)