diff options
Diffstat (limited to 'setuptools/command')
-rwxr-xr-x | setuptools/command/develop.py | 4 | ||||
-rwxr-xr-x | setuptools/command/easy_install.py | 64 | ||||
-rwxr-xr-x | setuptools/command/egg_info.py | 46 | ||||
-rw-r--r-- | setuptools/command/launcher manifest.xml | 15 | ||||
-rwxr-xr-x | setuptools/command/sdist.py | 7 | ||||
-rwxr-xr-x | setuptools/command/upload.py | 5 | ||||
-rw-r--r-- | setuptools/command/upload_docs.py | 128 |
7 files changed, 182 insertions, 87 deletions
diff --git a/setuptools/command/develop.py b/setuptools/command/develop.py index 709e349c..1d500040 100755 --- a/setuptools/command/develop.py +++ b/setuptools/command/develop.py @@ -132,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 open(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 diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 8ce71614..50339e8f 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -19,7 +19,9 @@ import zipfile import re import stat import random +import platform from glob import glob +import pkg_resources from setuptools import Command, _dont_write_bytecode from setuptools.sandbox import run_setup from distutils import log, dir_util @@ -493,7 +495,7 @@ Please make the appropriate changes for your system and try again. 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': @@ -524,6 +526,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) @@ -1276,7 +1282,7 @@ 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("distribute"), "site.py") + source = resource_string("setuptools", "site-patch.py") current = "" if os.path.exists(sitepy): @@ -1529,7 +1535,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)), '')) @@ -1794,6 +1803,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: @@ -1828,31 +1842,61 @@ def get_script_args(dist, executable=sys_executable, wininst=False): 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 is_64bit(): - launcher = launcher.replace(".", "-64.") - else: - launcher = launcher.replace(".", "-32.") if os.path.exists(new_header[2:-1]) 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), + 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) +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. diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py index 124c410e..12acc422 100755 --- a/setuptools/command/egg_info.py +++ b/setuptools/command/egg_info.py @@ -10,7 +10,7 @@ from distutils import log from setuptools.command.sdist import sdist from setuptools.compat import basestring 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 setuptools.command.sdist import walk_revctrl @@ -275,35 +275,34 @@ 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) -def compose(path): - # Apple's HFS Plus returns decomposed UTF-8. Since just about - # everyone else chokes on it, we must make sure to return fully - # composed UTF-8 only. - if sys.getfilesystemencoding().lower() == 'utf-8': - from unicodedata import normalize - if sys.version_info >= (3,): - path = normalize('NFC', path) - else: - path = normalize('NFC', path.decode('utf-8')).encode('utf-8') - return path class manifest_maker(sdist): @@ -330,7 +329,6 @@ class manifest_maker(sdist): self.prune_file_list() self.filelist.sort() self.filelist.remove_duplicates() - self.filelist.files = [compose(path) for path in self.filelist.files] self.write_manifest() def write_manifest (self): @@ -338,6 +336,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/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 56ef8a66..6f3f48c8 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -282,8 +282,13 @@ class sdist(_sdist): 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,): - line = line.decode('UTF-8') + 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: diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index bf9c0668..7edbf56a 100755 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -108,8 +108,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() diff --git a/setuptools/command/upload_docs.py b/setuptools/command/upload_docs.py index 505ddadb..178a8793 100644 --- a/setuptools/command/upload_docs.py +++ b/setuptools/command/upload_docs.py @@ -8,9 +8,12 @@ PyPI's packages.python.org). import os import socket import zipfile -import base64 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 @@ -24,18 +27,12 @@ from setuptools.compat import httplib, urlparse _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,43 +48,67 @@ 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() + f = open(filename, 'rb') + content = f.read() + f.close() meta = self.distribution.metadata data = { ':action': 'doc_upload', @@ -95,36 +116,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")) |