aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/__init__.py80
-rwxr-xr-xsetuptools/archive_util.py94
-rw-r--r--setuptools/cli-32.exebin0 -> 65536 bytes
-rw-r--r--setuptools/cli-64.exebin0 -> 74752 bytes
-rw-r--r--setuptools/cli-arm-32.exebin0 -> 69120 bytes
-rw-r--r--setuptools/cli.exebin0 -> 65536 bytes
-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
-rw-r--r--setuptools/compat.py83
-rw-r--r--setuptools/depends.py16
-rw-r--r--setuptools/dist.py580
-rw-r--r--setuptools/extension.py83
-rw-r--r--setuptools/gui-32.exebin0 -> 65536 bytes
-rw-r--r--setuptools/gui-64.exebin0 -> 75264 bytes
-rw-r--r--setuptools/gui-arm-32.exebin0 -> 69120 bytes
-rw-r--r--setuptools/gui.exebin0 -> 65536 bytes
-rw-r--r--setuptools/lib2to3_ex.py58
-rwxr-xr-xsetuptools/package_index.py1003
-rw-r--r--setuptools/py26compat.py19
-rw-r--r--setuptools/py27compat.py15
-rw-r--r--setuptools/py31compat.py11
-rwxr-xr-xsetuptools/sandbox.py155
-rw-r--r--setuptools/script template (dev).py11
-rw-r--r--setuptools/script template.py4
-rw-r--r--setuptools/site-patch.py76
-rw-r--r--setuptools/ssl_support.py234
-rw-r--r--setuptools/svn_utils.py564
-rw-r--r--setuptools/tests/__init__.py300
-rw-r--r--setuptools/tests/doctest.py76
-rw-r--r--setuptools/tests/entries-v10615
-rw-r--r--setuptools/tests/environment.py165
-rw-r--r--setuptools/tests/indexes/test_links_priority/external.html3
-rw-r--r--setuptools/tests/indexes/test_links_priority/simple/foobar/index.html4
-rw-r--r--setuptools/tests/py26compat.py14
-rw-r--r--setuptools/tests/script-with-bom.py3
-rw-r--r--setuptools/tests/server.py82
-rw-r--r--setuptools/tests/svn_data/dummy.zipbin0 -> 1771 bytes
-rw-r--r--setuptools/tests/svn_data/dummy13.zipbin0 -> 9243 bytes
-rw-r--r--setuptools/tests/svn_data/dummy14.zipbin0 -> 7496 bytes
-rw-r--r--setuptools/tests/svn_data/dummy15.zipbin0 -> 7506 bytes
-rw-r--r--setuptools/tests/svn_data/dummy16.zipbin0 -> 7155 bytes
-rw-r--r--setuptools/tests/svn_data/dummy17.zipbin0 -> 7512 bytes
-rw-r--r--setuptools/tests/svn_data/dummy18.zipbin0 -> 7639 bytes
-rw-r--r--setuptools/tests/svn_data/svn13_example.zipbin0 -> 48818 bytes
-rw-r--r--setuptools/tests/svn_data/svn13_ext_list.txt3
-rw-r--r--setuptools/tests/svn_data/svn13_ext_list.xml0
-rw-r--r--setuptools/tests/svn_data/svn13_info.xml121
-rw-r--r--setuptools/tests/svn_data/svn14_example.zipbin0 -> 31077 bytes
-rw-r--r--setuptools/tests/svn_data/svn14_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn14_ext_list.xml0
-rw-r--r--setuptools/tests/svn_data/svn14_info.xml119
-rw-r--r--setuptools/tests/svn_data/svn15_example.zipbin0 -> 31143 bytes
-rw-r--r--setuptools/tests/svn_data/svn15_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn15_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn15_info.xml125
-rw-r--r--setuptools/tests/svn_data/svn16_example.zipbin0 -> 29418 bytes
-rw-r--r--setuptools/tests/svn_data/svn16_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn16_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn16_info.xml125
-rw-r--r--setuptools/tests/svn_data/svn17_example.zipbin0 -> 46954 bytes
-rw-r--r--setuptools/tests/svn_data/svn17_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn17_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn17_info.xml130
-rw-r--r--setuptools/tests/svn_data/svn18_example.zipbin0 -> 47477 bytes
-rw-r--r--setuptools/tests/svn_data/svn18_ext_list.txt4
-rw-r--r--setuptools/tests/svn_data/svn18_ext_list.xml19
-rw-r--r--setuptools/tests/svn_data/svn18_info.xml136
-rw-r--r--setuptools/tests/test_bdist_egg.py69
-rw-r--r--setuptools/tests/test_build_ext.py20
-rw-r--r--setuptools/tests/test_develop.py122
-rw-r--r--setuptools/tests/test_dist_info.py83
-rw-r--r--setuptools/tests/test_easy_install.py456
-rw-r--r--setuptools/tests/test_egg_info.py173
-rw-r--r--setuptools/tests/test_markerlib.py68
-rw-r--r--setuptools/tests/test_packageindex.py203
-rw-r--r--setuptools/tests/test_resources.py390
-rw-r--r--setuptools/tests/test_sandbox.py79
-rw-r--r--setuptools/tests/test_sdist.py535
-rw-r--r--setuptools/tests/test_svn.py245
-rw-r--r--setuptools/tests/test_test.py124
-rw-r--r--setuptools/tests/test_upload_docs.py72
-rw-r--r--setuptools/tests/win_script_wrapper.txt154
-rw-r--r--setuptools/version.py1
104 files changed, 10053 insertions, 2253 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
index eeb2975b..fb2fffc0 100644
--- a/setuptools/__init__.py
+++ b/setuptools/__init__.py
@@ -1,20 +1,32 @@
"""Extensions to the 'distutils' for large or complex distributions"""
-from setuptools.dist import Distribution, Feature, _get_unpatched
-import distutils.core, setuptools.command
-from setuptools.extension import Extension
-from setuptools.depends import Require
+
+import os
+import sys
+import distutils.core
+import distutils.filelist
from distutils.core import Command as _Command
from distutils.util import convert_path
-import os.path
-__version__ = '0.6a0'
+import setuptools.version
+from setuptools.extension import Extension
+from setuptools.dist import Distribution, _get_unpatched
+from setuptools.depends import Require
+
__all__ = [
- 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
+ 'setup', 'Distribution', 'Command', 'Extension', 'Require',
'find_packages'
]
+__version__ = setuptools.version.__version__
+
bootstrap_install_from = None
+# If we run 2to3 on .py files, should we also convert docstrings?
+# Default: yes; assume that we can detect doctests reliably
+run_2to3_on_doctests = True
+# Standard package names for fixer packages
+lib2to3_fixer_packages = ['lib2to3.fixes']
+
def find_packages(where='.', exclude=()):
"""Return a list all Python packages found within directory 'where'
@@ -30,17 +42,21 @@ def find_packages(where='.', exclude=()):
where,prefix = stack.pop(0)
for name in os.listdir(where):
fn = os.path.join(where,name)
- if (os.path.isdir(fn) and
- os.path.isfile(os.path.join(fn,'__init__.py'))
- ):
- out.append(prefix+name); stack.append((fn,prefix+name+'.'))
- for pat in exclude:
+ looks_like_package = (
+ '.' not in name
+ and os.path.isdir(fn)
+ and os.path.isfile(os.path.join(fn, '__init__.py'))
+ )
+ if looks_like_package:
+ out.append(prefix+name)
+ stack.append((fn, prefix+name+'.'))
+ for pat in list(exclude)+['ez_setup']:
from fnmatch import fnmatchcase
out = [item for item in out if not fnmatchcase(item,pat)]
return out
setup = distutils.core.setup
-
+
_Command = _get_unpatched(_Command)
class Command(_Command):
@@ -53,30 +69,30 @@ class Command(_Command):
_Command.__init__(self,dist)
for k,v in kw.items():
setattr(self,k,v)
-
+
def reinitialize_command(self, command, reinit_subcommands=0, **kw):
cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
for k,v in kw.items():
setattr(cmd,k,v) # update command with keywords
return cmd
-import distutils.core
distutils.core.Command = Command # we can't patch distutils.cmd, alas
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+def findall(dir = os.curdir):
+ """Find all files under 'dir' and return the list of full filenames
+ (relative to 'dir').
+ """
+ all_files = []
+ for base, dirs, files in os.walk(dir):
+ if base==os.curdir or base.startswith(os.curdir+os.sep):
+ base = base[2:]
+ if base:
+ files = [os.path.join(base, f) for f in files]
+ all_files.extend(filter(os.path.isfile, files))
+ return all_files
+
+distutils.filelist.findall = findall # fix findall bug in distutils.
+
+# sys.dont_write_bytecode was introduced in Python 2.6.
+_dont_write_bytecode = getattr(sys, 'dont_write_bytecode',
+ bool(os.environ.get("PYTHONDONTWRITEBYTECODE")))
diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py
index d24c6c13..1109f346 100755
--- a/setuptools/archive_util.py
+++ b/setuptools/archive_util.py
@@ -3,10 +3,10 @@
__all__ = [
"unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter",
- "UnrecognizedFormat", "extraction_drivers"
+ "UnrecognizedFormat", "extraction_drivers", "unpack_directory",
]
-import zipfile, tarfile, os
+import zipfile, tarfile, os, shutil, posixpath
from pkg_resources import ensure_directory
from distutils.errors import DistutilsError
@@ -80,6 +80,47 @@ def unpack_archive(filename, extract_dir, progress_filter=default_filter,
+def unpack_directory(filename, extract_dir, progress_filter=default_filter):
+ """"Unpack" a directory, using the same interface as for archives
+
+ Raises ``UnrecognizedFormat`` if `filename` is not a directory
+ """
+ if not os.path.isdir(filename):
+ raise UnrecognizedFormat("%s is not a directory" % (filename,))
+
+ paths = {filename:('',extract_dir)}
+ for base, dirs, files in os.walk(filename):
+ src,dst = paths[base]
+ for d in dirs:
+ paths[os.path.join(base,d)] = src+d+'/', os.path.join(dst,d)
+ for f in files:
+ name = src+f
+ target = os.path.join(dst,f)
+ target = progress_filter(src+f, target)
+ if not target:
+ continue # skip non-files
+ ensure_directory(target)
+ f = os.path.join(base,f)
+ shutil.copyfile(f, target)
+ shutil.copystat(f, target)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
"""Unpack zip `filename` to `extract_dir`
@@ -97,7 +138,7 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
name = info.filename
# don't extract absolute paths or ones with .. in them
- if name.startswith('/') or '..' in name:
+ if name.startswith('/') or '..' in name.split('/'):
continue
target = os.path.join(extract_dir, *name.split('/'))
@@ -117,6 +158,9 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
finally:
f.close()
del data
+ unix_attributes = info.external_attr >> 16
+ if unix_attributes:
+ os.chmod(target, unix_attributes)
finally:
z.close()
@@ -128,37 +172,39 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
by ``tarfile.open()``). See ``unpack_archive()`` for an explanation
of the `progress_filter` argument.
"""
-
try:
tarobj = tarfile.open(filename)
except tarfile.TarError:
raise UnrecognizedFormat(
"%s is not a compressed or uncompressed tar file" % (filename,)
)
-
try:
tarobj.chown = lambda *args: None # don't do any chowning!
for member in tarobj:
- if member.isfile() or member.isdir():
- name = member.name
- # don't extract absolute paths or ones with .. in them
- if not name.startswith('/') and '..' not in name:
- dst = os.path.join(extract_dir, *name.split('/'))
- dst = progress_filter(name, dst)
- if dst:
- if dst.endswith(os.sep):
- dst = dst[:-1]
- tarobj._extract_member(member,dst) # XXX Ugh
+ name = member.name
+ # don't extract absolute paths or ones with .. in them
+ if not name.startswith('/') and '..' not in name.split('/'):
+ prelim_dst = os.path.join(extract_dir, *name.split('/'))
+
+ # resolve any links and to extract the link targets as normal files
+ while member is not None and (member.islnk() or member.issym()):
+ linkpath = member.linkname
+ if member.issym():
+ linkpath = posixpath.join(posixpath.dirname(member.name), linkpath)
+ linkpath = posixpath.normpath(linkpath)
+ member = tarobj._getmember(linkpath)
+
+ if member is not None and (member.isfile() or member.isdir()):
+ final_dst = progress_filter(name, prelim_dst)
+ if final_dst:
+ if final_dst.endswith(os.sep):
+ final_dst = final_dst[:-1]
+ try:
+ tarobj._extract_member(member, final_dst) # XXX Ugh
+ except tarfile.ExtractError:
+ pass # chown/chmod/mkfifo/mknode/makedev failed
return True
finally:
tarobj.close()
-
-
-
-extraction_drivers = unpack_zipfile, unpack_tarfile
-
-
-
-
-
+extraction_drivers = unpack_directory, unpack_zipfile, unpack_tarfile
diff --git a/setuptools/cli-32.exe b/setuptools/cli-32.exe
new file mode 100644
index 00000000..b1487b78
--- /dev/null
+++ b/setuptools/cli-32.exe
Binary files differ
diff --git a/setuptools/cli-64.exe b/setuptools/cli-64.exe
new file mode 100644
index 00000000..675e6bf3
--- /dev/null
+++ b/setuptools/cli-64.exe
Binary files differ
diff --git a/setuptools/cli-arm-32.exe b/setuptools/cli-arm-32.exe
new file mode 100644
index 00000000..2f40402d
--- /dev/null
+++ b/setuptools/cli-arm-32.exe
Binary files differ
diff --git a/setuptools/cli.exe b/setuptools/cli.exe
new file mode 100644
index 00000000..b1487b78
--- /dev/null
+++ b/setuptools/cli.exe
Binary files differ
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)
diff --git a/setuptools/compat.py b/setuptools/compat.py
new file mode 100644
index 00000000..7b824ba2
--- /dev/null
+++ b/setuptools/compat.py
@@ -0,0 +1,83 @@
+import sys
+import itertools
+
+if sys.version_info[0] < 3:
+ PY3 = False
+
+ basestring = basestring
+ import __builtin__ as builtins
+ import ConfigParser
+ from StringIO import StringIO
+ BytesIO = StringIO
+ execfile = execfile
+ func_code = lambda o: o.func_code
+ func_globals = lambda o: o.func_globals
+ im_func = lambda o: o.im_func
+ from htmlentitydefs import name2codepoint
+ import httplib
+ from BaseHTTPServer import HTTPServer
+ from SimpleHTTPServer import SimpleHTTPRequestHandler
+ from BaseHTTPServer import BaseHTTPRequestHandler
+ iteritems = lambda o: o.iteritems()
+ long_type = long
+ maxsize = sys.maxint
+ next = lambda o: o.next()
+ numeric_types = (int, long, float)
+ unichr = unichr
+ unicode = unicode
+ bytes = str
+ from urllib import url2pathname, splittag, pathname2url
+ import urllib2
+ from urllib2 import urlopen, HTTPError, URLError, unquote, splituser
+ from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit
+ filterfalse = itertools.ifilterfalse
+
+ exec("""def reraise(tp, value, tb=None):
+ raise tp, value, tb""")
+else:
+ PY3 = True
+
+ basestring = str
+ import builtins
+ import configparser as ConfigParser
+ from io import StringIO, BytesIO
+ func_code = lambda o: o.__code__
+ func_globals = lambda o: o.__globals__
+ im_func = lambda o: o.__func__
+ from html.entities import name2codepoint
+ import http.client as httplib
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
+ from http.server import BaseHTTPRequestHandler
+ iteritems = lambda o: o.items()
+ long_type = int
+ maxsize = sys.maxsize
+ next = next
+ numeric_types = (int, float)
+ unichr = chr
+ unicode = str
+ bytes = bytes
+ from urllib.error import HTTPError, URLError
+ import urllib.request as urllib2
+ from urllib.request import urlopen, url2pathname, pathname2url
+ from urllib.parse import (
+ urlparse, urlunparse, unquote, splituser, urljoin, urlsplit,
+ urlunsplit, splittag,
+ )
+ filterfalse = itertools.filterfalse
+
+ def execfile(fn, globs=None, locs=None):
+ if globs is None:
+ globs = globals()
+ if locs is None:
+ locs = globs
+ f = open(fn, 'rb')
+ try:
+ source = f.read()
+ finally:
+ f.close()
+ exec(compile(source, fn, 'exec'), globs, locs)
+
+ def reraise(tp, value, tb=None):
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
diff --git a/setuptools/depends.py b/setuptools/depends.py
index 20e5cecb..8b9d1217 100644
--- a/setuptools/depends.py
+++ b/setuptools/depends.py
@@ -36,7 +36,7 @@ class Require:
def version_ok(self,version):
"""Is 'version' sufficiently up-to-date?"""
return self.attribute is None or self.format is None or \
- str(version)<>"unknown" and version >= self.requested_version
+ str(version) != "unknown" and version >= self.requested_version
def get_version(self, paths=None, default="unknown"):
@@ -103,7 +103,7 @@ def _iter_code(code):
ptr += 3
if op==EXTENDED_ARG:
- extended_arg = arg * 65536L
+ extended_arg = arg * long_type(65536)
continue
else:
@@ -204,7 +204,6 @@ def get_module_constant(module, symbol, default=-1, paths=None):
def extract_constant(code,symbol,default=-1):
-
"""Extract the constant value of 'symbol' from 'code'
If the name 'symbol' is bound to a constant value by the Python code
@@ -237,10 +236,11 @@ def extract_constant(code,symbol,default=-1):
return const
else:
const = default
-
-
-
-
-
+
+if sys.platform.startswith('java') or sys.platform == 'cli':
+ # XXX it'd be better to test assertions about bytecode instead...
+ del extract_constant, get_module_constant
+ __all__.remove('extract_constant')
+ __all__.remove('get_module_constant')
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 31a07827..dcb9ba4e 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -1,16 +1,16 @@
-__all__ = ['Distribution', 'Feature']
+__all__ = ['Distribution']
+import re
+import os
+import sys
+import distutils.log
+import distutils.core
+import distutils.cmd
from distutils.core import Distribution as _Distribution
-from distutils.core import Extension
-from setuptools.depends import Require
-from setuptools.command.build_ext import build_ext
-from setuptools.command.install import install
-from setuptools.command.sdist import sdist
-from setuptools.command.install_lib import install_lib
-from distutils.errors import DistutilsOptionError, DistutilsPlatformError
from distutils.errors import DistutilsSetupError
-import setuptools, pkg_resources, distutils.core, distutils.dist, distutils.cmd
-import os
+
+from setuptools.compat import numeric_types, basestring
+import pkg_resources
def _get_unpatched(cls):
"""Protect against re-patching the distutils if reloaded
@@ -30,13 +30,15 @@ _Distribution = _get_unpatched(_Distribution)
sequence = tuple, list
-
-
-
-
-
-
-
+def check_importable(dist, attr, value):
+ try:
+ ep = pkg_resources.EntryPoint.parse('x='+value)
+ assert not ep.extras
+ except (TypeError,ValueError,AttributeError,AssertionError):
+ raise DistutilsSetupError(
+ "%r must be importable 'module:attrs' string (got %r)"
+ % (attr,value)
+ )
def assert_string_list(dist, attr, value):
@@ -47,22 +49,31 @@ def assert_string_list(dist, attr, value):
raise DistutilsSetupError(
"%r must be a list of strings (got %r)" % (attr,value)
)
-
def check_nsp(dist, attr, value):
"""Verify that namespace packages are valid"""
assert_string_list(dist,attr,value)
-
for nsp in value:
if not dist.has_contents_for(nsp):
raise DistutilsSetupError(
"Distribution contains no modules or packages for " +
"namespace package %r" % nsp
)
+ if '.' in nsp:
+ parent = '.'.join(nsp.split('.')[:-1])
+ if parent not in value:
+ distutils.log.warn(
+ "WARNING: %r is declared as a package namespace, but %r"
+ " is not: please correct this in setup.py", nsp, parent
+ )
def check_extras(dist, attr, value):
"""Verify that extras_require mapping is valid"""
try:
for k,v in value.items():
+ if ':' in k:
+ k,m = k.split(':',1)
+ if pkg_resources.invalid_marker(m):
+ raise DistutilsSetupError("Invalid environment marker: "+m)
list(pkg_resources.parse_requirements(v))
except (TypeError,ValueError,AttributeError):
raise DistutilsSetupError(
@@ -77,52 +88,53 @@ def assert_bool(dist, attr, value):
raise DistutilsSetupError(
"%r must be a boolean value (got %r)" % (attr,value)
)
-
-
-
-def check_install_requires(dist, attr, value):
+def check_requirements(dist, attr, value):
"""Verify that install_requires is a valid requirements list"""
try:
list(pkg_resources.parse_requirements(value))
except (TypeError,ValueError):
raise DistutilsSetupError(
- "'install_requires' must be a string or list of strings "
- "containing valid project/version requirement specifiers"
+ "%r must be a string or list of strings "
+ "containing valid project/version requirement specifiers" % (attr,)
)
-
def check_entry_points(dist, attr, value):
"""Verify that entry_points map is parseable"""
try:
pkg_resources.EntryPoint.parse_map(value)
- except ValueError, e:
+ except ValueError:
+ e = sys.exc_info()[1]
raise DistutilsSetupError(e)
-
def check_test_suite(dist, attr, value):
if not isinstance(value,basestring):
raise DistutilsSetupError("test_suite must be a string")
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+def check_package_data(dist, attr, value):
+ """Verify that value is a dictionary of package names to glob lists"""
+ if isinstance(value,dict):
+ for k,v in value.items():
+ if not isinstance(k,str): break
+ try: iter(v)
+ except TypeError:
+ break
+ else:
+ return
+ raise DistutilsSetupError(
+ attr+" must be a dictionary mapping package names to lists of "
+ "wildcard patterns"
+ )
+
+def check_packages(dist, attr, value):
+ for pkgname in value:
+ if not re.match(r'\w+(\.\w+)*', pkgname):
+ distutils.log.warn(
+ "WARNING: %r not a valid package name; please use only"
+ ".-separated package names in setup.py", pkgname
+ )
class Distribution(_Distribution):
- """Distribution with support for features, tests, and package data
+ """Distribution with support for tests and package data
This is an enhanced version of 'distutils.dist.Distribution' that
effectively adds the following new optional keyword arguments to 'setup()':
@@ -149,20 +161,6 @@ class Distribution(_Distribution):
EasyInstall and requests one of your extras, the corresponding
additional requirements will be installed if needed.
- 'features' -- a dictionary mapping option names to 'setuptools.Feature'
- objects. Features are a portion of the distribution that can be
- included or excluded based on user options, inter-feature dependencies,
- and availability on the current system. Excluded features are omitted
- from all setup commands, including source and binary distributions, so
- you can create multiple distributions from the same source tree.
- Feature names should be valid Python identifiers, except that they may
- contain the '-' (minus) sign. Features can be included or excluded
- via the command line options '--with-X' and '--without-X', where 'X' is
- the name of the feature. Whether a feature is included by default, and
- whether you are allowed to control this from the command line, is
- determined by the Feature object. See the 'Feature' class for more
- information.
-
'test_suite' -- the name of a test suite to run for the 'test' command.
If the user runs 'python setup.py test', the package will be installed,
and the named test suite will be run. The format is the same as
@@ -184,166 +182,100 @@ class Distribution(_Distribution):
for manipulating the distribution's contents. For example, the 'include()'
and 'exclude()' methods can be thought of as in-place add and subtract
commands that add or remove packages, modules, extensions, and so on from
- the distribution. They are used by the feature subsystem to configure the
- distribution for the included and excluded features.
+ the distribution.
"""
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- def __init__ (self, attrs=None):
+ _patched_dist = None
+
+ def patch_missing_pkg_info(self, attrs):
+ # Fake up a replacement for the data that would normally come from
+ # PKG-INFO, but which might not yet be built if this is a fresh
+ # checkout.
+ #
+ if not attrs or 'name' not in attrs or 'version' not in attrs:
+ return
+ key = pkg_resources.safe_name(str(attrs['name'])).lower()
+ dist = pkg_resources.working_set.by_key.get(key)
+ if dist is not None and not dist.has_metadata('PKG-INFO'):
+ dist._version = pkg_resources.safe_version(str(attrs['version']))
+ self._patched_dist = dist
+
+ def __init__(self, attrs=None):
have_package_data = hasattr(self, "package_data")
if not have_package_data:
self.package_data = {}
- self.requires = [] # XXX
- self.features = {}
self.dist_files = []
-
+ self.src_root = attrs and attrs.pop("src_root", None)
+ self.patch_missing_pkg_info(attrs)
+ # Make sure we have any eggs needed to interpret 'attrs'
+ if attrs is not None:
+ self.dependency_links = attrs.pop('dependency_links', [])
+ assert_string_list(self,'dependency_links',self.dependency_links)
if attrs and 'setup_requires' in attrs:
- # Make sure we have any eggs needed to interpret 'attrs'
self.fetch_build_eggs(attrs.pop('setup_requires'))
-
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
if not hasattr(self,ep.name):
setattr(self,ep.name,None)
-
_Distribution.__init__(self,attrs)
-
-
- def parse_command_line(self):
- """Process features after parsing command line options"""
- result = _Distribution.parse_command_line(self)
- if self.features:
- self._finalize_features()
- return result
-
- def _feature_attrname(self,name):
- """Convert feature name to corresponding option attribute name"""
- return 'with_'+name.replace('-','_')
+ if isinstance(self.metadata.version, numeric_types):
+ # Some people apparently take "version number" too literally :)
+ self.metadata.version = str(self.metadata.version)
def fetch_build_eggs(self, requires):
"""Resolve pre-setup requirements"""
from pkg_resources import working_set, parse_requirements
for dist in working_set.resolve(
- parse_requirements(requires), installer=self.fetch_build_egg
+ parse_requirements(requires), installer=self.fetch_build_egg,
+ replace_conflicting=True
):
- working_set.add(dist)
-
-
-
+ working_set.add(dist, replace=True)
def finalize_options(self):
_Distribution.finalize_options(self)
- if self.features:
- self._set_global_opts_from_features()
-
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
value = getattr(self,ep.name,None)
if value is not None:
ep.require(installer=self.fetch_build_egg)
ep.load()(self, ep.name, value)
-
+ if getattr(self, 'convert_2to3_doctests', None):
+ # XXX may convert to set here when we can rely on set being builtin
+ self.convert_2to3_doctests = [os.path.abspath(p) for p in self.convert_2to3_doctests]
+ else:
+ self.convert_2to3_doctests = []
def fetch_build_egg(self, req):
"""Fetch an egg needed for building"""
+
try:
cmd = self._egg_fetcher
+ cmd.package_index.to_scan = []
except AttributeError:
from setuptools.command.easy_install import easy_install
+ dist = self.__class__({'script_args':['easy_install']})
+ dist.parse_config_files()
+ opts = dist.get_option_dict('easy_install')
+ keep = (
+ 'find_links', 'site_dirs', 'index_url', 'optimize',
+ 'site_dirs', 'allow_hosts'
+ )
+ for key in list(opts):
+ if key not in keep:
+ del opts[key] # don't use any other settings
+ if self.dependency_links:
+ links = self.dependency_links[:]
+ if 'find_links' in opts:
+ links = opts['find_links'][1].split() + links
+ opts['find_links'] = ('setup', links)
cmd = easy_install(
- self.__class__({'script_args':['easy_install']}),
- args="x", install_dir=os.curdir, exclude_scripts=True,
+ dist, args=["x"], install_dir=os.curdir, exclude_scripts=True,
always_copy=False, build_directory=None, editable=False,
- upgrade=False
+ upgrade=False, multi_version=True, no_report=True, user=False
)
cmd.ensure_finalized()
- cmd.zip_ok = None # override any setup.cfg setting for these
- cmd.build_directory = None
self._egg_fetcher = cmd
-
return cmd.easy_install(req)
-
-
-
-
-
-
-
-
-
- def _set_global_opts_from_features(self):
- """Add --with-X/--without-X options based on optional features"""
-
- go = []
- no = self.negative_opt.copy()
-
- for name,feature in self.features.items():
- self._set_feature(name,None)
- feature.validate(self)
-
- if feature.optional:
- descr = feature.description
- incdef = ' (default)'
- excdef=''
- if not feature.include_by_default():
- excdef, incdef = incdef, excdef
-
- go.append(('with-'+name, None, 'include '+descr+incdef))
- go.append(('without-'+name, None, 'exclude '+descr+excdef))
- no['without-'+name] = 'with-'+name
-
- self.global_options = self.feature_options = go + self.global_options
- self.negative_opt = self.feature_negopt = no
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- def _finalize_features(self):
- """Add/remove features and resolve dependencies between them"""
-
- # First, flag all the enabled items (and thus their dependencies)
- for name,feature in self.features.items():
- enabled = self.feature_is_included(name)
- if enabled or (enabled is None and feature.include_by_default()):
- feature.include_in(self)
- self._set_feature(name,1)
-
- # Then disable the rest, so that off-by-default features don't
- # get flagged as errors when they're required by an enabled feature
- for name,feature in self.features.items():
- if not self.feature_is_included(name):
- feature.exclude_from(self)
- self._set_feature(name,0)
-
-
def get_command_class(self, command):
"""Pluggable version of get_command_class()"""
if command in self.cmdclass:
@@ -363,29 +295,6 @@ class Distribution(_Distribution):
self.cmdclass[ep.name] = cmdclass
return _Distribution.print_commands(self)
-
-
-
-
- def _set_feature(self,name,status):
- """Set feature's inclusion status"""
- setattr(self,self._feature_attrname(name),status)
-
- def feature_is_included(self,name):
- """Return 1 if feature is included, 0 if excluded, 'None' if unknown"""
- return getattr(self,self._feature_attrname(name))
-
- def include_feature(self,name):
- """Request inclusion of feature named 'name'"""
-
- if self.feature_is_included(name)==0:
- descr = self.features[name].description
- raise DistutilsOptionError(
- descr + " is required, but was excluded or is not available"
- )
- self.features[name].include_in(self)
- self._set_feature(name,1)
-
def include(self,**attrs):
"""Add items to distribution that are named in keyword arguments
@@ -415,22 +324,21 @@ class Distribution(_Distribution):
if self.packages:
self.packages = [
p for p in self.packages
- if p<>package and not p.startswith(pfx)
+ if p != package and not p.startswith(pfx)
]
if self.py_modules:
self.py_modules = [
p for p in self.py_modules
- if p<>package and not p.startswith(pfx)
+ if p != package and not p.startswith(pfx)
]
if self.ext_modules:
self.ext_modules = [
p for p in self.ext_modules
- if p.name<>package and not p.name.startswith(pfx)
+ if p.name != package and not p.name.startswith(pfx)
]
-
def has_contents_for(self,package):
"""Return true if 'exclude_package(package)' would do something"""
@@ -440,15 +348,6 @@ class Distribution(_Distribution):
if p==package or p.startswith(pfx):
return True
-
-
-
-
-
-
-
-
-
def _exclude_misc(self,name,value):
"""Handle 'exclude()' for list/tuple attrs without a special handler"""
if not isinstance(value,sequence):
@@ -518,18 +417,7 @@ class Distribution(_Distribution):
raise DistutilsSetupError(
"packages: setting must be a list or tuple (%r)" % (packages,)
)
- map(self.exclude_package, packages)
-
-
-
-
-
-
-
-
-
-
-
+ list(map(self.exclude_package, packages))
def _parse_command_opts(self, parser, args):
# Remove --with-X/--without-X options when processing command args
@@ -557,21 +445,6 @@ class Distribution(_Distribution):
return nargs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
def get_cmdline_options(self):
"""Return a '{cmd: {opt:val}}' map of all command-line options
@@ -612,7 +485,6 @@ class Distribution(_Distribution):
return d
-
def iter_distribution_names(self):
"""Yield all packages, modules, and extension names in distribution"""
@@ -624,197 +496,49 @@ class Distribution(_Distribution):
for ext in self.ext_modules or ():
if isinstance(ext,tuple):
- name,buildinfo = ext
- yield name
+ name, buildinfo = ext
else:
- yield ext.name
-
-# Install it throughout the distutils
-for module in distutils.dist, distutils.core, distutils.cmd:
- module.Distribution = Distribution
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-class Feature:
- """A subset of the distribution that can be excluded if unneeded/wanted
-
- Features are created using these keyword arguments:
-
- 'description' -- a short, human readable description of the feature, to
- be used in error messages, and option help messages.
-
- 'standard' -- if true, the feature is included by default if it is
- available on the current system. Otherwise, the feature is only
- included if requested via a command line '--with-X' option, or if
- another included feature requires it. The default setting is 'False'.
-
- 'available' -- if true, the feature is available for installation on the
- current system. The default setting is 'True'.
-
- 'optional' -- if true, the feature's inclusion can be controlled from the
- command line, using the '--with-X' or '--without-X' options. If
- false, the feature's inclusion status is determined automatically,
- based on 'availabile', 'standard', and whether any other feature
- requires it. The default setting is 'True'.
-
- 'requires' -- a string or sequence of strings naming features that should
- also be included if this feature is included. Defaults to empty list.
- May also contain 'Require' objects that should be added/removed from
- the distribution.
-
- 'remove' -- a string or list of strings naming packages to be removed
- from the distribution if this feature is *not* included. If the
- feature *is* included, this argument is ignored. This argument exists
- to support removing features that "crosscut" a distribution, such as
- defining a 'tests' feature that removes all the 'tests' subpackages
- provided by other features. The default for this argument is an empty
- list. (Note: the named package(s) or modules must exist in the base
- distribution when the 'setup()' function is initially called.)
-
- other keywords -- any other keyword arguments are saved, and passed to
- the distribution's 'include()' and 'exclude()' methods when the
- feature is included or excluded, respectively. So, for example, you
- could pass 'packages=["a","b"]' to cause packages 'a' and 'b' to be
- added or removed from the distribution as appropriate.
-
- A feature must include at least one 'requires', 'remove', or other
- keyword argument. Otherwise, it can't affect the distribution in any way.
- Note also that you can subclass 'Feature' to create your own specialized
- feature types that modify the distribution in other ways when included or
- excluded. See the docstrings for the various methods here for more detail.
- Aside from the methods, the only feature attributes that distributions look
- at are 'description' and 'optional'.
- """
-
- def __init__(self, description, standard=False, available=True,
- optional=True, requires=(), remove=(), **extras
- ):
-
- self.description = description
- self.standard = standard
- self.available = available
- self.optional = optional
- if isinstance(requires,(str,Require)):
- requires = requires,
-
- self.requires = [r for r in requires if isinstance(r,str)]
- er = [r for r in requires if not isinstance(r,str)]
- if er: extras['requires'] = er
-
- if isinstance(remove,str):
- remove = remove,
- self.remove = remove
- self.extras = extras
-
- if not remove and not requires and not extras:
- raise DistutilsSetupError(
- "Feature %s: must define 'requires', 'remove', or at least one"
- " of 'packages', 'py_modules', etc."
- )
-
-
- def include_by_default(self):
- """Should this feature be included by default?"""
- return self.available and self.standard
-
- def include_in(self,dist):
-
- """Ensure feature and its requirements are included in distribution
-
- You may override this in a subclass to perform additional operations on
- the distribution. Note that this method may be called more than once
- per feature, and so should be idempotent.
-
- """
-
- if not self.available:
- raise DistutilsPlatformError(
- self.description+" is required,"
- "but is not available on this platform"
- )
-
- dist.include(**self.extras)
-
- for f in self.requires:
- dist.include_feature(f)
-
-
-
- def exclude_from(self,dist):
-
- """Ensure feature is excluded from distribution
-
- You may override this in a subclass to perform additional operations on
- the distribution. This method will be called at most once per
- feature, and only after all included features have been asked to
- include themselves.
+ name = ext.name
+ if name.endswith('module'):
+ name = name[:-6]
+ yield name
+
+ def handle_display_options(self, option_order):
+ """If there were any non-global "display-only" options
+ (--help-commands or the metadata display options) on the command
+ line, display the requested info and return true; else return
+ false.
"""
+ import sys
- dist.exclude(**self.extras)
-
- if self.remove:
- for item in self.remove:
- dist.exclude_package(item)
-
-
-
- def validate(self,dist):
-
- """Verify that feature makes sense in context of distribution
-
- This method is called by the distribution just before it parses its
- command line. It checks to ensure that the 'remove' attribute, if any,
- contains only valid package/module names that are present in the base
- distribution when 'setup()' is called. You may override it in a
- subclass to perform any other required validation of the feature
- against a target distribution.
- """
-
- for item in self.remove:
- if not dist.has_contents_for(item):
- raise DistutilsSetupError(
- "%s wants to be able to remove %s, but the distribution"
- " doesn't contain any packages or modules under %s"
- % (self.description, item, item)
- )
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ if sys.version_info < (3,) or self.help_commands:
+ return _Distribution.handle_display_options(self, option_order)
+ # Stdout may be StringIO (e.g. in tests)
+ import io
+ if not isinstance(sys.stdout, io.TextIOWrapper):
+ return _Distribution.handle_display_options(self, option_order)
+ # Don't wrap stdout if utf-8 is already the encoding. Provides
+ # workaround for #334.
+ if sys.stdout.encoding.lower() in ('utf-8', 'utf8'):
+ return _Distribution.handle_display_options(self, option_order)
+ # Print metadata in UTF-8 no matter the platform
+ encoding = sys.stdout.encoding
+ errors = sys.stdout.errors
+ newline = sys.platform != 'win32' and '\n' or None
+ line_buffering = sys.stdout.line_buffering
+ sys.stdout = io.TextIOWrapper(
+ sys.stdout.detach(), 'utf-8', errors, newline, line_buffering)
+ try:
+ return _Distribution.handle_display_options(self, option_order)
+ finally:
+ sys.stdout = io.TextIOWrapper(
+ sys.stdout.detach(), encoding, errors, newline, line_buffering)
+# Install it throughout the distutils
+for module in distutils.dist, distutils.core, distutils.cmd:
+ module.Distribution = Distribution
diff --git a/setuptools/extension.py b/setuptools/extension.py
index 37b62576..d7892d3d 100644
--- a/setuptools/extension.py
+++ b/setuptools/extension.py
@@ -1,37 +1,46 @@
-from distutils.core import Extension as _Extension
-
-try:
- from Pyrex.Distutils.build_ext import build_ext
-
-except ImportError:
-
- # Pyrex isn't around, so fix up the sources
-
- from dist import _get_unpatched
- _Extension = _get_unpatched(_Extension)
-
- class Extension(_Extension):
-
- """Extension that uses '.c' files in place of '.pyx' files"""
-
- def __init__(self,*args,**kw):
- _Extension.__init__(self,*args,**kw)
- sources = []
- for s in self.sources:
- if s.endswith('.pyx'):
- sources.append(s[:-3]+'c')
- else:
- sources.append(s)
- self.sources = sources
-
- import sys, distutils.core, distutils.extension
- distutils.core.Extension = Extension
- distutils.extension.Extension = Extension
- if 'distutils.command.build_ext' in sys.modules:
- sys.modules['distutils.command.build_ext'].Extension = Extension
-
-else:
-
- # Pyrex is here, just use regular extension type
- Extension = _Extension
-
+import sys
+import distutils.core
+import distutils.extension
+
+from setuptools.dist import _get_unpatched
+
+_Extension = _get_unpatched(distutils.core.Extension)
+
+def have_pyrex():
+ """
+ Return True if Cython or Pyrex can be imported.
+ """
+ pyrex_impls = 'Cython.Distutils.build_ext', 'Pyrex.Distutils.build_ext'
+ for pyrex_impl in pyrex_impls:
+ try:
+ # from (pyrex_impl) import build_ext
+ __import__(pyrex_impl, fromlist=['build_ext']).build_ext
+ return True
+ except Exception:
+ pass
+ return False
+
+
+class Extension(_Extension):
+ """Extension that uses '.c' files in place of '.pyx' files"""
+
+ def __init__(self, *args, **kw):
+ _Extension.__init__(self, *args, **kw)
+ if not have_pyrex():
+ self._convert_pyx_sources_to_c()
+
+ def _convert_pyx_sources_to_c(self):
+ "convert .pyx extensions to .c"
+ def pyx_to_c(source):
+ if source.endswith('.pyx'):
+ source = source[:-4] + '.c'
+ return source
+ self.sources = list(map(pyx_to_c, self.sources))
+
+class Library(Extension):
+ """Just like a regular Extension, but built as a library instead"""
+
+distutils.core.Extension = Extension
+distutils.extension.Extension = Extension
+if 'distutils.command.build_ext' in sys.modules:
+ sys.modules['distutils.command.build_ext'].Extension = Extension
diff --git a/setuptools/gui-32.exe b/setuptools/gui-32.exe
new file mode 100644
index 00000000..f8d35096
--- /dev/null
+++ b/setuptools/gui-32.exe
Binary files differ
diff --git a/setuptools/gui-64.exe b/setuptools/gui-64.exe
new file mode 100644
index 00000000..330c51a5
--- /dev/null
+++ b/setuptools/gui-64.exe
Binary files differ
diff --git a/setuptools/gui-arm-32.exe b/setuptools/gui-arm-32.exe
new file mode 100644
index 00000000..537aff37
--- /dev/null
+++ b/setuptools/gui-arm-32.exe
Binary files differ
diff --git a/setuptools/gui.exe b/setuptools/gui.exe
new file mode 100644
index 00000000..f8d35096
--- /dev/null
+++ b/setuptools/gui.exe
Binary files differ
diff --git a/setuptools/lib2to3_ex.py b/setuptools/lib2to3_ex.py
new file mode 100644
index 00000000..feef591a
--- /dev/null
+++ b/setuptools/lib2to3_ex.py
@@ -0,0 +1,58 @@
+"""
+Customized Mixin2to3 support:
+
+ - adds support for converting doctests
+
+
+This module raises an ImportError on Python 2.
+"""
+
+from distutils.util import Mixin2to3 as _Mixin2to3
+from distutils import log
+from lib2to3.refactor import RefactoringTool, get_fixers_from_package
+import setuptools
+
+class DistutilsRefactoringTool(RefactoringTool):
+ def log_error(self, msg, *args, **kw):
+ log.error(msg, *args)
+
+ def log_message(self, msg, *args):
+ log.info(msg, *args)
+
+ def log_debug(self, msg, *args):
+ log.debug(msg, *args)
+
+class Mixin2to3(_Mixin2to3):
+ def run_2to3(self, files, doctests = False):
+ # See of the distribution option has been set, otherwise check the
+ # setuptools default.
+ if self.distribution.use_2to3 is not True:
+ return
+ if not files:
+ return
+ log.info("Fixing "+" ".join(files))
+ self.__build_fixer_names()
+ self.__exclude_fixers()
+ if doctests:
+ if setuptools.run_2to3_on_doctests:
+ r = DistutilsRefactoringTool(self.fixer_names)
+ r.refactor(files, write=True, doctests_only=True)
+ else:
+ _Mixin2to3.run_2to3(self, files)
+
+ def __build_fixer_names(self):
+ if self.fixer_names: return
+ self.fixer_names = []
+ for p in setuptools.lib2to3_fixer_packages:
+ self.fixer_names.extend(get_fixers_from_package(p))
+ if self.distribution.use_2to3_fixers is not None:
+ for p in self.distribution.use_2to3_fixers:
+ self.fixer_names.extend(get_fixers_from_package(p))
+
+ def __exclude_fixers(self):
+ excluded_fixers = getattr(self, 'exclude_fixers', [])
+ if self.distribution.use_2to3_exclude_fixers is not None:
+ excluded_fixers.extend(self.distribution.use_2to3_exclude_fixers)
+ for fixer_name in excluded_fixers:
+ if fixer_name in self.fixer_names:
+ self.fixer_names.remove(fixer_name)
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index 95b57178..167c34e5 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -1,14 +1,38 @@
"""PyPI and direct package downloading"""
-
-import sys, os.path, re, urlparse, urllib2
-from pkg_resources import *
+import sys
+import os
+import re
+import shutil
+import socket
+import base64
+import hashlib
+from functools import wraps
+
+from pkg_resources import (
+ CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST,
+ require, Environment, find_distributions, safe_name, safe_version,
+ to_filename, Requirement, DEVELOP_DIST,
+)
+from setuptools import ssl_support
from distutils import log
from distutils.errors import DistutilsError
-
-EGG_FRAGMENT = re.compile('^egg=(\\w+(-\\w+)?)$')
+from setuptools.compat import (urllib2, httplib, StringIO, HTTPError,
+ urlparse, urlunparse, unquote, splituser,
+ url2pathname, name2codepoint,
+ unichr, urljoin, urlsplit, urlunsplit,
+ ConfigParser)
+from setuptools.compat import filterfalse
+from fnmatch import translate
+from setuptools.py26compat import strip_fragment
+from setuptools.py27compat import get_all_headers
+
+EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$')
HREF = re.compile("""href\\s*=\\s*['"]?([^'"> ]+)""", re.I)
-# this is here to fix emacs' cruddy broken syntax highlighting: '
-
+# this is here to fix emacs' cruddy broken syntax highlighting
+PYPI_MD5 = re.compile(
+ '<a href="([^"#]+)">([^<]+)</a>\n\s+\\(<a (?:title="MD5 hash"\n\s+)'
+ 'href="[^?]+\?:action=show_md5&amp;digest=([0-9a-f]{32})">md5</a>\\)'
+)
URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):',re.I).match
EXTENSIONS = ".tar.gz .tar.bz2 .tar .zip .tgz".split()
@@ -17,69 +41,73 @@ __all__ = [
'interpret_distro_name',
]
+_SOCKET_TIMEOUT = 15
def parse_bdist_wininst(name):
"""Return (base,pyversion) or (None,None) for possible .exe name"""
lower = name.lower()
- base, py_ver = None, None
+ base, py_ver, plat = None, None, None
if lower.endswith('.exe'):
if lower.endswith('.win32.exe'):
base = name[:-10]
+ plat = 'win32'
elif lower.startswith('.win32-py',-16):
py_ver = name[-7:-4]
base = name[:-16]
-
- return base,py_ver
-
-
-
-
+ plat = 'win32'
+ elif lower.endswith('.win-amd64.exe'):
+ base = name[:-14]
+ plat = 'win-amd64'
+ elif lower.startswith('.win-amd64-py',-20):
+ py_ver = name[-7:-4]
+ base = name[:-20]
+ plat = 'win-amd64'
+ return base,py_ver,plat
+def egg_info_for_url(url):
+ scheme, server, path, parameters, query, fragment = urlparse(url)
+ base = unquote(path.split('/')[-1])
+ if server=='sourceforge.net' and base=='download': # XXX Yuck
+ base = unquote(path.split('/')[-2])
+ if '#' in base: base, fragment = base.split('#',1)
+ return base,fragment
def distros_for_url(url, metadata=None):
"""Yield egg or source distribution objects that might be found at a URL"""
-
- scheme, server, path, parameters, query, fragment = urlparse.urlparse(url)
- base = urllib2.unquote(path.split('/')[-1])
- dists = distros_for_location(url, base, metadata)
- if fragment and not dists:
+ base, fragment = egg_info_for_url(url)
+ for dist in distros_for_location(url, base, metadata): yield dist
+ if fragment:
match = EGG_FRAGMENT.match(fragment)
if match:
- return interpret_distro_name(
+ for dist in interpret_distro_name(
url, match.group(1), metadata, precedence = CHECKOUT_DIST
- )
- return dists
-
+ ):
+ yield dist
def distros_for_location(location, basename, metadata=None):
"""Yield egg or source distribution objects based on basename"""
if basename.endswith('.egg.zip'):
basename = basename[:-4] # strip the .zip
-
- if basename.endswith('.egg'): # only one, unambiguous interpretation
+ if basename.endswith('.egg') and '-' in basename:
+ # only one, unambiguous interpretation
return [Distribution.from_location(location, basename, metadata)]
-
if basename.endswith('.exe'):
- win_base, py_ver = parse_bdist_wininst(basename)
+ win_base, py_ver, platform = parse_bdist_wininst(basename)
if win_base is not None:
return interpret_distro_name(
- location, win_base, metadata, py_ver, BINARY_DIST, "win32"
+ location, win_base, metadata, py_ver, BINARY_DIST, platform
)
-
# Try source distro extensions (.zip, .tgz, etc.)
#
for ext in EXTENSIONS:
if basename.endswith(ext):
basename = basename[:-len(ext)]
return interpret_distro_name(location, basename, metadata)
-
return [] # no extension matched
-
-
def distros_for_filename(filename, metadata=None):
"""Yield possible egg or source distribution objects based on a filename"""
return distros_for_location(
@@ -87,16 +115,16 @@ def distros_for_filename(filename, metadata=None):
)
-def interpret_distro_name(location, basename, metadata,
- py_version=None, precedence=SOURCE_DIST, platform=None
-):
+def interpret_distro_name(
+ location, basename, metadata, py_version=None, precedence=SOURCE_DIST,
+ platform=None
+ ):
"""Generate alternative interpretations of a source distro name
Note: if `location` is a filesystem filename, you should call
``pkg_resources.normalize_path()`` on it before passing it to this
routine!
"""
-
# Generate alternative interpretations of a source distro name
# Because some packages are ambiguous as to name/versions split
# e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc.
@@ -110,6 +138,11 @@ def interpret_distro_name(location, basename, metadata,
# versions in distribution archive names (sdist and bdist).
parts = basename.split('-')
+ if not py_version:
+ for i,p in enumerate(parts[2:]):
+ if len(p)==5 and p.startswith('py2.'):
+ return # It's a bdist_dumb, not an sdist -- bail out
+
for p in range(1,len(parts)+1):
yield Distribution(
location, metadata, '-'.join(parts[:p]), '-'.join(parts[p:]),
@@ -117,152 +150,297 @@ def interpret_distro_name(location, basename, metadata,
platform = platform
)
+# From Python 2.7 docs
+def unique_everseen(iterable, key=None):
+ "List unique elements, preserving order. Remember all elements ever seen."
+ # unique_everseen('AAAABBBCCDAABBB') --> A B C D
+ # unique_everseen('ABBCcAD', str.lower) --> A B C D
+ seen = set()
+ seen_add = seen.add
+ if key is None:
+ for element in filterfalse(seen.__contains__, iterable):
+ seen_add(element)
+ yield element
+ else:
+ for element in iterable:
+ k = key(element)
+ if k not in seen:
+ seen_add(k)
+ yield element
+
+def unique_values(func):
+ """
+ Wrap a function returning an iterable such that the resulting iterable
+ only ever yields unique items.
+ """
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return unique_everseen(func(*args, **kwargs))
+ return wrapper
+
+REL = re.compile("""<([^>]*\srel\s*=\s*['"]?([^'">]+)[^>]*)>""", re.I)
+# this line is here to fix emacs' cruddy broken syntax highlighting
+
+@unique_values
+def find_external_links(url, page):
+ """Find rel="homepage" and rel="download" links in `page`, yielding URLs"""
+
+ for match in REL.finditer(page):
+ tag, rel = match.groups()
+ rels = set(map(str.strip, rel.lower().split(',')))
+ if 'homepage' in rels or 'download' in rels:
+ for match in HREF.finditer(tag):
+ yield urljoin(url, htmldecode(match.group(1)))
+
+ for tag in ("<th>Home Page", "<th>Download URL"):
+ pos = page.find(tag)
+ if pos!=-1:
+ match = HREF.search(page,pos)
+ if match:
+ yield urljoin(url, htmldecode(match.group(1)))
+
+user_agent = "Python-urllib/%s setuptools/%s" % (
+ sys.version[:3], require('setuptools')[0].version
+)
+
+class ContentChecker(object):
+ """
+ A null content checker that defines the interface for checking content
+ """
+ def feed(self, block):
+ """
+ Feed a block of data to the hash.
+ """
+ return
+
+ def is_valid(self):
+ """
+ Check the hash. Return False if validation fails.
+ """
+ return True
+
+ def report(self, reporter, template):
+ """
+ Call reporter with information about the checker (hash name)
+ substituted into the template.
+ """
+ return
+
+class HashChecker(ContentChecker):
+ pattern = re.compile(
+ r'(?P<hash_name>sha1|sha224|sha384|sha256|sha512|md5)='
+ r'(?P<expected>[a-f0-9]+)'
+ )
+
+ def __init__(self, hash_name, expected):
+ self.hash_name = hash_name
+ self.hash = hashlib.new(hash_name)
+ self.expected = expected
+
+ @classmethod
+ def from_url(cls, url):
+ "Construct a (possibly null) ContentChecker from a URL"
+ fragment = urlparse(url)[-1]
+ if not fragment:
+ return ContentChecker()
+ match = cls.pattern.search(fragment)
+ if not match:
+ return ContentChecker()
+ return cls(**match.groupdict())
+
+ def feed(self, block):
+ self.hash.update(block)
+ def is_valid(self):
+ return self.hash.hexdigest() == self.expected
+ def report(self, reporter, template):
+ msg = template % self.hash_name
+ return reporter(msg)
class PackageIndex(Environment):
"""A distribution index that scans web pages for download URLs"""
- def __init__(self,index_url="http://www.python.org/pypi",*args,**kw):
+ def __init__(
+ self, index_url="https://pypi.python.org/simple", hosts=('*',),
+ ca_bundle=None, verify_ssl=True, *args, **kw
+ ):
Environment.__init__(self,*args,**kw)
self.index_url = index_url + "/"[:not index_url.endswith('/')]
self.scanned_urls = {}
self.fetched_urls = {}
self.package_pages = {}
+ self.allows = re.compile('|'.join(map(translate,hosts))).match
+ self.to_scan = []
+ if verify_ssl and ssl_support.is_available and (ca_bundle or ssl_support.find_ca_bundle()):
+ self.opener = ssl_support.opener_for(ca_bundle)
+ else: self.opener = urllib2.urlopen
def process_url(self, url, retrieve=False):
"""Evaluate a URL as a possible download, and maybe retrieve it"""
-
if url in self.scanned_urls and not retrieve:
return
-
self.scanned_urls[url] = True
-
if not URL_SCHEME(url):
- # process filenames or directories
- if os.path.isfile(url):
- dists = list(distros_for_filename(url))
- elif os.path.isdir(url):
- url = os.path.realpath(url)
- for item in os.listdir(url):
- self.process_url(os.path.join(url,item))
- return
- else:
- self.warn("Not found: %s", url)
- return
+ self.process_filename(url)
+ return
else:
dists = list(distros_for_url(url))
+ if dists:
+ if not self.url_ok(url):
+ return
+ self.debug("Found link: %s", url)
- if dists:
- self.debug("Found link: %s", url)
if dists or not retrieve or url in self.fetched_urls:
- for dist in dists:
- self.add(dist)
- # don't need the actual page
+ list(map(self.add, dists))
+ return # don't need the actual page
+
+ if not self.url_ok(url):
+ self.fetched_urls[url] = True
return
self.info("Reading %s", url)
- f = self.open_url(url)
- self.fetched_urls[url] = self.fetched_urls[f.url] = True
-
- if 'html' not in f.headers['content-type'].lower():
+ self.fetched_urls[url] = True # prevent multiple fetch attempts
+ f = self.open_url(url, "Download error on %s: %%s -- Some packages may not be found!" % url)
+ if f is None: return
+ self.fetched_urls[f.url] = True
+ if 'html' not in f.headers.get('content-type', '').lower():
f.close() # not html, we can't process it
return
base = f.url # handle redirects
page = f.read()
+ if not isinstance(page, str): # We are in Python 3 and got bytes. We want str.
+ if isinstance(f, HTTPError):
+ # Errors have no charset, assume latin1:
+ charset = 'latin-1'
+ else:
+ charset = f.headers.get_param('charset') or 'latin-1'
+ page = page.decode(charset, "ignore")
f.close()
- if url.startswith(self.index_url):
- self.process_index(url, page)
-
for match in HREF.finditer(page):
- link = urlparse.urljoin(base, match.group(1))
+ link = urljoin(base, htmldecode(match.group(1)))
self.process_url(link)
+ if url.startswith(self.index_url) and getattr(f,'code',None)!=404:
+ page = self.process_index(url, page)
+ def process_filename(self, fn, nested=False):
+ # process filenames or directories
+ if not os.path.exists(fn):
+ self.warn("Not found: %s", fn)
+ return
+ if os.path.isdir(fn) and not nested:
+ path = os.path.realpath(fn)
+ for item in os.listdir(path):
+ self.process_filename(os.path.join(path,item), True)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ dists = distros_for_filename(fn)
+ if dists:
+ self.debug("Found: %s", fn)
+ list(map(self.add, dists))
+
+ def url_ok(self, url, fatal=False):
+ s = URL_SCHEME(url)
+ if (s and s.group(1).lower()=='file') or self.allows(urlparse(url)[1]):
+ return True
+ msg = ("\nNote: Bypassing %s (disallowed host; see "
+ "http://bit.ly/1dg9ijs for details).\n")
+ if fatal:
+ raise DistutilsError(msg % url)
+ else:
+ self.warn(msg, url)
+
+ def scan_egg_links(self, search_path):
+ for item in search_path:
+ if os.path.isdir(item):
+ for entry in os.listdir(item):
+ if entry.endswith('.egg-link'):
+ self.scan_egg_link(item, entry)
+
+ def scan_egg_link(self, path, entry):
+ lines = [_f for _f in map(str.strip,
+ open(os.path.join(path, entry))) if _f]
+ if len(lines)==2:
+ for dist in find_distributions(os.path.join(path, lines[0])):
+ dist.location = os.path.join(path, *lines)
+ dist.precedence = SOURCE_DIST
+ self.add(dist)
def process_index(self,url,page):
"""Process the contents of a PyPI page"""
-
def scan(link):
# Process a URL to see if it's for a package page
if link.startswith(self.index_url):
- parts = map(
- urllib2.unquote, link[len(self.index_url):].split('/')
- )
- if len(parts)==2:
+ parts = list(map(
+ unquote, link[len(self.index_url):].split('/')
+ ))
+ if len(parts)==2 and '#' not in parts[1]:
# it's a package page, sanitize and index it
pkg = safe_name(parts[0])
ver = safe_version(parts[1])
self.package_pages.setdefault(pkg.lower(),{})[link] = True
+ return to_filename(pkg), to_filename(ver)
+ return None, None
- if url==self.index_url or 'Index of Packages</title>' in page:
- # process an index page into the package-page index
- for match in HREF.finditer(page):
- scan( urlparse.urljoin(url, match.group(1)) )
+ # process an index page into the package-page index
+ for match in HREF.finditer(page):
+ try:
+ scan(urljoin(url, htmldecode(match.group(1))))
+ except ValueError:
+ pass
- else:
- scan(url) # ensure this page is in the page index
+ pkg, ver = scan(url) # ensure this page is in the page index
+ if pkg:
# process individual package page
- for tag in ("<th>Home Page", "<th>Download URL"):
- pos = page.find(tag)
- if pos!=-1:
- match = HREF.search(page,pos)
- if match:
- # Process the found URL
- self.scan_url(urlparse.urljoin(url, match.group(1)))
-
-
-
-
-
-
+ for new_url in find_external_links(url, page):
+ # Process the found URL
+ base, frag = egg_info_for_url(new_url)
+ if base.endswith('.py') and not frag:
+ if ver:
+ new_url+='#egg=%s-%s' % (pkg,ver)
+ else:
+ self.need_version_info(url)
+ self.scan_url(new_url)
+
+ return PYPI_MD5.sub(
+ lambda m: '<a href="%s#md5=%s">%s</a>' % m.group(1,3,2), page
+ )
+ else:
+ return "" # no sense double-scanning non-package pages
+ def need_version_info(self, url):
+ self.scan_all(
+ "Page at %s links to .py file(s) without version info; an index "
+ "scan is required.", url
+ )
+ def scan_all(self, msg=None, *args):
+ if self.index_url not in self.fetched_urls:
+ if msg: self.warn(msg,*args)
+ self.info(
+ "Scanning index of all packages (this may take a while)"
+ )
+ self.scan_url(self.index_url)
+ def find_packages(self, requirement):
+ self.scan_url(self.index_url + requirement.unsafe_name+'/')
+ if not self.package_pages.get(requirement.key):
+ # Fall back to safe version of the name
+ self.scan_url(self.index_url + requirement.project_name+'/')
- def find_packages(self, requirement):
- self.scan_url(self.index_url + requirement.project_name+'/')
if not self.package_pages.get(requirement.key):
# We couldn't find the target package, so search the index page too
- self.warn(
- "Couldn't find index page for %r (maybe misspelled?)",
- requirement.project_name
- )
- if self.index_url not in self.fetched_urls:
- self.warn(
- "Scanning index of all packages (this may take a while)"
- )
- self.scan_url(self.index_url)
+ self.not_found_in_index(requirement)
- for url in self.package_pages.get(requirement.key,()):
+ for url in list(self.package_pages.get(requirement.key,())):
# scan each page that might be related to the desired package
self.scan_url(url)
def obtain(self, requirement, installer=None):
+ self.prescan()
self.find_packages(requirement)
for dist in self[requirement.key]:
if dist in requirement:
@@ -270,42 +448,78 @@ class PackageIndex(Environment):
self.debug("%s does not match %s", requirement, dist)
return super(PackageIndex, self).obtain(requirement,installer)
+ def check_hash(self, checker, filename, tfp):
+ """
+ checker is a ContentChecker
+ """
+ checker.report(self.debug,
+ "Validating %%s checksum for %s" % filename)
+ if not checker.is_valid():
+ tfp.close()
+ os.unlink(filename)
+ raise DistutilsError(
+ "%s validation failed for %s; "
+ "possible download problem?" % (
+ checker.hash.name, os.path.basename(filename))
+ )
-
-
-
-
-
-
-
-
-
-
-
-
-
+ def add_find_links(self, urls):
+ """Add `urls` to the list that will be prescanned for searches"""
+ for url in urls:
+ if (
+ self.to_scan is None # if we have already "gone online"
+ or not URL_SCHEME(url) # or it's a local file/directory
+ or url.startswith('file:')
+ or list(distros_for_url(url)) # or a direct package link
+ ):
+ # then go ahead and process it now
+ self.scan_url(url)
+ else:
+ # otherwise, defer retrieval till later
+ self.to_scan.append(url)
+
+ def prescan(self):
+ """Scan urls scheduled for prescanning (e.g. --find-links)"""
+ if self.to_scan:
+ list(map(self.scan_url, self.to_scan))
+ self.to_scan = None # from now on, go ahead and process immediately
+
+ def not_found_in_index(self, requirement):
+ if self[requirement.key]: # we've seen at least one distro
+ meth, msg = self.info, "Couldn't retrieve index page for %r"
+ else: # no distros seen for this name, might be misspelled
+ meth, msg = (self.warn,
+ "Couldn't find index page for %r (maybe misspelled?)")
+ meth(msg, requirement.unsafe_name)
+ self.scan_all()
def download(self, spec, tmpdir):
"""Locate and/or download `spec` to `tmpdir`, returning a local path
`spec` may be a ``Requirement`` object, or a string containing a URL,
an existing local filename, or a project/version requirement spec
- (i.e. the string form of a ``Requirement`` object).
+ (i.e. the string form of a ``Requirement`` object). If it is the URL
+ of a .py file with an unambiguous ``#egg=name-version`` tag (i.e., one
+ that escapes ``-`` as ``_`` throughout), a trivial ``setup.py`` is
+ automatically created alongside the downloaded file.
If `spec` is a ``Requirement`` object or a string containing a
- project/version requirement spec, this method is equivalent to
- the ``fetch()`` method. If `spec` is a local, existing file or
- directory name, it is simply returned unchanged. If `spec` is a URL,
- it is downloaded to a subpath of `tmpdir`, and the local filename is
- returned. Various errors may be raised if a problem occurs during
- downloading.
+ project/version requirement spec, this method returns the location of
+ a matching distribution (possibly after downloading it to `tmpdir`).
+ If `spec` is a locally existing file or directory name, it is simply
+ returned unchanged. If `spec` is a URL, it is downloaded to a subpath
+ of `tmpdir`, and the local filename is returned. Various errors may be
+ raised if a problem occurs during downloading.
"""
if not isinstance(spec,Requirement):
scheme = URL_SCHEME(spec)
if scheme:
# It's a url, download it to tmpdir
- return self._download_url(scheme.group(1), spec, tmpdir)
-
+ found = self._download_url(scheme.group(1), spec, tmpdir)
+ base, fragment = egg_info_for_url(spec)
+ if base.endswith('.py'):
+ found = self.gen_setup(found,fragment,tmpdir)
+ return found
elif os.path.exists(spec):
# Existing file or directory, just return it
return spec
@@ -317,46 +531,65 @@ class PackageIndex(Environment):
"Not a URL, existing file, or requirement spec: %r" %
(spec,)
)
+ return getattr(self.fetch_distribution(spec, tmpdir),'location',None)
- return self.fetch(spec, tmpdir)
-
-
-
-
-
-
-
- def fetch(self, requirement, tmpdir, force_scan=False, source=False):
- """Obtain a file suitable for fulfilling `requirement`
+ def fetch_distribution(
+ self, requirement, tmpdir, force_scan=False, source=False,
+ develop_ok=False, local_index=None
+ ):
+ """Obtain a distribution suitable for fulfilling `requirement`
`requirement` must be a ``pkg_resources.Requirement`` instance.
If necessary, or if the `force_scan` flag is set, the requirement is
searched for in the (online) package index as well as the locally
installed packages. If a distribution matching `requirement` is found,
- the return value is the same as if you had called the ``download()``
- method with the matching distribution's URL. If no matching
- distribution is found, returns ``None``.
+ the returned distribution's ``location`` is the value you would have
+ gotten from calling the ``download()`` method with the matching
+ distribution's URL or filename. If no matching distribution is found,
+ ``None`` is returned.
If the `source` flag is set, only source distributions and source
- checkout links will be considered.
+ checkout links will be considered. Unless the `develop_ok` flag is
+ set, development and system eggs (i.e., those using the ``.egg-info``
+ format) will be ignored.
"""
# process a Requirement
self.info("Searching for %s", requirement)
+ skipped = {}
+ dist = None
+
+ def find(req, env=None):
+ if env is None:
+ env = self
+ # Find a matching distribution; may be called more than once
+
+ for dist in env[req.key]:
+
+ if dist.precedence==DEVELOP_DIST and not develop_ok:
+ if dist not in skipped:
+ self.warn("Skipping development or system egg: %s",dist)
+ skipped[dist] = 1
+ continue
- def find(req):
- for dist in self[req.key]:
if dist in req and (dist.precedence<=SOURCE_DIST or not source):
- self.info("Best match: %s", dist)
- return self.download(dist.location, tmpdir)
-
+ return dist
+
if force_scan:
+ self.prescan()
self.find_packages(requirement)
dist = find(requirement)
- else:
+
+ if local_index is not None:
+ dist = dist or find(requirement, local_index)
+
+ if dist is None:
+ if self.to_scan is not None:
+ self.prescan()
+ dist = find(requirement)
+
+ if dist is None and not force_scan:
+ self.find_packages(requirement)
dist = find(requirement)
- if dist is None:
- self.find_packages(requirement)
- dist = find(requirement)
if dist is None:
self.warn(
@@ -364,43 +597,98 @@ class PackageIndex(Environment):
(source and "a source distribution of " or ""),
requirement,
)
- return dist
+ else:
+ self.info("Best match: %s", dist)
+ return dist.clone(location=self.download(dist.location, tmpdir))
+ def fetch(self, requirement, tmpdir, force_scan=False, source=False):
+ """Obtain a file suitable for fulfilling `requirement`
- dl_blocksize = 8192
+ DEPRECATED; use the ``fetch_distribution()`` method now instead. For
+ backward compatibility, this routine is identical but returns the
+ ``location`` of the downloaded distribution instead of a distribution
+ object.
+ """
+ dist = self.fetch_distribution(requirement,tmpdir,force_scan,source)
+ if dist is not None:
+ return dist.location
+ return None
+ def gen_setup(self, filename, fragment, tmpdir):
+ match = EGG_FRAGMENT.match(fragment)
+ dists = match and [
+ d for d in
+ interpret_distro_name(filename, match.group(1), None) if d.version
+ ] or []
+
+ if len(dists)==1: # unambiguous ``#egg`` fragment
+ basename = os.path.basename(filename)
+
+ # Make sure the file has been downloaded to the temp dir.
+ if os.path.dirname(filename) != tmpdir:
+ dst = os.path.join(tmpdir, basename)
+ from setuptools.command.easy_install import samefile
+ if not samefile(filename, dst):
+ shutil.copy2(filename, dst)
+ filename=dst
+
+ file = open(os.path.join(tmpdir, 'setup.py'), 'w')
+ file.write(
+ "from setuptools import setup\n"
+ "setup(name=%r, version=%r, py_modules=[%r])\n"
+ % (
+ dists[0].project_name, dists[0].version,
+ os.path.splitext(basename)[0]
+ )
+ )
+ file.close()
+ return filename
+
+ elif match:
+ raise DistutilsError(
+ "Can't unambiguously interpret project/version identifier %r; "
+ "any dashes in the name or version should be escaped using "
+ "underscores. %r" % (fragment,dists)
+ )
+ else:
+ raise DistutilsError(
+ "Can't process plain .py files without an '#egg=name-version'"
+ " suffix to enable automatic setup script generation."
+ )
+
+ dl_blocksize = 8192
def _download_to(self, url, filename):
self.info("Downloading %s", url)
# Download the file
- fp, tfp = None, None
+ fp, tfp, info = None, None, None
try:
- url = url.split('#', 1)[0]
- fp = self.open_url(url)
- if isinstance(fp, urllib2.HTTPError):
+ checker = HashChecker.from_url(url)
+ fp = self.open_url(strip_fragment(url))
+ if isinstance(fp, HTTPError):
raise DistutilsError(
"Can't download %s: %s %s" % (url, fp.code,fp.msg)
)
-
headers = fp.info()
blocknum = 0
bs = self.dl_blocksize
size = -1
-
if "content-length" in headers:
- size = int(headers["Content-Length"])
+ # Some servers return multiple Content-Length headers :(
+ sizes = get_all_headers(headers, 'Content-Length')
+ size = max(map(int, sizes))
self.reporthook(url, filename, blocknum, bs, size)
-
tfp = open(filename,'wb')
while True:
block = fp.read(bs)
if block:
+ checker.feed(block)
tfp.write(block)
blocknum += 1
self.reporthook(url, filename, blocknum, bs, size)
else:
break
+ self.check_hash(checker, filename, tfp)
return headers
-
finally:
if fp: fp.close()
if tfp: tfp.close()
@@ -408,22 +696,51 @@ class PackageIndex(Environment):
def reporthook(self, url, filename, blocknum, blksize, size):
pass # no-op
- def open_url(self, url):
+ def open_url(self, url, warning=None):
+ if url.startswith('file:'):
+ return local_open(url)
try:
- return urllib2.urlopen(url)
- except urllib2.HTTPError, v:
+ return open_with_auth(url, self.opener)
+ except (ValueError, httplib.InvalidURL):
+ v = sys.exc_info()[1]
+ msg = ' '.join([str(arg) for arg in v.args])
+ if warning:
+ self.warn(warning, msg)
+ else:
+ raise DistutilsError('%s %s' % (url, msg))
+ except urllib2.HTTPError:
+ v = sys.exc_info()[1]
return v
- except urllib2.URLError, v:
- raise DistutilsError("Download error: %s" % v.reason)
-
+ except urllib2.URLError:
+ v = sys.exc_info()[1]
+ if warning:
+ self.warn(warning, v.reason)
+ else:
+ raise DistutilsError("Download error for %s: %s"
+ % (url, v.reason))
+ except httplib.BadStatusLine:
+ v = sys.exc_info()[1]
+ if warning:
+ self.warn(warning, v.line)
+ else:
+ raise DistutilsError(
+ '%s returned a bad status line. The server might be '
+ 'down, %s' %
+ (url, v.line)
+ )
+ except httplib.HTTPException:
+ v = sys.exc_info()[1]
+ if warning:
+ self.warn(warning, v)
+ else:
+ raise DistutilsError("Download error for %s: %s"
+ % (url, v))
def _download_url(self, scheme, url, tmpdir):
-
# Determine download filename
#
- name = filter(None,urlparse.urlparse(url)[2].split('/'))
+ name, fragment = egg_info_for_url(url)
if name:
- name = name[-1]
while '..' in name:
name = name.replace('..','.').replace('\\','_')
else:
@@ -438,47 +755,109 @@ class PackageIndex(Environment):
#
if scheme=='svn' or scheme.startswith('svn+'):
return self._download_svn(url, filename)
+ elif scheme=='git' or scheme.startswith('git+'):
+ return self._download_git(url, filename)
+ elif scheme.startswith('hg+'):
+ return self._download_hg(url, filename)
+ elif scheme=='file':
+ return url2pathname(urlparse(url)[2])
else:
- headers = self._download_to(url, filename)
- if 'html' in headers['content-type'].lower():
- return self._download_html(url, headers, filename, tmpdir)
- else:
- return filename
+ self.url_ok(url, True) # raises error if not allowed
+ return self._attempt_download(url, filename)
def scan_url(self, url):
self.process_url(url, True)
+ def _attempt_download(self, url, filename):
+ headers = self._download_to(url, filename)
+ if 'html' in headers.get('content-type','').lower():
+ return self._download_html(url, headers, filename)
+ else:
+ return filename
- def _download_html(self, url, headers, filename, tmpdir):
- # Check for a sourceforge URL
- sf_url = url.startswith('http://prdownloads.')
+ def _download_html(self, url, headers, filename):
file = open(filename)
for line in file:
if line.strip():
# Check for a subversion index page
- if re.search(r'<title>Revision \d+:', line):
+ if re.search(r'<title>([^- ]+ - )?Revision \d+:', line):
# it's a subversion index page:
file.close()
os.unlink(filename)
return self._download_svn(url, filename)
- # Check for a SourceForge header
- elif sf_url:
- if re.search(r'^<HTML><HEAD>', line, re.I):
- continue # skip first line
- elif re.search(r'<TITLE>Select a Mirror for File:',line):
- # Sourceforge mirror page
- page = file.read()
- file.close()
- os.unlink(filename)
- return self._download_sourceforge(url, page, tmpdir)
break # not an index page
file.close()
+ os.unlink(filename)
raise DistutilsError("Unexpected HTML page found at "+url)
def _download_svn(self, url, filename):
url = url.split('#',1)[0] # remove any fragment for svn's sake
+ creds = ''
+ if url.lower().startswith('svn:') and '@' in url:
+ scheme, netloc, path, p, q, f = urlparse(url)
+ if not netloc and path.startswith('//') and '/' in path[2:]:
+ netloc, path = path[2:].split('/',1)
+ auth, host = splituser(netloc)
+ if auth:
+ if ':' in auth:
+ user, pw = auth.split(':',1)
+ creds = " --username=%s --password=%s" % (user, pw)
+ else:
+ creds = " --username="+auth
+ netloc = host
+ url = urlunparse((scheme, netloc, url, p, q, f))
self.info("Doing subversion checkout from %s to %s", url, filename)
- os.system("svn checkout -q %s %s" % (url, filename))
+ os.system("svn checkout%s -q %s %s" % (creds, url, filename))
+ return filename
+
+ @staticmethod
+ def _vcs_split_rev_from_url(url, pop_prefix=False):
+ scheme, netloc, path, query, frag = urlsplit(url)
+
+ scheme = scheme.split('+', 1)[-1]
+
+ # Some fragment identification fails
+ path = path.split('#',1)[0]
+
+ rev = None
+ if '@' in path:
+ path, rev = path.rsplit('@', 1)
+
+ # Also, discard fragment
+ url = urlunsplit((scheme, netloc, path, query, ''))
+
+ return url, rev
+
+ def _download_git(self, url, filename):
+ filename = filename.split('#',1)[0]
+ url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
+
+ self.info("Doing git clone from %s to %s", url, filename)
+ os.system("git clone --quiet %s %s" % (url, filename))
+
+ if rev is not None:
+ self.info("Checking out %s", rev)
+ os.system("(cd %s && git checkout --quiet %s)" % (
+ filename,
+ rev,
+ ))
+
+ return filename
+
+ def _download_hg(self, url, filename):
+ filename = filename.split('#',1)[0]
+ url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
+
+ self.info("Doing hg clone from %s to %s", url, filename)
+ os.system("hg clone --quiet %s %s" % (url, filename))
+
+ if rev is not None:
+ self.info("Updating to %s", rev)
+ os.system("(cd %s && hg up -C -r %s >&-)" % (
+ filename,
+ rev,
+ ))
+
return filename
def debug(self, msg, *args):
@@ -490,44 +869,190 @@ class PackageIndex(Environment):
def warn(self, msg, *args):
log.warn(msg, *args)
- def _download_sourceforge(self, source_url, sf_page, tmpdir):
- """Download package from randomly-selected SourceForge mirror"""
-
- self.debug("Processing SourceForge mirror page")
+# This pattern matches a character entity reference (a decimal numeric
+# references, a hexadecimal numeric reference, or a named reference).
+entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub
+
+def uchr(c):
+ if not isinstance(c, int):
+ return c
+ if c>255: return unichr(c)
+ return chr(c)
+
+def decode_entity(match):
+ what = match.group(1)
+ if what.startswith('#x'):
+ what = int(what[2:], 16)
+ elif what.startswith('#'):
+ what = int(what[1:])
+ else:
+ what = name2codepoint.get(what, match.group(0))
+ return uchr(what)
+
+def htmldecode(text):
+ """Decode HTML entities in the given text."""
+ return entity_sub(decode_entity, text)
+
+def socket_timeout(timeout=15):
+ def _socket_timeout(func):
+ def _socket_timeout(*args, **kwargs):
+ old_timeout = socket.getdefaulttimeout()
+ socket.setdefaulttimeout(timeout)
+ try:
+ return func(*args, **kwargs)
+ finally:
+ socket.setdefaulttimeout(old_timeout)
+ return _socket_timeout
+ return _socket_timeout
+
+def _encode_auth(auth):
+ """
+ A function compatible with Python 2.3-3.3 that will encode
+ auth from a URL suitable for an HTTP header.
+ >>> str(_encode_auth('username%3Apassword'))
+ 'dXNlcm5hbWU6cGFzc3dvcmQ='
+
+ Long auth strings should not cause a newline to be inserted.
+ >>> long_auth = 'username:' + 'password'*10
+ >>> chr(10) in str(_encode_auth(long_auth))
+ False
+ """
+ auth_s = unquote(auth)
+ # convert to bytes
+ auth_bytes = auth_s.encode()
+ # use the legacy interface for Python 2.3 support
+ encoded_bytes = base64.encodestring(auth_bytes)
+ # convert back to a string
+ encoded = encoded_bytes.decode()
+ # strip the trailing carriage return
+ return encoded.replace('\n','')
+
+class Credential(object):
+ """
+ A username/password pair. Use like a namedtuple.
+ """
+ def __init__(self, username, password):
+ self.username = username
+ self.password = password
- mirror_regex = re.compile(r'HREF=(/.*?\?use_mirror=[^>]*)')
- urls = [m.group(1) for m in mirror_regex.finditer(sf_page)]
- if not urls:
- raise DistutilsError(
- "URL looks like a Sourceforge mirror page, but no URLs found"
- )
+ def __iter__(self):
+ yield self.username
+ yield self.password
- import random
- url = urlparse.urljoin(source_url, random.choice(urls))
+ def __str__(self):
+ return '%(username)s:%(password)s' % vars(self)
- self.info(
- "Requesting redirect to (randomly selected) %r mirror",
- url.split('=',1)[-1]
- )
+class PyPIConfig(ConfigParser.ConfigParser):
- f = self.open_url(url)
- match = re.search(
- r'<META HTTP-EQUIV="refresh" content=".*?URL=(.*?)"',
- f.read()
+ def __init__(self):
+ """
+ Load from ~/.pypirc
+ """
+ defaults = dict.fromkeys(['username', 'password', 'repository'], '')
+ ConfigParser.ConfigParser.__init__(self, defaults)
+
+ rc = os.path.join(os.path.expanduser('~'), '.pypirc')
+ if os.path.exists(rc):
+ self.read(rc)
+
+ @property
+ def creds_by_repository(self):
+ sections_with_repositories = [
+ section for section in self.sections()
+ if self.get(section, 'repository').strip()
+ ]
+
+ return dict(map(self._get_repo_cred, sections_with_repositories))
+
+ def _get_repo_cred(self, section):
+ repo = self.get(section, 'repository').strip()
+ return repo, Credential(
+ self.get(section, 'username').strip(),
+ self.get(section, 'password').strip(),
)
- f.close()
- if match:
- download_url = match.group(1)
- scheme = URL_SCHEME(download_url)
- return self._download_url(scheme.group(1), download_url, tmpdir)
+ def find_credential(self, url):
+ """
+ If the URL indicated appears to be a repository defined in this
+ config, return the credential for that repository.
+ """
+ for repository, cred in self.creds_by_repository.items():
+ if url.startswith(repository):
+ return cred
+
+
+def open_with_auth(url, opener=urllib2.urlopen):
+ """Open a urllib2 request, handling HTTP authentication"""
+
+ scheme, netloc, path, params, query, frag = urlparse(url)
+
+ # Double scheme does not raise on Mac OS X as revealed by a
+ # failing test. We would expect "nonnumeric port". Refs #20.
+ if netloc.endswith(':'):
+ raise httplib.InvalidURL("nonnumeric port: ''")
+
+ if scheme in ('http', 'https'):
+ auth, host = splituser(netloc)
+ else:
+ auth = None
+
+ if not auth:
+ cred = PyPIConfig().find_credential(url)
+ if cred:
+ auth = str(cred)
+ info = cred.username, url
+ log.info('Authenticating as %s for %s (from .pypirc)' % info)
+
+ if auth:
+ auth = "Basic " + _encode_auth(auth)
+ new_url = urlunparse((scheme,host,path,params,query,frag))
+ request = urllib2.Request(new_url)
+ request.add_header("Authorization", auth)
+ else:
+ request = urllib2.Request(url)
+
+ request.add_header('User-Agent', user_agent)
+ fp = opener(request)
+
+ if auth:
+ # Put authentication info back into request URL if same host,
+ # so that links found on the page will work
+ s2, h2, path2, param2, query2, frag2 = urlparse(fp.url)
+ if s2==scheme and h2==host:
+ fp.url = urlunparse((s2,netloc,path2,param2,query2,frag2))
+
+ return fp
+
+# adding a timeout to avoid freezing package_index
+open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth)
+
+
+def fix_sf_url(url):
+ return url # backward compatibility
+
+def local_open(url):
+ """Read a local path, with special support for directories"""
+ scheme, server, path, param, query, frag = urlparse(url)
+ filename = url2pathname(path)
+ if os.path.isfile(filename):
+ return urllib2.urlopen(url)
+ elif path.endswith('/') and os.path.isdir(filename):
+ files = []
+ for f in os.listdir(filename):
+ if f=='index.html':
+ fp = open(os.path.join(filename,f),'r')
+ body = fp.read()
+ fp.close()
+ break
+ elif os.path.isdir(os.path.join(filename,f)):
+ f+='/'
+ files.append("<a href=%r>%s</a>" % (f,f))
else:
- raise DistutilsError(
- 'No META HTTP-EQUIV="refresh" found in Sourceforge page at %s'
- % url
- )
-
-
-
-
-
+ body = ("<html><head><title>%s</title>" % url) + \
+ "</head><body>%s</body></html>" % '\n'.join(files)
+ status, message = 200, "OK"
+ else:
+ status, message, body = 404, "Path not found", "Not found"
+
+ headers = {'content-type': 'text/html'}
+ return HTTPError(url, status, message, headers, StringIO(body))
diff --git a/setuptools/py26compat.py b/setuptools/py26compat.py
new file mode 100644
index 00000000..738b0cc4
--- /dev/null
+++ b/setuptools/py26compat.py
@@ -0,0 +1,19 @@
+"""
+Compatibility Support for Python 2.6 and earlier
+"""
+
+import sys
+
+from setuptools.compat import splittag
+
+def strip_fragment(url):
+ """
+ In `Python 8280 <http://bugs.python.org/issue8280>`_, Python 2.7 and
+ later was patched to disregard the fragment when making URL requests.
+ Do the same for Python 2.6 and earlier.
+ """
+ url, fragment = splittag(url)
+ return url
+
+if sys.version_info >= (2,7):
+ strip_fragment = lambda x: x
diff --git a/setuptools/py27compat.py b/setuptools/py27compat.py
new file mode 100644
index 00000000..9d2886db
--- /dev/null
+++ b/setuptools/py27compat.py
@@ -0,0 +1,15 @@
+"""
+Compatibility Support for Python 2.7 and earlier
+"""
+
+import sys
+
+def get_all_headers(message, key):
+ """
+ Given an HTTPMessage, return all headers matching a given key.
+ """
+ return message.get_all(key)
+
+if sys.version_info < (3,):
+ def get_all_headers(message, key):
+ return message.getheaders(key)
diff --git a/setuptools/py31compat.py b/setuptools/py31compat.py
new file mode 100644
index 00000000..dbb324b0
--- /dev/null
+++ b/setuptools/py31compat.py
@@ -0,0 +1,11 @@
+__all__ = ['get_config_vars', 'get_path']
+
+try:
+ # Python 2.7 or >=3.2
+ from sysconfig import get_config_vars, get_path
+except ImportError:
+ from distutils.sysconfig import get_config_vars, get_python_lib
+ def get_path(name):
+ if name not in ('platlib', 'purelib'):
+ raise ValueError("Name must be purelib or platlib")
+ return get_python_lib(name=='platlib')
diff --git a/setuptools/sandbox.py b/setuptools/sandbox.py
index dbc24ede..042c5958 100755
--- a/setuptools/sandbox.py
+++ b/setuptools/sandbox.py
@@ -1,14 +1,33 @@
-import os, sys, __builtin__, tempfile
-_os = sys.modules[os.name]
+import os
+import sys
+import tempfile
+import operator
+import functools
+import itertools
+import re
+
+import pkg_resources
+
+if os.name == "java":
+ import org.python.modules.posix.PosixModule as _os
+else:
+ _os = sys.modules[os.name]
+try:
+ _file = file
+except NameError:
+ _file = None
_open = open
from distutils.errors import DistutilsError
+from pkg_resources import working_set
+
+from setuptools.compat import builtins, execfile
+
__all__ = [
"AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup",
]
def run_setup(setup_script, args):
"""Run a distutils setup script, sandboxed in its directory"""
-
old_dir = os.getcwd()
save_argv = sys.argv[:]
save_path = sys.path[:]
@@ -16,29 +35,45 @@ def run_setup(setup_script, args):
temp_dir = os.path.join(setup_dir,'temp')
if not os.path.isdir(temp_dir): os.makedirs(temp_dir)
save_tmp = tempfile.tempdir
-
+ save_modules = sys.modules.copy()
+ pr_state = pkg_resources.__getstate__()
try:
tempfile.tempdir = temp_dir
os.chdir(setup_dir)
try:
sys.argv[:] = [setup_script]+list(args)
sys.path.insert(0, setup_dir)
+ # reset to include setup dir, w/clean callback list
+ working_set.__init__()
+ working_set.callbacks.append(lambda dist:dist.activate())
DirectorySandbox(setup_dir).run(
lambda: execfile(
"setup.py",
{'__file__':setup_script, '__name__':'__main__'}
)
)
- except SystemExit, v:
+ except SystemExit:
+ v = sys.exc_info()[1]
if v.args and v.args[0]:
raise
# Normal exit, just return
finally:
+ pkg_resources.__setstate__(pr_state)
+ sys.modules.update(save_modules)
+ # remove any modules imported within the sandbox
+ del_modules = [
+ mod_name for mod_name in sys.modules
+ if mod_name not in save_modules
+ # exclude any encodings modules. See #285
+ and not mod_name.startswith('encodings.')
+ ]
+ list(map(sys.modules.__delitem__, del_modules))
os.chdir(old_dir)
sys.path[:] = save_path
sys.argv[:] = save_argv
tempfile.tempdir = save_tmp
+
class AbstractSandbox:
"""Wrap 'os' module and 'open()' builtin for virtualizing setup scripts"""
@@ -58,15 +93,18 @@ class AbstractSandbox:
"""Run 'func' under os sandboxing"""
try:
self._copy(self)
- __builtin__.open = __builtin__.file = self._open
+ if _file:
+ builtins.file = self._file
+ builtins.open = self._open
self._active = True
return func()
finally:
self._active = False
- __builtin__.open = __builtin__.file = _open
+ if _file:
+ builtins.file = _file
+ builtins.open = _open
self._copy(_os)
-
def _mk_dual_path_wrapper(name):
original = getattr(_os,name)
def wrap(self,src,dst,*args,**kw):
@@ -75,11 +113,9 @@ class AbstractSandbox:
return original(src,dst,*args,**kw)
return wrap
-
for name in ["rename", "link", "symlink"]:
if hasattr(_os,name): locals()[name] = _mk_dual_path_wrapper(name)
-
def _mk_single_path_wrapper(name, original=None):
original = original or getattr(_os,name)
def wrap(self,path,*args,**kw):
@@ -88,7 +124,9 @@ class AbstractSandbox:
return original(path,*args,**kw)
return wrap
- _open = _mk_single_path_wrapper('file', _open)
+ if _file:
+ _file = _mk_single_path_wrapper('file', _file)
+ _open = _mk_single_path_wrapper('open', _open)
for name in [
"stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir",
"remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat",
@@ -96,7 +134,6 @@ class AbstractSandbox:
]:
if hasattr(_os,name): locals()[name] = _mk_single_path_wrapper(name)
-
def _mk_single_with_return(name):
original = getattr(_os,name)
def wrap(self,path,*args,**kw):
@@ -141,6 +178,19 @@ class AbstractSandbox:
)
+if hasattr(os, 'devnull'):
+ _EXCEPTIONS = [os.devnull,]
+else:
+ _EXCEPTIONS = []
+
+try:
+ from win32com.client.gencache import GetGeneratePath
+ _EXCEPTIONS.append(GetGeneratePath())
+ del GetGeneratePath
+except ImportError:
+ # it appears pywin32 is not installed, so no need to exclude.
+ pass
+
class DirectorySandbox(AbstractSandbox):
"""Restrict operations to a single subdirectory - pseudo-chroot"""
@@ -149,44 +199,85 @@ class DirectorySandbox(AbstractSandbox):
"utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam",
])
- def __init__(self,sandbox):
+ _exception_patterns = [
+ # Allow lib2to3 to attempt to save a pickled grammar object (#121)
+ '.*lib2to3.*\.pickle$',
+ ]
+ "exempt writing to paths that match the pattern"
+
+ def __init__(self, sandbox, exceptions=_EXCEPTIONS):
self._sandbox = os.path.normcase(os.path.realpath(sandbox))
self._prefix = os.path.join(self._sandbox,'')
+ self._exceptions = [
+ os.path.normcase(os.path.realpath(path))
+ for path in exceptions
+ ]
AbstractSandbox.__init__(self)
def _violation(self, operation, *args, **kw):
raise SandboxViolation(operation, args, kw)
+ if _file:
+ def _file(self, path, mode='r', *args, **kw):
+ if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
+ self._violation("file", path, mode, *args, **kw)
+ return _file(path,mode,*args,**kw)
+
def _open(self, path, mode='r', *args, **kw):
- if mode not in ('r', 'rt', 'rb', 'rU') and not self._ok(path):
+ if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
self._violation("open", path, mode, *args, **kw)
return _open(path,mode,*args,**kw)
def tmpnam(self):
self._violation("tmpnam")
- def _ok(self,path):
+ def _ok(self, path):
active = self._active
try:
self._active = False
realpath = os.path.normcase(os.path.realpath(path))
- if realpath==self._sandbox or realpath.startswith(self._prefix):
- return True
+ return (
+ self._exempted(realpath)
+ or realpath == self._sandbox
+ or realpath.startswith(self._prefix)
+ )
finally:
self._active = active
- def _remap_input(self,operation,path,*args,**kw):
+ def _exempted(self, filepath):
+ start_matches = (
+ filepath.startswith(exception)
+ for exception in self._exceptions
+ )
+ pattern_matches = (
+ re.match(pattern, filepath)
+ for pattern in self._exception_patterns
+ )
+ candidates = itertools.chain(start_matches, pattern_matches)
+ return any(candidates)
+
+ def _remap_input(self, operation, path, *args, **kw):
"""Called for path inputs"""
if operation in self.write_ops and not self._ok(path):
self._violation(operation, os.path.realpath(path), *args, **kw)
return path
- def _remap_pair(self,operation,src,dst,*args,**kw):
+ def _remap_pair(self, operation, src, dst, *args, **kw):
"""Called for path pairs like rename, link, and symlink operations"""
if not self._ok(src) or not self._ok(dst):
self._violation(operation, src, dst, *args, **kw)
return (src,dst)
+ def open(self, file, flags, mode=0x1FF, *args, **kw): # 0777
+ """Called for low-level os.open()"""
+ if flags & WRITE_FLAGS and not self._ok(file):
+ self._violation("os.open", file, flags, mode, *args, **kw)
+ return _os.open(file,flags,mode, *args, **kw)
+
+WRITE_FLAGS = functools.reduce(
+ operator.or_, [getattr(_os, a, 0) for a in
+ "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()]
+)
class SandboxViolation(DistutilsError):
"""A setup script attempted to modify the filesystem outside the sandbox"""
@@ -203,3 +294,29 @@ script by hand. Please inform the package's author and the EasyInstall
maintainers to find out if a fix or workaround is available.""" % self.args
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#
diff --git a/setuptools/script template (dev).py b/setuptools/script template (dev).py
new file mode 100644
index 00000000..b3fe209e
--- /dev/null
+++ b/setuptools/script template (dev).py
@@ -0,0 +1,11 @@
+# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r
+__requires__ = """%(spec)r"""
+import sys
+from pkg_resources import require
+require("""%(spec)r""")
+del require
+__file__ = """%(dev_path)r"""
+if sys.version_info < (3, 0):
+ execfile(__file__)
+else:
+ exec(compile(open(__file__).read(), __file__, 'exec'))
diff --git a/setuptools/script template.py b/setuptools/script template.py
new file mode 100644
index 00000000..8dd5d510
--- /dev/null
+++ b/setuptools/script template.py
@@ -0,0 +1,4 @@
+# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r
+__requires__ = """%(spec)r"""
+import pkg_resources
+pkg_resources.run_script("""%(spec)r""", """%(script_name)r""")
diff --git a/setuptools/site-patch.py b/setuptools/site-patch.py
new file mode 100644
index 00000000..c2168019
--- /dev/null
+++ b/setuptools/site-patch.py
@@ -0,0 +1,76 @@
+def __boot():
+ import sys
+ import os
+ PYTHONPATH = os.environ.get('PYTHONPATH')
+ if PYTHONPATH is None or (sys.platform=='win32' and not PYTHONPATH):
+ PYTHONPATH = []
+ else:
+ PYTHONPATH = PYTHONPATH.split(os.pathsep)
+
+ pic = getattr(sys,'path_importer_cache',{})
+ stdpath = sys.path[len(PYTHONPATH):]
+ mydir = os.path.dirname(__file__)
+ #print "searching",stdpath,sys.path
+
+ for item in stdpath:
+ if item==mydir or not item:
+ continue # skip if current dir. on Windows, or my own directory
+ importer = pic.get(item)
+ if importer is not None:
+ loader = importer.find_module('site')
+ if loader is not None:
+ # This should actually reload the current module
+ loader.load_module('site')
+ break
+ else:
+ try:
+ import imp # Avoid import loop in Python >= 3.3
+ stream, path, descr = imp.find_module('site',[item])
+ except ImportError:
+ continue
+ if stream is None:
+ continue
+ try:
+ # This should actually reload the current module
+ imp.load_module('site',stream,path,descr)
+ finally:
+ stream.close()
+ break
+ else:
+ raise ImportError("Couldn't find the real 'site' module")
+
+ #print "loaded", __file__
+
+ known_paths = dict([(makepath(item)[1],1) for item in sys.path]) # 2.2 comp
+
+ oldpos = getattr(sys,'__egginsert',0) # save old insertion position
+ sys.__egginsert = 0 # and reset the current one
+
+ for item in PYTHONPATH:
+ addsitedir(item)
+
+ sys.__egginsert += oldpos # restore effective old position
+
+ d, nd = makepath(stdpath[0])
+ insert_at = None
+ new_path = []
+
+ for item in sys.path:
+ p, np = makepath(item)
+
+ if np==nd and insert_at is None:
+ # We've hit the first 'system' path entry, so added entries go here
+ insert_at = len(new_path)
+
+ if np in known_paths or insert_at is None:
+ new_path.append(item)
+ else:
+ # new path after the insert point, back-insert it
+ new_path.insert(insert_at, item)
+ insert_at += 1
+
+ sys.path[:] = new_path
+
+if __name__=='site':
+ __boot()
+ del __boot
diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py
new file mode 100644
index 00000000..7b5f429f
--- /dev/null
+++ b/setuptools/ssl_support.py
@@ -0,0 +1,234 @@
+import os
+import socket
+import atexit
+import re
+
+import pkg_resources
+from pkg_resources import ResolutionError, ExtractionError
+from setuptools.compat import urllib2
+
+try:
+ import ssl
+except ImportError:
+ ssl = None
+
+__all__ = [
+ 'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths',
+ 'opener_for'
+]
+
+cert_paths = """
+/etc/pki/tls/certs/ca-bundle.crt
+/etc/ssl/certs/ca-certificates.crt
+/usr/share/ssl/certs/ca-bundle.crt
+/usr/local/share/certs/ca-root.crt
+/etc/ssl/cert.pem
+/System/Library/OpenSSL/certs/cert.pem
+""".strip().split()
+
+
+HTTPSHandler = HTTPSConnection = object
+
+for what, where in (
+ ('HTTPSHandler', ['urllib2','urllib.request']),
+ ('HTTPSConnection', ['httplib', 'http.client']),
+):
+ for module in where:
+ try:
+ exec("from %s import %s" % (module, what))
+ except ImportError:
+ pass
+
+is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection)
+
+
+try:
+ from ssl import CertificateError, match_hostname
+except ImportError:
+ try:
+ from backports.ssl_match_hostname import CertificateError
+ from backports.ssl_match_hostname import match_hostname
+ except ImportError:
+ CertificateError = None
+ match_hostname = None
+
+if not CertificateError:
+ class CertificateError(ValueError):
+ pass
+
+if not match_hostname:
+ def _dnsname_match(dn, hostname, max_wildcards=1):
+ """Matching according to RFC 6125, section 6.4.3
+
+ http://tools.ietf.org/html/rfc6125#section-6.4.3
+ """
+ pats = []
+ if not dn:
+ return False
+
+ # Ported from python3-syntax:
+ # leftmost, *remainder = dn.split(r'.')
+ parts = dn.split(r'.')
+ leftmost = parts[0]
+ remainder = parts[1:]
+
+ wildcards = leftmost.count('*')
+ if wildcards > max_wildcards:
+ # Issue #17980: avoid denials of service by refusing more
+ # than one wildcard per fragment. A survey of established
+ # policy among SSL implementations showed it to be a
+ # reasonable choice.
+ raise CertificateError(
+ "too many wildcards in certificate DNS name: " + repr(dn))
+
+ # speed up common case w/o wildcards
+ if not wildcards:
+ return dn.lower() == hostname.lower()
+
+ # RFC 6125, section 6.4.3, subitem 1.
+ # The client SHOULD NOT attempt to match a presented identifier in which
+ # the wildcard character comprises a label other than the left-most label.
+ if leftmost == '*':
+ # When '*' is a fragment by itself, it matches a non-empty dotless
+ # fragment.
+ pats.append('[^.]+')
+ elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
+ # RFC 6125, section 6.4.3, subitem 3.
+ # The client SHOULD NOT attempt to match a presented identifier
+ # where the wildcard character is embedded within an A-label or
+ # U-label of an internationalized domain name.
+ pats.append(re.escape(leftmost))
+ else:
+ # Otherwise, '*' matches any dotless string, e.g. www*
+ pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
+
+ # add the remaining fragments, ignore any wildcards
+ for frag in remainder:
+ pats.append(re.escape(frag))
+
+ pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
+ return pat.match(hostname)
+
+ def match_hostname(cert, hostname):
+ """Verify that *cert* (in decoded format as returned by
+ SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
+ rules are followed, but IP addresses are not accepted for *hostname*.
+
+ CertificateError is raised on failure. On success, the function
+ returns nothing.
+ """
+ if not cert:
+ raise ValueError("empty or no certificate")
+ dnsnames = []
+ san = cert.get('subjectAltName', ())
+ for key, value in san:
+ if key == 'DNS':
+ if _dnsname_match(value, hostname):
+ return
+ dnsnames.append(value)
+ if not dnsnames:
+ # The subject is only checked when there is no dNSName entry
+ # in subjectAltName
+ for sub in cert.get('subject', ()):
+ for key, value in sub:
+ # XXX according to RFC 2818, the most specific Common Name
+ # must be used.
+ if key == 'commonName':
+ if _dnsname_match(value, hostname):
+ return
+ dnsnames.append(value)
+ if len(dnsnames) > 1:
+ raise CertificateError("hostname %r "
+ "doesn't match either of %s"
+ % (hostname, ', '.join(map(repr, dnsnames))))
+ elif len(dnsnames) == 1:
+ raise CertificateError("hostname %r "
+ "doesn't match %r"
+ % (hostname, dnsnames[0]))
+ else:
+ raise CertificateError("no appropriate commonName or "
+ "subjectAltName fields were found")
+
+
+class VerifyingHTTPSHandler(HTTPSHandler):
+ """Simple verifying handler: no auth, subclasses, timeouts, etc."""
+
+ def __init__(self, ca_bundle):
+ self.ca_bundle = ca_bundle
+ HTTPSHandler.__init__(self)
+
+ def https_open(self, req):
+ return self.do_open(
+ lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req
+ )
+
+
+class VerifyingHTTPSConn(HTTPSConnection):
+ """Simple verifying connection: no auth, subclasses, timeouts, etc."""
+ def __init__(self, host, ca_bundle, **kw):
+ HTTPSConnection.__init__(self, host, **kw)
+ self.ca_bundle = ca_bundle
+
+ def connect(self):
+ sock = socket.create_connection(
+ (self.host, self.port), getattr(self, 'source_address', None)
+ )
+
+ # Handle the socket if a (proxy) tunnel is present
+ if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None):
+ self.sock = sock
+ self._tunnel()
+
+ self.sock = ssl.wrap_socket(
+ sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
+ )
+ try:
+ match_hostname(self.sock.getpeercert(), self.host)
+ except CertificateError:
+ self.sock.shutdown(socket.SHUT_RDWR)
+ self.sock.close()
+ raise
+
+def opener_for(ca_bundle=None):
+ """Get a urlopen() replacement that uses ca_bundle for verification"""
+ return urllib2.build_opener(
+ VerifyingHTTPSHandler(ca_bundle or find_ca_bundle())
+ ).open
+
+
+_wincerts = None
+
+def get_win_certfile():
+ global _wincerts
+ if _wincerts is not None:
+ return _wincerts.name
+
+ try:
+ from wincertstore import CertFile
+ except ImportError:
+ return None
+
+ class MyCertFile(CertFile):
+ def __init__(self, stores=(), certs=()):
+ CertFile.__init__(self)
+ for store in stores:
+ self.addstore(store)
+ self.addcerts(certs)
+ atexit.register(self.close)
+
+ _wincerts = MyCertFile(stores=['CA', 'ROOT'])
+ return _wincerts.name
+
+
+def find_ca_bundle():
+ """Return an existing CA bundle path, or None"""
+ if os.name=='nt':
+ return get_win_certfile()
+ else:
+ for cert_path in cert_paths:
+ if os.path.isfile(cert_path):
+ return cert_path
+ try:
+ return pkg_resources.resource_filename('certifi', 'cacert.pem')
+ except (ImportError, ResolutionError, ExtractionError):
+ return None
diff --git a/setuptools/svn_utils.py b/setuptools/svn_utils.py
new file mode 100644
index 00000000..a9bdc5c3
--- /dev/null
+++ b/setuptools/svn_utils.py
@@ -0,0 +1,564 @@
+import os
+import re
+import sys
+from distutils import log
+import xml.dom.pulldom
+import shlex
+import locale
+import codecs
+import unicodedata
+import warnings
+from setuptools.compat import unicode
+from xml.sax.saxutils import unescape
+
+try:
+ import urlparse
+except ImportError:
+ import urllib.parse as urlparse
+
+from subprocess import Popen as _Popen, PIPE as _PIPE
+
+#NOTE: Use of the command line options require SVN 1.3 or newer (December 2005)
+# and SVN 1.3 hasn't been supported by the developers since mid 2008.
+
+#subprocess is called several times with shell=(sys.platform=='win32')
+#see the follow for more information:
+# http://bugs.python.org/issue8557
+# http://stackoverflow.com/questions/5658622/
+# python-subprocess-popen-environment-path
+
+
+def _run_command(args, stdout=_PIPE, stderr=_PIPE, encoding=None, stream=0):
+ #regarding the shell argument, see: http://bugs.python.org/issue8557
+ try:
+ proc = _Popen(args, stdout=stdout, stderr=stderr,
+ shell=(sys.platform == 'win32'))
+
+ data = proc.communicate()[stream]
+ except OSError:
+ return 1, ''
+
+ #doubled checked and
+ data = decode_as_string(data, encoding)
+
+ #communciate calls wait()
+ return proc.returncode, data
+
+
+def _get_entry_schedule(entry):
+ schedule = entry.getElementsByTagName('schedule')[0]
+ return "".join([t.nodeValue
+ for t in schedule.childNodes
+ if t.nodeType == t.TEXT_NODE])
+
+
+def _get_target_property(target):
+ property_text = target.getElementsByTagName('property')[0]
+ return "".join([t.nodeValue
+ for t in property_text.childNodes
+ if t.nodeType == t.TEXT_NODE])
+
+
+def _get_xml_data(decoded_str):
+ if sys.version_info < (3, 0):
+ #old versions want an encoded string
+ data = decoded_str.encode('utf-8')
+ else:
+ data = decoded_str
+ return data
+
+
+def joinpath(prefix, *suffix):
+ if not prefix or prefix == '.':
+ return os.path.join(*suffix)
+ return os.path.join(prefix, *suffix)
+
+def determine_console_encoding():
+ try:
+ #try for the preferred encoding
+ encoding = locale.getpreferredencoding()
+
+ #see if the locale.getdefaultlocale returns null
+ #some versions of python\platforms return US-ASCII
+ #when it cannot determine an encoding
+ if not encoding or encoding == "US-ASCII":
+ encoding = locale.getdefaultlocale()[1]
+
+ if encoding:
+ codecs.lookup(encoding) # make sure a lookup error is not made
+
+ except (locale.Error, LookupError):
+ encoding = None
+
+ is_osx = sys.platform == "darwin"
+ if not encoding:
+ return ["US-ASCII", "utf-8"][is_osx]
+ elif encoding.startswith("mac-") and is_osx:
+ #certain versions of python would return mac-roman as default
+ #OSX as a left over of earlier mac versions.
+ return "utf-8"
+ else:
+ return encoding
+
+_console_encoding = determine_console_encoding()
+
+def decode_as_string(text, encoding=None):
+ """
+ Decode the console or file output explicitly using getpreferredencoding.
+ The text paraemeter should be a encoded string, if not no decode occurs
+ If no encoding is given, getpreferredencoding is used. If encoding is
+ specified, that is used instead. This would be needed for SVN --xml
+ output. Unicode is explicitly put in composed NFC form.
+
+ --xml should be UTF-8 (SVN Issue 2938) the discussion on the Subversion
+ DEV List from 2007 seems to indicate the same.
+ """
+ #text should be a byte string
+
+ if encoding is None:
+ encoding = _console_encoding
+
+ if not isinstance(text, unicode):
+ text = text.decode(encoding)
+
+ text = unicodedata.normalize('NFC', text)
+
+ return text
+
+
+def parse_dir_entries(decoded_str):
+ '''Parse the entries from a recursive info xml'''
+ doc = xml.dom.pulldom.parseString(_get_xml_data(decoded_str))
+ entries = list()
+
+ for event, node in doc:
+ if event == 'START_ELEMENT' and node.nodeName == 'entry':
+ doc.expandNode(node)
+ if not _get_entry_schedule(node).startswith('delete'):
+ entries.append((node.getAttribute('path'),
+ node.getAttribute('kind')))
+
+ return entries[1:] # do not want the root directory
+
+
+def parse_externals_xml(decoded_str, prefix=''):
+ '''Parse a propget svn:externals xml'''
+ prefix = os.path.normpath(prefix)
+ prefix = os.path.normcase(prefix)
+
+ doc = xml.dom.pulldom.parseString(_get_xml_data(decoded_str))
+ externals = list()
+
+ for event, node in doc:
+ if event == 'START_ELEMENT' and node.nodeName == 'target':
+ doc.expandNode(node)
+ path = os.path.normpath(node.getAttribute('path'))
+
+ if os.path.normcase(path).startswith(prefix):
+ path = path[len(prefix)+1:]
+
+ data = _get_target_property(node)
+ #data should be decoded already
+ for external in parse_external_prop(data):
+ externals.append(joinpath(path, external))
+
+ return externals # do not want the root directory
+
+
+def parse_external_prop(lines):
+ """
+ Parse the value of a retrieved svn:externals entry.
+
+ possible token setups (with quotng and backscaping in laters versions)
+ URL[@#] EXT_FOLDERNAME
+ [-r#] URL EXT_FOLDERNAME
+ EXT_FOLDERNAME [-r#] URL
+ """
+ externals = []
+ for line in lines.splitlines():
+ line = line.lstrip() # there might be a "\ "
+ if not line:
+ continue
+
+ if sys.version_info < (3, 0):
+ #shlex handles NULLs just fine and shlex in 2.7 tries to encode
+ #as ascii automatiically
+ line = line.encode('utf-8')
+ line = shlex.split(line)
+ if sys.version_info < (3, 0):
+ line = [x.decode('utf-8') for x in line]
+
+ #EXT_FOLDERNAME is either the first or last depending on where
+ #the URL falls
+ if urlparse.urlsplit(line[-1])[0]:
+ external = line[0]
+ else:
+ external = line[-1]
+
+ external = decode_as_string(external, encoding="utf-8")
+ externals.append(os.path.normpath(external))
+
+ return externals
+
+
+def parse_prop_file(filename, key):
+ found = False
+ f = open(filename, 'rt')
+ data = ''
+ try:
+ 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 == key:
+ found = True
+ elif kind == 'V' and found:
+ break
+ finally:
+ f.close()
+
+ return data
+
+
+class SvnInfo(object):
+ '''
+ Generic svn_info object. No has little knowledge of how to extract
+ information. Use cls.load to instatiate according svn version.
+
+ Paths are not filesystem encoded.
+ '''
+
+ @staticmethod
+ def get_svn_version():
+ code, data = _run_command(['svn', '--version', '--quiet'])
+ if code == 0 and data:
+ return data.strip()
+ else:
+ return ''
+
+ #svnversion return values (previous implementations return max revision)
+ # 4123:4168 mixed revision working copy
+ # 4168M modified working copy
+ # 4123S switched working copy
+ # 4123:4168MS mixed revision, modified, switched working copy
+ revision_re = re.compile(r'(?:([\-0-9]+):)?(\d+)([a-z]*)\s*$', re.I)
+
+ @classmethod
+ def load(cls, dirname=''):
+ normdir = os.path.normpath(dirname)
+ code, data = _run_command(['svn', 'info', normdir])
+ # Must check for some contents, as some use empty directories
+ # in testcases
+ svn_dir = os.path.join(normdir, '.svn')
+ has_svn = (os.path.isfile(os.path.join(svn_dir, 'entries')) or
+ os.path.isfile(os.path.join(svn_dir, 'dir-props')) or
+ os.path.isfile(os.path.join(svn_dir, 'dir-prop-base')))
+
+ svn_version = tuple(cls.get_svn_version().split('.'))
+
+ try:
+ base_svn_version = tuple(int(x) for x in svn_version[:2])
+ except ValueError:
+ base_svn_version = tuple()
+
+ if not has_svn:
+ return SvnInfo(dirname)
+
+ if code or not base_svn_version or base_svn_version < (1, 3):
+ warnings.warn(("No SVN 1.3+ command found: falling back "
+ "on pre 1.7 .svn parsing"), DeprecationWarning)
+ return SvnFileInfo(dirname)
+
+ if base_svn_version < (1, 5):
+ return Svn13Info(dirname)
+
+ return Svn15Info(dirname)
+
+ def __init__(self, path=''):
+ self.path = path
+ self._entries = None
+ self._externals = None
+
+ def get_revision(self):
+ 'Retrieve the directory revision informatino using svnversion'
+ code, data = _run_command(['svnversion', '-c', self.path])
+ if code:
+ log.warn("svnversion failed")
+ return 0
+
+ parsed = self.revision_re.match(data)
+ if parsed:
+ return int(parsed.group(2))
+ else:
+ return 0
+
+ @property
+ def entries(self):
+ if self._entries is None:
+ self._entries = self.get_entries()
+ return self._entries
+
+ @property
+ def externals(self):
+ if self._externals is None:
+ self._externals = self.get_externals()
+ return self._externals
+
+ def iter_externals(self):
+ '''
+ Iterate over the svn:external references in the repository path.
+ '''
+ for item in self.externals:
+ yield item
+
+ def iter_files(self):
+ '''
+ Iterate over the non-deleted file entries in the repository path
+ '''
+ for item, kind in self.entries:
+ if kind.lower() == 'file':
+ yield item
+
+ def iter_dirs(self, include_root=True):
+ '''
+ Iterate over the non-deleted file entries in the repository path
+ '''
+ if include_root:
+ yield self.path
+ for item, kind in self.entries:
+ if kind.lower() == 'dir':
+ yield item
+
+ def get_entries(self):
+ return []
+
+ def get_externals(self):
+ return []
+
+
+class Svn13Info(SvnInfo):
+ def get_entries(self):
+ code, data = _run_command(['svn', 'info', '-R', '--xml', self.path],
+ encoding="utf-8")
+
+ if code:
+ log.debug("svn info failed")
+ return []
+
+ return parse_dir_entries(data)
+
+ def get_externals(self):
+ #Previous to 1.5 --xml was not supported for svn propget and the -R
+ #output format breaks the shlex compatible semantics.
+ cmd = ['svn', 'propget', 'svn:externals']
+ result = []
+ for folder in self.iter_dirs():
+ code, lines = _run_command(cmd + [folder], encoding="utf-8")
+ if code != 0:
+ log.warn("svn propget failed")
+ return []
+ #lines should a str
+ for external in parse_external_prop(lines):
+ if folder:
+ external = os.path.join(folder, external)
+ result.append(os.path.normpath(external))
+
+ return result
+
+
+class Svn15Info(Svn13Info):
+ def get_externals(self):
+ cmd = ['svn', 'propget', 'svn:externals', self.path, '-R', '--xml']
+ code, lines = _run_command(cmd, encoding="utf-8")
+ if code:
+ log.debug("svn propget failed")
+ return []
+ return parse_externals_xml(lines, prefix=os.path.abspath(self.path))
+
+
+class SvnFileInfo(SvnInfo):
+
+ def __init__(self, path=''):
+ super(SvnFileInfo, self).__init__(path)
+ self._directories = None
+ self._revision = None
+
+ def _walk_svn(self, base):
+ entry_file = joinpath(base, '.svn', 'entries')
+ if os.path.isfile(entry_file):
+ entries = SVNEntriesFile.load(base)
+ yield (base, False, entries.parse_revision())
+ for path in entries.get_undeleted_records():
+ path = decode_as_string(path)
+ path = joinpath(base, path)
+ if os.path.isfile(path):
+ yield (path, True, None)
+ elif os.path.isdir(path):
+ for item in self._walk_svn(path):
+ yield item
+
+ def _build_entries(self):
+ entries = list()
+
+ rev = 0
+ for path, isfile, dir_rev in self._walk_svn(self.path):
+ if isfile:
+ entries.append((path, 'file'))
+ else:
+ entries.append((path, 'dir'))
+ rev = max(rev, dir_rev)
+
+ self._entries = entries
+ self._revision = rev
+
+ def get_entries(self):
+ if self._entries is None:
+ self._build_entries()
+ return self._entries
+
+ def get_revision(self):
+ if self._revision is None:
+ self._build_entries()
+ return self._revision
+
+ def get_externals(self):
+ prop_files = [['.svn', 'dir-prop-base'],
+ ['.svn', 'dir-props']]
+ externals = []
+
+ for dirname in self.iter_dirs():
+ prop_file = None
+ for rel_parts in prop_files:
+ filename = joinpath(dirname, *rel_parts)
+ if os.path.isfile(filename):
+ prop_file = filename
+
+ if prop_file is not None:
+ ext_prop = parse_prop_file(prop_file, 'svn:externals')
+ #ext_prop should be utf-8 coming from svn:externals
+ ext_prop = decode_as_string(ext_prop, encoding="utf-8")
+ externals.extend(parse_external_prop(ext_prop))
+
+ return externals
+
+
+def svn_finder(dirname=''):
+ #combined externals due to common interface
+ #combined externals and entries due to lack of dir_props in 1.7
+ info = SvnInfo.load(dirname)
+ for path in info.iter_files():
+ yield path
+
+ for path in info.iter_externals():
+ sub_info = SvnInfo.load(path)
+ for sub_path in sub_info.iter_files():
+ yield sub_path
+
+
+class SVNEntriesFile(object):
+ def __init__(self, data):
+ self.data = data
+
+ @classmethod
+ def load(class_, base):
+ filename = os.path.join(base, '.svn', 'entries')
+ f = open(filename)
+ try:
+ result = SVNEntriesFile.read(f)
+ finally:
+ f.close()
+ return result
+
+ @classmethod
+ def read(class_, fileobj):
+ data = fileobj.read()
+ is_xml = data.startswith('<?xml')
+ class_ = [SVNEntriesFileText, SVNEntriesFileXML][is_xml]
+ return class_(data)
+
+ def parse_revision(self):
+ all_revs = self.parse_revision_numbers() + [0]
+ return max(all_revs)
+
+
+class SVNEntriesFileText(SVNEntriesFile):
+ known_svn_versions = {
+ '1.4.x': 8,
+ '1.5.x': 9,
+ '1.6.x': 10,
+ }
+
+ def __get_cached_sections(self):
+ return self.sections
+
+ def get_sections(self):
+ SECTION_DIVIDER = '\f\n'
+ sections = self.data.split(SECTION_DIVIDER)
+ sections = [x for x in map(str.splitlines, sections)]
+ try:
+ # remove the SVN version number from the first line
+ svn_version = int(sections[0].pop(0))
+ if not svn_version in self.known_svn_versions.values():
+ log.warn("Unknown subversion verson %d", svn_version)
+ except ValueError:
+ return
+ self.sections = sections
+ self.get_sections = self.__get_cached_sections
+ return self.sections
+
+ def is_valid(self):
+ return bool(self.get_sections())
+
+ def get_url(self):
+ return self.get_sections()[0][4]
+
+ def parse_revision_numbers(self):
+ revision_line_number = 9
+ rev_numbers = [
+ int(section[revision_line_number])
+ for section in self.get_sections()
+ if (len(section) > revision_line_number
+ and section[revision_line_number])
+ ]
+ return rev_numbers
+
+ def get_undeleted_records(self):
+ undeleted = lambda s: s and s[0] and (len(s) < 6 or s[5] != 'delete')
+ result = [
+ section[0]
+ for section in self.get_sections()
+ if undeleted(section)
+ ]
+ return result
+
+
+class SVNEntriesFileXML(SVNEntriesFile):
+ def is_valid(self):
+ return True
+
+ def get_url(self):
+ "Get repository URL"
+ urlre = re.compile('url="([^"]+)"')
+ return urlre.search(self.data).group(1)
+
+ def parse_revision_numbers(self):
+ revre = re.compile(r'committed-rev="(\d+)"')
+ return [
+ int(m.group(1))
+ for m in revre.finditer(self.data)
+ ]
+
+ def get_undeleted_records(self):
+ entries_pattern = \
+ re.compile(r'name="([^"]+)"(?![^>]+deleted="true")', re.I)
+ results = [
+ unescape(match.group(1))
+ for match in entries_pattern.finditer(self.data)
+ ]
+ return results
+
+
+if __name__ == '__main__':
+ for name in svn_finder(sys.argv[1]):
+ print(name)
diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py
index 7009b321..a8eb7110 100644
--- a/setuptools/tests/__init__.py
+++ b/setuptools/tests/__init__.py
@@ -1,18 +1,32 @@
"""Tests for the 'setuptools' package"""
-
-from unittest import TestSuite, TestCase, makeSuite, defaultTestLoader
-import distutils.core, distutils.cmd
+import sys
+import os
+import unittest
+from setuptools.tests import doctest
+import distutils.core
+import distutils.cmd
from distutils.errors import DistutilsOptionError, DistutilsPlatformError
from distutils.errors import DistutilsSetupError
-import setuptools, setuptools.dist
-from setuptools import Feature
from distutils.core import Extension
-from setuptools.depends import extract_constant, get_module_constant
-from setuptools.depends import find_module, Require
-from distutils.version import StrictVersion, LooseVersion
-from distutils.util import convert_path
-import sys, os.path
-
+from distutils.version import LooseVersion
+from setuptools.compat import func_code
+
+from setuptools.compat import func_code
+import setuptools.dist
+import setuptools.depends as dep
+from setuptools.depends import Require
+
+def additional_tests():
+ import doctest, unittest
+ suite = unittest.TestSuite((
+ doctest.DocFileSuite(
+ os.path.join('tests', 'api_tests.txt'),
+ optionflags=doctest.ELLIPSIS, package='pkg_resources',
+ ),
+ ))
+ if sys.platform == 'win32':
+ suite.addTest(doctest.DocFileSuite('win_script_wrapper.txt'))
+ return suite
def makeSetup(**args):
"""Return distribution from 'setup(**args)', without executing commands"""
@@ -25,88 +39,86 @@ def makeSetup(**args):
try:
return setuptools.setup(**args)
finally:
- distutils.core_setup_stop_after = None
-
-
-
-
-
-
-
-
-
-
+ distutils.core._setup_stop_after = None
-
-class DependsTests(TestCase):
+class DependsTests(unittest.TestCase):
def testExtractConst(self):
-
- from setuptools.depends import extract_constant
+ if not hasattr(dep, 'extract_constant'):
+ # skip on non-bytecode platforms
+ return
def f1():
- global x,y,z
+ global x, y, z
x = "test"
y = z
+ fc = func_code(f1)
# unrecognized name
- self.assertEqual(extract_constant(f1.func_code,'q', -1), None)
+ self.assertEqual(dep.extract_constant(fc,'q', -1), None)
# constant assigned
- self.assertEqual(extract_constant(f1.func_code,'x', -1), "test")
+ self.assertEqual(dep.extract_constant(fc,'x', -1), "test")
# expression assigned
- self.assertEqual(extract_constant(f1.func_code,'y', -1), -1)
+ self.assertEqual(dep.extract_constant(fc,'y', -1), -1)
# recognized name, not assigned
- self.assertEqual(extract_constant(f1.func_code,'z', -1), None)
-
+ self.assertEqual(dep.extract_constant(fc,'z', -1), None)
def testFindModule(self):
- self.assertRaises(ImportError, find_module, 'no-such.-thing')
- self.assertRaises(ImportError, find_module, 'setuptools.non-existent')
- f,p,i = find_module('setuptools.tests'); f.close()
+ self.assertRaises(ImportError, dep.find_module, 'no-such.-thing')
+ self.assertRaises(ImportError, dep.find_module, 'setuptools.non-existent')
+ f,p,i = dep.find_module('setuptools.tests')
+ f.close()
def testModuleExtract(self):
- from distutils import __version__
+ if not hasattr(dep, 'get_module_constant'):
+ # skip on non-bytecode platforms
+ return
+
+ from email import __version__
self.assertEqual(
- get_module_constant('distutils','__version__'), __version__
+ dep.get_module_constant('email','__version__'), __version__
)
self.assertEqual(
- get_module_constant('sys','version'), sys.version
+ dep.get_module_constant('sys','version'), sys.version
)
self.assertEqual(
- get_module_constant('setuptools.tests','__doc__'),__doc__
+ dep.get_module_constant('setuptools.tests','__doc__'),__doc__
)
def testRequire(self):
+ if not hasattr(dep, 'extract_constant'):
+ # skip on non-bytecode platformsh
+ return
- req = Require('Distutils','1.0.3','distutils')
+ req = Require('Email','1.0.3','email')
- self.assertEqual(req.name, 'Distutils')
- self.assertEqual(req.module, 'distutils')
+ self.assertEqual(req.name, 'Email')
+ self.assertEqual(req.module, 'email')
self.assertEqual(req.requested_version, '1.0.3')
self.assertEqual(req.attribute, '__version__')
- self.assertEqual(req.full_name(), 'Distutils-1.0.3')
+ self.assertEqual(req.full_name(), 'Email-1.0.3')
- from distutils import __version__
+ from email import __version__
self.assertEqual(req.get_version(), __version__)
- self.failUnless(req.version_ok('1.0.9'))
- self.failIf(req.version_ok('0.9.1'))
- self.failIf(req.version_ok('unknown'))
+ self.assertTrue(req.version_ok('1.0.9'))
+ self.assertTrue(not req.version_ok('0.9.1'))
+ self.assertTrue(not req.version_ok('unknown'))
- self.failUnless(req.is_present())
- self.failUnless(req.is_current())
+ self.assertTrue(req.is_present())
+ self.assertTrue(req.is_current())
- req = Require('Distutils 3000','03000','distutils',format=LooseVersion)
- self.failUnless(req.is_present())
- self.failIf(req.is_current())
- self.failIf(req.version_ok('unknown'))
+ req = Require('Email 3000','03000','email',format=LooseVersion)
+ self.assertTrue(req.is_present())
+ self.assertTrue(not req.is_current())
+ self.assertTrue(not req.version_ok('unknown'))
req = Require('Do-what-I-mean','1.0','d-w-i-m')
- self.failIf(req.is_present())
- self.failIf(req.is_current())
+ self.assertTrue(not req.is_present())
+ self.assertTrue(not req.is_current())
req = Require('Tests', None, 'tests', homepage="http://example.com")
self.assertEqual(req.format, None)
@@ -116,12 +128,11 @@ class DependsTests(TestCase):
self.assertEqual(req.homepage, 'http://example.com')
paths = [os.path.dirname(p) for p in __path__]
- self.failUnless(req.is_present(paths))
- self.failUnless(req.is_current(paths))
-
+ self.assertTrue(req.is_present(paths))
+ self.assertTrue(req.is_current(paths))
-class DistroTests(TestCase):
+class DistroTests(unittest.TestCase):
def setUp(self):
self.e1 = Extension('bar.ext',['bar.c'])
@@ -134,10 +145,8 @@ class DistroTests(TestCase):
package_dir = {},
)
-
def testDistroType(self):
- self.failUnless(isinstance(self.dist,setuptools.dist.Distribution))
-
+ self.assertTrue(isinstance(self.dist,setuptools.dist.Distribution))
def testExcludePackage(self):
self.dist.exclude_package('a')
@@ -156,12 +165,6 @@ class DistroTests(TestCase):
# test removals from unspecified options
makeSetup().exclude_package('x')
-
-
-
-
-
-
def testIncludeExclude(self):
# remove an extension
self.dist.exclude(ext_modules=[self.e1])
@@ -188,20 +191,17 @@ class DistroTests(TestCase):
dist.exclude(packages=['a'], py_modules=['b'], ext_modules=[self.e2])
def testContents(self):
- self.failUnless(self.dist.has_contents_for('a'))
+ self.assertTrue(self.dist.has_contents_for('a'))
self.dist.exclude_package('a')
- self.failIf(self.dist.has_contents_for('a'))
+ self.assertTrue(not self.dist.has_contents_for('a'))
- self.failUnless(self.dist.has_contents_for('b'))
+ self.assertTrue(self.dist.has_contents_for('b'))
self.dist.exclude_package('b')
- self.failIf(self.dist.has_contents_for('b'))
+ self.assertTrue(not self.dist.has_contents_for('b'))
- self.failUnless(self.dist.has_contents_for('c'))
+ self.assertTrue(self.dist.has_contents_for('c'))
self.dist.exclude_package('c')
- self.failIf(self.dist.has_contents_for('c'))
-
-
-
+ self.assertTrue(not self.dist.has_contents_for('c'))
def testInvalidIncludeExclude(self):
self.assertRaises(DistutilsSetupError,
@@ -230,107 +230,11 @@ class DistroTests(TestCase):
self.dist.exclude, package_dir=['q']
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-class FeatureTests(TestCase):
-
- def setUp(self):
- self.req = Require('Distutils','1.0.3','distutils')
- self.dist = makeSetup(
- features={
- 'foo': Feature("foo",standard=True,requires=['baz',self.req]),
- 'bar': Feature("bar", standard=True, packages=['pkg.bar'],
- py_modules=['bar_et'], remove=['bar.ext'],
- ),
- 'baz': Feature(
- "baz", optional=False, packages=['pkg.baz'],
- scripts = ['scripts/baz_it'],
- libraries=[('libfoo','foo/foofoo.c')]
- ),
- 'dwim': Feature("DWIM", available=False, remove='bazish'),
- },
- script_args=['--without-bar', 'install'],
- packages = ['pkg.bar', 'pkg.foo'],
- py_modules = ['bar_et', 'bazish'],
- ext_modules = [Extension('bar.ext',['bar.c'])]
- )
-
- def testDefaults(self):
- self.failIf(
- Feature(
- "test",standard=True,remove='x',available=False
- ).include_by_default()
- )
- self.failUnless(
- Feature("test",standard=True,remove='x').include_by_default()
- )
- # Feature must have either kwargs, removes, or requires
- self.assertRaises(DistutilsSetupError, Feature, "test")
-
- def testAvailability(self):
- self.assertRaises(
- DistutilsPlatformError,
- self.dist.features['dwim'].include_in, self.dist
- )
-
- def testFeatureOptions(self):
- dist = self.dist
- self.failUnless(
- ('with-dwim',None,'include DWIM') in dist.feature_options
- )
- self.failUnless(
- ('without-dwim',None,'exclude DWIM (default)') in dist.feature_options
- )
- self.failUnless(
- ('with-bar',None,'include bar (default)') in dist.feature_options
- )
- self.failUnless(
- ('without-bar',None,'exclude bar') in dist.feature_options
- )
- self.assertEqual(dist.feature_negopt['without-foo'],'with-foo')
- self.assertEqual(dist.feature_negopt['without-bar'],'with-bar')
- self.assertEqual(dist.feature_negopt['without-dwim'],'with-dwim')
- self.failIf('without-baz' in dist.feature_negopt)
-
- def testUseFeatures(self):
- dist = self.dist
- self.assertEqual(dist.with_foo,1)
- self.assertEqual(dist.with_bar,0)
- self.assertEqual(dist.with_baz,1)
- self.failIf('bar_et' in dist.py_modules)
- self.failIf('pkg.bar' in dist.packages)
- self.failUnless('pkg.baz' in dist.packages)
- self.failUnless('scripts/baz_it' in dist.scripts)
- self.failUnless(('libfoo','foo/foofoo.c') in dist.libraries)
- self.assertEqual(dist.ext_modules,[])
- self.assertEqual(dist.requires, [self.req])
-
- # If we ask for bar, it should fail because we explicitly disabled
- # it on the command line
- self.assertRaises(DistutilsOptionError, dist.include_feature, 'bar')
-
- def testFeatureWithInvalidRemove(self):
- self.assertRaises(
- SystemExit, makeSetup, features = {'x':Feature('x', remove='y')}
- )
-
-class TestCommandTests(TestCase):
+class TestCommandTests(unittest.TestCase):
def testTestIsCommand(self):
test_cmd = makeSetup().get_command_obj('test')
- self.failUnless(isinstance(test_cmd, distutils.cmd.Command))
+ self.assertTrue(isinstance(test_cmd, distutils.cmd.Command))
def testLongOptSuiteWNoDefault(self):
ts1 = makeSetup(script_args=['test','--test-suite=foo.tests.suite'])
@@ -362,49 +266,3 @@ class TestCommandTests(TestCase):
ts5 = makeSetup().get_command_obj('test')
ts5.ensure_finalized()
self.assertEqual(ts5.test_suite, None)
-
-
-
-
-
-def test_api():
- import doctest
- return doctest.DocFileSuite(
- 'api_tests.txt', optionflags=doctest.ELLIPSIS, package='pkg_resources',
- )
-
-
-testClasses = (DependsTests, DistroTests, FeatureTests, TestCommandTests)
-testNames = ["setuptools.tests.test_resources", "setuptools.tests.test_api"]
-
-def test_suite():
- return TestSuite(
- [makeSuite(t,'test') for t in testClasses]+
- [defaultTestLoader.loadTestsFromName(n) for n in testNames]
- )
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/setuptools/tests/doctest.py b/setuptools/tests/doctest.py
index fcaf3a4d..47293c3c 100644
--- a/setuptools/tests/doctest.py
+++ b/setuptools/tests/doctest.py
@@ -9,7 +9,7 @@
try:
basestring
except NameError:
- basestring = str,unicode
+ basestring = str
try:
enumerate
@@ -109,7 +109,7 @@ import __future__
import sys, traceback, inspect, linecache, os, re, types
import unittest, difflib, pdb, tempfile
import warnings
-from StringIO import StringIO
+from setuptools.compat import StringIO, execfile, func_code, im_func
# Don't whine about the deprecated is_private function in this
# module's tests.
@@ -240,7 +240,7 @@ def _normalize_module(module, depth=2):
"""
if inspect.ismodule(module):
return module
- elif isinstance(module, (str, unicode)):
+ elif isinstance(module, basestring):
return __import__(module, globals(), locals(), ["*"])
elif module is None:
return sys.modules[sys._getframe(depth).f_globals['__name__']]
@@ -367,9 +367,9 @@ class _OutputRedirectingPdb(pdb.Pdb):
# [XX] Normalize with respect to os.path.pardir?
def _module_relative_path(module, path):
if not inspect.ismodule(module):
- raise TypeError, 'Expected a module: %r' % module
+ raise TypeError('Expected a module: %r' % module)
if path.startswith('/'):
- raise ValueError, 'Module-relative files may not have absolute paths'
+ raise ValueError('Module-relative files may not have absolute paths')
# Find the base directory for the path.
if hasattr(module, '__file__'):
@@ -877,7 +877,7 @@ class DocTestFinder:
if module is None:
return True
elif inspect.isfunction(object):
- return module.__dict__ is object.func_globals
+ return module.__dict__ is func_globals(object)
elif inspect.isclass(object):
return module.__name__ == object.__module__
elif inspect.getmodule(object) is not None:
@@ -895,7 +895,7 @@ class DocTestFinder:
add them to `tests`.
"""
if self._verbose:
- print 'Finding tests in %s' % name
+ print('Finding tests in %s' % name)
# If we've already processed this object, then ignore it.
if id(obj) in seen:
@@ -948,7 +948,7 @@ class DocTestFinder:
if isinstance(val, staticmethod):
val = getattr(obj, valname)
if isinstance(val, classmethod):
- val = getattr(obj, valname).im_func
+ val = im_func(getattr(obj, valname))
# Recurse to methods, properties, and nested classes.
if ((inspect.isfunction(val) or inspect.isclass(val) or
@@ -1020,8 +1020,8 @@ class DocTestFinder:
break
# Find the line number for functions & methods.
- if inspect.ismethod(obj): obj = obj.im_func
- if inspect.isfunction(obj): obj = obj.func_code
+ if inspect.ismethod(obj): obj = im_func(obj)
+ if inspect.isfunction(obj): obj = func_code(obj)
if inspect.istraceback(obj): obj = obj.tb_frame
if inspect.isframe(obj): obj = obj.f_code
if inspect.iscode(obj):
@@ -1250,8 +1250,8 @@ class DocTestRunner:
# keyboard interrupts.)
try:
# Don't blink! This is where the user's code gets run.
- exec compile(example.source, filename, "single",
- compileflags, 1) in test.globs
+ exec(compile(example.source, filename, "single",
+ compileflags, 1), test.globs)
self.debugger.set_continue() # ==== Example Finished ====
exception = None
except KeyboardInterrupt:
@@ -1330,11 +1330,13 @@ class DocTestRunner:
__LINECACHE_FILENAME_RE = re.compile(r'<doctest '
r'(?P<name>[\w\.]+)'
r'\[(?P<examplenum>\d+)\]>$')
- def __patched_linecache_getlines(self, filename):
+ def __patched_linecache_getlines(self, filename, module_globals=None):
m = self.__LINECACHE_FILENAME_RE.match(filename)
if m and m.group('name') == self.test.name:
example = self.test.examples[int(m.group('examplenum'))]
return example.source.splitlines(True)
+ elif func_code(self.save_linecache_getlines).co_argcount > 1:
+ return self.save_linecache_getlines(filename, module_globals)
else:
return self.save_linecache_getlines(filename)
@@ -1425,28 +1427,28 @@ class DocTestRunner:
failed.append(x)
if verbose:
if notests:
- print len(notests), "items had no tests:"
+ print(len(notests), "items had no tests:")
notests.sort()
for thing in notests:
- print " ", thing
+ print(" ", thing)
if passed:
- print len(passed), "items passed all tests:"
+ print(len(passed), "items passed all tests:")
passed.sort()
for thing, count in passed:
- print " %3d tests in %s" % (count, thing)
+ print(" %3d tests in %s" % (count, thing))
if failed:
- print self.DIVIDER
- print len(failed), "items had failures:"
+ print(self.DIVIDER)
+ print(len(failed), "items had failures:")
failed.sort()
for thing, (f, t) in failed:
- print " %3d of %3d in %s" % (f, t, thing)
+ print(" %3d of %3d in %s" % (f, t, thing))
if verbose:
- print totalt, "tests in", len(self._name2ft), "items."
- print totalt - totalf, "passed and", totalf, "failed."
+ print(totalt, "tests in", len(self._name2ft), "items.")
+ print(totalt - totalf, "passed and", totalf, "failed.")
if totalf:
- print "***Test Failed***", totalf, "failures."
+ print("***Test Failed***", totalf, "failures.")
elif verbose:
- print "Test passed."
+ print("Test passed.")
return totalf, totalt
#/////////////////////////////////////////////////////////////////
@@ -1456,8 +1458,8 @@ class DocTestRunner:
d = self._name2ft
for name, (f, t) in other._name2ft.items():
if name in d:
- print "*** DocTestRunner.merge: '" + name + "' in both" \
- " testers; summing outcomes."
+ print("*** DocTestRunner.merge: '" + name + "' in both" \
+ " testers; summing outcomes.")
f2, t2 = d[name]
f = f + f2
t = t + t2
@@ -1966,7 +1968,9 @@ def testfile(filename, module_relative=True, name=None, package=None,
runner = DocTestRunner(verbose=verbose, optionflags=optionflags)
# Read the file, convert it to a test, and run it.
- s = open(filename).read()
+ f = open(filename)
+ s = f.read()
+ f.close()
test = parser.get_doctest(s, globs, name, filename, 0)
runner.run(test)
@@ -2035,10 +2039,10 @@ class Tester:
def runstring(self, s, name):
test = DocTestParser().get_doctest(s, self.globs, name, None, None)
if self.verbose:
- print "Running string", name
+ print("Running string", name)
(f,t) = self.testrunner.run(test)
if self.verbose:
- print f, "of", t, "examples failed in string", name
+ print(f, "of", t, "examples failed in string", name)
return (f,t)
def rundoc(self, object, name=None, module=None):
@@ -2051,16 +2055,16 @@ class Tester:
return (f,t)
def rundict(self, d, name, module=None):
- import new
- m = new.module(name)
+ import types
+ m = types.ModuleType(name)
m.__dict__.update(d)
if module is None:
module = False
return self.rundoc(m, name, module)
def run__test__(self, d, name):
- import new
- m = new.module(name)
+ import types
+ m = types.ModuleType(name)
m.__test__ = d
return self.rundoc(m, name)
@@ -2351,7 +2355,9 @@ def DocFileTest(path, module_relative=True, package=None,
# Find the file and read it.
name = os.path.basename(path)
- doc = open(path).read()
+ f = open(path)
+ doc = f.read()
+ f.close()
# Convert it to a test, and wrap it in a DocFileCase.
test = parser.get_doctest(doc, globs, name, path, 0)
@@ -2550,7 +2556,7 @@ def debug_script(src, pm=False, globs=None):
try:
execfile(srcfilename, globs, globs)
except:
- print sys.exc_info()[1]
+ print(sys.exc_info()[1])
pdb.post_mortem(sys.exc_info()[2])
else:
# Note that %r is vital here. '%s' instead can, e.g., cause
diff --git a/setuptools/tests/entries-v10 b/setuptools/tests/entries-v10
new file mode 100644
index 00000000..4446c501
--- /dev/null
+++ b/setuptools/tests/entries-v10
@@ -0,0 +1,615 @@
+10
+
+dir
+89001
+http://svn.python.org/projects/sandbox/branches/setuptools-0.6
+http://svn.python.org/projects
+
+
+
+2013-06-03T17:26:03.052972Z
+89000
+phillip.eby
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6015fed2-1504-0410-9fe1-9d1591cc4771
+
+api_tests.txt
+file
+
+
+
+
+2013-06-19T13:20:47.948712Z
+dec366372ca14fbeaeb26f492bcf5725
+2013-05-15T22:04:59.389374Z
+88997
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+12312
+
+setuptools.egg-info
+dir
+
+README.txt
+file
+
+
+
+
+2013-06-19T13:20:47.948712Z
+26f0dd5d095522ba3ad999b6b6777b92
+2011-05-31T20:10:56.416725Z
+88846
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7615
+
+easy_install.py
+file
+
+
+
+
+2013-06-19T13:20:47.948712Z
+97b52fe7253bf4683f9f626f015eb72e
+2006-09-20T20:48:18.716070Z
+51935
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+126
+
+setuptools
+dir
+
+launcher.c
+file
+
+
+
+
+2013-06-19T13:20:47.924700Z
+e5a8e77de9022688b80f77fc6d742fee
+2009-10-19T21:03:29.785400Z
+75544
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7476
+
+ez_setup.py
+file
+
+
+
+
+2013-06-19T13:20:47.924700Z
+17e8ec5e08faccfcb08b5f8d5167ca14
+2011-01-20T18:50:00.815420Z
+88124
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8350
+
+version
+file
+
+
+
+
+2013-06-19T13:20:47.924700Z
+e456da09e0c9e224a56302f8316b6dbf
+2007-01-09T19:21:05.921317Z
+53317
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1143
+
+setup.py
+file
+
+
+
+
+2013-06-19T13:20:47.924700Z
+d4e5b3c16bd61bfef6c0bb9377a3a3ea
+2013-05-15T22:04:59.389374Z
+88997
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5228
+
+release.sh
+file
+
+
+
+
+2013-06-19T13:20:47.932704Z
+b1fd4054a1c107ff0f27baacd97be94c
+2009-10-28T17:12:45.227140Z
+75925
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1044
+
+pkg_resources.txt
+file
+
+
+
+
+2013-06-19T13:20:47.928702Z
+f497e7c92a4de207cbd9ab1943f93388
+2009-10-12T20:00:02.336146Z
+75385
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+94518
+
+site.py
+file
+
+
+
+
+2013-06-19T13:20:47.932704Z
+ebaac6fb6525f77ca950d22e6f8315df
+2006-03-11T00:39:09.666740Z
+42965
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2362
+
+version.dat
+file
+
+
+
+
+2013-06-19T13:20:47.932704Z
+8e14ecea32b9874cd7d29277494554c0
+2009-10-28T17:12:45.227140Z
+75925
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+80
+
+virtual-python.py
+file
+
+
+
+
+2013-06-19T13:20:47.932704Z
+aa857add3b5563238f0a904187f5ded9
+2005-10-17T02:26:39.000000Z
+41262
+pje
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+3898
+
+setup.cfg
+file
+
+
+
+
+2013-06-19T13:20:47.932704Z
+eda883e744fce83f8107ad8dc8303536
+2006-09-21T22:26:48.050256Z
+51965
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+296
+
+setuptools.txt
+file
+
+
+
+
+2013-06-19T13:20:47.940708Z
+11926256f06046b196eaf814772504e7
+2013-05-15T22:04:59.389374Z
+88997
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+149832
+
+pkg_resources.py
+file
+
+
+
+
+2013-06-19T13:20:47.940708Z
+b63a30f5f0f0225a788c2c0e3430b3cf
+2013-05-15T22:04:59.389374Z
+88997
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+90397
+
+tests
+dir
+
+wikiup.cfg
+file
+
+
+
+
+2013-06-19T13:20:47.944710Z
+34ad845a5e0a0b46458557fa910bf429
+2008-08-21T17:23:50.797633Z
+65935
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+136
+
+EasyInstall.txt
+file
+
+
+
+
+2013-06-19T13:20:47.944710Z
+e97387c517f70fc18a377e42d19d64d4
+2013-05-15T22:04:59.389374Z
+88997
+phillip.eby
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+82495
+
diff --git a/setuptools/tests/environment.py b/setuptools/tests/environment.py
new file mode 100644
index 00000000..476d280a
--- /dev/null
+++ b/setuptools/tests/environment.py
@@ -0,0 +1,165 @@
+import os
+import zipfile
+import sys
+import tempfile
+import unittest
+import shutil
+import stat
+import unicodedata
+
+from subprocess import Popen as _Popen, PIPE as _PIPE
+
+
+def _extract(self, member, path=None, pwd=None):
+ """for zipfile py2.5 borrowed from cpython"""
+ if not isinstance(member, zipfile.ZipInfo):
+ member = self.getinfo(member)
+
+ if path is None:
+ path = os.getcwd()
+
+ return _extract_member(self, member, path, pwd)
+
+
+def _extract_from_zip(self, name, dest_path):
+ dest_file = open(dest_path, 'wb')
+ try:
+ dest_file.write(self.read(name))
+ finally:
+ dest_file.close()
+
+
+def _extract_member(self, member, targetpath, pwd):
+ """for zipfile py2.5 borrowed from cpython"""
+ # build the destination pathname, replacing
+ # forward slashes to platform specific separators.
+ # Strip trailing path separator, unless it represents the root.
+ if (targetpath[-1:] in (os.path.sep, os.path.altsep)
+ and len(os.path.splitdrive(targetpath)[1]) > 1):
+ targetpath = targetpath[:-1]
+
+ # don't include leading "/" from file name if present
+ if member.filename[0] == '/':
+ targetpath = os.path.join(targetpath, member.filename[1:])
+ else:
+ targetpath = os.path.join(targetpath, member.filename)
+
+ targetpath = os.path.normpath(targetpath)
+
+ # Create all upper directories if necessary.
+ upperdirs = os.path.dirname(targetpath)
+ if upperdirs and not os.path.exists(upperdirs):
+ os.makedirs(upperdirs)
+
+ if member.filename[-1] == '/':
+ if not os.path.isdir(targetpath):
+ os.mkdir(targetpath)
+ return targetpath
+
+ _extract_from_zip(self, member.filename, targetpath)
+
+ return targetpath
+
+
+def _remove_dir(target):
+
+ #on windows this seems to a problem
+ for dir_path, dirs, files in os.walk(target):
+ os.chmod(dir_path, stat.S_IWRITE)
+ for filename in files:
+ os.chmod(os.path.join(dir_path, filename), stat.S_IWRITE)
+ shutil.rmtree(target)
+
+
+class ZippedEnvironment(unittest.TestCase):
+
+ datafile = None
+ dataname = None
+ old_cwd = None
+
+ def setUp(self):
+ if self.datafile is None or self.dataname is None:
+ return
+
+ if not os.path.isfile(self.datafile):
+ self.old_cwd = None
+ return
+
+ self.old_cwd = os.getcwd()
+
+ self.temp_dir = tempfile.mkdtemp()
+ zip_file, source, target = [None, None, None]
+ try:
+ zip_file = zipfile.ZipFile(self.datafile)
+ for files in zip_file.namelist():
+ _extract(zip_file, files, self.temp_dir)
+ finally:
+ if zip_file:
+ zip_file.close()
+ del zip_file
+
+ os.chdir(os.path.join(self.temp_dir, self.dataname))
+
+ def tearDown(self):
+ #Assume setUp was never completed
+ if self.dataname is None or self.datafile is None:
+ return
+
+ try:
+ if self.old_cwd:
+ os.chdir(self.old_cwd)
+ _remove_dir(self.temp_dir)
+ except OSError:
+ #sigh?
+ pass
+
+
+def _which_dirs(cmd):
+ result = set()
+ for path in os.environ.get('PATH', '').split(os.pathsep):
+ filename = os.path.join(path, cmd)
+ if os.access(filename, os.X_OK):
+ result.add(path)
+ return result
+
+
+def run_setup_py(cmd, pypath=None, path=None,
+ data_stream=0, env=None):
+ """
+ Execution command for tests, separate from those used by the
+ code directly to prevent accidental behavior issues
+ """
+ if env is None:
+ env = dict()
+ for envname in os.environ:
+ env[envname] = os.environ[envname]
+
+ #override the python path if needed
+ if pypath is not None:
+ env["PYTHONPATH"] = pypath
+
+ #overide the execution path if needed
+ if path is not None:
+ env["PATH"] = path
+ if not env.get("PATH", ""):
+ env["PATH"] = _which_dirs("tar").union(_which_dirs("gzip"))
+ env["PATH"] = os.pathsep.join(env["PATH"])
+
+ cmd = [sys.executable, "setup.py"] + list(cmd)
+
+ #regarding the shell argument, see: http://bugs.python.org/issue8557
+ try:
+ proc = _Popen(cmd, stdout=_PIPE, stderr=_PIPE,
+ shell=(sys.platform == 'win32'), env=env)
+
+ data = proc.communicate()[data_stream]
+ except OSError:
+ return 1, ''
+
+ #decode the console string if needed
+ if hasattr(data, "decode"):
+ data = data.decode() # should use the preffered encoding
+ data = unicodedata.normalize('NFC', data)
+
+ #communciate calls wait()
+ return proc.returncode, data
diff --git a/setuptools/tests/indexes/test_links_priority/external.html b/setuptools/tests/indexes/test_links_priority/external.html
new file mode 100644
index 00000000..92e4702f
--- /dev/null
+++ b/setuptools/tests/indexes/test_links_priority/external.html
@@ -0,0 +1,3 @@
+<html><body>
+<a href="/foobar-0.1.tar.gz#md5=1__bad_md5___">bad old link</a>
+</body></html>
diff --git a/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html b/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html
new file mode 100644
index 00000000..fefb028b
--- /dev/null
+++ b/setuptools/tests/indexes/test_links_priority/simple/foobar/index.html
@@ -0,0 +1,4 @@
+<html><body>
+<a href="/foobar-0.1.tar.gz#md5=0_correct_md5">foobar-0.1.tar.gz</a><br/>
+<a href="../../external.html" rel="homepage">external homepage</a><br/>
+</body></html>
diff --git a/setuptools/tests/py26compat.py b/setuptools/tests/py26compat.py
new file mode 100644
index 00000000..d4fb891a
--- /dev/null
+++ b/setuptools/tests/py26compat.py
@@ -0,0 +1,14 @@
+import unittest
+
+try:
+ # provide skipIf for Python 2.4-2.6
+ skipIf = unittest.skipIf
+except AttributeError:
+ def skipIf(condition, reason):
+ def skipper(func):
+ def skip(*args, **kwargs):
+ return
+ if condition:
+ return skip
+ return func
+ return skipper
diff --git a/setuptools/tests/script-with-bom.py b/setuptools/tests/script-with-bom.py
new file mode 100644
index 00000000..22dee0d2
--- /dev/null
+++ b/setuptools/tests/script-with-bom.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+result = 'passed'
diff --git a/setuptools/tests/server.py b/setuptools/tests/server.py
new file mode 100644
index 00000000..ae2381e3
--- /dev/null
+++ b/setuptools/tests/server.py
@@ -0,0 +1,82 @@
+"""Basic http server for tests to simulate PyPI or custom indexes
+"""
+import sys
+import time
+import threading
+from setuptools.compat import BaseHTTPRequestHandler
+from setuptools.compat import (urllib2, URLError, HTTPServer,
+ SimpleHTTPRequestHandler)
+
+class IndexServer(HTTPServer):
+ """Basic single-threaded http server simulating a package index
+
+ You can use this server in unittest like this::
+ s = IndexServer()
+ s.start()
+ index_url = s.base_url() + 'mytestindex'
+ # do some test requests to the index
+ # The index files should be located in setuptools/tests/indexes
+ s.stop()
+ """
+ def __init__(self, server_address=('', 0),
+ RequestHandlerClass=SimpleHTTPRequestHandler):
+ HTTPServer.__init__(self, server_address, RequestHandlerClass)
+ self._run = True
+
+ def serve(self):
+ while self._run:
+ self.handle_request()
+
+ def start(self):
+ self.thread = threading.Thread(target=self.serve)
+ self.thread.start()
+
+ def stop(self):
+ "Stop the server"
+
+ # Let the server finish the last request and wait for a new one.
+ time.sleep(0.1)
+
+ # self.shutdown is not supported on python < 2.6, so just
+ # set _run to false, and make a request, causing it to
+ # terminate.
+ self._run = False
+ url = 'http://127.0.0.1:%(server_port)s/' % vars(self)
+ try:
+ if sys.version_info >= (2, 6):
+ urllib2.urlopen(url, timeout=5)
+ else:
+ urllib2.urlopen(url)
+ except URLError:
+ # ignore any errors; all that's important is the request
+ pass
+ self.thread.join()
+ self.socket.close()
+
+ def base_url(self):
+ port = self.server_port
+ return 'http://127.0.0.1:%s/setuptools/tests/indexes/' % port
+
+class RequestRecorder(BaseHTTPRequestHandler):
+ def do_GET(self):
+ requests = vars(self.server).setdefault('requests', [])
+ requests.append(self)
+ self.send_response(200, 'OK')
+
+class MockServer(HTTPServer, threading.Thread):
+ """
+ A simple HTTP Server that records the requests made to it.
+ """
+ def __init__(self, server_address=('', 0),
+ RequestHandlerClass=RequestRecorder):
+ HTTPServer.__init__(self, server_address, RequestHandlerClass)
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+ self.requests = []
+
+ def run(self):
+ self.serve_forever()
+
+ def url(self):
+ return 'http://localhost:%(server_port)s/' % vars(self)
+ url = property(url)
diff --git a/setuptools/tests/svn_data/dummy.zip b/setuptools/tests/svn_data/dummy.zip
new file mode 100644
index 00000000..1347be53
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy13.zip b/setuptools/tests/svn_data/dummy13.zip
new file mode 100644
index 00000000..47764342
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy13.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy14.zip b/setuptools/tests/svn_data/dummy14.zip
new file mode 100644
index 00000000..02ed8cf0
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy14.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy15.zip b/setuptools/tests/svn_data/dummy15.zip
new file mode 100644
index 00000000..ed8daeeb
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy15.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy16.zip b/setuptools/tests/svn_data/dummy16.zip
new file mode 100644
index 00000000..b6e98d6c
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy16.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy17.zip b/setuptools/tests/svn_data/dummy17.zip
new file mode 100644
index 00000000..d96e1513
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy17.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/dummy18.zip b/setuptools/tests/svn_data/dummy18.zip
new file mode 100644
index 00000000..a7267838
--- /dev/null
+++ b/setuptools/tests/svn_data/dummy18.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn13_example.zip b/setuptools/tests/svn_data/svn13_example.zip
new file mode 100644
index 00000000..d85fb84f
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn13_ext_list.txt b/setuptools/tests/svn_data/svn13_ext_list.txt
new file mode 100644
index 00000000..0bb0f438
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_ext_list.txt
@@ -0,0 +1,3 @@
+third_party3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party2 -r3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party -r1 file:///C:/development/svn_example/repos/svn13/extra1
diff --git a/setuptools/tests/svn_data/svn13_ext_list.xml b/setuptools/tests/svn_data/svn13_ext_list.xml
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_ext_list.xml
diff --git a/setuptools/tests/svn_data/svn13_info.xml b/setuptools/tests/svn_data/svn13_info.xml
new file mode 100644
index 00000000..5c96520a
--- /dev/null
+++ b/setuptools/tests/svn_data/svn13_info.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<info>
+<entry
+ kind="dir"
+ path="svn13_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<prop-updated>2013-07-13T15:33:23.187500Z</prop-updated>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:33:28.359375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:21.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:33:21.312500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<text-updated>2013-07-13T15:33:28.140625Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:33:28.359375Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn13_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<prop-updated>2013-07-13T15:33:26.187500Z</prop-updated>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:33:26.312500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:20.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:33:20.312500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn13_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn13/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn13/main</root>
+<uuid>d2996769-47b0-9946-b618-da1aa3eceda3</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:33:19.375000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:33:19.609375Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn14_example.zip b/setuptools/tests/svn_data/svn14_example.zip
new file mode 100644
index 00000000..57093c0b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn14_ext_list.txt b/setuptools/tests/svn_data/svn14_ext_list.txt
new file mode 100644
index 00000000..800a0965
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_ext_list.txt
@@ -0,0 +1,4 @@
+third_party3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party2 -r3 file:///C:/development/svn_example/repos/svn13/extra1
+third_party -r1 file:///C:/development/svn_example/repos/svn13/extra1
+
diff --git a/setuptools/tests/svn_data/svn14_ext_list.xml b/setuptools/tests/svn_data/svn14_ext_list.xml
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_ext_list.xml
diff --git a/setuptools/tests/svn_data/svn14_info.xml b/setuptools/tests/svn_data/svn14_info.xml
new file mode 100644
index 00000000..a896a77f
--- /dev/null
+++ b/setuptools/tests/svn_data/svn14_info.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<info>
+<entry
+ kind="dir"
+ path="svn14_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:14.406250Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:08.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:34:08.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<text-updated>2013-07-13T15:34:14.125000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:14.406250Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn14_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:34:12.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:07.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:34:07.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn14_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn14/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn14/main</root>
+<uuid>c75942e5-8b7a-354d-b1cf-73dee23fa94f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<text-updated>2013-07-13T15:34:06.250000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:34:06.531250Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn15_example.zip b/setuptools/tests/svn_data/svn15_example.zip
new file mode 100644
index 00000000..52a1d45b
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn15_ext_list.txt b/setuptools/tests/svn_data/svn15_ext_list.txt
new file mode 100644
index 00000000..75fde4e6
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_ext_list.txt
@@ -0,0 +1,4 @@
+third_party3 file:///C:/development/svn_example/repos/svn15/extra1
+-r3 file:///C:/development/svn_example/repos/svn15/extra1 third_party2
+file:///C:/development/svn_example/repos/svn15/extra1@r1 third_party
+
diff --git a/setuptools/tests/svn_data/svn15_ext_list.xml b/setuptools/tests/svn_data/svn15_ext_list.xml
new file mode 100644
index 00000000..6950b3c5
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn15_example/folder">
+<property
+ name="svn:externals">third_party3 file:///C:/development/svn_example/repos/svn15/extra2
+-r3 file:///C:/development/svn_example/repos/svn15/extra2 third_party2
+file:///C:/development/svn_example/repos/svn15/extra2@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn15_example">
+<property
+ name="svn:externals">third_party3 file:///C:/development/svn_example/repos/svn15/extra1
+-r3 file:///C:/development/svn_example/repos/svn15/extra1 third_party2
+file:///C:/development/svn_example/repos/svn15/extra1@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn15_info.xml b/setuptools/tests/svn_data/svn15_info.xml
new file mode 100644
index 00000000..0b3550af
--- /dev/null
+++ b/setuptools/tests/svn_data/svn15_info.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<info>
+<entry
+ kind="dir"
+ path="svn15_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:49.562500Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:43.109375Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:34:43.484375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:49.125000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:34:49.562500Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn15_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:34:47.515625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:42.109375Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:34:42.484375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn15_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn15/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn15/main</root>
+<uuid>4eab6983-54fe-384b-a282-9306f52d948f</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:34:41.375000Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:34:41.734375Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn16_example.zip b/setuptools/tests/svn_data/svn16_example.zip
new file mode 100644
index 00000000..e886b2af
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn16_ext_list.txt b/setuptools/tests/svn_data/svn16_ext_list.txt
new file mode 100644
index 00000000..3ca54893
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_ext_list.txt
@@ -0,0 +1,4 @@
+"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+'third party3b' file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party
diff --git a/setuptools/tests/svn_data/svn16_ext_list.xml b/setuptools/tests/svn_data/svn16_ext_list.xml
new file mode 100644
index 00000000..8ddaed0a
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn16_example/folder">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra2
+-r3 file:///C:/development/svn_example/repos/svn16/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra2@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn16_example">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn16_info.xml b/setuptools/tests/svn_data/svn16_info.xml
new file mode 100644
index 00000000..745469c9
--- /dev/null
+++ b/setuptools/tests/svn_data/svn16_info.xml
@@ -0,0 +1,125 @@
+<?xml version="1.0"?>
+<info>
+<entry
+ kind="dir"
+ path="svn16_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:17.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:14.578125Z</text-updated>
+<checksum>a6166e5e98a5a503089cde9bc8031293</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:14.906250Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:17.046875Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:17.390625Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn16_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:16.406250Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:14.078125Z</text-updated>
+<checksum>795240c6a830c14f83961e57e07dad12</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:14.421875Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn16_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn16/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn16/main</root>
+<uuid>bd8d2cfc-1a74-de45-b166-262010c17c0a</uuid>
+</repository>
+<wc-info>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:12.171875Z</text-updated>
+<checksum>d41d8cd98f00b204e9800998ecf8427e</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:13.906250Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn17_example.zip b/setuptools/tests/svn_data/svn17_example.zip
new file mode 100644
index 00000000..ba0e8823
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn17_ext_list.txt b/setuptools/tests/svn_data/svn17_ext_list.txt
new file mode 100644
index 00000000..a8b832a8
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_ext_list.txt
@@ -0,0 +1,4 @@
+"third party3" file:///C:/development/svn_example/repos/svn17/extra1
+'third party3b' file:///C:/development/svn_example/repos/svn17/extra1
+-r3 file:///C:/development/svn_example/repos/svn17/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn17/extra1@r1 third_party
diff --git a/setuptools/tests/svn_data/svn17_ext_list.xml b/setuptools/tests/svn_data/svn17_ext_list.xml
new file mode 100644
index 00000000..2879bb65
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn17_example">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn17_example/folder">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn17/extra2
+-r3 file:///C:/development/svn_example/repos/svn17/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn17/extra2@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn17_info.xml b/setuptools/tests/svn_data/svn17_info.xml
new file mode 100644
index 00000000..6cffeffd
--- /dev/null
+++ b/setuptools/tests/svn_data/svn17_info.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<info>
+<entry
+ kind="dir"
+ path="svn17_example"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:36.171875Z</date>
+</commit>
+</entry>
+<entry
+ path="svn17_example\folder"
+ revision="6"
+ kind="dir">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:34.859375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn17_example\folder\quest.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder/quest.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:32.812500Z</text-updated>
+<checksum>bc80eba9e7a10c0a571a4678c520bc9683f3bac2</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:33.109375Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn17_example\folder\lalala.txt"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn17/main/folder/lalala.txt</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:32.343750Z</text-updated>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:32.687500Z</date>
+</commit>
+</entry>
+<entry
+ path="svn17_example\a file"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn17/main/a%20file</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:33.187500Z</text-updated>
+<checksum>43785ab4b1816b49f242990883292813cd4f486c</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:33.515625Z</date>
+</commit>
+</entry>
+<entry
+ path="svn17_example\to_delete"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn17/main/to_delete</url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn17/main</root>
+<uuid>5ba45434-5197-164e-afab-81923f4744f5</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn17_example</wcroot-abspath>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:36.171875Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/svn_data/svn18_example.zip b/setuptools/tests/svn_data/svn18_example.zip
new file mode 100644
index 00000000..4362f8e9
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_example.zip
Binary files differ
diff --git a/setuptools/tests/svn_data/svn18_ext_list.txt b/setuptools/tests/svn_data/svn18_ext_list.txt
new file mode 100644
index 00000000..c90a5f11
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_ext_list.txt
@@ -0,0 +1,4 @@
+"third party3" file:///C:/development/svn_example/repos/svn18/extra1
+'third party3b' file:///C:/development/svn_example/repos/svn18/extra1
+-r3 file:///C:/development/svn_example/repos/svn18/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn18/extra1@r1 third_party
diff --git a/setuptools/tests/svn_data/svn18_ext_list.xml b/setuptools/tests/svn_data/svn18_ext_list.xml
new file mode 100644
index 00000000..9b5e9e96
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_ext_list.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<properties>
+<target
+ path="C:/development/svn_example/svn18_example">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn16/extra1
+-r3 file:///C:/development/svn_example/repos/svn16/extra1 third\ party2
+file:///C:/development/svn_example/repos/svn16/extra1@r1 third_party大介
+</property>
+</target>
+<target
+ path="C:/development/svn_example/svn18_example/folder">
+<property
+ name="svn:externals">"third party3" file:///C:/development/svn_example/repos/svn18/extra2
+-r3 file:///C:/development/svn_example/repos/svn18/extra2 third\ party2
+file:///C:/development/svn_example/repos/svn18/extra2@r1 third_party大介
+</property>
+</target>
+</properties>
diff --git a/setuptools/tests/svn_data/svn18_info.xml b/setuptools/tests/svn_data/svn18_info.xml
new file mode 100644
index 00000000..7ca55995
--- /dev/null
+++ b/setuptools/tests/svn_data/svn18_info.xml
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<info>
+<entry
+ path="svn18_example"
+ revision="6"
+ kind="dir">
+<url>file:///C:/development/svn_example/repos/svn18/main</url>
+<relative-url>^/</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:57.796875Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn18_example\a file"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/a%20file</url>
+<relative-url>^/a%20file</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.906250Z</text-updated>
+<checksum>43785ab4b1816b49f242990883292813cd4f486c</checksum>
+</wc-info>
+<commit
+ revision="3">
+<author>ptt</author>
+<date>2013-07-13T15:35:55.265625Z</date>
+</commit>
+</entry>
+<entry
+ kind="file"
+ path="svn18_example\to_delete"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/to_delete</url>
+<relative-url>^/to_delete</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>delete</schedule>
+<depth>infinity</depth>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="6">
+<author>ptt</author>
+<date>2013-07-13T15:35:57.796875Z</date>
+</commit>
+</entry>
+<entry
+ kind="dir"
+ path="svn18_example\folder"
+ revision="6">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder</url>
+<relative-url>^/folder</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+</wc-info>
+<commit
+ revision="5">
+<author>ptt</author>
+<date>2013-07-13T15:35:56.750000Z</date>
+</commit>
+</entry>
+<entry
+ path="svn18_example\folder\quest.txt"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder/quest.txt</url>
+<relative-url>^/folder/quest.txt</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.484375Z</text-updated>
+<checksum>bc80eba9e7a10c0a571a4678c520bc9683f3bac2</checksum>
+</wc-info>
+<commit
+ revision="2">
+<author>ptt</author>
+<date>2013-07-13T15:35:54.843750Z</date>
+</commit>
+</entry>
+<entry
+ path="svn18_example\folder\lalala.txt"
+ revision="6"
+ kind="file">
+<url>file:///C:/development/svn_example/repos/svn18/main/folder/lalala.txt</url>
+<relative-url>^/folder/lalala.txt</relative-url>
+<repository>
+<root>file:///C:/development/svn_example/repos/svn18/main</root>
+<uuid>3c5e3929-c92b-7045-9ba9-5e65d3dd1ee9</uuid>
+</repository>
+<wc-info>
+<wcroot-abspath>C:/development/svn_example/svn18_example</wcroot-abspath>
+<schedule>normal</schedule>
+<depth>infinity</depth>
+<text-updated>2013-07-13T15:35:54.015625Z</text-updated>
+<checksum>da39a3ee5e6b4b0d3255bfef95601890afd80709</checksum>
+</wc-info>
+<commit
+ revision="1">
+<author>ptt</author>
+<date>2013-07-13T15:35:54.375000Z</date>
+</commit>
+</entry>
+</info>
diff --git a/setuptools/tests/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
new file mode 100644
index 00000000..1a122186
--- /dev/null
+++ b/setuptools/tests/test_bdist_egg.py
@@ -0,0 +1,69 @@
+"""develop tests
+"""
+import sys
+import os, re, shutil, tempfile, unittest
+import tempfile
+import site
+
+from distutils.errors import DistutilsError
+from setuptools.compat import StringIO
+from setuptools.command.bdist_egg import bdist_egg
+from setuptools.command import easy_install as easy_install_pkg
+from setuptools.dist import Distribution
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(name='foo', py_modules=['hi'])
+"""
+
+class TestDevelopTest(unittest.TestCase):
+
+ def setUp(self):
+ self.dir = tempfile.mkdtemp()
+ self.old_cwd = os.getcwd()
+ os.chdir(self.dir)
+ f = open('setup.py', 'w')
+ f.write(SETUP_PY)
+ f.close()
+ f = open('hi.py', 'w')
+ f.write('1\n')
+ f.close()
+ if sys.version >= "2.6":
+ self.old_base = site.USER_BASE
+ site.USER_BASE = tempfile.mkdtemp()
+ self.old_site = site.USER_SITE
+ site.USER_SITE = tempfile.mkdtemp()
+
+ def tearDown(self):
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.dir)
+ if sys.version >= "2.6":
+ shutil.rmtree(site.USER_BASE)
+ shutil.rmtree(site.USER_SITE)
+ site.USER_BASE = self.old_base
+ site.USER_SITE = self.old_site
+
+ def test_bdist_egg(self):
+ dist = Distribution(dict(
+ script_name='setup.py',
+ script_args=['bdist_egg'],
+ name='foo',
+ py_modules=['hi']
+ ))
+ os.makedirs(os.path.join('build', 'src'))
+ old_stdout = sys.stdout
+ sys.stdout = o = StringIO()
+ try:
+ dist.parse_command_line()
+ dist.run_commands()
+ finally:
+ sys.stdout = old_stdout
+
+ # let's see if we got our egg link at the right place
+ [content] = os.listdir('dist')
+ self.assertTrue(re.match('foo-0.0.0-py[23].\d.egg$', content))
+
+def test_suite():
+ return unittest.makeSuite(TestDevelopTest)
+
diff --git a/setuptools/tests/test_build_ext.py b/setuptools/tests/test_build_ext.py
new file mode 100644
index 00000000..a520ced9
--- /dev/null
+++ b/setuptools/tests/test_build_ext.py
@@ -0,0 +1,20 @@
+"""build_ext tests
+"""
+import os, shutil, tempfile, unittest
+from distutils.command.build_ext import build_ext as distutils_build_ext
+from setuptools.command.build_ext import build_ext
+from setuptools.dist import Distribution
+
+class TestBuildExtTest(unittest.TestCase):
+
+ def test_get_ext_filename(self):
+ # setuptools needs to give back the same
+ # result than distutils, even if the fullname
+ # is not in ext_map
+ dist = Distribution()
+ cmd = build_ext(dist)
+ cmd.ext_map['foo/bar'] = ''
+ res = cmd.get_ext_filename('foo')
+ wanted = distutils_build_ext.get_ext_filename(cmd, 'foo')
+ assert res == wanted
+
diff --git a/setuptools/tests/test_develop.py b/setuptools/tests/test_develop.py
new file mode 100644
index 00000000..7b90161a
--- /dev/null
+++ b/setuptools/tests/test_develop.py
@@ -0,0 +1,122 @@
+"""develop tests
+"""
+import sys
+import os, shutil, tempfile, unittest
+import tempfile
+import site
+
+from distutils.errors import DistutilsError
+from setuptools.command.develop import develop
+from setuptools.command import easy_install as easy_install_pkg
+from setuptools.compat import StringIO
+from setuptools.dist import Distribution
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(name='foo',
+ packages=['foo'],
+ use_2to3=True,
+)
+"""
+
+INIT_PY = """print "foo"
+"""
+
+class TestDevelopTest(unittest.TestCase):
+
+ def setUp(self):
+ if sys.version < "2.6" or hasattr(sys, 'real_prefix'):
+ return
+
+ # Directory structure
+ self.dir = tempfile.mkdtemp()
+ os.mkdir(os.path.join(self.dir, 'foo'))
+ # setup.py
+ setup = os.path.join(self.dir, 'setup.py')
+ f = open(setup, 'w')
+ f.write(SETUP_PY)
+ f.close()
+ self.old_cwd = os.getcwd()
+ # foo/__init__.py
+ init = os.path.join(self.dir, 'foo', '__init__.py')
+ f = open(init, 'w')
+ f.write(INIT_PY)
+ f.close()
+
+ os.chdir(self.dir)
+ self.old_base = site.USER_BASE
+ site.USER_BASE = tempfile.mkdtemp()
+ self.old_site = site.USER_SITE
+ site.USER_SITE = tempfile.mkdtemp()
+
+ def tearDown(self):
+ if sys.version < "2.6" or hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
+ return
+
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.dir)
+ shutil.rmtree(site.USER_BASE)
+ shutil.rmtree(site.USER_SITE)
+ site.USER_BASE = self.old_base
+ site.USER_SITE = self.old_site
+
+ def test_develop(self):
+ if sys.version < "2.6" or hasattr(sys, 'real_prefix'):
+ return
+ dist = Distribution(
+ dict(name='foo',
+ packages=['foo'],
+ use_2to3=True,
+ version='0.0',
+ ))
+ dist.script_name = 'setup.py'
+ cmd = develop(dist)
+ cmd.user = 1
+ cmd.ensure_finalized()
+ cmd.install_dir = site.USER_SITE
+ cmd.user = 1
+ old_stdout = sys.stdout
+ #sys.stdout = StringIO()
+ try:
+ cmd.run()
+ finally:
+ sys.stdout = old_stdout
+
+ # let's see if we got our egg link at the right place
+ content = os.listdir(site.USER_SITE)
+ content.sort()
+ self.assertEqual(content, ['easy-install.pth', 'foo.egg-link'])
+
+ # Check that we are using the right code.
+ egg_link_file = open(os.path.join(site.USER_SITE, 'foo.egg-link'), 'rt')
+ try:
+ path = egg_link_file.read().split()[0].strip()
+ finally:
+ egg_link_file.close()
+ init_file = open(os.path.join(path, 'foo', '__init__.py'), 'rt')
+ try:
+ init = init_file.read().strip()
+ finally:
+ init_file.close()
+ if sys.version < "3":
+ self.assertEqual(init, 'print "foo"')
+ else:
+ self.assertEqual(init, 'print("foo")')
+
+ def notest_develop_with_setup_requires(self):
+
+ wanted = ("Could not find suitable distribution for "
+ "Requirement.parse('I-DONT-EXIST')")
+ old_dir = os.getcwd()
+ os.chdir(self.dir)
+ try:
+ try:
+ dist = Distribution({'setup_requires': ['I_DONT_EXIST']})
+ except DistutilsError:
+ e = sys.exc_info()[1]
+ error = str(e)
+ if error == wanted:
+ pass
+ finally:
+ os.chdir(old_dir)
diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py
new file mode 100644
index 00000000..a8adb68c
--- /dev/null
+++ b/setuptools/tests/test_dist_info.py
@@ -0,0 +1,83 @@
+"""Test .dist-info style distributions.
+"""
+import os
+import shutil
+import tempfile
+import unittest
+import textwrap
+
+try:
+ import ast
+except:
+ pass
+
+import pkg_resources
+
+from setuptools.tests.py26compat import skipIf
+
+def DALS(s):
+ "dedent and left-strip"
+ return textwrap.dedent(s).lstrip()
+
+class TestDistInfo(unittest.TestCase):
+
+ def test_distinfo(self):
+ dists = {}
+ for d in pkg_resources.find_distributions(self.tmpdir):
+ dists[d.project_name] = d
+
+ assert len(dists) == 2, dists
+
+ unversioned = dists['UnversionedDistribution']
+ versioned = dists['VersionedDistribution']
+
+ assert versioned.version == '2.718' # from filename
+ assert unversioned.version == '0.3' # from METADATA
+
+ @skipIf('ast' not in globals(),
+ "ast is used to test conditional dependencies (Python >= 2.6)")
+ def test_conditional_dependencies(self):
+ requires = [pkg_resources.Requirement.parse('splort==4'),
+ pkg_resources.Requirement.parse('quux>=1.1')]
+
+ for d in pkg_resources.find_distributions(self.tmpdir):
+ self.assertEqual(d.requires(), requires[:1])
+ self.assertEqual(d.requires(extras=('baz',)), requires)
+ self.assertEqual(d.extras, ['baz'])
+
+ def setUp(self):
+ self.tmpdir = tempfile.mkdtemp()
+ versioned = os.path.join(self.tmpdir,
+ 'VersionedDistribution-2.718.dist-info')
+ os.mkdir(versioned)
+ metadata_file = open(os.path.join(versioned, 'METADATA'), 'w+')
+ try:
+ metadata_file.write(DALS(
+ """
+ Metadata-Version: 1.2
+ Name: VersionedDistribution
+ Requires-Dist: splort (4)
+ Provides-Extra: baz
+ Requires-Dist: quux (>=1.1); extra == 'baz'
+ """))
+ finally:
+ metadata_file.close()
+ unversioned = os.path.join(self.tmpdir,
+ 'UnversionedDistribution.dist-info')
+ os.mkdir(unversioned)
+ metadata_file = open(os.path.join(unversioned, 'METADATA'), 'w+')
+ try:
+ metadata_file.write(DALS(
+ """
+ Metadata-Version: 1.2
+ Name: UnversionedDistribution
+ Version: 0.3
+ Requires-Dist: splort (==4)
+ Provides-Extra: baz
+ Requires-Dist: quux (>=1.1); extra == 'baz'
+ """))
+ finally:
+ metadata_file.close()
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir)
diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py
new file mode 100644
index 00000000..d2cc7a0f
--- /dev/null
+++ b/setuptools/tests/test_easy_install.py
@@ -0,0 +1,456 @@
+"""Easy install Tests
+"""
+import sys
+import os
+import shutil
+import tempfile
+import unittest
+import site
+import contextlib
+import textwrap
+import tarfile
+import logging
+import distutils.core
+
+from setuptools.compat import StringIO, BytesIO, next, urlparse
+from setuptools.sandbox import run_setup, SandboxViolation
+from setuptools.command.easy_install import (
+ easy_install, fix_jython_executable, get_script_args, nt_quote_arg)
+from setuptools.command.easy_install import PthDistributions
+from setuptools.command import easy_install as easy_install_pkg
+from setuptools.dist import Distribution
+from pkg_resources import working_set, VersionConflict
+from pkg_resources import Distribution as PRDistribution
+import setuptools.tests.server
+import pkg_resources
+
+class FakeDist(object):
+ def get_entry_map(self, group):
+ if group != 'console_scripts':
+ return {}
+ return {'name': 'ep'}
+
+ def as_requirement(self):
+ return 'spec'
+
+WANTED = """\
+#!%s
+# EASY-INSTALL-ENTRY-SCRIPT: 'spec','console_scripts','name'
+__requires__ = 'spec'
+import sys
+from pkg_resources import load_entry_point
+
+if __name__ == '__main__':
+ sys.exit(
+ load_entry_point('spec', 'console_scripts', 'name')()
+ )
+""" % nt_quote_arg(fix_jython_executable(sys.executable, ""))
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(name='foo')
+"""
+
+class TestEasyInstallTest(unittest.TestCase):
+
+ def test_install_site_py(self):
+ dist = Distribution()
+ cmd = easy_install(dist)
+ cmd.sitepy_installed = False
+ cmd.install_dir = tempfile.mkdtemp()
+ try:
+ cmd.install_site_py()
+ sitepy = os.path.join(cmd.install_dir, 'site.py')
+ self.assertTrue(os.path.exists(sitepy))
+ finally:
+ shutil.rmtree(cmd.install_dir)
+
+ def test_get_script_args(self):
+ dist = FakeDist()
+
+ old_platform = sys.platform
+ try:
+ name, script = [i for i in next(get_script_args(dist))][0:2]
+ finally:
+ sys.platform = old_platform
+
+ self.assertEqual(script, WANTED)
+
+ def test_no_find_links(self):
+ # new option '--no-find-links', that blocks find-links added at
+ # the project level
+ dist = Distribution()
+ cmd = easy_install(dist)
+ cmd.check_pth_processing = lambda: True
+ cmd.no_find_links = True
+ cmd.find_links = ['link1', 'link2']
+ cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
+ cmd.args = ['ok']
+ cmd.ensure_finalized()
+ self.assertEqual(cmd.package_index.scanned_urls, {})
+
+ # let's try without it (default behavior)
+ cmd = easy_install(dist)
+ cmd.check_pth_processing = lambda: True
+ cmd.find_links = ['link1', 'link2']
+ cmd.install_dir = os.path.join(tempfile.mkdtemp(), 'ok')
+ cmd.args = ['ok']
+ cmd.ensure_finalized()
+ keys = sorted(cmd.package_index.scanned_urls.keys())
+ self.assertEqual(keys, ['link1', 'link2'])
+
+
+class TestPTHFileWriter(unittest.TestCase):
+ def test_add_from_cwd_site_sets_dirty(self):
+ '''a pth file manager should set dirty
+ if a distribution is in site but also the cwd
+ '''
+ pth = PthDistributions('does-not_exist', [os.getcwd()])
+ self.assertTrue(not pth.dirty)
+ pth.add(PRDistribution(os.getcwd()))
+ self.assertTrue(pth.dirty)
+
+ def test_add_from_site_is_ignored(self):
+ if os.name != 'nt':
+ location = '/test/location/does-not-have-to-exist'
+ else:
+ location = 'c:\\does_not_exist'
+ pth = PthDistributions('does-not_exist', [location, ])
+ self.assertTrue(not pth.dirty)
+ pth.add(PRDistribution(location))
+ self.assertTrue(not pth.dirty)
+
+
+class TestUserInstallTest(unittest.TestCase):
+
+ def setUp(self):
+ self.dir = tempfile.mkdtemp()
+ setup = os.path.join(self.dir, 'setup.py')
+ f = open(setup, 'w')
+ f.write(SETUP_PY)
+ f.close()
+ self.old_cwd = os.getcwd()
+ os.chdir(self.dir)
+
+ self.old_enable_site = site.ENABLE_USER_SITE
+ self.old_file = easy_install_pkg.__file__
+ self.old_base = site.USER_BASE
+ site.USER_BASE = tempfile.mkdtemp()
+ self.old_site = site.USER_SITE
+ site.USER_SITE = tempfile.mkdtemp()
+ easy_install_pkg.__file__ = site.USER_SITE
+
+ def tearDown(self):
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.dir)
+
+ shutil.rmtree(site.USER_BASE)
+ shutil.rmtree(site.USER_SITE)
+ site.USER_BASE = self.old_base
+ site.USER_SITE = self.old_site
+ site.ENABLE_USER_SITE = self.old_enable_site
+ easy_install_pkg.__file__ = self.old_file
+
+ def test_user_install_implied(self):
+ site.ENABLE_USER_SITE = True # disabled sometimes
+ #XXX: replace with something meaningfull
+ dist = Distribution()
+ dist.script_name = 'setup.py'
+ cmd = easy_install(dist)
+ cmd.args = ['py']
+ cmd.ensure_finalized()
+ self.assertTrue(cmd.user, 'user should be implied')
+
+ def test_multiproc_atexit(self):
+ try:
+ __import__('multiprocessing')
+ except ImportError:
+ # skip the test if multiprocessing is not available
+ return
+
+ log = logging.getLogger('test_easy_install')
+ logging.basicConfig(level=logging.INFO, stream=sys.stderr)
+ log.info('this should not break')
+
+ def test_user_install_not_implied_without_usersite_enabled(self):
+ site.ENABLE_USER_SITE = False # usually enabled
+ #XXX: replace with something meaningfull
+ dist = Distribution()
+ dist.script_name = 'setup.py'
+ cmd = easy_install(dist)
+ cmd.args = ['py']
+ cmd.initialize_options()
+ self.assertFalse(cmd.user, 'NOT user should be implied')
+
+ def test_local_index(self):
+ # make sure the local index is used
+ # when easy_install looks for installed
+ # packages
+ new_location = tempfile.mkdtemp()
+ target = tempfile.mkdtemp()
+ egg_file = os.path.join(new_location, 'foo-1.0.egg-info')
+ f = open(egg_file, 'w')
+ try:
+ f.write('Name: foo\n')
+ finally:
+ f.close()
+
+ sys.path.append(target)
+ old_ppath = os.environ.get('PYTHONPATH')
+ os.environ['PYTHONPATH'] = os.path.pathsep.join(sys.path)
+ try:
+ dist = Distribution()
+ dist.script_name = 'setup.py'
+ cmd = easy_install(dist)
+ cmd.install_dir = target
+ cmd.args = ['foo']
+ cmd.ensure_finalized()
+ cmd.local_index.scan([new_location])
+ res = cmd.easy_install('foo')
+ self.assertEqual(os.path.realpath(res.location),
+ os.path.realpath(new_location))
+ finally:
+ sys.path.remove(target)
+ for basedir in [new_location, target, ]:
+ if not os.path.exists(basedir) or not os.path.isdir(basedir):
+ continue
+ try:
+ shutil.rmtree(basedir)
+ except:
+ pass
+ if old_ppath is not None:
+ os.environ['PYTHONPATH'] = old_ppath
+ else:
+ del os.environ['PYTHONPATH']
+
+ def test_setup_requires(self):
+ """Regression test for Distribute issue #318
+
+ Ensure that a package with setup_requires can be installed when
+ setuptools is installed in the user site-packages without causing a
+ SandboxViolation.
+ """
+
+ test_pkg = create_setup_requires_package(self.dir)
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+
+ try:
+ with quiet_context():
+ with reset_setup_stop_context():
+ run_setup(test_setup_py, ['install'])
+ except SandboxViolation:
+ self.fail('Installation caused SandboxViolation')
+
+
+class TestSetupRequires(unittest.TestCase):
+
+ def test_setup_requires_honors_fetch_params(self):
+ """
+ When easy_install installs a source distribution which specifies
+ setup_requires, it should honor the fetch parameters (such as
+ allow-hosts, index-url, and find-links).
+ """
+ # set up a server which will simulate an alternate package index.
+ p_index = setuptools.tests.server.MockServer()
+ p_index.start()
+ netloc = 1
+ p_index_loc = urlparse(p_index.url)[netloc]
+ if p_index_loc.endswith(':0'):
+ # Some platforms (Jython) don't find a port to which to bind,
+ # so skip this test for them.
+ return
+ with quiet_context():
+ # create an sdist that has a build-time dependency.
+ with TestSetupRequires.create_sdist() as dist_file:
+ with tempdir_context() as temp_install_dir:
+ with environment_context(PYTHONPATH=temp_install_dir):
+ ei_params = ['--index-url', p_index.url,
+ '--allow-hosts', p_index_loc,
+ '--exclude-scripts', '--install-dir', temp_install_dir,
+ dist_file]
+ with reset_setup_stop_context():
+ with argv_context(['easy_install']):
+ # attempt to install the dist. It should fail because
+ # it doesn't exist.
+ self.assertRaises(SystemExit,
+ easy_install_pkg.main, ei_params)
+ # there should have been two or three requests to the server
+ # (three happens on Python 3.3a)
+ self.assertTrue(2 <= len(p_index.requests) <= 3)
+ self.assertEqual(p_index.requests[0].path, '/does-not-exist/')
+
+ @staticmethod
+ @contextlib.contextmanager
+ def create_sdist():
+ """
+ Return an sdist with a setup_requires dependency (of something that
+ doesn't exist)
+ """
+ with tempdir_context() as dir:
+ dist_path = os.path.join(dir, 'setuptools-test-fetcher-1.0.tar.gz')
+ make_trivial_sdist(
+ dist_path,
+ textwrap.dedent("""
+ import setuptools
+ setuptools.setup(
+ name="setuptools-test-fetcher",
+ version="1.0",
+ setup_requires = ['does-not-exist'],
+ )
+ """).lstrip())
+ yield dist_path
+
+ def test_setup_requires_overrides_version_conflict(self):
+ """
+ Regression test for issue #323.
+
+ Ensures that a distribution's setup_requires requirements can still be
+ installed and used locally even if a conflicting version of that
+ requirement is already on the path.
+ """
+
+ pr_state = pkg_resources.__getstate__()
+ fake_dist = PRDistribution('does-not-matter', project_name='foobar',
+ version='0.0')
+ working_set.add(fake_dist)
+
+ try:
+ with tempdir_context() as temp_dir:
+ test_pkg = create_setup_requires_package(temp_dir)
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ with quiet_context() as (stdout, stderr):
+ with reset_setup_stop_context():
+ try:
+ # Don't even need to install the package, just
+ # running the setup.py at all is sufficient
+ run_setup(test_setup_py, ['--name'])
+ except VersionConflict:
+ self.fail('Installing setup.py requirements '
+ 'caused a VersionConflict')
+
+ lines = stdout.readlines()
+ self.assertTrue(len(lines) > 0)
+ self.assertTrue(lines[-1].strip(), 'test_pkg')
+ finally:
+ pkg_resources.__setstate__(pr_state)
+
+
+def create_setup_requires_package(path):
+ """Creates a source tree under path for a trivial test package that has a
+ single requirement in setup_requires--a tarball for that requirement is
+ also created and added to the dependency_links argument.
+ """
+
+ test_setup_attrs = {
+ 'name': 'test_pkg', 'version': '0.0',
+ 'setup_requires': ['foobar==0.1'],
+ 'dependency_links': [os.path.abspath(path)]
+ }
+
+ test_pkg = os.path.join(path, 'test_pkg')
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ os.mkdir(test_pkg)
+
+ f = open(test_setup_py, 'w')
+ f.write(textwrap.dedent("""\
+ import setuptools
+ setuptools.setup(**%r)
+ """ % test_setup_attrs))
+ f.close()
+
+ foobar_path = os.path.join(path, 'foobar-0.1.tar.gz')
+ make_trivial_sdist(
+ foobar_path,
+ textwrap.dedent("""\
+ import setuptools
+ setuptools.setup(
+ name='foobar',
+ version='0.1'
+ )
+ """))
+
+ return test_pkg
+
+
+def make_trivial_sdist(dist_path, setup_py):
+ """Create a simple sdist tarball at dist_path, containing just a
+ setup.py, the contents of which are provided by the setup_py string.
+ """
+
+ setup_py_file = tarfile.TarInfo(name='setup.py')
+ try:
+ # Python 3 (StringIO gets converted to io module)
+ MemFile = BytesIO
+ except AttributeError:
+ MemFile = StringIO
+ setup_py_bytes = MemFile(setup_py.encode('utf-8'))
+ setup_py_file.size = len(setup_py_bytes.getvalue())
+ dist = tarfile.open(dist_path, 'w:gz')
+ try:
+ dist.addfile(setup_py_file, fileobj=setup_py_bytes)
+ finally:
+ dist.close()
+
+
+@contextlib.contextmanager
+def tempdir_context(cd=lambda dir:None):
+ temp_dir = tempfile.mkdtemp()
+ orig_dir = os.getcwd()
+ try:
+ cd(temp_dir)
+ yield temp_dir
+ finally:
+ cd(orig_dir)
+ shutil.rmtree(temp_dir)
+
+@contextlib.contextmanager
+def environment_context(**updates):
+ old_env = os.environ.copy()
+ os.environ.update(updates)
+ try:
+ yield
+ finally:
+ for key in updates:
+ del os.environ[key]
+ os.environ.update(old_env)
+
+@contextlib.contextmanager
+def argv_context(repl):
+ old_argv = sys.argv[:]
+ sys.argv[:] = repl
+ yield
+ sys.argv[:] = old_argv
+
+@contextlib.contextmanager
+def reset_setup_stop_context():
+ """
+ When the setuptools tests are run using setup.py test, and then
+ one wants to invoke another setup() command (such as easy_install)
+ within those tests, it's necessary to reset the global variable
+ in distutils.core so that the setup() command will run naturally.
+ """
+ setup_stop_after = distutils.core._setup_stop_after
+ distutils.core._setup_stop_after = None
+ yield
+ distutils.core._setup_stop_after = setup_stop_after
+
+
+@contextlib.contextmanager
+def quiet_context():
+ """
+ Redirect stdout/stderr to StringIO objects to prevent console output from
+ distutils commands.
+ """
+
+ old_stdout = sys.stdout
+ old_stderr = sys.stderr
+ new_stdout = sys.stdout = StringIO()
+ new_stderr = sys.stderr = StringIO()
+ try:
+ yield new_stdout, new_stderr
+ finally:
+ new_stdout.seek(0)
+ new_stderr.seek(0)
+ sys.stdout = old_stdout
+ sys.stderr = old_stderr
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
new file mode 100644
index 00000000..27854366
--- /dev/null
+++ b/setuptools/tests/test_egg_info.py
@@ -0,0 +1,173 @@
+
+import os
+import sys
+import tempfile
+import shutil
+import unittest
+
+import pkg_resources
+import warnings
+from setuptools.command import egg_info
+from setuptools import svn_utils
+from setuptools.tests import environment, test_svn
+from setuptools.tests.py26compat import skipIf
+
+ENTRIES_V10 = pkg_resources.resource_string(__name__, 'entries-v10')
+"An entries file generated with svn 1.6.17 against the legacy Setuptools repo"
+
+
+class TestEggInfo(unittest.TestCase):
+
+ def setUp(self):
+ self.test_dir = tempfile.mkdtemp()
+ os.mkdir(os.path.join(self.test_dir, '.svn'))
+
+ self.old_cwd = os.getcwd()
+ os.chdir(self.test_dir)
+
+ def tearDown(self):
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.test_dir)
+
+ def _write_entries(self, entries):
+ fn = os.path.join(self.test_dir, '.svn', 'entries')
+ entries_f = open(fn, 'wb')
+ entries_f.write(entries)
+ entries_f.close()
+
+ @skipIf(not test_svn._svn_check, "No SVN to text, in the first place")
+ def test_version_10_format(self):
+ """
+ """
+ #keeping this set for 1.6 is a good check on the get_svn_revision
+ #to ensure I return using svnversion what would had been returned
+ version_str = svn_utils.SvnInfo.get_svn_version()
+ version = [int(x) for x in version_str.split('.')[:2]]
+ if version != [1, 6]:
+ if hasattr(self, 'skipTest'):
+ self.skipTest('')
+ else:
+ sys.stderr.write('\n Skipping due to SVN Version\n')
+ return
+
+ self._write_entries(ENTRIES_V10)
+ rev = egg_info.egg_info.get_svn_revision()
+ self.assertEqual(rev, '89000')
+
+ def test_version_10_format_legacy_parser(self):
+ """
+ """
+ path_variable = None
+ for env in os.environ:
+ if env.lower() == 'path':
+ path_variable = env
+
+ if path_variable:
+ old_path = os.environ[path_variable]
+ os.environ[path_variable] = ''
+ #catch_warnings not available until py26
+ warning_filters = warnings.filters
+ warnings.filters = warning_filters[:]
+ try:
+ warnings.simplefilter("ignore", DeprecationWarning)
+ self._write_entries(ENTRIES_V10)
+ rev = egg_info.egg_info.get_svn_revision()
+ finally:
+ #restore the warning filters
+ warnings.filters = warning_filters
+ #restore the os path
+ if path_variable:
+ os.environ[path_variable] = old_path
+
+ self.assertEqual(rev, '89000')
+
+DUMMY_SOURCE_TXT = """CHANGES.txt
+CONTRIBUTORS.txt
+HISTORY.txt
+LICENSE
+MANIFEST.in
+README.txt
+setup.py
+dummy/__init__.py
+dummy/test.txt
+dummy.egg-info/PKG-INFO
+dummy.egg-info/SOURCES.txt
+dummy.egg-info/dependency_links.txt
+dummy.egg-info/top_level.txt"""
+
+
+class TestSvnDummy(environment.ZippedEnvironment):
+
+ def setUp(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ if not version: # None or Empty
+ return None
+
+ self.base_version = tuple([int(x) for x in version.split('.')][:2])
+
+ if not self.base_version:
+ raise ValueError('No SVN tools installed')
+ elif self.base_version < (1, 3):
+ raise ValueError('Insufficient SVN Version %s' % version)
+ elif self.base_version >= (1, 9):
+ #trying the latest version
+ self.base_version = (1, 8)
+
+ self.dataname = "dummy%i%i" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvnDummy, self).setUp()
+
+ @skipIf(not test_svn._svn_check, "No SVN to text, in the first place")
+ def test_sources(self):
+ code, data = environment.run_setup_py(["sdist"],
+ pypath=self.old_cwd,
+ data_stream=1)
+ if code:
+ raise AssertionError(data)
+
+ sources = os.path.join('dummy.egg-info', 'SOURCES.txt')
+ infile = open(sources, 'r')
+ try:
+ read_contents = infile.read()
+ finally:
+ infile.close()
+ del infile
+
+ self.assertEqual(DUMMY_SOURCE_TXT, read_contents)
+
+ return data
+
+
+class TestSvnDummyLegacy(environment.ZippedEnvironment):
+
+ def setUp(self):
+ self.base_version = (1, 6)
+ self.dataname = "dummy%i%i" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvnDummyLegacy, self).setUp()
+
+ def test_sources(self):
+ code, data = environment.run_setup_py(["sdist"],
+ pypath=self.old_cwd,
+ path="",
+ data_stream=1)
+ if code:
+ raise AssertionError(data)
+
+ sources = os.path.join('dummy.egg-info', 'SOURCES.txt')
+ infile = open(sources, 'r')
+ try:
+ read_contents = infile.read()
+ finally:
+ infile.close()
+ del infile
+
+ self.assertEqual(DUMMY_SOURCE_TXT, read_contents)
+
+ return data
+
+
+def test_suite():
+ return unittest.defaultTestLoader.loadTestsFromName(__name__)
diff --git a/setuptools/tests/test_markerlib.py b/setuptools/tests/test_markerlib.py
new file mode 100644
index 00000000..dae71cba
--- /dev/null
+++ b/setuptools/tests/test_markerlib.py
@@ -0,0 +1,68 @@
+import os
+import unittest
+from setuptools.tests.py26compat import skipIf
+
+try:
+ import ast
+except ImportError:
+ pass
+
+class TestMarkerlib(unittest.TestCase):
+
+ @skipIf('ast' not in globals(),
+ "ast not available (Python < 2.6?)")
+ def test_markers(self):
+ from _markerlib import interpret, default_environment, compile
+
+ os_name = os.name
+
+ self.assertTrue(interpret(""))
+
+ self.assertTrue(interpret("os.name != 'buuuu'"))
+ self.assertTrue(interpret("os_name != 'buuuu'"))
+ self.assertTrue(interpret("python_version > '1.0'"))
+ self.assertTrue(interpret("python_version < '5.0'"))
+ self.assertTrue(interpret("python_version <= '5.0'"))
+ self.assertTrue(interpret("python_version >= '1.0'"))
+ self.assertTrue(interpret("'%s' in os.name" % os_name))
+ self.assertTrue(interpret("'%s' in os_name" % os_name))
+ self.assertTrue(interpret("'buuuu' not in os.name"))
+
+ self.assertFalse(interpret("os.name == 'buuuu'"))
+ self.assertFalse(interpret("os_name == 'buuuu'"))
+ self.assertFalse(interpret("python_version < '1.0'"))
+ self.assertFalse(interpret("python_version > '5.0'"))
+ self.assertFalse(interpret("python_version >= '5.0'"))
+ self.assertFalse(interpret("python_version <= '1.0'"))
+ self.assertFalse(interpret("'%s' not in os.name" % os_name))
+ self.assertFalse(interpret("'buuuu' in os.name and python_version >= '5.0'"))
+ self.assertFalse(interpret("'buuuu' in os_name and python_version >= '5.0'"))
+
+ environment = default_environment()
+ environment['extra'] = 'test'
+ self.assertTrue(interpret("extra == 'test'", environment))
+ self.assertFalse(interpret("extra == 'doc'", environment))
+
+ def raises_nameError():
+ try:
+ interpret("python.version == '42'")
+ except NameError:
+ pass
+ else:
+ raise Exception("Expected NameError")
+
+ raises_nameError()
+
+ def raises_syntaxError():
+ try:
+ interpret("(x for x in (4,))")
+ except SyntaxError:
+ pass
+ else:
+ raise Exception("Expected SyntaxError")
+
+ raises_syntaxError()
+
+ statement = "python_version == '5'"
+ self.assertEqual(compile(statement).__doc__, statement)
+
diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py
new file mode 100644
index 00000000..664566a3
--- /dev/null
+++ b/setuptools/tests/test_packageindex.py
@@ -0,0 +1,203 @@
+"""Package Index Tests
+"""
+import sys
+import os
+import unittest
+import pkg_resources
+from setuptools.compat import urllib2, httplib, HTTPError, unicode, pathname2url
+import distutils.errors
+import setuptools.package_index
+from setuptools.tests.server import IndexServer
+
+class TestPackageIndex(unittest.TestCase):
+
+ def test_bad_url_bad_port(self):
+ index = setuptools.package_index.PackageIndex()
+ url = 'http://127.0.0.1:0/nonesuch/test_package_index'
+ try:
+ v = index.open_url(url)
+ except Exception:
+ v = sys.exc_info()[1]
+ self.assertTrue(url in str(v))
+ else:
+ self.assertTrue(isinstance(v, HTTPError))
+
+ def test_bad_url_typo(self):
+ # issue 16
+ # easy_install inquant.contentmirror.plone breaks because of a typo
+ # in its home URL
+ index = setuptools.package_index.PackageIndex(
+ hosts=('www.example.com',)
+ )
+
+ url = 'url:%20https://svn.plone.org/svn/collective/inquant.contentmirror.plone/trunk'
+ try:
+ v = index.open_url(url)
+ except Exception:
+ v = sys.exc_info()[1]
+ self.assertTrue(url in str(v))
+ else:
+ self.assertTrue(isinstance(v, HTTPError))
+
+ def test_bad_url_bad_status_line(self):
+ index = setuptools.package_index.PackageIndex(
+ hosts=('www.example.com',)
+ )
+
+ def _urlopen(*args):
+ raise httplib.BadStatusLine('line')
+
+ index.opener = _urlopen
+ url = 'http://example.com'
+ try:
+ v = index.open_url(url)
+ except Exception:
+ v = sys.exc_info()[1]
+ self.assertTrue('line' in str(v))
+ else:
+ raise AssertionError('Should have raise here!')
+
+ def test_bad_url_double_scheme(self):
+ """
+ A bad URL with a double scheme should raise a DistutilsError.
+ """
+ index = setuptools.package_index.PackageIndex(
+ hosts=('www.example.com',)
+ )
+
+ # issue 20
+ url = 'http://http://svn.pythonpaste.org/Paste/wphp/trunk'
+ try:
+ index.open_url(url)
+ except distutils.errors.DistutilsError:
+ error = sys.exc_info()[1]
+ msg = unicode(error)
+ assert 'nonnumeric port' in msg or 'getaddrinfo failed' in msg or 'Name or service not known' in msg
+ return
+ raise RuntimeError("Did not raise")
+
+ def test_bad_url_screwy_href(self):
+ index = setuptools.package_index.PackageIndex(
+ hosts=('www.example.com',)
+ )
+
+ # issue #160
+ if sys.version_info[0] == 2 and sys.version_info[1] == 7:
+ # this should not fail
+ url = 'http://example.com'
+ page = ('<a href="http://www.famfamfam.com]('
+ 'http://www.famfamfam.com/">')
+ index.process_index(url, page)
+
+ def test_url_ok(self):
+ index = setuptools.package_index.PackageIndex(
+ hosts=('www.example.com',)
+ )
+ url = 'file:///tmp/test_package_index'
+ self.assertTrue(index.url_ok(url, True))
+
+ def test_links_priority(self):
+ """
+ Download links from the pypi simple index should be used before
+ external download links.
+ https://bitbucket.org/tarek/distribute/issue/163
+
+ Usecase :
+ - someone uploads a package on pypi, a md5 is generated
+ - someone manually copies this link (with the md5 in the url) onto an
+ external page accessible from the package page.
+ - someone reuploads the package (with a different md5)
+ - while easy_installing, an MD5 error occurs because the external link
+ is used
+ -> Setuptools should use the link from pypi, not the external one.
+ """
+ if sys.platform.startswith('java'):
+ # Skip this test on jython because binding to :0 fails
+ return
+
+ # start an index server
+ server = IndexServer()
+ server.start()
+ index_url = server.base_url() + 'test_links_priority/simple/'
+
+ # scan a test index
+ pi = setuptools.package_index.PackageIndex(index_url)
+ requirement = pkg_resources.Requirement.parse('foobar')
+ pi.find_packages(requirement)
+ server.stop()
+
+ # the distribution has been found
+ self.assertTrue('foobar' in pi)
+ # we have only one link, because links are compared without md5
+ self.assertTrue(len(pi['foobar'])==1)
+ # the link should be from the index
+ self.assertTrue('correct_md5' in pi['foobar'][0].location)
+
+ def test_parse_bdist_wininst(self):
+ self.assertEqual(setuptools.package_index.parse_bdist_wininst(
+ 'reportlab-2.5.win32-py2.4.exe'), ('reportlab-2.5', '2.4', 'win32'))
+ self.assertEqual(setuptools.package_index.parse_bdist_wininst(
+ 'reportlab-2.5.win32.exe'), ('reportlab-2.5', None, 'win32'))
+ self.assertEqual(setuptools.package_index.parse_bdist_wininst(
+ 'reportlab-2.5.win-amd64-py2.7.exe'), ('reportlab-2.5', '2.7', 'win-amd64'))
+ self.assertEqual(setuptools.package_index.parse_bdist_wininst(
+ 'reportlab-2.5.win-amd64.exe'), ('reportlab-2.5', None, 'win-amd64'))
+
+ def test__vcs_split_rev_from_url(self):
+ """
+ Test the basic usage of _vcs_split_rev_from_url
+ """
+ vsrfu = setuptools.package_index.PackageIndex._vcs_split_rev_from_url
+ url, rev = vsrfu('https://example.com/bar@2995')
+ self.assertEqual(url, 'https://example.com/bar')
+ self.assertEqual(rev, '2995')
+
+ def test_local_index(self):
+ """
+ local_open should be able to read an index from the file system.
+ """
+ f = open('index.html', 'w')
+ f.write('<div>content</div>')
+ f.close()
+ try:
+ url = 'file:' + pathname2url(os.getcwd()) + '/'
+ res = setuptools.package_index.local_open(url)
+ finally:
+ os.remove('index.html')
+ assert 'content' in res.read()
+
+
+class TestContentCheckers(unittest.TestCase):
+
+ def test_md5(self):
+ checker = setuptools.package_index.HashChecker.from_url(
+ 'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478')
+ checker.feed('You should probably not be using MD5'.encode('ascii'))
+ self.assertEqual(checker.hash.hexdigest(),
+ 'f12895fdffbd45007040d2e44df98478')
+ self.assertTrue(checker.is_valid())
+
+ def test_other_fragment(self):
+ "Content checks should succeed silently if no hash is present"
+ checker = setuptools.package_index.HashChecker.from_url(
+ 'http://foo/bar#something%20completely%20different')
+ checker.feed('anything'.encode('ascii'))
+ self.assertTrue(checker.is_valid())
+
+ def test_blank_md5(self):
+ "Content checks should succeed if a hash is empty"
+ checker = setuptools.package_index.HashChecker.from_url(
+ 'http://foo/bar#md5=')
+ checker.feed('anything'.encode('ascii'))
+ self.assertTrue(checker.is_valid())
+
+ def test_get_hash_name_md5(self):
+ checker = setuptools.package_index.HashChecker.from_url(
+ 'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478')
+ self.assertEqual(checker.hash_name, 'md5')
+
+ def test_report(self):
+ checker = setuptools.package_index.HashChecker.from_url(
+ 'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478')
+ rep = checker.report(lambda x: x, 'My message about %s')
+ self.assertEqual(rep, 'My message about md5')
diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py
index ee3de5c5..c9fcf76c 100644
--- a/setuptools/tests/test_resources.py
+++ b/setuptools/tests/test_resources.py
@@ -1,9 +1,38 @@
-from unittest import TestCase, makeSuite
-from pkg_resources import *
-import pkg_resources, sys
-from sets import ImmutableSet
-
-class Metadata(EmptyProvider):
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# NOTE: the shebang and encoding lines are for ScriptHeaderTests do not remove
+
+import os
+import sys
+import tempfile
+import shutil
+from unittest import TestCase
+
+import pkg_resources
+from pkg_resources import (parse_requirements, VersionConflict, parse_version,
+ Distribution, EntryPoint, Requirement, safe_version, safe_name,
+ WorkingSet)
+
+from setuptools.command.easy_install import (get_script_header, is_sh,
+ nt_quote_arg)
+from setuptools.compat import StringIO, iteritems
+
+try:
+ frozenset
+except NameError:
+ from sets import ImmutableSet as frozenset
+
+def safe_repr(obj, short=False):
+ """ copied from Python2.7"""
+ try:
+ result = repr(obj)
+ except Exception:
+ result = object.__repr__(obj)
+ if not short or len(result) < pkg_resources._MAX_LENGTH:
+ return result
+ return result[:pkg_resources._MAX_LENGTH] + ' [truncated]...'
+
+class Metadata(pkg_resources.EmptyProvider):
"""Mock object to return metadata as if from an on-disk distribution"""
def __init__(self,*pairs):
@@ -16,29 +45,26 @@ class Metadata(EmptyProvider):
return self.metadata[name]
def get_metadata_lines(self,name):
- return yield_lines(self.get_metadata(name))
+ return pkg_resources.yield_lines(self.get_metadata(name))
+dist_from_fn = pkg_resources.Distribution.from_filename
class DistroTests(TestCase):
def testCollection(self):
# empty path should produce no distributions
- ad = Environment([], python=None)
+ ad = pkg_resources.Environment([], platform=None, python=None)
self.assertEqual(list(ad), [])
self.assertEqual(ad['FooPkg'],[])
-
- ad.add(Distribution.from_filename("FooPkg-1.3_1.egg"))
- ad.add(Distribution.from_filename("FooPkg-1.4-py2.4-win32.egg"))
- ad.add(Distribution.from_filename("FooPkg-1.2-py2.4.egg"))
+ ad.add(dist_from_fn("FooPkg-1.3_1.egg"))
+ ad.add(dist_from_fn("FooPkg-1.4-py2.4-win32.egg"))
+ ad.add(dist_from_fn("FooPkg-1.2-py2.4.egg"))
# Name is in there now
- self.failUnless(ad['FooPkg'])
-
+ self.assertTrue(ad['FooPkg'])
# But only 1 package
self.assertEqual(list(ad), ['foopkg'])
-
-
# Distributions sort by version
self.assertEqual(
[dist.version for dist in ad['FooPkg']], ['1.4','1.3-1','1.2']
@@ -49,27 +75,33 @@ class DistroTests(TestCase):
[dist.version for dist in ad['FooPkg']], ['1.4','1.2']
)
# And inserting adds them in order
- ad.add(Distribution.from_filename("FooPkg-1.9.egg"))
+ ad.add(dist_from_fn("FooPkg-1.9.egg"))
self.assertEqual(
[dist.version for dist in ad['FooPkg']], ['1.9','1.4','1.2']
)
ws = WorkingSet([])
- foo12 = Distribution.from_filename("FooPkg-1.2-py2.4.egg")
- foo14 = Distribution.from_filename("FooPkg-1.4-py2.4-win32.egg")
+ foo12 = dist_from_fn("FooPkg-1.2-py2.4.egg")
+ foo14 = dist_from_fn("FooPkg-1.4-py2.4-win32.egg")
req, = parse_requirements("FooPkg>=1.3")
# Nominal case: no distros on path, should yield all applicable
self.assertEqual(ad.best_match(req,ws).version, '1.9')
# If a matching distro is already installed, should return only that
- ws.add(foo14); self.assertEqual(ad.best_match(req,ws).version, '1.4')
+ ws.add(foo14)
+ self.assertEqual(ad.best_match(req,ws).version, '1.4')
# If the first matching distro is unsuitable, it's a version conflict
- ws = WorkingSet([]); ws.add(foo12); ws.add(foo14)
+ ws = WorkingSet([])
+ ws.add(foo12)
+ ws.add(foo14)
self.assertRaises(VersionConflict, ad.best_match, req, ws)
# If more than one match on the path, the first one takes precedence
- ws = WorkingSet([]); ws.add(foo14); ws.add(foo12); ws.add(foo14);
+ ws = WorkingSet([])
+ ws.add(foo14)
+ ws.add(foo12)
+ ws.add(foo14)
self.assertEqual(ad.best_match(req,ws).version, '1.4')
def checkFooPkg(self,d):
@@ -92,7 +124,9 @@ class DistroTests(TestCase):
self.assertEqual(d.platform, None)
def testDistroParse(self):
- d = Distribution.from_filename("FooPkg-1.3_1-py2.4-win32.egg")
+ d = dist_from_fn("FooPkg-1.3_1-py2.4-win32.egg")
+ self.checkFooPkg(d)
+ d = dist_from_fn("FooPkg-1.3_1-py2.4-win32.egg-info")
self.checkFooPkg(d)
def testDistroMetadata(self):
@@ -104,7 +138,6 @@ class DistroTests(TestCase):
)
self.checkFooPkg(d)
-
def distRequires(self, txt):
return Distribution("/foo", metadata=Metadata(('depends.txt', txt)))
@@ -118,34 +151,34 @@ class DistroTests(TestCase):
for v in "Twisted>=1.5", "Twisted>=1.5\nZConfig>=2.0":
self.checkRequires(self.distRequires(v), v)
-
-
-
def testResolve(self):
- ad = Environment([]); ws = WorkingSet([])
-
+ ad = pkg_resources.Environment([])
+ ws = WorkingSet([])
# Resolving no requirements -> nothing to install
- self.assertEqual( list(ws.resolve([],ad)), [] )
-
+ self.assertEqual(list(ws.resolve([],ad)), [])
# Request something not in the collection -> DistributionNotFound
self.assertRaises(
- DistributionNotFound, ws.resolve, parse_requirements("Foo"), ad
+ pkg_resources.DistributionNotFound, ws.resolve, parse_requirements("Foo"), ad
)
-
Foo = Distribution.from_filename(
"/foo_dir/Foo-1.2.egg",
metadata=Metadata(('depends.txt', "[bar]\nBaz>=2.0"))
)
ad.add(Foo)
+ ad.add(Distribution.from_filename("Foo-0.9.egg"))
# Request thing(s) that are available -> list to activate
- self.assertEqual(
- list(ws.resolve(parse_requirements("Foo"), ad)), [Foo]
- )
+ for i in range(3):
+ targets = list(ws.resolve(parse_requirements("Foo"), ad))
+ self.assertEqual(targets, [Foo])
+ list(map(ws.add,targets))
+ self.assertRaises(VersionConflict, ws.resolve,
+ parse_requirements("Foo==0.9"), ad)
+ ws = WorkingSet([]) # reset
# Request an extra that causes an unresolved dependency for "Baz"
self.assertRaises(
- DistributionNotFound, ws.resolve,parse_requirements("Foo[bar]"), ad
+ pkg_resources.DistributionNotFound, ws.resolve,parse_requirements("Foo[bar]"), ad
)
Baz = Distribution.from_filename(
"/foo_dir/Baz-2.1.egg", metadata=Metadata(('depends.txt', "Foo"))
@@ -157,10 +190,8 @@ class DistroTests(TestCase):
list(ws.resolve(parse_requirements("Foo[bar]"), ad)), [Foo,Baz]
)
# Requests for conflicting versions produce VersionConflict
- self.assertRaises(
- VersionConflict,
- ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad
- )
+ self.assertRaises(VersionConflict,
+ ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad)
def testDistroDependsOptions(self):
d = self.distRequires("""
@@ -185,22 +216,7 @@ class DistroTests(TestCase):
d,"Twisted>=1.5 fcgiapp>=0.1 ZConfig>=2.0 docutils>=0.3".split(),
["fastcgi", "docgen"]
)
- self.assertRaises(UnknownExtra, d.requires, ["foo"])
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ self.assertRaises(pkg_resources.UnknownExtra, d.requires, ["foo"])
class EntryPointTests(TestCase):
@@ -210,7 +226,7 @@ class EntryPointTests(TestCase):
self.assertEqual(ep.module_name,"setuptools.tests.test_resources")
self.assertEqual(ep.attrs, ("EntryPointTests",))
self.assertEqual(ep.extras, ("x",))
- self.failUnless(ep.load() is EntryPointTests)
+ self.assertTrue(ep.load() is EntryPointTests)
self.assertEqual(
str(ep),
"foo = setuptools.tests.test_resources:EntryPointTests [x]"
@@ -253,19 +269,20 @@ class EntryPointTests(TestCase):
else: raise AssertionError("Should've been bad", ep)
def checkSubMap(self, m):
- self.assertEqual(str(m),
- "{"
- "'feature2': EntryPoint.parse("
- "'feature2 = another.module:SomeClass [extra1,extra2]'), "
- "'feature1': EntryPoint.parse("
- "'feature1 = somemodule:somefunction')"
- "}"
- )
-
+ self.assertEqual(len(m), len(self.submap_expect))
+ for key, ep in iteritems(self.submap_expect):
+ self.assertEqual(repr(m.get(key)), repr(ep))
+
+ submap_expect = dict(
+ feature1=EntryPoint('feature1', 'somemodule', ['somefunction']),
+ feature2=EntryPoint('feature2', 'another.module', ['SomeClass'], ['extra1','extra2']),
+ feature3=EntryPoint('feature3', 'this.module', extras=['something'])
+ )
submap_str = """
# define features for blah blah
feature1 = somemodule:somefunction
feature2 = another.module:SomeClass [extra1,extra2]
+ feature3 = this.module [something]
"""
def testParseList(self):
@@ -277,54 +294,52 @@ class EntryPointTests(TestCase):
def testParseMap(self):
m = EntryPoint.parse_map({'xyz':self.submap_str})
self.checkSubMap(m['xyz'])
- self.assertEqual(m.keys(),['xyz'])
+ self.assertEqual(list(m.keys()),['xyz'])
m = EntryPoint.parse_map("[xyz]\n"+self.submap_str)
self.checkSubMap(m['xyz'])
- self.assertEqual(m.keys(),['xyz'])
+ self.assertEqual(list(m.keys()),['xyz'])
self.assertRaises(ValueError, EntryPoint.parse_map, ["[xyz]", "[xyz]"])
self.assertRaises(ValueError, EntryPoint.parse_map, self.submap_str)
-
class RequirementsTests(TestCase):
def testBasics(self):
r = Requirement.parse("Twisted>=1.2")
self.assertEqual(str(r),"Twisted>=1.2")
self.assertEqual(repr(r),"Requirement.parse('Twisted>=1.2')")
- self.assertEqual(r, Requirement("Twisted", [('>=','1.2')]))
- self.assertEqual(r, Requirement("twisTed", [('>=','1.2')]))
- self.assertNotEqual(r, Requirement("Twisted", [('>=','2.0')]))
- self.assertNotEqual(r, Requirement("Zope", [('>=','1.2')]))
- self.assertNotEqual(r, Requirement("Zope", [('>=','3.0')]))
+ self.assertEqual(r, Requirement("Twisted", [('>=','1.2')], ()))
+ self.assertEqual(r, Requirement("twisTed", [('>=','1.2')], ()))
+ self.assertNotEqual(r, Requirement("Twisted", [('>=','2.0')], ()))
+ self.assertNotEqual(r, Requirement("Zope", [('>=','1.2')], ()))
+ self.assertNotEqual(r, Requirement("Zope", [('>=','3.0')], ()))
self.assertNotEqual(r, Requirement.parse("Twisted[extras]>=1.2"))
def testOrdering(self):
- r1 = Requirement("Twisted", [('==','1.2c1'),('>=','1.2')])
- r2 = Requirement("Twisted", [('>=','1.2'),('==','1.2c1')])
+ r1 = Requirement("Twisted", [('==','1.2c1'),('>=','1.2')], ())
+ r2 = Requirement("Twisted", [('>=','1.2'),('==','1.2c1')], ())
self.assertEqual(r1,r2)
self.assertEqual(str(r1),str(r2))
self.assertEqual(str(r2),"Twisted==1.2c1,>=1.2")
def testBasicContains(self):
- r = Requirement("Twisted", [('>=','1.2')])
+ r = Requirement("Twisted", [('>=','1.2')], ())
foo_dist = Distribution.from_filename("FooPkg-1.3_1.egg")
- twist11 = Distribution.from_filename("Twisted-1.1.egg")
- twist12 = Distribution.from_filename("Twisted-1.2.egg")
- self.failUnless(parse_version('1.2') in r)
- self.failUnless(parse_version('1.1') not in r)
- self.failUnless('1.2' in r)
- self.failUnless('1.1' not in r)
- self.failUnless(foo_dist not in r)
- self.failUnless(twist11 not in r)
- self.failUnless(twist12 in r)
+ twist11 = Distribution.from_filename("Twisted-1.1.egg")
+ twist12 = Distribution.from_filename("Twisted-1.2.egg")
+ self.assertTrue(parse_version('1.2') in r)
+ self.assertTrue(parse_version('1.1') not in r)
+ self.assertTrue('1.2' in r)
+ self.assertTrue('1.1' not in r)
+ self.assertTrue(foo_dist not in r)
+ self.assertTrue(twist11 not in r)
+ self.assertTrue(twist12 in r)
def testAdvancedContains(self):
r, = parse_requirements("Foo>=1.2,<=1.3,==1.9,>2.0,!=2.5,<3.0,==4.5")
for v in ('1.2','1.2.2','1.3','1.9','2.0.1','2.3','2.6','3.0c1','4.5'):
- self.failUnless(v in r, (v,r))
+ self.assertTrue(v in r, (v,r))
for v in ('1.2c1','1.3.1','1.5','1.9.1','2.0','2.5','3.0','4.0'):
- self.failUnless(v not in r, (v,r))
-
+ self.assertTrue(v not in r, (v,r))
def testOptionsAndHashing(self):
r1 = Requirement.parse("Twisted[foo,bar]>=1.2")
@@ -333,38 +348,41 @@ class RequirementsTests(TestCase):
self.assertEqual(r1,r2)
self.assertEqual(r1,r3)
self.assertEqual(r1.extras, ("foo","bar"))
- self.assertEqual(r2.extras, ("bar","FOO"))
+ self.assertEqual(r2.extras, ("bar","foo")) # extras are normalized
self.assertEqual(hash(r1), hash(r2))
self.assertEqual(
hash(r1), hash(("twisted", ((">=",parse_version("1.2")),),
- ImmutableSet(["foo","bar"])))
+ frozenset(["foo","bar"])))
)
def testVersionEquality(self):
- r1 = Requirement.parse("setuptools==0.3a2")
- r2 = Requirement.parse("setuptools!=0.3a4")
+ r1 = Requirement.parse("foo==0.3a2")
+ r2 = Requirement.parse("foo!=0.3a4")
d = Distribution.from_filename
- self.failIf(d("setuptools-0.3a4.egg") in r1)
- self.failIf(d("setuptools-0.3a1.egg") in r1)
- self.failIf(d("setuptools-0.3a4.egg") in r2)
-
- self.failUnless(d("setuptools-0.3a2.egg") in r1)
- self.failUnless(d("setuptools-0.3a2.egg") in r2)
- self.failUnless(d("setuptools-0.3a3.egg") in r2)
- self.failUnless(d("setuptools-0.3a5.egg") in r2)
-
-
-
-
-
-
-
-
-
+ self.assertTrue(d("foo-0.3a4.egg") not in r1)
+ self.assertTrue(d("foo-0.3a1.egg") not in r1)
+ self.assertTrue(d("foo-0.3a4.egg") not in r2)
+ self.assertTrue(d("foo-0.3a2.egg") in r1)
+ self.assertTrue(d("foo-0.3a2.egg") in r2)
+ self.assertTrue(d("foo-0.3a3.egg") in r2)
+ self.assertTrue(d("foo-0.3a5.egg") in r2)
+ def testSetuptoolsProjectName(self):
+ """
+ The setuptools project should implement the setuptools package.
+ """
+ self.assertEqual(
+ Requirement.parse('setuptools').project_name, 'setuptools')
+ # setuptools 0.7 and higher means setuptools.
+ self.assertEqual(
+ Requirement.parse('setuptools == 0.7').project_name, 'setuptools')
+ self.assertEqual(
+ Requirement.parse('setuptools == 0.7a1').project_name, 'setuptools')
+ self.assertEqual(
+ Requirement.parse('setuptools >= 0.7').project_name, 'setuptools')
class ParseTests(TestCase):
@@ -380,9 +398,7 @@ class ParseTests(TestCase):
self.assertEqual(list(pkg_resources.yield_lines(inp)),out)
def testSplitting(self):
- self.assertEqual(
- list(
- pkg_resources.split_sections("""
+ sample = """
x
[Y]
z
@@ -395,8 +411,7 @@ class ParseTests(TestCase):
[q]
v
"""
- )
- ),
+ self.assertEqual(list(pkg_resources.split_sections(sample)),
[(None,["x"]), ("Y",["z","a"]), ("b",["c"]), ("d",[]), ("q",["v"])]
)
self.assertRaises(ValueError,list,pkg_resources.split_sections("[foo"))
@@ -406,7 +421,7 @@ class ParseTests(TestCase):
self.assertEqual(safe_name("WSGI Utils"), "WSGI-Utils")
self.assertEqual(safe_name("WSGI Utils"), "WSGI-Utils")
self.assertEqual(safe_name("Money$$$Maker"), "Money-Maker")
- self.assertEqual(safe_name("peak.web"), "peak-web")
+ self.assertNotEqual(safe_name("peak.web"), "peak-web")
def testSafeVersion(self):
self.assertEqual(safe_version("1.2-1"), "1.2-1")
@@ -418,15 +433,15 @@ class ParseTests(TestCase):
def testSimpleRequirements(self):
self.assertEqual(
list(parse_requirements('Twis-Ted>=1.2-1')),
- [Requirement('Twis-Ted',[('>=','1.2-1')])]
+ [Requirement('Twis-Ted',[('>=','1.2-1')], ())]
)
self.assertEqual(
list(parse_requirements('Twisted >=1.2, \ # more\n<2.0')),
- [Requirement('Twisted',[('>=','1.2'),('<','2.0')])]
+ [Requirement('Twisted',[('>=','1.2'),('<','2.0')], ())]
)
self.assertEqual(
Requirement.parse("FooBar==1.99a3"),
- Requirement("FooBar", [('==','1.99a3')])
+ Requirement("FooBar", [('==','1.99a3')], ())
)
self.assertRaises(ValueError,Requirement.parse,">=2.3")
self.assertRaises(ValueError,Requirement.parse,"x\\")
@@ -434,25 +449,26 @@ class ParseTests(TestCase):
self.assertRaises(ValueError,Requirement.parse,"X==1\nY==2")
self.assertRaises(ValueError,Requirement.parse,"#")
-
def testVersionEquality(self):
def c(s1,s2):
p1, p2 = parse_version(s1),parse_version(s2)
self.assertEqual(p1,p2, (s1,s2,p1,p2))
+ c('1.2-rc1', '1.2rc1')
c('0.4', '0.4.0')
c('0.4.0.0', '0.4.0')
c('0.4.0-0', '0.4-0')
c('0pl1', '0.0pl1')
c('0pre1', '0.0c1')
c('0.0.0preview1', '0c1')
- c('0.0c1', '0rc1')
- c('1.2a1', '1.2.a.1'); c('1.2...a', '1.2a')
+ c('0.0c1', '0-rc1')
+ c('1.2a1', '1.2.a.1')
+ c('1.2...a', '1.2a')
def testVersionOrdering(self):
def c(s1,s2):
p1, p2 = parse_version(s1),parse_version(s2)
- self.failUnless(p1<p2, (s1,s2,p1,p2))
+ self.assertTrue(p1<p2, (s1,s2,p1,p2))
c('2.1','2.1.1')
c('2a1','2b0')
@@ -470,6 +486,8 @@ class ParseTests(TestCase):
c('0.4', '4.0')
c('0.0.4', '0.4.0')
c('0pl1', '0.4pl1')
+ c('2.1.0-rc1','2.1.0')
+ c('2.1dev','2.1a0')
torture ="""
0.80.1-3 0.80.1-2 0.80.1-1 0.79.9999+0.80.0pre4-1
@@ -482,11 +500,121 @@ class ParseTests(TestCase):
c(v2,v1)
+class ScriptHeaderTests(TestCase):
+ non_ascii_exe = '/Users/José/bin/python'
+ exe_with_spaces = r'C:\Program Files\Python33\python.exe'
+
+ def test_get_script_header(self):
+ if not sys.platform.startswith('java') or not is_sh(sys.executable):
+ # This test is for non-Jython platforms
+ expected = '#!%s\n' % nt_quote_arg(os.path.normpath(sys.executable))
+ self.assertEqual(get_script_header('#!/usr/local/bin/python'),
+ expected)
+ expected = '#!%s -x\n' % nt_quote_arg(os.path.normpath(sys.executable))
+ self.assertEqual(get_script_header('#!/usr/bin/python -x'),
+ expected)
+ self.assertEqual(get_script_header('#!/usr/bin/python',
+ executable=self.non_ascii_exe),
+ '#!%s -x\n' % self.non_ascii_exe)
+ candidate = get_script_header('#!/usr/bin/python',
+ executable=self.exe_with_spaces)
+ self.assertEqual(candidate, '#!"%s"\n' % self.exe_with_spaces)
+
+ def test_get_script_header_jython_workaround(self):
+ # This test doesn't work with Python 3 in some locales
+ if (sys.version_info >= (3,) and os.environ.get("LC_CTYPE")
+ in (None, "C", "POSIX")):
+ return
+
+ class java:
+ class lang:
+ class System:
+ @staticmethod
+ def getProperty(property):
+ return ""
+ sys.modules["java"] = java
+
+ platform = sys.platform
+ sys.platform = 'java1.5.0_13'
+ stdout, stderr = sys.stdout, sys.stderr
+ try:
+ # A mock sys.executable that uses a shebang line (this file)
+ exe = os.path.normpath(os.path.splitext(__file__)[0] + '.py')
+ self.assertEqual(
+ get_script_header('#!/usr/local/bin/python', executable=exe),
+ '#!/usr/bin/env %s\n' % exe)
+
+ # Ensure we generate what is basically a broken shebang line
+ # when there's options, with a warning emitted
+ sys.stdout = sys.stderr = StringIO()
+ self.assertEqual(get_script_header('#!/usr/bin/python -x',
+ executable=exe),
+ '#!%s -x\n' % exe)
+ self.assertTrue('Unable to adapt shebang line' in sys.stdout.getvalue())
+ sys.stdout = sys.stderr = StringIO()
+ self.assertEqual(get_script_header('#!/usr/bin/python',
+ executable=self.non_ascii_exe),
+ '#!%s -x\n' % self.non_ascii_exe)
+ self.assertTrue('Unable to adapt shebang line' in sys.stdout.getvalue())
+ finally:
+ del sys.modules["java"]
+ sys.platform = platform
+ sys.stdout, sys.stderr = stdout, stderr
+
+
+class NamespaceTests(TestCase):
-
-
-
-
-
-
-
+ def setUp(self):
+ self._ns_pkgs = pkg_resources._namespace_packages.copy()
+ self._tmpdir = tempfile.mkdtemp(prefix="tests-setuptools-")
+ os.makedirs(os.path.join(self._tmpdir, "site-pkgs"))
+ self._prev_sys_path = sys.path[:]
+ sys.path.append(os.path.join(self._tmpdir, "site-pkgs"))
+
+ def tearDown(self):
+ shutil.rmtree(self._tmpdir)
+ pkg_resources._namespace_packages = self._ns_pkgs.copy()
+ sys.path = self._prev_sys_path[:]
+
+ def _assertIn(self, member, container):
+ """ assertIn and assertTrue does not exist in Python2.3"""
+ if member not in container:
+ standardMsg = '%s not found in %s' % (safe_repr(member),
+ safe_repr(container))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def test_two_levels_deep(self):
+ """
+ Test nested namespace packages
+ Create namespace packages in the following tree :
+ site-packages-1/pkg1/pkg2
+ site-packages-2/pkg1/pkg2
+ Check both are in the _namespace_packages dict and that their __path__
+ is correct
+ """
+ sys.path.append(os.path.join(self._tmpdir, "site-pkgs2"))
+ os.makedirs(os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"))
+ os.makedirs(os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2"))
+ ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n"
+ for site in ["site-pkgs", "site-pkgs2"]:
+ pkg1_init = open(os.path.join(self._tmpdir, site,
+ "pkg1", "__init__.py"), "w")
+ pkg1_init.write(ns_str)
+ pkg1_init.close()
+ pkg2_init = open(os.path.join(self._tmpdir, site,
+ "pkg1", "pkg2", "__init__.py"), "w")
+ pkg2_init.write(ns_str)
+ pkg2_init.close()
+ import pkg1
+ self._assertIn("pkg1", pkg_resources._namespace_packages.keys())
+ try:
+ import pkg1.pkg2
+ except ImportError:
+ self.fail("Setuptools tried to import the parent namespace package")
+ # check the _namespace_packages dict
+ self._assertIn("pkg1.pkg2", pkg_resources._namespace_packages.keys())
+ self.assertEqual(pkg_resources._namespace_packages["pkg1"], ["pkg1.pkg2"])
+ # check the __path__ attribute contains both paths
+ self.assertEqual(pkg1.pkg2.__path__, [
+ os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"),
+ os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2")])
diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py
new file mode 100644
index 00000000..3dad1376
--- /dev/null
+++ b/setuptools/tests/test_sandbox.py
@@ -0,0 +1,79 @@
+"""develop tests
+"""
+import sys
+import os
+import shutil
+import unittest
+import tempfile
+import types
+
+import pkg_resources
+import setuptools.sandbox
+from setuptools.sandbox import DirectorySandbox, SandboxViolation
+
+def has_win32com():
+ """
+ Run this to determine if the local machine has win32com, and if it
+ does, include additional tests.
+ """
+ if not sys.platform.startswith('win32'):
+ return False
+ try:
+ mod = __import__('win32com')
+ except ImportError:
+ return False
+ return True
+
+class TestSandbox(unittest.TestCase):
+
+ def setUp(self):
+ self.dir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.dir)
+
+ def test_devnull(self):
+ if sys.version < '2.4':
+ return
+ sandbox = DirectorySandbox(self.dir)
+ sandbox.run(self._file_writer(os.devnull))
+
+ def _file_writer(path):
+ def do_write():
+ f = open(path, 'w')
+ f.write('xxx')
+ f.close()
+ return do_write
+
+ _file_writer = staticmethod(_file_writer)
+
+ if has_win32com():
+ def test_win32com(self):
+ """
+ win32com should not be prevented from caching COM interfaces
+ in gen_py.
+ """
+ import win32com
+ gen_py = win32com.__gen_path__
+ target = os.path.join(gen_py, 'test_write')
+ sandbox = DirectorySandbox(self.dir)
+ try:
+ try:
+ sandbox.run(self._file_writer(target))
+ except SandboxViolation:
+ self.fail("Could not create gen_py file due to SandboxViolation")
+ finally:
+ if os.path.exists(target): os.remove(target)
+
+ def test_setup_py_with_BOM(self):
+ """
+ It should be possible to execute a setup.py with a Byte Order Mark
+ """
+ target = pkg_resources.resource_filename(__name__,
+ 'script-with-bom.py')
+ namespace = types.ModuleType('namespace')
+ setuptools.sandbox.execfile(target, vars(namespace))
+ assert namespace.result == 'passed'
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
new file mode 100644
index 00000000..71d10757
--- /dev/null
+++ b/setuptools/tests/test_sdist.py
@@ -0,0 +1,535 @@
+# -*- coding: utf-8 -*-
+"""sdist tests"""
+
+import locale
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+import unicodedata
+import re
+from setuptools.tests import environment, test_svn
+from setuptools.tests.py26compat import skipIf
+
+from setuptools.compat import StringIO, unicode
+from setuptools.tests.py26compat import skipIf
+from setuptools.command.sdist import sdist, walk_revctrl
+from setuptools.command.egg_info import manifest_maker
+from setuptools.dist import Distribution
+from setuptools import svn_utils
+
+SETUP_ATTRS = {
+ 'name': 'sdist_test',
+ 'version': '0.0',
+ 'packages': ['sdist_test'],
+ 'package_data': {'sdist_test': ['*.txt']}
+}
+
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(**%r)
+""" % SETUP_ATTRS
+
+
+if sys.version_info >= (3,):
+ LATIN1_FILENAME = 'smörbröd.py'.encode('latin-1')
+else:
+ LATIN1_FILENAME = 'sm\xf6rbr\xf6d.py'
+
+
+# Cannot use context manager because of Python 2.4
+def quiet():
+ global old_stdout, old_stderr
+ old_stdout, old_stderr = sys.stdout, sys.stderr
+ sys.stdout, sys.stderr = StringIO(), StringIO()
+
+def unquiet():
+ sys.stdout, sys.stderr = old_stdout, old_stderr
+
+
+# Fake byte literals for Python <= 2.5
+def b(s, encoding='utf-8'):
+ if sys.version_info >= (3,):
+ return s.encode(encoding)
+ return s
+
+
+# Convert to POSIX path
+def posix(path):
+ if sys.version_info >= (3,) and not isinstance(path, str):
+ return path.replace(os.sep.encode('ascii'), b('/'))
+ else:
+ return path.replace(os.sep, '/')
+
+
+# HFS Plus uses decomposed UTF-8
+def decompose(path):
+ if isinstance(path, unicode):
+ return unicodedata.normalize('NFD', path)
+ try:
+ path = path.decode('utf-8')
+ path = unicodedata.normalize('NFD', path)
+ path = path.encode('utf-8')
+ except UnicodeError:
+ pass # Not UTF-8
+ return path
+
+
+class TestSdistTest(unittest.TestCase):
+
+ def setUp(self):
+ self.temp_dir = tempfile.mkdtemp()
+ f = open(os.path.join(self.temp_dir, 'setup.py'), 'w')
+ f.write(SETUP_PY)
+ f.close()
+ # Set up the rest of the test package
+ test_pkg = os.path.join(self.temp_dir, 'sdist_test')
+ os.mkdir(test_pkg)
+ # *.rst was not included in package_data, so c.rst should not be
+ # automatically added to the manifest when not under version control
+ for fname in ['__init__.py', 'a.txt', 'b.txt', 'c.rst']:
+ # Just touch the files; their contents are irrelevant
+ open(os.path.join(test_pkg, fname), 'w').close()
+
+ self.old_cwd = os.getcwd()
+ os.chdir(self.temp_dir)
+
+ def tearDown(self):
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.temp_dir)
+
+ def test_package_data_in_sdist(self):
+ """Regression test for pull request #4: ensures that files listed in
+ package_data are included in the manifest even if they're not added to
+ version control.
+ """
+
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # squelch output
+ quiet()
+ try:
+ cmd.run()
+ finally:
+ unquiet()
+
+ manifest = cmd.filelist.files
+ self.assertTrue(os.path.join('sdist_test', 'a.txt') in manifest)
+ self.assertTrue(os.path.join('sdist_test', 'b.txt') in manifest)
+ self.assertTrue(os.path.join('sdist_test', 'c.rst') not in manifest)
+
+ def test_manifest_is_written_with_utf8_encoding(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ mm = manifest_maker(dist)
+ mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ os.mkdir('sdist_test.egg-info')
+
+ # UTF-8 filename
+ filename = os.path.join('sdist_test', 'smörbröd.py')
+
+ # Add UTF-8 filename and write manifest
+ quiet()
+ try:
+ mm.run()
+ mm.filelist.files.append(filename)
+ mm.write_manifest()
+ finally:
+ unquiet()
+
+ manifest = open(mm.manifest, 'rbU')
+ contents = manifest.read()
+ manifest.close()
+
+ # The manifest should be UTF-8 encoded
+ try:
+ u_contents = contents.decode('UTF-8')
+ except UnicodeDecodeError:
+ e = sys.exc_info()[1]
+ self.fail(e)
+
+ # The manifest should contain the UTF-8 filename
+ if sys.version_info >= (3,):
+ self.assertTrue(posix(filename) in u_contents)
+ else:
+ self.assertTrue(posix(filename) in contents)
+
+ # Python 3 only
+ if sys.version_info >= (3,):
+
+ def test_write_manifest_allows_utf8_filenames(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ mm = manifest_maker(dist)
+ mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ os.mkdir('sdist_test.egg-info')
+
+ # UTF-8 filename
+ filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
+
+ # Add filename and write manifest
+ quiet()
+ try:
+ mm.run()
+ u_filename = filename.decode('utf-8')
+ mm.filelist.files.append(u_filename)
+ # Re-write manifest
+ mm.write_manifest()
+ finally:
+ unquiet()
+
+ manifest = open(mm.manifest, 'rbU')
+ contents = manifest.read()
+ manifest.close()
+
+ # The manifest should be UTF-8 encoded
+ try:
+ contents.decode('UTF-8')
+ except UnicodeDecodeError:
+ e = sys.exc_info()[1]
+ self.fail(e)
+
+ # The manifest should contain the UTF-8 filename
+ self.assertTrue(posix(filename) in contents)
+
+ # The filelist should have been updated as well
+ self.assertTrue(u_filename in mm.filelist.files)
+
+ def test_write_manifest_skips_non_utf8_filenames(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ mm = manifest_maker(dist)
+ mm.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ os.mkdir('sdist_test.egg-info')
+
+ # Latin-1 filename
+ filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
+
+ # Add filename with surrogates and write manifest
+ quiet()
+ try:
+ mm.run()
+ u_filename = filename.decode('utf-8', 'surrogateescape')
+ mm.filelist.files.append(u_filename)
+ # Re-write manifest
+ mm.write_manifest()
+ finally:
+ unquiet()
+
+ manifest = open(mm.manifest, 'rbU')
+ contents = manifest.read()
+ manifest.close()
+
+ # The manifest should be UTF-8 encoded
+ try:
+ contents.decode('UTF-8')
+ except UnicodeDecodeError:
+ e = sys.exc_info()[1]
+ self.fail(e)
+
+ # The Latin-1 filename should have been skipped
+ self.assertFalse(posix(filename) in contents)
+
+ # The filelist should have been updated as well
+ self.assertFalse(u_filename in mm.filelist.files)
+
+ def test_manifest_is_read_with_utf8_encoding(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # Create manifest
+ quiet()
+ try:
+ cmd.run()
+ finally:
+ unquiet()
+
+ # Add UTF-8 filename to manifest
+ filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
+ cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ manifest = open(cmd.manifest, 'ab')
+ manifest.write(b('\n')+filename)
+ manifest.close()
+
+ # The file must exist to be included in the filelist
+ open(filename, 'w').close()
+
+ # Re-read manifest
+ cmd.filelist.files = []
+ quiet()
+ try:
+ cmd.read_manifest()
+ finally:
+ unquiet()
+
+ # The filelist should contain the UTF-8 filename
+ if sys.version_info >= (3,):
+ filename = filename.decode('utf-8')
+ self.assertTrue(filename in cmd.filelist.files)
+
+ # Python 3 only
+ if sys.version_info >= (3,):
+
+ def test_read_manifest_skips_non_utf8_filenames(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # Create manifest
+ quiet()
+ try:
+ cmd.run()
+ finally:
+ unquiet()
+
+ # Add Latin-1 filename to manifest
+ filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
+ cmd.manifest = os.path.join('sdist_test.egg-info', 'SOURCES.txt')
+ manifest = open(cmd.manifest, 'ab')
+ manifest.write(b('\n')+filename)
+ manifest.close()
+
+ # The file must exist to be included in the filelist
+ open(filename, 'w').close()
+
+ # Re-read manifest
+ cmd.filelist.files = []
+ quiet()
+ try:
+ try:
+ cmd.read_manifest()
+ except UnicodeDecodeError:
+ e = sys.exc_info()[1]
+ self.fail(e)
+ finally:
+ unquiet()
+
+ # The Latin-1 filename should have been skipped
+ filename = filename.decode('latin-1')
+ self.assertFalse(filename in cmd.filelist.files)
+
+ @skipIf(sys.version_info >= (3,) and locale.getpreferredencoding() != 'UTF-8',
+ 'Unittest fails if locale is not utf-8 but the manifests is recorded correctly')
+ def test_sdist_with_utf8_encoded_filename(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # UTF-8 filename
+ filename = os.path.join(b('sdist_test'), b('smörbröd.py'))
+ open(filename, 'w').close()
+
+ quiet()
+ try:
+ cmd.run()
+ finally:
+ unquiet()
+
+ if sys.platform == 'darwin':
+ filename = decompose(filename)
+
+ if sys.version_info >= (3,):
+ fs_enc = sys.getfilesystemencoding()
+
+ if sys.platform == 'win32':
+ if fs_enc == 'cp1252':
+ # Python 3 mangles the UTF-8 filename
+ filename = filename.decode('cp1252')
+ self.assertTrue(filename in cmd.filelist.files)
+ else:
+ filename = filename.decode('mbcs')
+ self.assertTrue(filename in cmd.filelist.files)
+ else:
+ filename = filename.decode('utf-8')
+ self.assertTrue(filename in cmd.filelist.files)
+ else:
+ self.assertTrue(filename in cmd.filelist.files)
+
+ def test_sdist_with_latin1_encoded_filename(self):
+ # Test for #303.
+ dist = Distribution(SETUP_ATTRS)
+ dist.script_name = 'setup.py'
+ cmd = sdist(dist)
+ cmd.ensure_finalized()
+
+ # Latin-1 filename
+ filename = os.path.join(b('sdist_test'), LATIN1_FILENAME)
+ open(filename, 'w').close()
+ self.assertTrue(os.path.isfile(filename))
+
+ quiet()
+ try:
+ cmd.run()
+ finally:
+ unquiet()
+
+ if sys.version_info >= (3,):
+ #not all windows systems have a default FS encoding of cp1252
+ if sys.platform == 'win32':
+ # Latin-1 is similar to Windows-1252 however
+ # on mbcs filesys it is not in latin-1 encoding
+ fs_enc = sys.getfilesystemencoding()
+ if fs_enc == 'mbcs':
+ filename = filename.decode('mbcs')
+ else:
+ filename = filename.decode('latin-1')
+
+ self.assertTrue(filename in cmd.filelist.files)
+ else:
+ # The Latin-1 filename should have been skipped
+ filename = filename.decode('latin-1')
+ self.assertFalse(filename in cmd.filelist.files)
+ else:
+ # No conversion takes place under Python 2 and the file
+ # is included. We shall keep it that way for BBB.
+ self.assertTrue(filename in cmd.filelist.files)
+
+
+class TestDummyOutput(environment.ZippedEnvironment):
+
+ def setUp(self):
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', "dummy.zip")
+ self.dataname = "dummy"
+ super(TestDummyOutput, self).setUp()
+
+ def _run(self):
+ code, data = environment.run_setup_py(["sdist"],
+ pypath=self.old_cwd,
+ data_stream=0)
+ if code:
+ info = "DIR: " + os.path.abspath('.')
+ info += "\n SDIST RETURNED: %i\n\n" % code
+ info += data
+ raise AssertionError(info)
+
+ datalines = data.splitlines()
+
+ possible = (
+ "running sdist",
+ "running egg_info",
+ "creating dummy\.egg-info",
+ "writing dummy\.egg-info",
+ "writing top-level names to dummy\.egg-info",
+ "writing dependency_links to dummy\.egg-info",
+ "writing manifest file 'dummy\.egg-info",
+ "reading manifest file 'dummy\.egg-info",
+ "reading manifest template 'MANIFEST\.in'",
+ "writing manifest file 'dummy\.egg-info",
+ "creating dummy-0.1.1",
+ "making hard links in dummy-0\.1\.1",
+ "copying files to dummy-0\.1\.1",
+ "copying \S+ -> dummy-0\.1\.1",
+ "copying dummy",
+ "copying dummy\.egg-info",
+ "hard linking \S+ -> dummy-0\.1\.1",
+ "hard linking dummy",
+ "hard linking dummy\.egg-info",
+ "Writing dummy-0\.1\.1",
+ "creating dist",
+ "creating 'dist",
+ "Creating tar archive",
+ "running check",
+ "adding 'dummy-0\.1\.1",
+ "tar .+ dist/dummy-0\.1\.1\.tar dummy-0\.1\.1",
+ "gzip .+ dist/dummy-0\.1\.1\.tar",
+ "removing 'dummy-0\.1\.1' \\(and everything under it\\)",
+ )
+
+ print(" DIR: " + os.path.abspath('.'))
+ for line in datalines:
+ found = False
+ for pattern in possible:
+ if re.match(pattern, line):
+ print(" READ: " + line)
+ found = True
+ break
+ if not found:
+ raise AssertionError("Unexpexected: %s\n-in-\n%s"
+ % (line, data))
+
+ return data
+
+ def test_sources(self):
+ self._run()
+
+
+class TestSvn(environment.ZippedEnvironment):
+
+ def setUp(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ if not version: # None or Empty
+ return
+
+ self.base_version = tuple([int(x) for x in version.split('.')][:2])
+
+ if not self.base_version:
+ raise ValueError('No SVN tools installed')
+ elif self.base_version < (1, 3):
+ raise ValueError('Insufficient SVN Version %s' % version)
+ elif self.base_version >= (1, 9):
+ #trying the latest version
+ self.base_version = (1, 8)
+
+ self.dataname = "svn%i%i_example" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvn, self).setUp()
+
+ @skipIf(not test_svn._svn_check, "No SVN to text, in the first place")
+ def test_walksvn(self):
+ if self.base_version >= (1, 6):
+ folder2 = 'third party2'
+ folder3 = 'third party3'
+ else:
+ folder2 = 'third_party2'
+ folder3 = 'third_party3'
+
+ #TODO is this right
+ expected = set([
+ os.path.join('a file'),
+ os.path.join(folder2, 'Changes.txt'),
+ os.path.join(folder2, 'MD5SUMS'),
+ os.path.join(folder2, 'README.txt'),
+ os.path.join(folder3, 'Changes.txt'),
+ os.path.join(folder3, 'MD5SUMS'),
+ os.path.join(folder3, 'README.txt'),
+ os.path.join(folder3, 'TODO.txt'),
+ os.path.join(folder3, 'fin'),
+ os.path.join('third_party', 'README.txt'),
+ os.path.join('folder', folder2, 'Changes.txt'),
+ os.path.join('folder', folder2, 'MD5SUMS'),
+ os.path.join('folder', folder2, 'WatashiNiYomimasu.txt'),
+ os.path.join('folder', folder3, 'Changes.txt'),
+ os.path.join('folder', folder3, 'fin'),
+ os.path.join('folder', folder3, 'MD5SUMS'),
+ os.path.join('folder', folder3, 'oops'),
+ os.path.join('folder', folder3, 'WatashiNiYomimasu.txt'),
+ os.path.join('folder', folder3, 'ZuMachen.txt'),
+ os.path.join('folder', 'third_party', 'WatashiNiYomimasu.txt'),
+ os.path.join('folder', 'lalala.txt'),
+ os.path.join('folder', 'quest.txt'),
+ # The example will have a deleted file
+ # (or should) but shouldn't return it
+ ])
+ self.assertEqual(set(x for x in walk_revctrl()), expected)
+
+
+def test_suite():
+ return unittest.defaultTestLoader.loadTestsFromName(__name__)
diff --git a/setuptools/tests/test_svn.py b/setuptools/tests/test_svn.py
new file mode 100644
index 00000000..33400362
--- /dev/null
+++ b/setuptools/tests/test_svn.py
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+"""svn tests"""
+
+import io
+import os
+import subprocess
+import sys
+import unittest
+from setuptools.tests import environment
+from setuptools.compat import unicode, unichr
+
+from setuptools import svn_utils
+from setuptools.tests.py26compat import skipIf
+
+
+def _do_svn_check():
+ try:
+ subprocess.check_call(["svn", "--version"],
+ shell=(sys.platform == 'win32'))
+ return True
+ except (OSError, subprocess.CalledProcessError):
+ return False
+_svn_check = _do_svn_check()
+
+
+class TestSvnVersion(unittest.TestCase):
+
+ def test_no_svn_found(self):
+ path_variable = None
+ for env in os.environ:
+ if env.lower() == 'path':
+ path_variable = env
+
+ if path_variable is None:
+ try:
+ self.skipTest('Cannot figure out how to modify path')
+ except AttributeError: # PY26 doesn't have this
+ return
+
+ old_path = os.environ[path_variable]
+ os.environ[path_variable] = ''
+ try:
+ version = svn_utils.SvnInfo.get_svn_version()
+ self.assertEqual(version, '')
+ finally:
+ os.environ[path_variable] = old_path
+
+ @skipIf(not _svn_check, "No SVN to text, in the first place")
+ def test_svn_should_exist(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ self.assertNotEqual(version, '')
+
+def _read_utf8_file(path):
+ fileobj = None
+ try:
+ fileobj = io.open(path, 'r', encoding='utf-8')
+ data = fileobj.read()
+ return data
+ finally:
+ if fileobj:
+ fileobj.close()
+
+
+class ParserInfoXML(unittest.TestCase):
+
+ def parse_tester(self, svn_name, ext_spaces):
+ path = os.path.join('setuptools', 'tests',
+ 'svn_data', svn_name + '_info.xml')
+ #Remember these are pre-generated to test XML parsing
+ # so these paths might not valid on your system
+ example_base = "%s_example" % svn_name
+
+ data = _read_utf8_file(path)
+
+ expected = set([
+ ("\\".join((example_base, 'a file')), 'file'),
+ ("\\".join((example_base, 'folder')), 'dir'),
+ ("\\".join((example_base, 'folder', 'lalala.txt')), 'file'),
+ ("\\".join((example_base, 'folder', 'quest.txt')), 'file'),
+ ])
+ self.assertEqual(set(x for x in svn_utils.parse_dir_entries(data)),
+ expected)
+
+ def test_svn13(self):
+ self.parse_tester('svn13', False)
+
+ def test_svn14(self):
+ self.parse_tester('svn14', False)
+
+ def test_svn15(self):
+ self.parse_tester('svn15', False)
+
+ def test_svn16(self):
+ self.parse_tester('svn16', True)
+
+ def test_svn17(self):
+ self.parse_tester('svn17', True)
+
+ def test_svn18(self):
+ self.parse_tester('svn18', True)
+
+class ParserExternalXML(unittest.TestCase):
+
+ def parse_tester(self, svn_name, ext_spaces):
+ path = os.path.join('setuptools', 'tests',
+ 'svn_data', svn_name + '_ext_list.xml')
+ example_base = svn_name + '_example'
+ data = _read_utf8_file(path)
+
+ if ext_spaces:
+ folder2 = 'third party2'
+ folder3 = 'third party3'
+ else:
+ folder2 = 'third_party2'
+ folder3 = 'third_party3'
+
+ expected = set([
+ os.sep.join((example_base, folder2)),
+ os.sep.join((example_base, folder3)),
+ # folder is third_party大介
+ os.sep.join((example_base,
+ unicode('third_party') +
+ unichr(0x5927) + unichr(0x4ecb))),
+ os.sep.join((example_base, 'folder', folder2)),
+ os.sep.join((example_base, 'folder', folder3)),
+ os.sep.join((example_base, 'folder',
+ unicode('third_party') +
+ unichr(0x5927) + unichr(0x4ecb))),
+ ])
+
+ expected = set(os.path.normpath(x) for x in expected)
+ dir_base = os.sep.join(('C:', 'development', 'svn_example'))
+ self.assertEqual(set(x for x
+ in svn_utils.parse_externals_xml(data, dir_base)), expected)
+
+ def test_svn15(self):
+ self.parse_tester('svn15', False)
+
+ def test_svn16(self):
+ self.parse_tester('svn16', True)
+
+ def test_svn17(self):
+ self.parse_tester('svn17', True)
+
+ def test_svn18(self):
+ self.parse_tester('svn18', True)
+
+
+class ParseExternal(unittest.TestCase):
+
+ def parse_tester(self, svn_name, ext_spaces):
+ path = os.path.join('setuptools', 'tests',
+ 'svn_data', svn_name + '_ext_list.txt')
+ data = _read_utf8_file(path)
+
+ if ext_spaces:
+ expected = set(['third party2', 'third party3',
+ 'third party3b', 'third_party'])
+ else:
+ expected = set(['third_party2', 'third_party3', 'third_party'])
+
+ self.assertEqual(set(x for x in svn_utils.parse_external_prop(data)),
+ expected)
+
+ def test_svn13(self):
+ self.parse_tester('svn13', False)
+
+ def test_svn14(self):
+ self.parse_tester('svn14', False)
+
+ def test_svn15(self):
+ self.parse_tester('svn15', False)
+
+ def test_svn16(self):
+ self.parse_tester('svn16', True)
+
+ def test_svn17(self):
+ self.parse_tester('svn17', True)
+
+ def test_svn18(self):
+ self.parse_tester('svn18', True)
+
+
+class TestSvn(environment.ZippedEnvironment):
+
+ def setUp(self):
+ version = svn_utils.SvnInfo.get_svn_version()
+ if not version: # empty or null
+ self.dataname = None
+ self.datafile = None
+ return
+
+ self.base_version = tuple([int(x) for x in version.split('.')[:2]])
+
+ if self.base_version < (1,3):
+ raise ValueError('Insufficient SVN Version %s' % version)
+ elif self.base_version >= (1,9):
+ #trying the latest version
+ self.base_version = (1,8)
+
+ self.dataname = "svn%i%i_example" % self.base_version
+ self.datafile = os.path.join('setuptools', 'tests',
+ 'svn_data', self.dataname + ".zip")
+ super(TestSvn, self).setUp()
+
+ @skipIf(not _svn_check, "No SVN to text, in the first place")
+ def test_revision(self):
+ rev = svn_utils.SvnInfo.load('.').get_revision()
+ self.assertEqual(rev, 6)
+
+ @skipIf(not _svn_check, "No SVN to text, in the first place")
+ def test_entries(self):
+ expected = set([
+ (os.path.join('a file'), 'file'),
+ (os.path.join('folder'), 'dir'),
+ (os.path.join('folder', 'lalala.txt'), 'file'),
+ (os.path.join('folder', 'quest.txt'), 'file'),
+ #The example will have a deleted file (or should)
+ #but shouldn't return it
+ ])
+ info = svn_utils.SvnInfo.load('.')
+ self.assertEqual(set(x for x in info.entries), expected)
+
+ @skipIf(not _svn_check, "No SVN to text, in the first place")
+ def test_externals(self):
+ if self.base_version >= (1,6):
+ folder2 = 'third party2'
+ folder3 = 'third party3'
+ else:
+ folder2 = 'third_party2'
+ folder3 = 'third_party3'
+
+ expected = set([
+ os.path.join(folder2),
+ os.path.join(folder3),
+ os.path.join('third_party'),
+ os.path.join('folder', folder2),
+ os.path.join('folder', folder3),
+ os.path.join('folder', 'third_party'),
+ ])
+ info = svn_utils.SvnInfo.load('.')
+ self.assertEqual(set([x for x in info.externals]), expected)
+
+def test_suite():
+ return unittest.defaultTestLoader.loadTestsFromName(__name__)
diff --git a/setuptools/tests/test_test.py b/setuptools/tests/test_test.py
new file mode 100644
index 00000000..7a06a403
--- /dev/null
+++ b/setuptools/tests/test_test.py
@@ -0,0 +1,124 @@
+# -*- coding: UTF-8 -*-
+
+"""develop tests
+"""
+import sys
+import os, shutil, tempfile, unittest
+import tempfile
+import site
+
+from distutils.errors import DistutilsError
+from setuptools.compat import StringIO
+from setuptools.command.test import test
+from setuptools.command import easy_install as easy_install_pkg
+from setuptools.dist import Distribution
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+)
+"""
+
+NS_INIT = """# -*- coding: Latin-1 -*-
+# Söme Arbiträry Ünicode to test Issüé 310
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
+"""
+# Make sure this is Latin-1 binary, before writing:
+if sys.version_info < (3,):
+ NS_INIT = NS_INIT.decode('UTF-8')
+NS_INIT = NS_INIT.encode('Latin-1')
+
+TEST_PY = """import unittest
+
+class TestTest(unittest.TestCase):
+ def test_test(self):
+ print "Foo" # Should fail under Python 3 unless 2to3 is used
+
+test_suite = unittest.makeSuite(TestTest)
+"""
+
+class TestTestTest(unittest.TestCase):
+
+ def setUp(self):
+ if sys.version < "2.6" or hasattr(sys, 'real_prefix'):
+ return
+
+ # Directory structure
+ self.dir = tempfile.mkdtemp()
+ os.mkdir(os.path.join(self.dir, 'name'))
+ os.mkdir(os.path.join(self.dir, 'name', 'space'))
+ os.mkdir(os.path.join(self.dir, 'name', 'space', 'tests'))
+ # setup.py
+ setup = os.path.join(self.dir, 'setup.py')
+ f = open(setup, 'wt')
+ f.write(SETUP_PY)
+ f.close()
+ self.old_cwd = os.getcwd()
+ # name/__init__.py
+ init = os.path.join(self.dir, 'name', '__init__.py')
+ f = open(init, 'wb')
+ f.write(NS_INIT)
+ f.close()
+ # name/space/__init__.py
+ init = os.path.join(self.dir, 'name', 'space', '__init__.py')
+ f = open(init, 'wt')
+ f.write('#empty\n')
+ f.close()
+ # name/space/tests/__init__.py
+ init = os.path.join(self.dir, 'name', 'space', 'tests', '__init__.py')
+ f = open(init, 'wt')
+ f.write(TEST_PY)
+ f.close()
+
+ os.chdir(self.dir)
+ self.old_base = site.USER_BASE
+ site.USER_BASE = tempfile.mkdtemp()
+ self.old_site = site.USER_SITE
+ site.USER_SITE = tempfile.mkdtemp()
+
+ def tearDown(self):
+ if sys.version < "2.6" or hasattr(sys, 'real_prefix'):
+ return
+
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.dir)
+ shutil.rmtree(site.USER_BASE)
+ shutil.rmtree(site.USER_SITE)
+ site.USER_BASE = self.old_base
+ site.USER_SITE = self.old_site
+
+ def test_test(self):
+ if sys.version < "2.6" or hasattr(sys, 'real_prefix'):
+ return
+
+ dist = Distribution(dict(
+ name='foo',
+ packages=['name', 'name.space', 'name.space.tests'],
+ namespace_packages=['name'],
+ test_suite='name.space.tests.test_suite',
+ use_2to3=True,
+ ))
+ dist.script_name = 'setup.py'
+ cmd = test(dist)
+ cmd.user = 1
+ cmd.ensure_finalized()
+ cmd.install_dir = site.USER_SITE
+ cmd.user = 1
+ old_stdout = sys.stdout
+ sys.stdout = StringIO()
+ try:
+ try: # try/except/finally doesn't work in Python 2.4, so we need nested try-statements.
+ cmd.run()
+ except SystemExit: # The test runner calls sys.exit, stop that making an error.
+ pass
+ finally:
+ sys.stdout = old_stdout
+
diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py
new file mode 100644
index 00000000..769f16cc
--- /dev/null
+++ b/setuptools/tests/test_upload_docs.py
@@ -0,0 +1,72 @@
+"""build_ext tests
+"""
+import sys, os, shutil, tempfile, unittest, site, zipfile
+from setuptools.command.upload_docs import upload_docs
+from setuptools.dist import Distribution
+
+SETUP_PY = """\
+from setuptools import setup
+
+setup(name='foo')
+"""
+
+class TestUploadDocsTest(unittest.TestCase):
+ def setUp(self):
+ self.dir = tempfile.mkdtemp()
+ setup = os.path.join(self.dir, 'setup.py')
+ f = open(setup, 'w')
+ f.write(SETUP_PY)
+ f.close()
+ self.old_cwd = os.getcwd()
+ os.chdir(self.dir)
+
+ self.upload_dir = os.path.join(self.dir, 'build')
+ os.mkdir(self.upload_dir)
+
+ # A test document.
+ f = open(os.path.join(self.upload_dir, 'index.html'), 'w')
+ f.write("Hello world.")
+ f.close()
+
+ # An empty folder.
+ os.mkdir(os.path.join(self.upload_dir, 'empty'))
+
+ if sys.version >= "2.6":
+ self.old_base = site.USER_BASE
+ site.USER_BASE = upload_docs.USER_BASE = tempfile.mkdtemp()
+ self.old_site = site.USER_SITE
+ site.USER_SITE = upload_docs.USER_SITE = tempfile.mkdtemp()
+
+ def tearDown(self):
+ os.chdir(self.old_cwd)
+ shutil.rmtree(self.dir)
+ if sys.version >= "2.6":
+ shutil.rmtree(site.USER_BASE)
+ shutil.rmtree(site.USER_SITE)
+ site.USER_BASE = self.old_base
+ site.USER_SITE = self.old_site
+
+ def test_create_zipfile(self):
+ # Test to make sure zipfile creation handles common cases.
+ # This explicitly includes a folder containing an empty folder.
+
+ dist = Distribution()
+
+ cmd = upload_docs(dist)
+ cmd.upload_dir = self.upload_dir
+ cmd.target_dir = self.upload_dir
+ tmp_dir = tempfile.mkdtemp()
+ tmp_file = os.path.join(tmp_dir, 'foo.zip')
+ try:
+ zip_file = cmd.create_zipfile(tmp_file)
+
+ assert zipfile.is_zipfile(tmp_file)
+
+ zip_file = zipfile.ZipFile(tmp_file) # woh...
+
+ assert zip_file.namelist() == ['index.html']
+
+ zip_file.close()
+ finally:
+ shutil.rmtree(tmp_dir)
+
diff --git a/setuptools/tests/win_script_wrapper.txt b/setuptools/tests/win_script_wrapper.txt
new file mode 100644
index 00000000..731243dd
--- /dev/null
+++ b/setuptools/tests/win_script_wrapper.txt
@@ -0,0 +1,154 @@
+Python Script Wrapper for Windows
+=================================
+
+setuptools includes wrappers for Python scripts that allows them to be
+executed like regular windows programs. There are 2 wrappers, once
+for command-line programs, cli.exe, and one for graphica programs,
+gui.exe. These programs are almost identical, function pretty much
+the same way, and are generated from the same source file. The
+wrapper programs are used by copying them to the directory containing
+the script they are to wrap and with the same name as the script they
+are to wrap. In the rest of this document, we'll give an example that
+will illustrate this.
+
+Let's create a simple script, foo-script.py:
+
+ >>> import os, sys, tempfile
+ >>> from setuptools.command.easy_install import nt_quote_arg
+ >>> sample_directory = tempfile.mkdtemp()
+ >>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w')
+ >>> bytes_written = f.write(
+ ... """#!%(python_exe)s
+ ... import sys
+ ... input = repr(sys.stdin.read())
+ ... print(sys.argv[0][-14:])
+ ... print(sys.argv[1:])
+ ... print(input)
+ ... if __debug__:
+ ... print('non-optimized')
+ ... """ % dict(python_exe=nt_quote_arg(sys.executable)))
+ >>> f.close()
+
+Note that the script starts with a Unix-style '#!' line saying which
+Python executable to run. The wrapper will use this to find the
+correct Python executable.
+
+We'll also copy cli.exe to the sample-directory with the name foo.exe:
+
+ >>> import pkg_resources
+ >>> f = open(os.path.join(sample_directory, 'foo.exe'), 'wb')
+ >>> bytes_written = f.write(
+ ... pkg_resources.resource_string('setuptools', 'cli-32.exe')
+ ... )
+ >>> f.close()
+
+When the copy of cli.exe, foo.exe in this example, runs, it examines
+the path name it was run with and computes a Python script path name
+by removing the '.exe' suffic and adding the '-script.py' suffix. (For
+GUI programs, the suffix '-script-pyw' is added.) This is why we
+named out script the way we did. Now we can run out script by running
+the wrapper:
+
+ >>> import subprocess
+ >>> cmd = [os.path.join(sample_directory, 'foo.exe'), 'arg1', 'arg 2',
+ ... 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
+ >>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
+ >>> stdout, stderr = proc.communicate('hello\nworld\n'.encode('ascii'))
+ >>> bytes = sys.stdout.write(stdout.decode('ascii').replace('\r\n', '\n'))
+ \foo-script.py
+ ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
+ 'hello\nworld\n'
+ non-optimized
+
+This example was a little pathological in that it exercised windows
+(MS C runtime) quoting rules:
+
+- Strings containing spaces are surrounded by double quotes.
+
+- Double quotes in strings need to be escaped by preceding them with
+ back slashes.
+
+- One or more backslashes preceding double quotes quotes need to be
+ escaped by preceding each of them them with back slashes.
+
+
+Specifying Python Command-line Options
+--------------------------------------
+
+You can specify a single argument on the '#!' line. This can be used
+to specify Python options like -O, to run in optimized mode or -i
+to start the interactive interpreter. You can combine multiple
+options as usual. For example, to run in optimized mode and
+enter the interpreter after running the script, you could use -Oi:
+
+ >>> f = open(os.path.join(sample_directory, 'foo-script.py'), 'w')
+ >>> bytes_written = f.write(
+ ... """#!%(python_exe)s -Oi
+ ... import sys
+ ... input = repr(sys.stdin.read())
+ ... print(sys.argv[0][-14:])
+ ... print(sys.argv[1:])
+ ... print(input)
+ ... if __debug__:
+ ... print('non-optimized')
+ ... sys.ps1 = '---'
+ ... """ % dict(python_exe=nt_quote_arg(sys.executable)))
+ >>> f.close()
+ >>> cmd = [os.path.join(sample_directory, 'foo.exe')]
+ >>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
+ >>> stdout, stderr = proc.communicate()
+ >>> bytes = sys.stdout.write(stdout.decode('ascii').replace('\r\n', '\n'))
+ \foo-script.py
+ []
+ ''
+ ---
+
+Testing the GUI Version
+-----------------------
+
+Now let's test the GUI version with the simple scipt, bar-script.py:
+
+ >>> import os, sys, tempfile
+ >>> from setuptools.command.easy_install import nt_quote_arg
+ >>> sample_directory = tempfile.mkdtemp()
+ >>> f = open(os.path.join(sample_directory, 'bar-script.pyw'), 'w')
+ >>> bytes_written = f.write(
+ ... """#!%(python_exe)s
+ ... import sys
+ ... f = open(sys.argv[1], 'wb')
+ ... bytes_written = f.write(repr(sys.argv[2]).encode('utf-8'))
+ ... f.close()
+ ... """ % dict(python_exe=nt_quote_arg(sys.executable)))
+ >>> f.close()
+
+We'll also copy gui.exe to the sample-directory with the name bar.exe:
+
+ >>> import pkg_resources
+ >>> f = open(os.path.join(sample_directory, 'bar.exe'), 'wb')
+ >>> bytes_written = f.write(
+ ... pkg_resources.resource_string('setuptools', 'gui-32.exe')
+ ... )
+ >>> f.close()
+
+Finally, we'll run the script and check the result:
+
+ >>> cmd = [
+ ... os.path.join(sample_directory, 'bar.exe'),
+ ... os.path.join(sample_directory, 'test_output.txt'),
+ ... 'Test Argument',
+ ... ]
+ >>> proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT)
+ >>> stdout, stderr = proc.communicate()
+ >>> print(stdout.decode('ascii'))
+ <BLANKLINE>
+ >>> f_out = open(os.path.join(sample_directory, 'test_output.txt'), 'rb')
+ >>> print(f_out.read().decode('ascii'))
+ 'Test Argument'
+ >>> f_out.close()
+
+
+We're done with the sample_directory:
+
+ >>> import shutil
+ >>> shutil.rmtree(sample_directory)
+
diff --git a/setuptools/version.py b/setuptools/version.py
new file mode 100644
index 00000000..f7d2e869
--- /dev/null
+++ b/setuptools/version.py
@@ -0,0 +1 @@
+__version__ = '3.0'