summaryrefslogtreecommitdiff
path: root/setuptools/_distutils/command
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools/_distutils/command')
-rw-r--r--setuptools/_distutils/command/bdist_msi.py2
-rw-r--r--setuptools/_distutils/command/build.py3
-rw-r--r--setuptools/_distutils/command/build_ext.py4
-rw-r--r--setuptools/_distutils/command/build_scripts.py216
-rw-r--r--setuptools/_distutils/command/check.py42
-rw-r--r--setuptools/_distutils/command/install.py155
6 files changed, 263 insertions, 159 deletions
diff --git a/setuptools/_distutils/command/bdist_msi.py b/setuptools/_distutils/command/bdist_msi.py
index 0863a188..15259532 100644
--- a/setuptools/_distutils/command/bdist_msi.py
+++ b/setuptools/_distutils/command/bdist_msi.py
@@ -27,7 +27,7 @@ class PyDialog(Dialog):
def __init__(self, *args, **kw):
"""Dialog(database, name, x, y, w, h, attributes, title, first,
default, cancel, bitmap=true)"""
- Dialog.__init__(self, *args)
+ super().__init__(*args)
ruler = self.h - 36
bmwidth = 152*ruler/328
#if kw.get("bitmap", True):
diff --git a/setuptools/_distutils/command/build.py b/setuptools/_distutils/command/build.py
index 4355a632..9606b81a 100644
--- a/setuptools/_distutils/command/build.py
+++ b/setuptools/_distutils/command/build.py
@@ -81,7 +81,8 @@ class build(Command):
"--plat-name only supported on Windows (try "
"using './configure --help' on your platform)")
- plat_specifier = ".%s-%d.%d" % (self.plat_name, *sys.version_info[:2])
+ plat_specifier = ".%s-%s" % (self.plat_name,
+ sys.implementation.cache_tag)
# Make it so Python 2.x and Python 2.x with --with-pydebug don't
# share the same build directories. Doing so confuses the build
diff --git a/setuptools/_distutils/command/build_ext.py b/setuptools/_distutils/command/build_ext.py
index 22628baf..181671bf 100644
--- a/setuptools/_distutils/command/build_ext.py
+++ b/setuptools/_distutils/command/build_ext.py
@@ -202,9 +202,7 @@ class build_ext(Command):
# Append the source distribution include and library directories,
# this allows distutils on windows to work in the source tree
self.include_dirs.append(os.path.dirname(get_config_h_filename()))
- _sys_home = getattr(sys, '_home', None)
- if _sys_home:
- self.library_dirs.append(_sys_home)
+ self.library_dirs.append(sys.base_exec_prefix)
# Use the .lib files for the correct architecture
if self.plat_name == 'win32':
diff --git a/setuptools/_distutils/command/build_scripts.py b/setuptools/_distutils/command/build_scripts.py
index e3312cf0..e56511da 100644
--- a/setuptools/_distutils/command/build_scripts.py
+++ b/setuptools/_distutils/command/build_scripts.py
@@ -2,7 +2,8 @@
Implements the Distutils 'build_scripts' command."""
-import os, re
+import os
+import re
from stat import ST_MODE
from distutils import sysconfig
from distutils.core import Command
@@ -11,8 +12,14 @@ from distutils.util import convert_path
from distutils import log
import tokenize
-# check if Python is called on the first line with this expression
-first_line_re = re.compile(b'^#!.*python[0-9.]*([ \t].*)?$')
+shebang_pattern = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
+"""
+Pattern matching a Python interpreter indicated in first line of a script.
+"""
+
+# for Setuptools compatibility
+first_line_re = shebang_pattern
+
class build_scripts(Command):
@@ -26,13 +33,11 @@ class build_scripts(Command):
boolean_options = ['force']
-
def initialize_options(self):
self.build_dir = None
self.scripts = None
self.force = None
self.executable = None
- self.outfiles = None
def finalize_options(self):
self.set_undefined_options('build',
@@ -49,104 +54,117 @@ class build_scripts(Command):
return
self.copy_scripts()
-
def copy_scripts(self):
- r"""Copy each script listed in 'self.scripts'; if it's marked as a
- Python script in the Unix way (first line matches 'first_line_re',
- ie. starts with "\#!" and contains "python"), then adjust the first
- line to refer to the current Python interpreter as we copy.
+ """
+ Copy each script listed in ``self.scripts``.
+
+ If a script is marked as a Python script (first line matches
+ 'shebang_pattern', i.e. starts with ``#!`` and contains
+ "python"), then adjust in the copy the first line to refer to
+ the current Python interpreter.
"""
self.mkpath(self.build_dir)
outfiles = []
updated_files = []
for script in self.scripts:
- adjust = False
- script = convert_path(script)
- outfile = os.path.join(self.build_dir, os.path.basename(script))
- outfiles.append(outfile)
-
- if not self.force and not newer(script, outfile):
- log.debug("not copying %s (up-to-date)", script)
- continue
-
- # Always open the file, but ignore failures in dry-run mode --
- # that way, we'll get accurate feedback if we can read the
- # script.
- try:
- f = open(script, "rb")
- except OSError:
- if not self.dry_run:
- raise
- f = None
- else:
- encoding, lines = tokenize.detect_encoding(f.readline)
- f.seek(0)
- first_line = f.readline()
- if not first_line:
- self.warn("%s is an empty file (skipping)" % script)
- continue
-
- match = first_line_re.match(first_line)
- if match:
- adjust = True
- post_interp = match.group(1) or b''
-
- if adjust:
- log.info("copying and adjusting %s -> %s", script,
- self.build_dir)
- updated_files.append(outfile)
- if not self.dry_run:
- if not sysconfig.python_build:
- executable = self.executable
- else:
- executable = os.path.join(
- sysconfig.get_config_var("BINDIR"),
- "python%s%s" % (sysconfig.get_config_var("VERSION"),
- sysconfig.get_config_var("EXE")))
- executable = os.fsencode(executable)
- shebang = b"#!" + executable + post_interp + b"\n"
- # Python parser starts to read a script using UTF-8 until
- # it gets a #coding:xxx cookie. The shebang has to be the
- # first line of a file, the #coding:xxx cookie cannot be
- # written before. So the shebang has to be decodable from
- # UTF-8.
- try:
- shebang.decode('utf-8')
- except UnicodeDecodeError:
- raise ValueError(
- "The shebang ({!r}) is not decodable "
- "from utf-8".format(shebang))
- # If the script is encoded to a custom encoding (use a
- # #coding:xxx cookie), the shebang has to be decodable from
- # the script encoding too.
- try:
- shebang.decode(encoding)
- except UnicodeDecodeError:
- raise ValueError(
- "The shebang ({!r}) is not decodable "
- "from the script encoding ({})"
- .format(shebang, encoding))
- with open(outfile, "wb") as outf:
- outf.write(shebang)
- outf.writelines(f.readlines())
- if f:
- f.close()
- else:
- if f:
- f.close()
- updated_files.append(outfile)
- self.copy_file(script, outfile)
-
- if os.name == 'posix':
- for file in outfiles:
- if self.dry_run:
- log.info("changing mode of %s", file)
- else:
- oldmode = os.stat(file)[ST_MODE] & 0o7777
- newmode = (oldmode | 0o555) & 0o7777
- if newmode != oldmode:
- log.info("changing mode of %s from %o to %o",
- file, oldmode, newmode)
- os.chmod(file, newmode)
- # XXX should we modify self.outfiles?
+ self._copy_script(script, outfiles, updated_files)
+
+ self._change_modes(outfiles)
+
return outfiles, updated_files
+
+ def _copy_script(self, script, outfiles, updated_files):
+ shebang_match = None
+ script = convert_path(script)
+ outfile = os.path.join(self.build_dir, os.path.basename(script))
+ outfiles.append(outfile)
+
+ if not self.force and not newer(script, outfile):
+ log.debug("not copying %s (up-to-date)", script)
+ return
+
+ # Always open the file, but ignore failures in dry-run mode
+ # in order to attempt to copy directly.
+ try:
+ f = tokenize.open(script)
+ except OSError:
+ if not self.dry_run:
+ raise
+ f = None
+ else:
+ first_line = f.readline()
+ if not first_line:
+ self.warn("%s is an empty file (skipping)" % script)
+ return
+
+ shebang_match = shebang_pattern.match(first_line)
+
+ updated_files.append(outfile)
+ if shebang_match:
+ log.info("copying and adjusting %s -> %s", script,
+ self.build_dir)
+ if not self.dry_run:
+ if not sysconfig.python_build:
+ executable = self.executable
+ else:
+ executable = os.path.join(
+ sysconfig.get_config_var("BINDIR"),
+ "python%s%s" % (
+ sysconfig.get_config_var("VERSION"),
+ sysconfig.get_config_var("EXE")))
+ post_interp = shebang_match.group(1) or ''
+ shebang = "#!" + executable + post_interp + "\n"
+ self._validate_shebang(shebang, f.encoding)
+ with open(outfile, "w", encoding=f.encoding) as outf:
+ outf.write(shebang)
+ outf.writelines(f.readlines())
+ if f:
+ f.close()
+ else:
+ if f:
+ f.close()
+ self.copy_file(script, outfile)
+
+ def _change_modes(self, outfiles):
+ if os.name != 'posix':
+ return
+
+ for file in outfiles:
+ self._change_mode(file)
+
+ def _change_mode(self, file):
+ if self.dry_run:
+ log.info("changing mode of %s", file)
+ return
+
+ oldmode = os.stat(file)[ST_MODE] & 0o7777
+ newmode = (oldmode | 0o555) & 0o7777
+ if newmode != oldmode:
+ log.info("changing mode of %s from %o to %o",
+ file, oldmode, newmode)
+ os.chmod(file, newmode)
+
+ @staticmethod
+ def _validate_shebang(shebang, encoding):
+ # Python parser starts to read a script using UTF-8 until
+ # it gets a #coding:xxx cookie. The shebang has to be the
+ # first line of a file, the #coding:xxx cookie cannot be
+ # written before. So the shebang has to be encodable to
+ # UTF-8.
+ try:
+ shebang.encode('utf-8')
+ except UnicodeEncodeError:
+ raise ValueError(
+ "The shebang ({!r}) is not encodable "
+ "to utf-8".format(shebang))
+
+ # If the script is encoded to a custom encoding (use a
+ # #coding:xxx cookie), the shebang has to be encodable to
+ # the script encoding too.
+ try:
+ shebang.encode(encoding)
+ except UnicodeEncodeError:
+ raise ValueError(
+ "The shebang ({!r}) is not encodable "
+ "to the script encoding ({})"
+ .format(shebang, encoding))
diff --git a/setuptools/_distutils/command/check.py b/setuptools/_distutils/command/check.py
index ada25006..af311ca9 100644
--- a/setuptools/_distutils/command/check.py
+++ b/setuptools/_distutils/command/check.py
@@ -2,6 +2,8 @@
Implements the Distutils 'check' command.
"""
+from email.utils import getaddresses
+
from distutils.core import Command
from distutils.errors import DistutilsSetupError
@@ -17,7 +19,7 @@ try:
def __init__(self, source, report_level, halt_level, stream=None,
debug=0, encoding='ascii', error_handler='replace'):
self.messages = []
- Reporter.__init__(self, source, report_level, halt_level, stream,
+ super().__init__(source, report_level, halt_level, stream,
debug, encoding, error_handler)
def system_message(self, level, message, *children, **kwargs):
@@ -96,19 +98,39 @@ class check(Command):
if missing:
self.warn("missing required meta-data: %s" % ', '.join(missing))
- if metadata.author:
- if not metadata.author_email:
- self.warn("missing meta-data: if 'author' supplied, " +
- "'author_email' should be supplied too")
- elif metadata.maintainer:
- if not metadata.maintainer_email:
- self.warn("missing meta-data: if 'maintainer' supplied, " +
- "'maintainer_email' should be supplied too")
- else:
+ if not (
+ self._check_contact("author", metadata) or
+ self._check_contact("maintainer", metadata)
+ ):
self.warn("missing meta-data: either (author and author_email) " +
"or (maintainer and maintainer_email) " +
"should be supplied")
+ def _check_contact(self, kind, metadata):
+ """
+ Returns True if the contact's name is specified and False otherwise.
+ This function will warn if the contact's email is not specified.
+ """
+ name = getattr(metadata, kind) or ''
+ email = getattr(metadata, kind + '_email') or ''
+
+ msg = ("missing meta-data: if '{}' supplied, " +
+ "'{}' should be supplied too")
+
+ if name and email:
+ return True
+
+ if name:
+ self.warn(msg.format(kind, kind + '_email'))
+ return True
+
+ addresses = [(alias, addr) for alias, addr in getaddresses([email])]
+ if any(alias and addr for alias, addr in addresses):
+ # The contact's name can be encoded in the email: `Name <email>`
+ return True
+
+ return False
+
def check_restructuredtext(self):
"""Checks if the long string fields are reST-compliant."""
data = self.distribution.get_long_description()
diff --git a/setuptools/_distutils/command/install.py b/setuptools/_distutils/command/install.py
index c756b6db..41c17d8a 100644
--- a/setuptools/_distutils/command/install.py
+++ b/setuptools/_distutils/command/install.py
@@ -17,6 +17,7 @@ from distutils.file_util import write_file
from distutils.util import convert_path, subst_vars, change_root
from distutils.util import get_platform
from distutils.errors import DistutilsOptionError
+from .. import _collections
from site import USER_BASE
from site import USER_SITE
@@ -67,8 +68,8 @@ if HAS_USER_SITE:
INSTALL_SCHEMES['nt_user'] = {
'purelib': '{usersite}',
'platlib': '{usersite}',
- 'headers': '{userbase}/{implementation}{py_version_nodot}/Include/{dist_name}',
- 'scripts': '{userbase}/{implementation}{py_version_nodot}/Scripts',
+ 'headers': '{userbase}/{implementation}{py_version_nodot_plat}/Include/{dist_name}',
+ 'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts',
'data' : '{userbase}',
}
@@ -118,6 +119,65 @@ def _get_implementation():
return 'Python'
+def _select_scheme(ob, name):
+ scheme = _inject_headers(name, _load_scheme(_resolve_scheme(name)))
+ vars(ob).update(_remove_set(ob, _scheme_attrs(scheme)))
+
+
+def _remove_set(ob, attrs):
+ """
+ Include only attrs that are None in ob.
+ """
+ return {
+ key: value
+ for key, value in attrs.items()
+ if getattr(ob, key) is None
+ }
+
+
+def _resolve_scheme(name):
+ os_name, sep, key = name.partition('_')
+ try:
+ resolved = sysconfig.get_preferred_scheme(key)
+ except Exception:
+ resolved = _pypy_hack(name)
+ return resolved
+
+
+def _load_scheme(name):
+ return _load_schemes()[name]
+
+
+def _inject_headers(name, scheme):
+ """
+ Given a scheme name and the resolved scheme,
+ if the scheme does not include headers, resolve
+ the fallback scheme for the name and use headers
+ from it. pypa/distutils#88
+ """
+ # Bypass the preferred scheme, which may not
+ # have defined headers.
+ fallback = _load_scheme(_pypy_hack(name))
+ scheme.setdefault('headers', fallback['headers'])
+ return scheme
+
+
+def _scheme_attrs(scheme):
+ """Resolve install directories by applying the install schemes."""
+ return {
+ f'install_{key}': scheme[key]
+ for key in SCHEME_KEYS
+ }
+
+
+def _pypy_hack(name):
+ PY37 = sys.version_info < (3, 8)
+ old_pypy = hasattr(sys, 'pypy_version_info') and PY37
+ prefix = not name.endswith(('_user', '_home'))
+ pypy_name = 'pypy' + '_nt' * (os.name == 'nt')
+ return pypy_name if old_pypy and prefix else name
+
+
class install(Command):
description = "install everything from build directory"
@@ -335,25 +395,35 @@ class install(Command):
except AttributeError:
# sys.abiflags may not be defined on all platforms.
abiflags = ''
- 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': '%d.%d' % sys.version_info[:2],
- 'py_version_nodot': '%d%d' % sys.version_info[:2],
- 'sys_prefix': prefix,
- 'prefix': prefix,
- 'sys_exec_prefix': exec_prefix,
- 'exec_prefix': exec_prefix,
- 'abiflags': abiflags,
- 'platlibdir': getattr(sys, 'platlibdir', 'lib'),
- 'implementation_lower': _get_implementation().lower(),
- 'implementation': _get_implementation(),
- }
+ local_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': '%d.%d' % sys.version_info[:2],
+ 'py_version_nodot': '%d%d' % sys.version_info[:2],
+ 'sys_prefix': prefix,
+ 'prefix': prefix,
+ 'sys_exec_prefix': exec_prefix,
+ 'exec_prefix': exec_prefix,
+ 'abiflags': abiflags,
+ 'platlibdir': getattr(sys, 'platlibdir', 'lib'),
+ 'implementation_lower': _get_implementation().lower(),
+ 'implementation': _get_implementation(),
+ }
+
+ # vars for compatibility on older Pythons
+ compat_vars = dict(
+ # Python 3.9 and earlier
+ py_version_nodot_plat=getattr(sys, 'winver', '').replace('.', ''),
+ )
if HAS_USER_SITE:
- self.config_vars['userbase'] = self.install_userbase
- self.config_vars['usersite'] = self.install_usersite
+ local_vars['userbase'] = self.install_userbase
+ local_vars['usersite'] = self.install_usersite
+
+ self.config_vars = _collections.DictStack(
+ [compat_vars, sysconfig.get_config_vars(), local_vars])
self.expand_basedirs()
@@ -361,15 +431,13 @@ class install(Command):
# Now define config vars for the base directories so we can expand
# everything else.
- self.config_vars['base'] = self.install_base
- self.config_vars['platbase'] = self.install_platbase
- self.config_vars['installed_base'] = (
- sysconfig.get_config_vars()['installed_base'])
+ local_vars['base'] = self.install_base
+ local_vars['platbase'] = self.install_platbase
if DEBUG:
from pprint import pprint
print("config vars:")
- pprint(self.config_vars)
+ pprint(dict(self.config_vars))
# Expand "~" and configuration variables in the installation
# directories.
@@ -445,12 +513,17 @@ class install(Command):
def finalize_unix(self):
"""Finalizes options for posix platforms."""
if self.install_base is not None or self.install_platbase is not None:
- if ((self.install_lib is None and
- self.install_purelib is None and
- self.install_platlib is None) or
+ incomplete_scheme = (
+ (
+ self.install_lib is None and
+ self.install_purelib is None and
+ self.install_platlib is None
+ ) or
self.install_headers is None or
self.install_scripts is None or
- self.install_data is None):
+ self.install_data is None
+ )
+ if incomplete_scheme:
raise DistutilsOptionError(
"install-base or install-platbase supplied, but "
"installation scheme is incomplete")
@@ -471,8 +544,13 @@ class install(Command):
raise DistutilsOptionError(
"must not supply exec-prefix without prefix")
- self.prefix = os.path.normpath(sys.prefix)
- self.exec_prefix = os.path.normpath(sys.exec_prefix)
+ # Allow Fedora to add components to the prefix
+ _prefix_addition = getattr(sysconfig, '_prefix_addition', "")
+
+ self.prefix = (
+ os.path.normpath(sys.prefix) + _prefix_addition)
+ self.exec_prefix = (
+ os.path.normpath(sys.exec_prefix) + _prefix_addition)
else:
if self.exec_prefix is None:
@@ -505,20 +583,7 @@ class install(Command):
"I don't know how to install stuff on '%s'" % os.name)
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!
- if (hasattr(sys, 'pypy_version_info') and
- sys.version_info < (3, 8) and
- not name.endswith(('_user', '_home'))):
- if os.name == 'nt':
- name = 'pypy_nt'
- else:
- name = 'pypy'
- scheme = _load_schemes()[name]
- for key in SCHEME_KEYS:
- attrname = 'install_' + key
- if getattr(self, attrname) is None:
- setattr(self, attrname, scheme[key])
+ _select_scheme(self, name)
def _expand_attrs(self, attrs):
for attr in attrs:
@@ -592,7 +657,7 @@ class install(Command):
return
home = convert_path(os.path.expanduser("~"))
for name, path in self.config_vars.items():
- if path.startswith(home) and not os.path.isdir(path):
+ if str(path).startswith(home) and not os.path.isdir(path):
self.debug_print("os.makedirs('%s', 0o700)" % path)
os.makedirs(path, 0o700)