summaryrefslogtreecommitdiff
path: root/setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools')
-rwxr-xr-xsetuptools/archive_util.py3
-rw-r--r--setuptools/command/bdist_egg.py12
-rw-r--r--setuptools/command/build_py.py6
-rwxr-xr-xsetuptools/command/easy_install.py5
-rwxr-xr-xsetuptools/command/egg_info.py39
-rwxr-xr-xsetuptools/command/sdist.py32
-rw-r--r--setuptools/command/test.py17
-rw-r--r--setuptools/command/upload_docs.py125
-rw-r--r--setuptools/dist.py37
-rwxr-xr-xsetuptools/package_index.py60
-rw-r--r--setuptools/tests/test_dist_info.py14
-rw-r--r--setuptools/tests/test_markerlib.py40
-rw-r--r--setuptools/tests/test_sdist.py322
-rw-r--r--setuptools/tests/test_upload_docs.py15
14 files changed, 600 insertions, 127 deletions
diff --git a/setuptools/archive_util.py b/setuptools/archive_util.py
index 5787753f..e22b25c0 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()
diff --git a/setuptools/command/bdist_egg.py b/setuptools/command/bdist_egg.py
index 0ee9c55b..17fae984 100644
--- a/setuptools/command/bdist_egg.py
+++ b/setuptools/command/bdist_egg.py
@@ -425,12 +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
- try:
- code = marshal.load(f); f.close()
- except ValueError:
- f.seek(0); f.read(12) # skip magic & date & file size; file size added in Python 3.3
- 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__']:
diff --git a/setuptools/command/build_py.py b/setuptools/command/build_py.py
index 505dd4f3..8751acd4 100644
--- a/setuptools/command/build_py.py
+++ b/setuptools/command/build_py.py
@@ -51,10 +51,8 @@ try:
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 not in self.fixer_names:
- log.warn("Excluded fixer %s not found", fixer_name)
- continue
- self.fixer_names.remove(fixer_name)
+ if fixer_name in self.fixer_names:
+ self.fixer_names.remove(fixer_name)
except ImportError:
class Mixin2to3:
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
index f2260236..337532bc 100755
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -1522,7 +1522,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)), ''))
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
index 46cdf4e0..0c2ea0cc 100755
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -9,7 +9,7 @@ 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
@@ -162,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)
@@ -269,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)
@@ -318,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]
diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py
index a176f635..2fa3771a 100755
--- a/setuptools/command/sdist.py
+++ b/setuptools/command/sdist.py
@@ -262,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 e5cb9bb5..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):
@@ -139,6 +140,22 @@ 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()
diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py
index 213f7b58..98fb7233 100644
--- a/setuptools/command/upload_docs.py
+++ b/setuptools/command/upload_docs.py
@@ -9,10 +9,13 @@ import os
import socket
import zipfile
import httplib
-import base64
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
@@ -22,20 +25,13 @@ try:
except ImportError:
from setuptools.command.upload import upload
-_IS_PYTHON3 = sys.version > '3'
-
-try:
- bytes
-except NameError:
- bytes = str
-def b(str_or_bytes):
- """Return bytes by either encoding the argument as ASCII or simply return
- the argument as-is."""
- if not isinstance(str_or_bytes, bytes):
- return str_or_bytes.encode('ascii')
- else:
- return str_or_bytes
+# 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):
@@ -51,40 +47,62 @@ class upload_docs(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:
- build = self.get_finalized_command('build')
- self.upload_dir = os.path.join(build.build_base, 'docs')
- self.mkpath(self.upload_dir)
- self.ensure_dirname('upload_dir')
- self.announce('Using upload directory %s' % self.upload_dir)
+ 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):
- name = self.distribution.metadata.get_name()
- tmp_dir = tempfile.mkdtemp()
- tmp_file = os.path.join(tmp_dir, "%s.zip" % name)
- zip_file = zipfile.ZipFile(tmp_file, "w")
- for root, dirs, files in os.walk(self.upload_dir):
- if root == self.upload_dir and not files:
- raise DistutilsOptionError(
- "no files found in upload directory '%s'"
- % self.upload_dir)
- for name in files:
- full = os.path.join(root, name)
- relative = root[len(self.upload_dir):].lstrip(os.path.sep)
- dest = os.path.join(relative, name)
- zip_file.write(full, dest)
- zip_file.close()
- return tmp_file
+ 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):
- zip_file = self.create_zipfile()
- self.upload_file(zip_file)
+ # 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):
content = open(filename, 'rb').read()
@@ -95,36 +113,33 @@ class upload_docs(upload):
'content': (os.path.basename(filename), content),
}
# set up the authentication
- credentials = self.username + ':' + self.password
- if _IS_PYTHON3: # base64 only works with bytes in Python 3.
- encoded_creds = base64.encodebytes(credentials.encode('utf8'))
- auth = bytes("Basic ")
- else:
- encoded_creds = base64.encodestring(credentials)
- auth = "Basic "
- auth += encoded_creds.strip()
+ 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 = b('--------------GHSKFJDLGDS7543FJKLFHRE75642756743254')
- sep_boundary = b('\n--') + boundary
+ boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+ sep_boundary = b('\n--') + b(boundary)
end_boundary = sep_boundary + b('--')
body = []
- for key, values in data.items():
+ 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:
- fn = b(';filename="%s"' % value[0])
+ title += '; filename="%s"' % value[0]
value = value[1]
else:
- fn = b("")
+ value = b(value)
body.append(sep_boundary)
- body.append(b('\nContent-Disposition: form-data; name="%s"'%key))
- body.append(fn)
+ body.append(b(title))
body.append(b("\n\n"))
- body.append(b(value))
- if value and value[-1] == b('\r'):
+ 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"))
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 2061c0a2..6236d5be 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -641,6 +641,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
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
index d0896feb..0ee21e3b 100755
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -657,6 +657,10 @@ 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 urllib.url2pathname(urlparse.urlparse(url)[2])
else:
@@ -697,6 +701,55 @@ class PackageIndex(Environment):
os.system("svn checkout -q %s %s" % (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)
@@ -779,6 +832,11 @@ def open_with_auth(url):
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 = urllib2.splituser(netloc)
else:
@@ -859,4 +917,4 @@ def local_open(url):
-# this line is a kludge to keep the trailing blank lines for pje's editor \ No newline at end of file
+# this line is a kludge to keep the trailing blank lines for pje's editor
diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py
index 70dce2d4..fcb78c36 100644
--- a/setuptools/tests/test_dist_info.py
+++ b/setuptools/tests/test_dist_info.py
@@ -7,7 +7,7 @@ import unittest
import textwrap
try:
- import _markerlib
+ import ast
except:
pass
@@ -34,8 +34,8 @@ class TestDistInfo(unittest.TestCase):
assert versioned.version == '2.718' # from filename
assert unversioned.version == '0.3' # from METADATA
- @skipIf('_markerlib' not in globals(),
- "_markerlib is used to test conditional dependencies (Python >= 2.5)")
+ @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')]
@@ -50,7 +50,8 @@ class TestDistInfo(unittest.TestCase):
versioned = os.path.join(self.tmpdir,
'VersionedDistribution-2.718.dist-info')
os.mkdir(versioned)
- open(os.path.join(versioned, 'METADATA'), 'w+').write(DALS(
+ metadata_file = open(os.path.join(versioned, 'METADATA'), 'w+')
+ metadata_file.write(DALS(
"""
Metadata-Version: 1.2
Name: VersionedDistribution
@@ -58,11 +59,13 @@ class TestDistInfo(unittest.TestCase):
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)
- open(os.path.join(unversioned, 'METADATA'), 'w+').write(DALS(
+ metadata_file = open(os.path.join(unversioned, 'METADATA'), 'w+')
+ metadata_file.write(DALS(
"""
Metadata-Version: 1.2
Name: UnversionedDistribution
@@ -71,6 +74,7 @@ class TestDistInfo(unittest.TestCase):
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_markerlib.py b/setuptools/tests/test_markerlib.py
index 4cce0430..7ff2f584 100644
--- a/setuptools/tests/test_markerlib.py
+++ b/setuptools/tests/test_markerlib.py
@@ -3,14 +3,14 @@ import unittest
from setuptools.tests.py26compat import skipIf
try:
- import _ast
+ import ast
except ImportError:
pass
class TestMarkerlib(unittest.TestCase):
- @skipIf('_ast' not in globals(),
- "ast not available (Python < 2.5?)")
+ @skipIf('ast' not in globals(),
+ "ast not available (Python < 2.6?)")
def test_markers(self):
from _markerlib import interpret, default_environment, compile
@@ -62,37 +62,3 @@ class TestMarkerlib(unittest.TestCase):
statement = "python_version == '5'"
self.assertEqual(compile(statement).__doc__, statement)
- @skipIf('_ast' not in globals(),
- "ast not available (Python < 2.5?)")
- def test_ast(self):
- try:
- import ast, nose
- raise nose.SkipTest()
- except ImportError:
- pass
-
- # Nonsensical code coverage tests.
- import _markerlib._markers_ast as _markers_ast
-
- class Node(_ast.AST):
- _fields = ('bogus')
- list(_markers_ast.iter_fields(Node()))
-
- class Node2(_ast.AST):
- def __init__(self):
- self._fields = ('bogus',)
- self.bogus = [Node()]
-
- class NoneTransformer(_markers_ast.NodeTransformer):
- def visit_Attribute(self, node):
- return None
-
- def visit_Str(self, node):
- return None
-
- def visit_Node(self, node):
- return []
-
- NoneTransformer().visit(_markers_ast.parse('a.b = "c"'))
- NoneTransformer().visit(Node2())
-
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
index 8d9ed922..a9d5d6e5 100644
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
"""sdist tests"""
@@ -6,10 +7,13 @@ 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
@@ -28,7 +32,52 @@ 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')
@@ -62,18 +111,273 @@ class TestSdistTest(unittest.TestCase):
cmd.ensure_finalized()
# squelch output
- old_stdout = sys.stdout
- old_stderr = sys.stderr
- sys.stdout = StringIO()
- sys.stderr = StringIO()
+ quiet()
try:
cmd.run()
finally:
- sys.stdout = old_stdout
- sys.stderr = old_stderr
+ 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,):
+ if sys.platform == 'win32':
+ # Python 3 mangles the UTF-8 filename
+ filename = filename.decode('cp1252')
+ 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()
+
+ quiet()
+ try:
+ cmd.run()
+ finally:
+ unquiet()
+
+ if sys.version_info >= (3,):
+ filename = filename.decode('latin-1')
+ if sys.platform == 'win32':
+ # Latin-1 is similar to Windows-1252
+ self.assertTrue(filename in cmd.filelist.files)
+ else:
+ # The Latin-1 filename should have been skipped
+ 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__)
- self.assert_(os.path.join('sdist_test', 'a.txt') in manifest)
- self.assert_(os.path.join('sdist_test', 'b.txt') in manifest)
- self.assert_(os.path.join('sdist_test', 'c.rst') not in manifest)
diff --git a/setuptools/tests/test_upload_docs.py b/setuptools/tests/test_upload_docs.py
index 8b2dc892..769f16cc 100644
--- a/setuptools/tests/test_upload_docs.py
+++ b/setuptools/tests/test_upload_docs.py
@@ -54,12 +54,19 @@ class TestUploadDocsTest(unittest.TestCase):
cmd = upload_docs(dist)
cmd.upload_dir = self.upload_dir
- zip_file = cmd.create_zipfile()
+ 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(zip_file)
+ assert zipfile.is_zipfile(tmp_file)
- zip_f = zipfile.ZipFile(zip_file) # woh...
+ zip_file = zipfile.ZipFile(tmp_file) # woh...
- assert zip_f.namelist() == ['index.html']
+ assert zip_file.namelist() == ['index.html']
+ zip_file.close()
+ finally:
+ shutil.rmtree(tmp_dir)