aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/__init__.py22
-rwxr-xr-xsetuptools/archive_util.py19
-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--[-rwxr-xr-x]setuptools/cli.exebin7168 -> 65536 bytes
-rw-r--r--setuptools/command/__init__.py4
-rw-r--r--setuptools/command/bdist_egg.py29
-rw-r--r--setuptools/command/build_ext.py5
-rw-r--r--setuptools/command/build_py.py89
-rwxr-xr-xsetuptools/command/develop.py85
-rwxr-xr-xsetuptools/command/easy_install.py442
-rwxr-xr-xsetuptools/command/egg_info.py51
-rw-r--r--setuptools/command/install.py7
-rwxr-xr-xsetuptools/command/install_egg_info.py8
-rwxr-xr-xsetuptools/command/install_scripts.py46
-rw-r--r--setuptools/command/launcher manifest.xml15
-rwxr-xr-xsetuptools/command/sdist.py100
-rw-r--r--setuptools/command/test.py46
-rwxr-xr-xsetuptools/command/upload.py12
-rw-r--r--setuptools/command/upload_docs.py195
-rw-r--r--setuptools/dist.py74
-rw-r--r--setuptools/extension.py54
-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--[-rwxr-xr-x]setuptools/gui.exebin7168 -> 65536 bytes
-rwxr-xr-xsetuptools/package_index.py236
-rw-r--r--setuptools/py24compat.py11
-rw-r--r--setuptools/py27compat.py15
-rwxr-xr-xsetuptools/sandbox.py74
-rw-r--r--setuptools/script template (dev).py6
-rw-r--r--setuptools/script template.py4
-rw-r--r--setuptools/site-patch.py83
-rw-r--r--setuptools/ssl_support.py27
-rw-r--r--setuptools/tests/__init__.py179
-rw-r--r--setuptools/tests/doctest.py16
-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/server.py82
-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.py118
-rw-r--r--setuptools/tests/test_dist_info.py80
-rw-r--r--setuptools/tests/test_easy_install.py425
-rw-r--r--setuptools/tests/test_markerlib.py64
-rw-r--r--setuptools/tests/test_packageindex.py130
-rw-r--r--setuptools/tests/test_resources.py165
-rw-r--r--setuptools/tests/test_sandbox.py66
-rw-r--r--setuptools/tests/test_sdist.py398
-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.txt315
54 files changed, 3453 insertions, 650 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py
index 71eeff49..414b4b5d 100644
--- a/setuptools/__init__.py
+++ b/setuptools/__init__.py
@@ -5,9 +5,10 @@ import distutils.core, setuptools.command
from setuptools.depends import Require
from distutils.core import Command as _Command
from distutils.util import convert_path
-import os.path
+import os
+import sys
-__version__ = '0.6c12'
+__version__ = '0.7'
__all__ = [
'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require',
'find_packages'
@@ -15,6 +16,12 @@ __all__ = [
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'
@@ -40,7 +47,7 @@ def find_packages(where='.', exclude=()):
return out
setup = distutils.core.setup
-
+
_Command = _get_unpatched(_Command)
class Command(_Command):
@@ -53,7 +60,7 @@ 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():
@@ -79,4 +86,9 @@ def findall(dir = os.curdir):
import distutils.filelist
distutils.filelist.findall = findall # fix findall bug in distutils.
-
+# sys.dont_write_bytecode was introduced in Python 2.6.
+if ((hasattr(sys, "dont_write_bytecode") and sys.dont_write_bytecode) or
+ (not hasattr(sys, "dont_write_bytecode") and os.environ.get("PYTHONDONTWRITEBYTECODE"))):
+ _dont_write_bytecode = True
+else:
+ _dont_write_bytecode = False
diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py
index d44264f8..1109f346 100755
--- a/setuptools/archive_util.py
+++ b/setuptools/archive_util.py
@@ -158,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()
@@ -181,21 +184,23 @@ def unpack_tarfile(filename, extract_dir, progress_filter=default_filter):
name = member.name
# don't extract absolute paths or ones with .. in them
if not name.startswith('/') and '..' not in name.split('/'):
- dst = os.path.join(extract_dir, *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()):
- dst = progress_filter(name, dst)
- if dst:
- if dst.endswith(os.sep):
- dst = dst[:-1]
+ 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,dst) # XXX Ugh
+ tarobj._extract_member(member, final_dst) # XXX Ugh
except tarfile.ExtractError:
pass # chown/chmod/mkfifo/mknode/makedev failed
return True
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
index 8906ff77..b1487b78 100755..100644
--- a/setuptools/cli.exe
+++ b/setuptools/cli.exe
Binary files differ
diff --git a/setuptools/command/__init__.py b/setuptools/command/__init__.py
index f898822b..b063fa19 100644
--- a/setuptools/command/__init__.py
+++ b/setuptools/command/__init__.py
@@ -2,10 +2,12 @@ __all__ = [
'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
'sdist', 'setopt', 'test', 'upload', 'install_egg_info', 'install_scripts',
- 'register', 'bdist_wininst',
+ 'register', 'bdist_wininst', 'upload_docs',
]
+from setuptools.command import install_scripts
import sys
+
if sys.version>='2.5':
# In Python 2.5 and above, distutils includes its own upload command
__all__.remove('upload')
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 7e5a3799..c3356bb7 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -6,7 +6,12 @@ Build .egg distributions"""
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:
+ from distutils.sysconfig import get_python_version, get_python_lib
+except ImportError:
+ from sysconfig import get_python_version
+ from distutils.sysconfig import get_python_lib
+
from distutils import log
from distutils.errors import DistutilsSetupError
from pkg_resources import get_build_platform, Distribution, ensure_directory
@@ -327,7 +332,11 @@ class bdist_egg(Command):
def copy_metadata_to(self, target_dir):
- prefix = os.path.join(self.egg_info,'')
+ "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):])
@@ -401,7 +410,7 @@ def write_safety_flag(egg_dir, safe):
if safe is None or bool(safe)!=flag:
os.unlink(fn)
elif safe is not None and bool(safe)==flag:
- f=open(fn,'wb'); f.write('\n'); f.close()
+ f=open(fn,'wt'); f.write('\n'); f.close()
safety_flags = {
True: 'zip-safe',
@@ -416,8 +425,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__']:
@@ -525,9 +538,11 @@ def make_zipfile(zip_filename, base_dir, verbose=0, dry_run=0, compress=None,
compression = [zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED][bool(compress)]
if not dry_run:
z = zipfile.ZipFile(zip_filename, mode, compression=compression)
- os.path.walk(base_dir, visit, z)
+ 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/build_ext.py b/setuptools/command/build_ext.py
index f6f3355d..f2a53258 100644
--- a/setuptools/command/build_ext.py
+++ b/setuptools/command/build_ext.py
@@ -111,6 +111,11 @@ class build_ext(_build_ext):
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)
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
index 79570bc2..8751acd4 100644
--- a/setuptools/command/build_py.py
+++ b/setuptools/command/build_py.py
@@ -3,7 +3,64 @@ from distutils.command.build_py import build_py as _build_py
from distutils.util import convert_path
from glob import glob
-class build_py(_build_py):
+try:
+ from distutils.util import Mixin2to3 as _Mixin2to3
+ # add support for converting doctests that is missing in 3.1 distutils
+ 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)
+
+except ImportError:
+ class Mixin2to3:
+ def run_2to3(self, files, doctests=True):
+ # Nothing done in 2.x
+ pass
+
+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()'.
@@ -17,6 +74,8 @@ class build_py(_build_py):
self.package_data = self.distribution.package_data
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"""
@@ -30,6 +89,10 @@ 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))
@@ -39,6 +102,12 @@ class build_py(_build_py):
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()
@@ -77,7 +146,11 @@ class build_py(_build_py):
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)
+ 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):
@@ -140,8 +213,8 @@ class build_py(_build_py):
else:
return init_py
- f = open(init_py,'rU')
- if 'declare_namespace' not in f.read():
+ f = open(init_py,'rbU')
+ if 'declare_namespace'.encode() not in f.read():
from distutils import log
log.warn(
"WARNING: %s is a namespace package, but its __init__.py does\n"
@@ -157,9 +230,11 @@ class build_py(_build_py):
_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):
diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py
index f128b803..1d500040 100755
--- a/setuptools/command/develop.py
+++ b/setuptools/command/develop.py
@@ -1,9 +1,9 @@
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
-from distutils.errors import *
-import sys, os, setuptools, glob
+from distutils.errors import DistutilsError, DistutilsOptionError
+import os, sys, setuptools, glob
class develop(easy_install):
"""Set up package for development"""
@@ -36,9 +36,6 @@ class develop(easy_install):
-
-
-
def finalize_options(self):
ei = self.get_finalized_command("egg_info")
if ei.broken_egg_info:
@@ -46,8 +43,14 @@ class develop(easy_install):
"Please rename %r to %r before using 'develop'"
% (ei.egg_info, ei.broken_egg_info)
)
- self.args = [ei.egg_name]
+ 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'))
@@ -62,7 +65,7 @@ class develop(easy_install):
"--egg-path must be a relative path from the install"
" directory to "+target
)
-
+
# Make a distribution for the package's source
self.dist = Distribution(
target,
@@ -81,11 +84,35 @@ class develop(easy_install):
" installation directory", p, normalize_path(os.curdir))
def install_for_development(self):
- # Ensure metadata is up-to-date
- self.run_command('egg_info')
- # Build extensions in-place
- self.reinitialize_command('build_ext', inplace=1)
- self.run_command('build_ext')
+ 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)
@@ -105,7 +132,9 @@ class develop(easy_install):
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)]
+ 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
@@ -117,10 +146,6 @@ class develop(easy_install):
# 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
@@ -129,7 +154,7 @@ class develop(easy_install):
# create wrapper scripts in the script dir, pointing to dist.scripts
# new-style...
- self.install_wrapper_scripts(dist)
+ self.install_wrapper_scripts(dist)
# ...and old-style
for script_name in self.distribution.scripts or []:
@@ -140,25 +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 71794865..f29faf9d 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -7,21 +7,42 @@ 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
+__ http://packages.python.org/setuptools/easy_install.html
+
"""
-import sys, os.path, zipimport, shutil, tempfile, zipfile, re, stat, random
+import sys
+import os
+import zipimport
+import shutil
+import tempfile
+import zipfile
+import re
+import stat
+import random
+import platform
from glob import glob
-from setuptools import Command
+import pkg_resources
+from setuptools import Command, _dont_write_bytecode
from setuptools.sandbox import run_setup
from distutils import log, dir_util
-from distutils.sysconfig import get_python_lib
+from distutils.util import get_platform
+from distutils.util import convert_path, subst_vars
+from distutils.sysconfig import get_python_lib, get_config_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 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.path.normpath(sys.executable)
__all__ = [
@@ -29,6 +50,13 @@ __all__ = [
'main', 'get_exe_prefixes',
]
+import site
+HAS_USER_SITE = not sys.version < "2.6" and site.ENABLE_USER_SITE
+
+import struct
+def is_64bit():
+ return struct.calcsize("P") == 8
+
def samefile(p1,p2):
if hasattr(os.path,'samefile') and (
os.path.exists(p1) and os.path.exists(p2)
@@ -39,6 +67,25 @@ def samefile(p1,p2):
os.path.normpath(os.path.normcase(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"
@@ -71,16 +118,32 @@ class easy_install(Command):
('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',
- 'no-deps', 'local-snapshots-ok',
+ 'no-deps', 'local-snapshots-ok', 'version'
]
+
+ if HAS_USER_SITE:
+ user_options.append(('user', None,
+ "install in user site-package '%s'" % site.USER_SITE))
+ boolean_options.append('user')
+
+
negative_opt = {'always-unzip': 'zip-ok'}
create_index = PackageIndex
def initialize_options(self):
+ if HAS_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
@@ -91,6 +154,22 @@ class easy_install(Command):
self.upgrade = self.always_copy = self.multi_version = None
self.editable = self.no_deps = self.allow_hosts = None
self.root = self.prefix = self.no_report = None
+ 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 HAS_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
@@ -122,12 +201,56 @@ class easy_install(Command):
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 HAS_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 HAS_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()
+
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.
@@ -138,6 +261,10 @@ 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'))
normpath = map(normalize_path, sys.path)
@@ -179,7 +306,8 @@ class easy_install(Command):
self.find_links = []
if self.local_snapshots_ok:
self.package_index.scan_egg_links(self.shadow_path+sys.path)
- self.package_index.add_find_links(self.find_links)
+ 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:
@@ -203,8 +331,29 @@ class easy_install(Command):
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 spec in self.args:
@@ -246,6 +395,7 @@ class easy_install(Command):
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')
@@ -317,7 +467,7 @@ variable.
For information on other options, you may wish to consult the
documentation at:
- http://peak.telecommunity.com/EasyInstall.html
+ http://packages.python.org/setuptools/easy_install.html
Please make the appropriate changes for your system and try again.
"""
@@ -335,12 +485,15 @@ Please make the appropriate changes for your system and try again.
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;open(%r,'w').write('OK')\n" % (ok_file,))
+ 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':
@@ -371,6 +524,10 @@ Please make the appropriate changes for your system and try again.
"""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)
@@ -487,6 +644,15 @@ Please make the appropriate changes for your system and try again.
+ 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])
+
@@ -497,7 +663,8 @@ Please make the appropriate changes for your system and try again.
self.install_egg_scripts(dist)
self.installed_projects[dist.key] = dist
log.info(self.installation_report(requirement, dist, *info))
- if dist.has_metadata('dependency_links.txt'):
+ 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')
)
@@ -577,23 +744,27 @@ Please make the appropriate changes for your system and try again.
spec = str(dist.as_requirement())
is_script = is_python_script(script_text, script_name)
- if is_script and dev_path:
- script_text = get_script_header(script_text) + (
- "# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r\n"
- "__requires__ = %(spec)r\n"
- "from pkg_resources import require; require(%(spec)r)\n"
- "del require\n"
- "__file__ = %(dev_path)r\n"
- "execfile(__file__)\n"
- ) % locals()
- elif is_script:
- script_text = get_script_header(script_text) + (
- "# EASY-INSTALL-SCRIPT: %(spec)r,%(script_name)r\n"
- "__requires__ = %(spec)r\n"
- "import pkg_resources\n"
- "pkg_resources.run_script(%(spec)r, %(script_name)r)\n"
- ) % locals()
- self.write_script(script_name, script_text, 'b')
+ def 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:
+ 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"""
@@ -603,12 +774,13 @@ Please make the appropriate changes for your system and try again.
target = os.path.join(self.script_dir, script_name)
self.add_output(target)
+ mask = current_umask()
if not self.dry_run:
ensure_directory(target)
f = open(target,"w"+mode)
f.write(contents)
f.close()
- chmod(target,0755)
+ chmod(target, 0777-mask)
@@ -705,7 +877,7 @@ Please make the appropriate changes for your system and try again.
# Create a dummy distribution object until we build the real distro
dist = Distribution(None,
project_name=cfg.get('metadata','name'),
- version=cfg.get('metadata','version'), platform="win32"
+ version=cfg.get('metadata','version'), platform=get_platform()
)
# Convert the .exe to an unpacked egg
@@ -781,7 +953,9 @@ Please make the appropriate changes for your system and try again.
if locals()[name]:
txt = os.path.join(egg_tmp, 'EGG-INFO', name+'.txt')
if not os.path.exists(txt):
- open(txt,'w').write('\n'.join(locals()[name])+'\n')
+ f = open(txt,'w')
+ f.write('\n'.join(locals()[name])+'\n')
+ f.close()
def check_conflicts(self, dist):
"""Verify that there are no conflicting "old-style" packages"""
@@ -922,11 +1096,14 @@ See the setuptools documentation for the "develop" command for more info.
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 = []
@@ -941,6 +1118,30 @@ See the setuptools documentation for the "develop" command for more info.
rmtree(dist_dir)
log.set_verbosity(self.verbose) # restore our log verbosity
+ 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.iteritems():
+ 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
@@ -1001,6 +1202,10 @@ See the setuptools documentation for the "develop" command for more info.
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
@@ -1049,7 +1254,7 @@ Here are some of your options for correcting the problem:
* You can set up the installation directory to support ".pth" files by
using one of the approaches described here:
- http://peak.telecommunity.com/EasyInstall.html#custom-installation-locations
+ http://packages.python.org/setuptools/easy_install.html#custom-installation-locations
Please make the appropriate changes for your system and try again.""" % (
self.install_dir, os.environ.get('PYTHONPATH','')
@@ -1071,12 +1276,18 @@ Please make the appropriate changes for your system and try again.""" % (
return # already did it, or don't need to
sitepy = os.path.join(self.install_dir, "site.py")
- source = resource_string(Requirement.parse("setuptools"), "site.py")
+ source = resource_string("setuptools", "site-patch.py")
current = ""
if os.path.exists(sitepy):
log.debug("Checking existing site.py in %s", self.install_dir)
- current = open(sitepy,'rb').read()
+ 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"
@@ -1097,7 +1308,15 @@ Please make the appropriate changes for your system and try again.""" % (
-
+ def create_home_path(self):
+ """Create directories under ~."""
+ if not self.user:
+ return
+ home = convert_path(os.path.expanduser("~"))
+ for name, path in self.config_vars.iteritems():
+ if path.startswith(home) and not os.path.isdir(path):
+ self.debug_print("os.makedirs('%s', 0700)" % path)
+ os.makedirs(path, 0700)
@@ -1183,7 +1402,11 @@ def get_site_dirs():
site_lib = get_python_lib(plat_specific)
if site_lib not in sitedirs: sitedirs.append(site_lib)
+ if HAS_USER_SITE:
+ sitedirs.append(site.USER_SITE)
+
sitedirs = map(normalize_path, sitedirs)
+
return sitedirs
@@ -1252,7 +1475,19 @@ def extract_wininst_cfg(dist_filename):
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 on Python 3, it must be
+ # unicode for the RawConfigParser, so decode it. Is this the
+ # right encoding?
+ config = config.decode('ascii')
+ cfg.readfp(StringIO.StringIO(config))
except ConfigParser.Error:
return None
if not cfg.has_section('metadata') or not cfg.has_section('Setup'):
@@ -1274,8 +1509,9 @@ def get_exe_prefixes(exe_filename):
prefixes = [
('PURELIB/', ''), ('PLATLIB/pywin32_system32', ''),
- ('PLATLIB/', ''), ('DATA/lib/site-packages/', ''),
- ('SCRIPTS/', 'EGG-INFO/scripts/')
+ ('PLATLIB/', ''),
+ ('SCRIPTS/', 'EGG-INFO/scripts/'),
+ ('DATA/lib/site-packages', ''),
]
z = zipfile.ZipFile(exe_filename)
try:
@@ -1291,7 +1527,10 @@ def get_exe_prefixes(exe_filename):
if name.endswith('-nspkg.pth'):
continue
if parts[0].upper() in ('PURELIB','PLATLIB'):
- for pth in yield_lines(z.read(name)):
+ 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)), ''))
@@ -1327,7 +1566,8 @@ class PthDistributions(Environment):
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
@@ -1345,6 +1585,7 @@ class PthDistributions(Environment):
self.dirty = True # we cleaned up, so we're dirty now :)
continue
seen[path] = 1
+ f.close()
if self.paths and not saw_import:
self.dirty = True # ensure anything we touch has import wrappers
@@ -1370,7 +1611,7 @@ class PthDistributions(Environment):
if os.path.islink(self.filename):
os.unlink(self.filename)
- f = open(self.filename,'wb')
+ f = open(self.filename,'wt')
f.write(data); f.close()
elif os.path.exists(self.filename):
@@ -1381,8 +1622,12 @@ class PthDistributions(Environment):
def add(self,dist):
"""Add `dist` to the distribution map"""
- if dist.location not in self.paths and dist.location not in self.sitedirs:
- self.paths.append(dist.location); self.dirty = True
+ 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):
@@ -1410,6 +1655,11 @@ class PthDistributions(Environment):
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 = ''
@@ -1421,7 +1671,7 @@ def get_script_header(script_text, executable=sys_executable, wininst=False):
else:
executable = nt_quote_arg(executable)
hdr = "#!%(executable)s%(options)s\n" % locals()
- if unicode(hdr,'ascii','ignore').encode('ascii') != hdr:
+ if not isascii(hdr):
# Non-ascii path to sys.executable, use -x to prevent warnings
if options:
if options.strip().startswith('-'):
@@ -1543,6 +1793,11 @@ def chmod(path, mode):
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:
@@ -1561,82 +1816,76 @@ def get_script_args(dist, executable=sys_executable, wininst=False):
spec = str(dist.as_requirement())
header = get_script_header("", executable, wininst)
for group in 'console_scripts', 'gui_scripts':
- for name,ep in dist.get_entry_map(group).items():
+ for name, ep in dist.get_entry_map(group).items():
script_text = (
"# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r\n"
"__requires__ = %(spec)r\n"
"import sys\n"
"from pkg_resources import load_entry_point\n"
"\n"
- "sys.exit(\n"
- " load_entry_point(%(spec)r, %(group)r, %(name)r)()\n"
- ")\n"
+ "if __name__ == '__main__':"
+ "\n"
+ " sys.exit(\n"
+ " load_entry_point(%(spec)r, %(group)r, %(name)r)()\n"
+ " )\n"
) % locals()
if sys.platform=='win32' or wininst:
# On Windows/wininst, add a .py extension and an .exe launcher
if group=='gui_scripts':
- ext, launcher = '-script.pyw', 'gui.exe'
+ launcher_type = 'gui'
+ ext = '-script.pyw'
old = ['.pyw']
new_header = re.sub('(?i)python.exe','pythonw.exe',header)
else:
- ext, launcher = '-script.py', 'cli.exe'
+ launcher_type = 'cli'
+ ext = '-script.py'
old = ['.py','.pyc','.pyo']
new_header = re.sub('(?i)pythonw.exe','python.exe',header)
-
if os.path.exists(new_header[2:-1].strip('"')) or sys.platform!='win32':
hdr = new_header
else:
hdr = header
yield (name+ext, hdr+script_text, 't', [name+x for x in old])
yield (
- name+'.exe', resource_string('setuptools', launcher),
- 'b') # write in binary mode
- yield (name+'.exe.manifest', _launcher_manifest % (name,), 't')
+ 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')
else:
# On other platforms, we assume the right thing to do is to
# just write the stub with no extension.
yield (name, header+script_text)
-_launcher_manifest = """
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
- <assemblyIdentity version="1.0.0.0"
- processorArchitecture="X86"
- name="%s.exe"
- type="win32"/>
-
- <!-- Identify the application security requirements. -->
- <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
- <security>
- <requestedPrivileges>
- <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
- </requestedPrivileges>
- </security>
- </trustInfo>
-</assembly>"""
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+def 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.
@@ -1673,12 +1922,16 @@ def rmtree(path, ignore_errors=False, onerror=auto_chmod):
except os.error:
onerror(os.rmdir, path, sys.exc_info())
+def current_umask():
+ tmp = os.umask(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
@@ -1703,6 +1956,7 @@ usage: %(script)s [options] requirement_or_url ...
class DistributionWithoutHelpCommands(Distribution):
common_usage = ""
+
def _show_help(self,*args,**kw):
with_ei_usage(lambda: Distribution._show_help(self,*args,**kw))
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 5a8b2db8..cd3ea198 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -3,13 +3,13 @@
Create a distribution's .egg-info directory and contents"""
# This module should be kept compatible with Python 2.3
-import os, re
+import os, re, sys
from setuptools import Command
from distutils.errors import *
from distutils import log
from setuptools.command.sdist import sdist
from distutils.util import convert_path
-from distutils.filelist import FileList
+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 sdist import walk_revctrl
@@ -148,6 +148,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)
@@ -160,7 +162,12 @@ class egg_info(Command):
os.unlink(filename)
def tagged_version(self):
- return safe_version(self.distribution.get_version() + self.vtags)
+ 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):
self.mkpath(self.egg_info)
@@ -229,7 +236,7 @@ class egg_info(Command):
continue
data = map(str.splitlines,data.split('\n\x0c\n'))
- del data[0][0] # get rid of the '8' or '9'
+ del data[0][0] # get rid of the '8' or '9' or '10'
dirurl = data[0][3]
localrev = max([int(d[9]) for d in data if len(d)>9 and d[9]]+[0])
if base==os.curdir:
@@ -267,16 +274,28 @@ class egg_info(Command):
self.broken_egg_info = self.egg_info
self.egg_info = bei # make it work for now
-class FileList(FileList):
+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 os.path.exists(path):
- self.files.append(path)
+ 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)
@@ -316,6 +335,18 @@ class manifest_maker(sdist):
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]
@@ -351,8 +382,11 @@ 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("\n".join(contents))
+ f.write(contents)
f.close()
@@ -444,6 +478,7 @@ def get_pkg_info_revision():
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 a150c435..247c4f25 100644
--- a/setuptools/command/install.py
+++ b/setuptools/command/install.py
@@ -18,9 +18,6 @@ class install(_install):
('install_scripts', lambda self: True),
]
_nc = dict(new_commands)
- sub_commands = [
- cmd for cmd in _install.sub_commands if cmd[0] not in _nc
- ] + new_commands
def initialize_options(self):
_install.initialize_options(self)
@@ -104,6 +101,10 @@ 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
index 939340c5..f44b34b5 100755
--- a/setuptools/command/install_egg_info.py
+++ b/setuptools/command/install_egg_info.py
@@ -87,8 +87,10 @@ class install_egg_info(Command):
filename += '-nspkg.pth'; self.outputs.append(filename)
log.info("Installing %s",filename)
if not self.dry_run:
- f = open(filename,'wb')
+ 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:
@@ -97,12 +99,12 @@ class install_egg_info(Command):
% ('.'.join(pth[:-1]), pth[-1])
)
f.write(
- "import sys,new,os; "
+ "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,new.module(%(pkg)r)); "
+ "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()
diff --git a/setuptools/command/install_scripts.py b/setuptools/command/install_scripts.py
index c2dc2d59..82456035 100755
--- a/setuptools/command/install_scripts.py
+++ b/setuptools/command/install_scripts.py
@@ -1,6 +1,5 @@
from distutils.command.install_scripts import install_scripts \
as _install_scripts
-from easy_install import get_script_args, sys_executable, chmod
from pkg_resources import Distribution, PathMetadata, ensure_directory
import os
from distutils import log
@@ -11,8 +10,11 @@ class install_scripts(_install_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
@@ -20,9 +22,9 @@ class install_scripts(_install_scripts):
self.outfiles = []
if self.no_ep:
# don't install entry point scripts into .egg file!
- return
+ return
- ei_cmd = self.get_finalized_command("egg_info")
+ 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,
@@ -35,48 +37,18 @@ class install_scripts(_install_scripts):
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,0755)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ chmod(target, 0777-mask)
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/sdist.py b/setuptools/command/sdist.py
index d84afdb8..f8f964b3 100755
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -3,6 +3,9 @@ from distutils.util import convert_path
from distutils import log
from glob import glob
import os, re, sys, pkg_resources
+from glob import glob
+
+READMES = ('README', 'README.rst', 'README.txt')
entities = [
("&lt;","<"), ("&gt;", ">"), ("&quot;", '"'), ("&apos;", "'"),
@@ -59,7 +62,7 @@ def _default_revctrl(dirname=''):
def externals_finder(dirname, filename):
"""Find any 'svn:externals' directories"""
found = False
- f = open(filename,'rb')
+ f = open(filename,'rt')
for line in iter(f.readline, ''): # can't use direct iter!
parts = line.split()
if len(parts)==2:
@@ -94,9 +97,10 @@ def entries_finder(dirname, filename):
try: svnver = int(data.splitlines()[0])
except: pass
if svnver<8:
- log.warn("unrecognized .svn/entries format in %s", dirname)
+ log.warn("unrecognized .svn/entries format in %s", os.path.abspath(dirname))
return
for record in map(str.splitlines, data.split('\n\x0c\n')[1:]):
+ # subversion 1.6/1.5/1.4
if not record or len(record)>=6 and record[5]=="delete":
continue # skip deleted
yield joinpath(dirname, record[0])
@@ -143,7 +147,17 @@ class sdist(_sdist):
self.filelist = ei_cmd.filelist
self.filelist.append(os.path.join(ei_cmd.egg_info,'SOURCES.txt'))
self.check_readme()
- self.check_metadata()
+
+ # 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',[])
@@ -152,25 +166,31 @@ class sdist(_sdist):
if data not in dist_files:
dist_files.append(data)
- def read_template(self):
+ 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:
- # grody hack to close the template file (MANIFEST.in)
- # this prevents easy_install's attempt at deleting the file from
- # dying and thus masking the real error
sys.exc_info()[2].tb_next.tb_frame.f_locals['template'].close()
raise
-
- # Cribbed from old distutils code, to work around new distutils code
- # that tries to do some of the same stuff as we do, in a way that makes
- # us loop.
-
- def add_defaults (self):
- standards = [('README', 'README.txt'), self.distribution.script_name]
-
+ # 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.
+ if (
+ sys.version_info < (2,7,2)
+ or (3,0) <= sys.version_info < (3,1,4)
+ or (3,2) <= sys.version_info < (3,2,1)
+ ):
+ read_template = __read_template_hack
+
+ def add_defaults(self):
+ standards = [READMES,
+ self.distribution.script_name]
for fn in standards:
- if type(fn) is tuple:
+ if isinstance(fn, tuple):
alts = fn
got_it = 0
for fn in alts:
@@ -189,15 +209,23 @@ class sdist(_sdist):
self.warn("standard file '%s' not found" % fn)
optional = ['test/test*.py', 'setup.cfg']
-
for pattern in optional:
files = 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')
@@ -211,15 +239,13 @@ class sdist(_sdist):
build_scripts = self.get_finalized_command('build_scripts')
self.filelist.extend(build_scripts.get_source_files())
-
def check_readme(self):
- alts = ("README", "README.txt")
- for f in alts:
+ for f in READMES:
if os.path.exists(f):
return
else:
self.warn(
- "standard file not found: should have one of " +', '.join(alts)
+ "standard file not found: should have one of " +', '.join(READMES)
)
@@ -236,7 +262,39 @@ class sdist(_sdist):
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/test.py b/setuptools/command/test.py
index db918dae..a02ac142 100644
--- a/setuptools/command/test.py
+++ b/setuptools/command/test.py
@@ -2,6 +2,7 @@ from setuptools import Command
from distutils.errors import DistutilsOptionError
import sys
from pkg_resources import *
+from pkg_resources import _namespace_packages
from unittest import TestLoader, main
class ScanningLoader(TestLoader):
@@ -81,12 +82,28 @@ class test(Command):
def with_project_on_sys_path(self, func):
- # Ensure metadata is up-to-date
- self.run_command('egg_info')
+ if sys.version_info >= (3,) and getattr(self.distribution, 'use_2to3', False):
+ # If we run 2to3 we can not do this inplace:
- # Build extensions in-place
- self.reinitialize_command('build_ext', inplace=1)
- self.run_command('build_ext')
+ # 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')
ei_cmd = self.get_finalized_command("egg_info")
@@ -123,11 +140,28 @@ class test(Command):
def run_tests(self):
import unittest
+
+ # 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)
+ 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 = loader_class()
+ testLoader = cks
)
diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py
index 7ac08c22..21b9615c 100755
--- a/setuptools/command/upload.py
+++ b/setuptools/command/upload.py
@@ -83,14 +83,16 @@ class upload(Command):
dry_run=self.dry_run)
# Fill in the data
- content = open(filename,'rb').read()
+ f = open(filename,'rb')
+ content = f.read()
+ f.close()
basename = os.path.basename(filename)
comment = ''
if command=='bdist_egg' and self.distribution.has_ext_modules():
comment = "built on %s" % platform.platform(terse=1)
data = {
':action':'file_upload',
- 'protcol_version':'1',
+ 'protocol_version':'1',
'name':self.distribution.get_name(),
'version':self.distribution.get_version(),
'content':(basename,content),
@@ -107,8 +109,9 @@ class upload(Command):
data['comment'] = comment
if self.sign:
- data['gpg_signature'] = (os.path.basename(filename) + ".asc",
- open(filename+".asc").read())
+ asc_file = open(filename + ".asc")
+ data['gpg_signature'] = (os.path.basename(filename) + ".asc", asc_file.read())
+ asc_file.close()
# set up the authentication
auth = "Basic " + base64.encodestring(self.username + ":" + self.password).strip()
@@ -179,3 +182,4 @@ class upload(Command):
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..1d5a7445
--- /dev/null
+++ b/setuptools/command/upload_docs.py
@@ -0,0 +1,195 @@
+# -*- coding: utf-8 -*-
+"""upload_docs
+
+Implements a Distutils 'upload_docs' subcommand (upload documentation to
+PyPI's packages.python.org).
+"""
+
+import os
+import socket
+import zipfile
+import httplib
+import urlparse
+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
+
+try:
+ from distutils.command.upload import upload
+except ImportError:
+ from setuptools.command.upload import upload
+
+
+# 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)
+ 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 sys.version_info >= (3,):
+ 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 data.iteritems():
+ title = '\nContent-Disposition: form-data; name="%s"' % key
+ # handle multiple entries for the same name
+ if type(values) != type([]):
+ 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.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 = ''
+ loglevel = log.INFO
+ try:
+ conn.connect()
+ conn.putrequest("POST", url)
+ conn.putheader('Content-type',
+ 'multipart/form-data; boundary=%s'%boundary)
+ conn.putheader('Content-length', str(len(body)))
+ conn.putheader('Authorization', auth)
+ conn.endheaders()
+ conn.send(body)
+ except socket.error, e:
+ 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 = 'http://packages.python.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/dist.py b/setuptools/dist.py
index 582cc557..907ce550 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -1,5 +1,6 @@
__all__ = ['Distribution']
+import re
from distutils.core import Distribution as _Distribution
from setuptools.depends import Require
from setuptools.command.install import install
@@ -8,7 +9,7 @@ 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, distutils.log, re
+import os, distutils.log
def _get_unpatched(cls):
"""Protect against re-patching the distutils if reloaded
@@ -251,6 +252,7 @@ class Distribution(_Distribution):
self.require_features = []
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:
@@ -295,11 +297,18 @@ class Distribution(_Distribution):
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']})
@@ -320,7 +329,7 @@ class Distribution(_Distribution):
cmd = easy_install(
dist, args=["x"], install_dir=os.curdir, exclude_scripts=True,
always_copy=False, build_directory=None, editable=False,
- upgrade=False, multi_version=True, no_report = True
+ upgrade=False, multi_version=True, no_report=True, user=False
)
cmd.ensure_finalized()
self._egg_fetcher = cmd
@@ -456,19 +465,19 @@ 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)
]
@@ -672,6 +681,43 @@ class Distribution(_Distribution):
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
+
+ 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
@@ -841,21 +887,3 @@ class Feature:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/setuptools/extension.py b/setuptools/extension.py
index 2bef84e5..eb8b836c 100644
--- a/setuptools/extension.py
+++ b/setuptools/extension.py
@@ -1,36 +1,46 @@
-from distutils.core import Extension as _Extension
-from dist import _get_unpatched
-_Extension = _get_unpatched(_Extension)
+import sys
+import distutils.core
+import distutils.extension
-try:
- from Pyrex.Distutils.build_ext import build_ext
-except ImportError:
- have_pyrex = False
-else:
- have_pyrex = True
+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"""
- if not have_pyrex:
- # convert .pyx extensions to .c
- 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
+ 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 = map(pyx_to_c, self.sources)
class Library(Extension):
"""Just like a regular Extension, but built as a library instead"""
-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
-
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
index 474838d5..f8d35096 100755..100644
--- a/setuptools/gui.exe
+++ b/setuptools/gui.exe
Binary files differ
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index aab2bb23..4f39c70a 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -1,6 +1,9 @@
"""PyPI and direct package downloading"""
import sys, os.path, re, urlparse, urllib2, shutil, random, socket, cStringIO
-import httplib, urllib; from setuptools import ssl_support
+import itertools
+import base64
+import httplib, urllib
+from setuptools import ssl_support
from pkg_resources import *
from distutils import log
from distutils.errors import DistutilsError
@@ -9,6 +12,9 @@ try:
except ImportError:
from md5 import md5
from fnmatch import translate
+from setuptools.py24compat import wraps
+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
@@ -24,20 +30,31 @@ __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]
+ 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
- return base,py_ver
def egg_info_for_url(url):
scheme, server, path, parameters, query, fragment = urlparse.urlparse(url)
@@ -67,10 +84,10 @@ def distros_for_location(location, basename, metadata=None):
# 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.)
#
@@ -121,9 +138,38 @@ 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 itertools.ifilterfalse(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"""
@@ -142,7 +188,7 @@ def find_external_links(url, page):
yield urlparse.urljoin(url, htmldecode(match.group(1)))
user_agent = "Python-urllib/%s setuptools/%s" % (
- urllib2.__version__, require('setuptools')[0].version
+ sys.version[:3], require('setuptools')[0].version
)
class PackageIndex(Environment):
@@ -187,7 +233,7 @@ class PackageIndex(Environment):
self.info("Reading %s", url)
self.fetched_urls[url] = True # prevent multiple fetch attempts
- f = self.open_url(url, "Download error: %s -- Some packages may not be found!")
+ 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():
@@ -196,12 +242,19 @@ class PackageIndex(Environment):
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, urllib2.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) and getattr(f,'code',None)!=404:
- page = self.process_index(url, page)
for match in HREF.finditer(page):
link = urlparse.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
@@ -237,7 +290,7 @@ class PackageIndex(Environment):
self.scan_egg_link(item, entry)
def scan_egg_link(self, path, entry):
- lines = filter(None, map(str.strip, file(os.path.join(path, entry))))
+ lines = filter(None, map(str.strip, open(os.path.join(path, entry))))
if len(lines)==2:
for dist in find_distributions(os.path.join(path, lines[0])):
dist.location = os.path.join(path, *lines)
@@ -262,7 +315,10 @@ class PackageIndex(Environment):
# process an index page into the package-page index
for match in HREF.finditer(page):
- scan( urlparse.urljoin(url, htmldecode(match.group(1))) )
+ try:
+ scan( urlparse.urljoin(url, htmldecode(match.group(1))) )
+ except ValueError:
+ pass
pkg, ver = scan(url) # ensure this page is in the page index
if pkg:
@@ -410,7 +466,7 @@ class PackageIndex(Environment):
def fetch_distribution(self,
requirement, tmpdir, force_scan=False, source=False, develop_ok=False,
- local_index=None,
+ local_index=None
):
"""Obtain a distribution suitable for fulfilling `requirement`
@@ -433,7 +489,9 @@ class PackageIndex(Environment):
skipped = {}
dist = None
- def find(env, req):
+ 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]:
@@ -452,18 +510,18 @@ class PackageIndex(Environment):
if force_scan:
self.prescan()
self.find_packages(requirement)
- dist = find(self, requirement)
-
+ dist = find(requirement)
+
if local_index is not None:
- dist = dist or find(local_index, requirement)
+ dist = dist or find(requirement, local_index)
if dist is None and self.to_scan is not None:
self.prescan()
- dist = find(self, requirement)
+ dist = find(requirement)
if dist is None and not force_scan:
self.find_packages(requirement)
- dist = find(self, requirement)
+ dist = find(requirement)
if dist is None:
self.warn(
@@ -550,7 +608,9 @@ class PackageIndex(Environment):
bs = self.dl_blocksize
size = -1
if "content-length" in headers:
- size = max(map(int,headers.getheaders("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:
@@ -573,20 +633,37 @@ class PackageIndex(Environment):
def open_url(self, url, warning=None):
- if url.startswith('file:'): return local_open(url)
+ if url.startswith('file:'):
+ return local_open(url)
try:
return open_with_auth(url, self.opener)
- except urllib2.HTTPError:
- return sys.exc_info()[1]
- except urllib2.URLError:
- reason = sys.exc_info()[1].reason
- except httplib.HTTPException:
- v = sys.exc_info()[1]
- reason = "%s: %s" % (v.__doc__ or v.__class__.__name__, v)
- if warning:
- self.warn(warning, reason)
- else:
- raise DistutilsError("Download error for %s: %s" % (url, reason))
+ except (ValueError, httplib.InvalidURL), v:
+ 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:
+ return v
+ except urllib2.URLError, v:
+ if warning:
+ self.warn(warning, v.reason)
+ else:
+ raise DistutilsError("Download error for %s: %s"
+ % (url, v.reason))
+ except httplib.BadStatusLine, v:
+ 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:
+ 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
@@ -607,8 +684,12 @@ 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 urllib2.url2pathname(urlparse.urlparse(url)[2])
+ return urllib.url2pathname(urlparse.urlparse(url)[2])
else:
self.url_ok(url, True) # raises error if not allowed
return self._attempt_download(url, filename)
@@ -674,6 +755,55 @@ class PackageIndex(Environment):
os.system("svn checkout%s -q %s %s" % (creds, url, filename))
return filename
+ def _vcs_split_rev_from_url(self, url, pop_prefix=False):
+ scheme, netloc, path, query, frag = urlparse.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 = urlparse.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):
log.debug(msg, *args)
@@ -734,20 +864,52 @@ def htmldecode(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.
+ >>> _encode_auth('username%3Apassword')
+ u'dXNlcm5hbWU6cGFzc3dvcmQ='
+ """
+ auth_s = urllib2.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.rstrip()
def open_with_auth(url, opener=urllib2.urlopen):
"""Open a urllib2 request, handling HTTP authentication"""
scheme, netloc, path, params, query, frag = urlparse.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 = urllib.splituser(netloc)
else:
auth = None
if auth:
- auth = "Basic " + urllib2.unquote(auth).encode('base64').strip()
+ auth = "Basic " + _encode_auth(auth)
new_url = urlparse.urlunparse((scheme,host,path,params,query,frag))
request = urllib2.Request(new_url)
request.add_header("Authorization", auth)
@@ -766,6 +928,8 @@ def open_with_auth(url, opener=urllib2.urlopen):
return fp
+# adding a timeout to avoid freezing package_index
+open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth)
@@ -783,14 +947,16 @@ def fix_sf_url(url):
def local_open(url):
"""Read a local path, with special support for directories"""
scheme, server, path, param, query, frag = urlparse.urlparse(url)
- filename = urllib2.url2pathname(path)
+ filename = urllib.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':
- body = open(os.path.join(filename,f),'rb').read()
+ fp = open(os.path.join(filename,f),'rb')
+ body = fp.read()
+ fp.close()
break
elif os.path.isdir(os.path.join(filename,f)):
f+='/'
diff --git a/setuptools/py24compat.py b/setuptools/py24compat.py
new file mode 100644
index 00000000..c5d7d204
--- /dev/null
+++ b/setuptools/py24compat.py
@@ -0,0 +1,11 @@
+"""
+Forward-compatibility support for Python 2.4 and earlier
+"""
+
+# from jaraco.compat 1.2
+try:
+ from functools import wraps
+except ImportError:
+ def wraps(func):
+ "Just return the function unwrapped"
+ return 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/sandbox.py b/setuptools/sandbox.py
index 8b889a78..f3095125 100755
--- a/setuptools/sandbox.py
+++ b/setuptools/sandbox.py
@@ -1,8 +1,13 @@
import os, sys, __builtin__, tempfile, operator, pkg_resources
-_os = sys.modules[os.name]
+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
-_file = file
-
from distutils.errors import DistutilsError
from pkg_resources import working_set
@@ -38,7 +43,6 @@ __all__ = [
-
def run_setup(setup_script, args):
"""Run a distutils setup script, sandboxed in its directory"""
old_dir = os.getcwd()
@@ -51,12 +55,13 @@ def run_setup(setup_script, args):
save_modules = sys.modules.copy()
pr_state = pkg_resources.__getstate__()
try:
- tempfile.tempdir = temp_dir; os.chdir(setup_dir)
+ 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.__init__()
working_set.callbacks.append(lambda dist:dist.activate())
DirectorySandbox(setup_dir).run(
lambda: execfile(
@@ -71,8 +76,14 @@ def run_setup(setup_script, args):
finally:
pkg_resources.__setstate__(pr_state)
sys.modules.update(save_modules)
- for key in list(sys.modules):
- if key not in save_modules: del sys.modules[key]
+ # 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.')
+ ]
+ map(sys.modules.__delitem__, del_modules)
os.chdir(old_dir)
sys.path[:] = save_path
sys.argv[:] = save_argv
@@ -99,14 +110,16 @@ class AbstractSandbox:
"""Run 'func' under os sandboxing"""
try:
self._copy(self)
- __builtin__.file = self._file
+ if _file:
+ __builtin__.file = self._file
__builtin__.open = self._open
self._active = True
return func()
finally:
self._active = False
+ if _file:
+ __builtin__.file = _file
__builtin__.open = _open
- __builtin__.file = _file
self._copy(_os)
def _mk_dual_path_wrapper(name):
@@ -129,8 +142,9 @@ class AbstractSandbox:
return original(path,*args,**kw)
return wrap
+ if _file:
+ _file = _mk_single_path_wrapper('file', _file)
_open = _mk_single_path_wrapper('open', _open)
- _file = _mk_single_path_wrapper('file', _file)
for name in [
"stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir",
"remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat",
@@ -182,6 +196,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"""
@@ -190,32 +217,44 @@ class DirectorySandbox(AbstractSandbox):
"utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam",
])
- def __init__(self,sandbox):
+ 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', '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 tmpnam(self):
+ self._violation("tmpnam")
def _ok(self,path):
- if hasattr(os,'devnull') and path==os.devnull: return True
active = self._active
try:
self._active = False
realpath = os.path.normcase(os.path.realpath(path))
- if realpath==self._sandbox or realpath.startswith(self._prefix):
+ if (self._exempted(realpath) or realpath == self._sandbox
+ or realpath.startswith(self._prefix)):
return True
finally:
self._active = active
+ def _exempted(self, filepath):
+ exception_matches = map(filepath.startswith, self._exceptions)
+ return True in exception_matches
+
def _remap_input(self,operation,path,*args,**kw):
"""Called for path inputs"""
if operation in self.write_ops and not self._ok(path):
@@ -228,11 +267,6 @@ class DirectorySandbox(AbstractSandbox):
self._violation(operation, src, dst, *args, **kw)
return (src,dst)
- 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, file, flags, mode=0777):
"""Called for low-level os.open()"""
if flags & WRITE_FLAGS and not self._ok(file):
diff --git a/setuptools/script template (dev).py b/setuptools/script template (dev).py
new file mode 100644
index 00000000..6dd9dd45
--- /dev/null
+++ b/setuptools/script template (dev).py
@@ -0,0 +1,6 @@
+# EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r
+__requires__ = """%(spec)r"""
+from pkg_resources import require; require("""%(spec)r""")
+del require
+__file__ = """%(dev_path)r"""
+execfile(__file__)
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..a7166f14
--- /dev/null
+++ b/setuptools/site-patch.py
@@ -0,0 +1,83 @@
+def __boot():
+ import sys, os, os.path
+ 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
index 2e615db3..32c8ee3c 100644
--- a/setuptools/ssl_support.py
+++ b/setuptools/ssl_support.py
@@ -1,5 +1,6 @@
-import sys, os, socket, urllib2, atexit
-from pkg_resources import ResolutionError, ExtractionError, resource_filename
+import sys, os, socket, urllib2, atexit, re
+import pkg_resources
+from pkg_resources import ResolutionError, ExtractionError
try:
import ssl
@@ -46,7 +47,7 @@ except ImportError:
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
"""Connect to *address* and return the socket object.
-
+
Convenience function. Connect to *address* (a 2-tuple ``(host,
port)``) and return the socket object. Passing the optional
*timeout* parameter will set the timeout on the socket instance
@@ -55,7 +56,7 @@ except ImportError:
is used. If *source_address* is set it must be a tuple of (host, port)
for the socket to bind as a source address before making the connection.
An host of '' or port 0 tells the OS to use the default.
- """
+ """
host, port = address
err = None
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
@@ -73,7 +74,7 @@ except ImportError:
except error:
err = True
if sock is not None:
- sock.close()
+ sock.close()
if err:
raise
else:
@@ -85,7 +86,7 @@ try:
except ImportError:
class CertificateError(ValueError):
pass
-
+
def _dnsname_to_pat(dn):
pats = []
for frag in dn.split(r'.'):
@@ -98,12 +99,12 @@ except ImportError:
frag = re.escape(frag)
pats.append(frag.replace(r'\*', '[^.]*'))
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
-
+
def match_hostname(cert, hostname):
"""Verify that *cert* (in decoded format as returned by
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
are mostly followed, but IP addresses are not accepted for *hostname*.
-
+
CertificateError is raised on failure. On success, the function
returns nothing.
"""
@@ -177,7 +178,7 @@ class VerifyingHTTPSHandler(HTTPSHandler):
class VerifyingHTTPSConn(HTTPSConnection):
"""Simple verifying connection: no auth, subclasses, timeouts, etc."""
- def __init__(self, host, ca_bundle, **kw):
+ def __init__(self, host, ca_bundle, **kw):
HTTPSConnection.__init__(self, host, **kw)
self.ca_bundle = ca_bundle
@@ -187,7 +188,7 @@ class VerifyingHTTPSConn(HTTPSConnection):
)
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:
@@ -201,7 +202,7 @@ def opener_for(ca_bundle=None):
VerifyingHTTPSHandler(ca_bundle or find_ca_bundle())
).open
-
+
_wincerts = None
@@ -210,7 +211,7 @@ def get_win_certfile():
if _wincerts is not None:
return _wincerts.name
- try:
+ try:
from wincertstore import CertFile
except ImportError:
return None
@@ -221,7 +222,7 @@ def get_win_certfile():
for store in stores:
self.addstore(store)
self.addcerts(certs)
- atexit.register(self.close)
+ atexit.register(self.close)
_wincerts = MyCertFile(stores=['CA', 'ROOT'])
return _wincerts.name
diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py
index 287bc240..b6988a08 100644
--- a/setuptools/tests/__init__.py
+++ b/setuptools/tests/__init__.py
@@ -1,22 +1,25 @@
"""Tests for the 'setuptools' package"""
-from unittest import TestSuite, TestCase, makeSuite, defaultTestLoader
-import distutils.core, distutils.cmd
+import sys
+import os
+import unittest
+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
-extract_constant, get_module_constant = None, None
-from setuptools.depends import *
-from distutils.version import StrictVersion, LooseVersion
-from distutils.util import convert_path
-import sys, os.path
+from distutils.version import LooseVersion
+
+import setuptools.dist
+import setuptools.depends as dep
+from setuptools import Feature
+from setuptools.depends import Require
def additional_tests():
import doctest, unittest
suite = unittest.TestSuite((
doctest.DocFileSuite(
- 'api_tests.txt',
+ os.path.join('tests', 'api_tests.txt'),
optionflags=doctest.ELLIPSIS, package='pkg_resources',
),
))
@@ -35,80 +38,85 @@ 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):
- if not extract_constant: return # skip on non-bytecode platforms
+ 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
# unrecognized name
- self.assertEqual(extract_constant(f1.func_code,'q', -1), None)
+ self.assertEqual(dep.extract_constant(f1.func_code,'q', -1), None)
# constant assigned
- self.assertEqual(extract_constant(f1.func_code,'x', -1), "test")
+ self.assertEqual(dep.extract_constant(f1.func_code,'x', -1), "test")
# expression assigned
- self.assertEqual(extract_constant(f1.func_code,'y', -1), -1)
+ self.assertEqual(dep.extract_constant(f1.func_code,'y', -1), -1)
# recognized name, not assigned
- self.assertEqual(extract_constant(f1.func_code,'z', -1), None)
-
+ self.assertEqual(dep.extract_constant(f1.func_code,'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):
- if not get_module_constant: return # skip on non-bytecode platforms
- 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 extract_constant: return # skip on non-bytecode platforms
+ 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)
@@ -118,11 +126,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'])
@@ -135,10 +143,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')
@@ -157,12 +163,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])
@@ -189,20 +189,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,
@@ -232,20 +229,7 @@ class DistroTests(TestCase):
)
-
-
-
-
-
-
-
-
-
-
-
-
-
-class FeatureTests(TestCase):
+class FeatureTests(unittest.TestCase):
def setUp(self):
self.req = Require('Distutils','1.0.3','distutils')
@@ -269,12 +253,12 @@ class FeatureTests(TestCase):
)
def testDefaults(self):
- self.failIf(
+ self.assertTrue(not
Feature(
"test",standard=True,remove='x',available=False
).include_by_default()
)
- self.failUnless(
+ self.assertTrue(
Feature("test",standard=True,remove='x').include_by_default()
)
# Feature must have either kwargs, removes, or require_features
@@ -288,33 +272,33 @@ class FeatureTests(TestCase):
def testFeatureOptions(self):
dist = self.dist
- self.failUnless(
+ self.assertTrue(
('with-dwim',None,'include DWIM') in dist.feature_options
)
- self.failUnless(
+ self.assertTrue(
('without-dwim',None,'exclude DWIM (default)') in dist.feature_options
)
- self.failUnless(
+ self.assertTrue(
('with-bar',None,'include bar (default)') in dist.feature_options
)
- self.failUnless(
+ self.assertTrue(
('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)
+ self.assertTrue(not '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.assertTrue(not 'bar_et' in dist.py_modules)
+ self.assertTrue(not 'pkg.bar' in dist.packages)
+ self.assertTrue('pkg.baz' in dist.packages)
+ self.assertTrue('scripts/baz_it' in dist.scripts)
+ self.assertTrue(('libfoo','foo/foofoo.c') in dist.libraries)
self.assertEqual(dist.ext_modules,[])
self.assertEqual(dist.require_features, [self.req])
@@ -327,11 +311,11 @@ class FeatureTests(TestCase):
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'])
@@ -363,8 +347,3 @@ class TestCommandTests(TestCase):
ts5 = makeSetup().get_command_obj('test')
ts5.ensure_finalized()
self.assertEqual(ts5.test_suite, None)
-
-
-
-
-
diff --git a/setuptools/tests/doctest.py b/setuptools/tests/doctest.py
index bffce58f..cc1e06c3 100644
--- a/setuptools/tests/doctest.py
+++ b/setuptools/tests/doctest.py
@@ -1968,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)
@@ -2053,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)
@@ -2353,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)
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/server.py b/setuptools/tests/server.py
new file mode 100644
index 00000000..b2ab7acc
--- /dev/null
+++ b/setuptools/tests/server.py
@@ -0,0 +1,82 @@
+"""Basic http server for tests to simulate PyPI or custom indexes
+"""
+import urllib2
+import sys
+import time
+import threading
+import BaseHTTPServer
+from BaseHTTPServer import HTTPServer
+from SimpleHTTPServer import 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 urllib2.URLError:
+ # ignore any errors; all that's important is the request
+ pass
+ self.thread.join()
+
+ def base_url(self):
+ port = self.server_port
+ return 'http://127.0.0.1:%s/setuptools/tests/indexes/' % port
+
+class RequestRecorder(BaseHTTPServer.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/test_bdist_egg.py b/setuptools/tests/test_bdist_egg.py
new file mode 100644
index 00000000..7da122cc
--- /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 StringIO import StringIO
+
+from distutils.errors import DistutilsError
+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..315058c5
--- /dev/null
+++ b/setuptools/tests/test_develop.py
@@ -0,0 +1,118 @@
+"""develop tests
+"""
+import sys
+import os, shutil, tempfile, unittest
+import tempfile
+import site
+from StringIO import StringIO
+
+from distutils.errors import DistutilsError
+from setuptools.command.develop import develop
+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=['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'):
+ 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')
+ path = egg_link_file.read().split()[0].strip()
+ egg_link_file.close()
+ init_file = open(os.path.join(path, 'foo', '__init__.py'), 'rt')
+ init = init_file.read().strip()
+ 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:
+ 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..fcb78c36
--- /dev/null
+++ b/setuptools/tests/test_dist_info.py
@@ -0,0 +1,80 @@
+"""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+')
+ metadata_file.write(DALS(
+ """
+ Metadata-Version: 1.2
+ Name: VersionedDistribution
+ Requires-Dist: splort (4)
+ Provides-Extra: baz
+ Requires-Dist: quux (>=1.1); extra == 'baz'
+ """))
+ metadata_file.close()
+
+ unversioned = os.path.join(self.tmpdir,
+ 'UnversionedDistribution.dist-info')
+ os.mkdir(unversioned)
+ metadata_file = open(os.path.join(unversioned, 'METADATA'), 'w+')
+ 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'
+ """))
+ 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..395056e7
--- /dev/null
+++ b/setuptools/tests/test_easy_install.py
@@ -0,0 +1,425 @@
+"""Easy install Tests
+"""
+import sys
+import os
+import shutil
+import tempfile
+import unittest
+import site
+import textwrap
+import tarfile
+import urlparse
+import StringIO
+import distutils.core
+
+from setuptools.sandbox import run_setup, SandboxViolation
+from setuptools.command.easy_install import easy_install, fix_jython_executable, get_script_args
+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 Distribution as PRDistribution
+import setuptools.tests.server
+
+try:
+ # import multiprocessing solely for the purpose of testing its existence
+ __import__('multiprocessing')
+ import logging
+ _LOG = logging.getLogger('test_easy_install')
+ logging.basicConfig(level=logging.INFO, stream=sys.stderr)
+ _MULTIPROC = True
+except ImportError:
+ _MULTIPROC = False
+ _LOG = None
+
+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')()
+ )
+""" % 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 get_script_args(dist).next()][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 = cmd.package_index.scanned_urls.keys()
+ keys.sort()
+ 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)
+ if sys.version >= "2.6":
+ self.old_has_site = easy_install_pkg.HAS_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)
+ 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
+ easy_install_pkg.HAS_USER_SITE = self.old_has_site
+ easy_install_pkg.__file__ = self.old_file
+
+ def test_user_install_implied(self):
+ easy_install_pkg.HAS_USER_SITE = True # disabled sometimes
+ #XXX: replace with something meaningfull
+ if sys.version < "2.6":
+ return #SKIP
+ 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):
+ if not _MULTIPROC:
+ return
+ _LOG.info('this should not break')
+
+ def test_user_install_not_implied_without_usersite_enabled(self):
+ easy_install_pkg.HAS_USER_SITE = False # usually enabled
+ #XXX: replace with something meaningfull
+ if sys.version < "2.6":
+ return #SKIP
+ 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_setup_attrs = {
+ 'name': 'test_pkg', 'version': '0.0',
+ 'setup_requires': ['foobar'],
+ 'dependency_links': [os.path.abspath(self.dir)]
+ }
+
+ test_pkg = os.path.join(self.dir, 'test_pkg')
+ test_setup_py = os.path.join(test_pkg, 'setup.py')
+ test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
+ 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(self.dir, 'foobar-0.1.tar.gz')
+ make_trivial_sdist(
+ foobar_path,
+ textwrap.dedent("""\
+ import setuptools
+ setuptools.setup(
+ name='foobar',
+ version='0.1'
+ )
+ """))
+
+ old_stdout = sys.stdout
+ old_stderr = sys.stderr
+ sys.stdout = StringIO.StringIO()
+ sys.stderr = StringIO.StringIO()
+ try:
+ reset_setup_stop_context(
+ lambda: run_setup(test_setup_py, ['install'])
+ )
+ except SandboxViolation:
+ self.fail('Installation caused SandboxViolation')
+ finally:
+ sys.stdout = old_stdout
+ sys.stderr = old_stderr
+
+
+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.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
+
+ # I realize this is all-but-impossible to read, because it was
+ # ported from some well-factored, safe code using 'with'. If you
+ # need to maintain this code, consider making the changes in
+ # the parent revision (of this comment) and then port the changes
+ # back for Python 2.4 (or deprecate Python 2.4).
+
+ def install(dist_file):
+ def install_at(temp_install_dir):
+ def install_env():
+ ei_params = ['--index-url', p_index.url,
+ '--allow-hosts', p_index_loc,
+ '--exclude-scripts', '--install-dir', temp_install_dir,
+ dist_file]
+ def install_clean_reset():
+ def install_clean_argv():
+ # attempt to install the dist. It should fail because
+ # it doesn't exist.
+ self.assertRaises(SystemExit,
+ easy_install_pkg.main, ei_params)
+ argv_context(install_clean_argv, ['easy_install'])
+ reset_setup_stop_context(install_clean_reset)
+ environment_context(install_env, PYTHONPATH=temp_install_dir)
+ tempdir_context(install_at)
+
+ # create an sdist that has a build-time dependency.
+ self.create_sdist(install)
+
+ # 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/')
+
+ def create_sdist(self, installer):
+ """
+ Create an sdist with a setup_requires dependency (of something that
+ doesn't exist) and invoke installer on it.
+ """
+ def build_sdist(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())
+ installer(dist_path)
+ tempdir_context(build_sdist)
+
+
+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 = StringIO.BytesIO
+ except AttributeError:
+ MemFile = StringIO.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()
+
+
+def tempdir_context(f, cd=lambda dir:None):
+ """
+ Invoke f in the context
+ """
+ temp_dir = tempfile.mkdtemp()
+ orig_dir = os.getcwd()
+ try:
+ cd(temp_dir)
+ f(temp_dir)
+ finally:
+ cd(orig_dir)
+ shutil.rmtree(temp_dir)
+
+def environment_context(f, **updates):
+ """
+ Invoke f in the context
+ """
+ old_env = os.environ.copy()
+ os.environ.update(updates)
+ try:
+ f()
+ finally:
+ for key in updates:
+ del os.environ[key]
+ os.environ.update(old_env)
+
+def argv_context(f, repl):
+ """
+ Invoke f in the context
+ """
+ old_argv = sys.argv[:]
+ sys.argv[:] = repl
+ try:
+ f()
+ finally:
+ sys.argv[:] = old_argv
+
+def reset_setup_stop_context(f):
+ """
+ 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
+ try:
+ f()
+ finally:
+ distutils.core._setup_stop_after = setup_stop_after
diff --git a/setuptools/tests/test_markerlib.py b/setuptools/tests/test_markerlib.py
new file mode 100644
index 00000000..aa461846
--- /dev/null
+++ b/setuptools/tests/test_markerlib.py
@@ -0,0 +1,64 @@
+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("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("'buuuu' not in os.name"))
+
+ 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'"))
+
+ 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
index 0231eda8..ad856e81 100644
--- a/setuptools/tests/test_packageindex.py
+++ b/setuptools/tests/test_packageindex.py
@@ -1,27 +1,141 @@
"""Package Index Tests
"""
-# More would be better!
-
-import os, shutil, tempfile, unittest, urllib2
+import sys
+import unittest
+import urllib2
import pkg_resources
+import httplib
+import distutils.errors
import setuptools.package_index
+from server import IndexServer
class TestPackageIndex(unittest.TestCase):
- def test_bad_urls(self):
+ def test_bad_url_bad_port(self):
index = setuptools.package_index.PackageIndex()
- url = 'http://127.0.0.1/nonesuch/test_package_index'
+ url = 'http://127.0.0.1:0/nonesuch/test_package_index'
+ try:
+ v = index.open_url(url)
+ except Exception, v:
+ self.assertTrue(url in str(v))
+ else:
+ self.assertTrue(isinstance(v,urllib2.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:
+ self.assertTrue(url in str(v))
+ else:
+ self.assertTrue(isinstance(v, urllib2.HTTPError))
+
+ def test_bad_url_bad_status_line(self):
+ index = setuptools.package_index.PackageIndex(
+ hosts=('www.example.com',)
+ )
+
+ def _urlopen(*args):
+ import httplib
+ raise httplib.BadStatusLine('line')
+
+ index.opener = _urlopen
+ url = 'http://example.com'
try:
v = index.open_url(url)
except Exception, v:
- self.assert_(url in str(v))
+ self.assertTrue('line' in str(v))
else:
- self.assert_(isinstance(v,urllib2.HTTPError))
+ 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:
+ 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.assert_(index.url_ok(url, True))
+ 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.
+ http://bitbucket.org/tarek/distribute/issue/163/md5-validation-error
+
+ 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'))
diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py
index fdd405af..34e341b5 100644
--- a/setuptools/tests/test_resources.py
+++ b/setuptools/tests/test_resources.py
@@ -3,11 +3,21 @@
# NOTE: the shebang and encoding lines are for ScriptHeaderTests; do not remove
from unittest import TestCase, makeSuite; from pkg_resources import *
from setuptools.command.easy_install import get_script_header, is_sh
-import os, pkg_resources, sys, StringIO
+import os, pkg_resources, sys, StringIO, tempfile, shutil
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) < _MAX_LENGTH:
+ return result
+ return result[:_MAX_LENGTH] + ' [truncated]...'
+
class Metadata(EmptyProvider):
"""Mock object to return metadata as if from an on-disk distribution"""
@@ -35,7 +45,7 @@ class DistroTests(TestCase):
ad.add(Distribution.from_filename("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'])
@@ -143,7 +153,7 @@ class DistroTests(TestCase):
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
@@ -161,7 +171,7 @@ class DistroTests(TestCase):
self.assertRaises( VersionConflict,
ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad
)
-
+
def testDistroDependsOptions(self):
d = self.distRequires("""
Twisted>=1.5
@@ -188,21 +198,6 @@ class DistroTests(TestCase):
self.assertRaises(UnknownExtra, d.requires, ["foo"])
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
class EntryPointTests(TestCase):
def assertfields(self, ep):
@@ -210,7 +205,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]"
@@ -310,20 +305,20 @@ class RequirementsTests(TestCase):
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)
+ 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):
@@ -341,21 +336,33 @@ class RequirementsTests(TestCase):
)
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')
@@ -452,7 +459,7 @@ class ParseTests(TestCase):
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')
@@ -505,6 +512,19 @@ class ScriptHeaderTests(TestCase):
'#!%s -x\n' % self.non_ascii_exe)
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
@@ -521,13 +541,74 @@ class ScriptHeaderTests(TestCase):
self.assertEqual(get_script_header('#!/usr/bin/python -x',
executable=exe),
'#!%s -x\n' % exe)
- self.assert_('Unable to adapt shebang line' in sys.stdout.getvalue())
+ self.assertTrue('Unable to adapt shebang line' in sys.stdout.getvalue())
sys.stdout = sys.stderr = StringIO.StringIO()
self.assertEqual(get_script_header('#!/usr/bin/python',
executable=self.non_ascii_exe),
'#!%s -x\n' % self.non_ascii_exe)
- self.assert_('Unable to adapt shebang line' in sys.stdout.getvalue())
+ 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, e:
+ 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..1609ee86
--- /dev/null
+++ b/setuptools/tests/test_sandbox.py
@@ -0,0 +1,66 @@
+"""develop tests
+"""
+import sys
+import os
+import shutil
+import unittest
+import tempfile
+
+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)
+
+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..f51d4567
--- /dev/null
+++ b/setuptools/tests/test_sdist.py
@@ -0,0 +1,398 @@
+# -*- coding: utf-8 -*-
+"""sdist tests"""
+
+
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+import urllib
+import unicodedata
+from StringIO import StringIO
+
+
+from setuptools.command.sdist import sdist
+from setuptools.command.egg_info import manifest_maker
+from setuptools.dist import Distribution
+
+
+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, unicode):
+ 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:
+ 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:
+ 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:
+ 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:
+ 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)
+
+ 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)
+
+
+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..ad7cbd0f
--- /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 StringIO import StringIO
+
+from distutils.errors import DistutilsError
+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
+ \ No newline at end of file
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
index 2d95502e..db1daf6b 100644
--- a/setuptools/tests/win_script_wrapper.txt
+++ b/setuptools/tests/win_script_wrapper.txt
@@ -1,137 +1,178 @@
-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()
- >>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').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)))
-
-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
- >>> open(os.path.join(sample_directory, 'foo.exe'), 'wb').write(
- ... pkg_resources.resource_string('setuptools', 'cli.exe')
- ... )
-
-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 os
- >>> input, output = os.popen4('"'+nt_quote_arg(os.path.join(sample_directory, 'foo.exe'))
- ... + r' arg1 "arg 2" "arg \"2\\\"" "arg 4\\" "arg5 a\\b"')
- >>> input.write('hello\nworld\n')
- >>> input.close()
- >>> print output.read(),
- \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:
-
- >>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').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)))
-
- >>> input, output = os.popen4(nt_quote_arg(os.path.join(sample_directory, 'foo.exe')))
- >>> input.close()
- >>> print output.read(),
- \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()
- >>> open(os.path.join(sample_directory, 'bar-script.pyw'), 'w').write(
- ... """#!%(python_exe)s
- ... import sys
- ... open(sys.argv[1], 'wb').write(repr(sys.argv[2]))
- ... """ % dict(python_exe=nt_quote_arg(sys.executable)))
-
-We'll also copy gui.exe to the sample-directory with the name bar.exe:
-
- >>> import pkg_resources
- >>> open(os.path.join(sample_directory, 'bar.exe'), 'wb').write(
- ... pkg_resources.resource_string('setuptools', 'gui.exe')
- ... )
-
-Finally, we'll run the script and check the result:
-
- >>> import os
- >>> input, output = os.popen4('"'+nt_quote_arg(os.path.join(sample_directory, 'bar.exe'))
- ... + r' "%s" "Test Argument"' % os.path.join(sample_directory, 'test_output.txt'))
- >>> input.close()
- >>> print output.read()
- <BLANKLINE>
- >>> print open(os.path.join(sample_directory, 'test_output.txt'), 'rb').read()
- 'Test Argument'
-
-
-We're done with the sample_directory:
-
- >>> import shutil
- >>> shutil.rmtree(sample_directory)
-
+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:
+
+ >>> from subprocess import Popen, PIPE, STDOUT
+ >>> try:
+ ... unicode=unicode
+ ... except:
+ ... unicode=str
+ >>> def popen4(cmd, *args):
+ ... if hasattr(os, 'popen4'):
+ ... input, output = os.popen4(cmd + " ".join(args))
+ ... return input, output
+ ... else:
+ ... #emulate popen4 in python 3
+ ... if cmd[0] == '"' and cmd[-1] != '"':
+ ... cmd = cmd[1:]
+ ... cmd += " ".join(args)
+ ... p = Popen(cmd, shell=True, bufsize=0,
+ ... stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+ ... return p.stdin, p.stdout
+
+ >>> input, output = popen4('"' + nt_quote_arg(os.path.join(sample_directory, 'foo.exe')),
+ ... r' arg1', r'"arg 2"', r'"arg \"2\\\""', r'"arg 4\\"', r'"arg5 a\\b"')
+ >>> bytes_written = input.write('hello\nworld\n'.encode('utf-8'))
+ >>> input.close()
+ >>> # This is needed for line ending differences between py2 and py3 on win32
+ >>> msg = unicode(output.read(), encoding='utf-8').split("\n")
+ >>> for line in msg:
+ ... print(line.strip())
+ \foo-script.py
+ ['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
+ 'hello\nworld\n'
+ non-optimized
+ <BLANKLINE>
+
+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()
+
+ >>> input, output = popen4(nt_quote_arg(os.path.join(sample_directory, 'foo.exe')))
+ >>> input.close()
+ >>> # This is needed for line ending differences between py2 and py3 on win32
+ >>> msg = unicode(output.read(), encoding='utf-8').split("\n")
+ >>> for line in msg:
+ ... print(line.strip())
+ \foo-script.py
+ []
+ ''
+ ---
+ <BLANKLINE>
+
+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:
+
+ >>> input, output = popen4('"'+nt_quote_arg(os.path.join(sample_directory, 'bar.exe')),
+ ... r' "%s" "Test Argument"' % os.path.join(sample_directory, 'test_output.txt'))
+ >>> input.close()
+ >>> # This is needed for line ending differences between py2 and py3 on win32
+ >>> msg = unicode(output.read(), encoding='utf-8').split("\n")
+ >>> for line in msg:
+ ... print(line.strip())
+ <BLANKLINE>
+ >>> f = open(os.path.join(sample_directory, 'test_output.txt'), 'rb')
+ >>> print(unicode(f.read(), encoding='utf-8'))
+ 'Test Argument'
+ >>> f.close()
+
+
+We're done with the sample_directory:
+
+ >>> import shutil
+ >>> shutil.rmtree(sample_directory)
+