Change venv
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
"""Extensions to the 'distutils' for large or complex distributions"""
|
||||
|
||||
from fnmatch import fnmatchcase
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
|
||||
import _distutils_hack.override # noqa: F401
|
||||
|
||||
import distutils.core
|
||||
from distutils.errors import DistutilsOptionError
|
||||
from distutils.util import convert_path
|
||||
from distutils.util import convert_path as _convert_path
|
||||
|
||||
from ._deprecation_warning import SetuptoolsDeprecationWarning
|
||||
|
||||
@@ -17,7 +17,9 @@ import setuptools.version
|
||||
from setuptools.extension import Extension
|
||||
from setuptools.dist import Distribution
|
||||
from setuptools.depends import Require
|
||||
from setuptools.discovery import PackageFinder, PEP420PackageFinder
|
||||
from . import monkey
|
||||
from . import logging
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -36,85 +38,6 @@ __version__ = setuptools.version.__version__
|
||||
bootstrap_install_from = None
|
||||
|
||||
|
||||
class PackageFinder:
|
||||
"""
|
||||
Generate a list of all Python packages found within a directory
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def find(cls, where='.', exclude=(), include=('*',)):
|
||||
"""Return a list all Python packages found within directory 'where'
|
||||
|
||||
'where' is the root directory which will be searched for packages. It
|
||||
should be supplied as a "cross-platform" (i.e. URL-style) path; it will
|
||||
be converted to the appropriate local path syntax.
|
||||
|
||||
'exclude' is a sequence of package names to exclude; '*' can be used
|
||||
as a wildcard in the names, such that 'foo.*' will exclude all
|
||||
subpackages of 'foo' (but not 'foo' itself).
|
||||
|
||||
'include' is a sequence of package names to include. If it's
|
||||
specified, only the named packages will be included. If it's not
|
||||
specified, all found packages will be included. 'include' can contain
|
||||
shell style wildcard patterns just like 'exclude'.
|
||||
"""
|
||||
|
||||
return list(
|
||||
cls._find_packages_iter(
|
||||
convert_path(where),
|
||||
cls._build_filter('ez_setup', '*__pycache__', *exclude),
|
||||
cls._build_filter(*include),
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _find_packages_iter(cls, where, exclude, include):
|
||||
"""
|
||||
All the packages found in 'where' that pass the 'include' filter, but
|
||||
not the 'exclude' filter.
|
||||
"""
|
||||
for root, dirs, files in os.walk(where, followlinks=True):
|
||||
# Copy dirs to iterate over it, then empty dirs.
|
||||
all_dirs = dirs[:]
|
||||
dirs[:] = []
|
||||
|
||||
for dir in all_dirs:
|
||||
full_path = os.path.join(root, dir)
|
||||
rel_path = os.path.relpath(full_path, where)
|
||||
package = rel_path.replace(os.path.sep, '.')
|
||||
|
||||
# Skip directory trees that are not valid packages
|
||||
if '.' in dir or not cls._looks_like_package(full_path):
|
||||
continue
|
||||
|
||||
# Should this package be included?
|
||||
if include(package) and not exclude(package):
|
||||
yield package
|
||||
|
||||
# Keep searching subdirectories, as there may be more packages
|
||||
# down there, even if the parent was excluded.
|
||||
dirs.append(dir)
|
||||
|
||||
@staticmethod
|
||||
def _looks_like_package(path):
|
||||
"""Does a directory look like a package?"""
|
||||
return os.path.isfile(os.path.join(path, '__init__.py'))
|
||||
|
||||
@staticmethod
|
||||
def _build_filter(*patterns):
|
||||
"""
|
||||
Given a list of patterns, return a callable that will be true only if
|
||||
the input matches at least one of the patterns.
|
||||
"""
|
||||
return lambda name: any(fnmatchcase(name, pat=pat) for pat in patterns)
|
||||
|
||||
|
||||
class PEP420PackageFinder(PackageFinder):
|
||||
@staticmethod
|
||||
def _looks_like_package(path):
|
||||
return True
|
||||
|
||||
|
||||
find_packages = PackageFinder.find
|
||||
find_namespace_packages = PEP420PackageFinder.find
|
||||
|
||||
@@ -131,7 +54,17 @@ def _install_setup_requires(attrs):
|
||||
def __init__(self, attrs):
|
||||
_incl = 'dependency_links', 'setup_requires'
|
||||
filtered = {k: attrs[k] for k in set(_incl) & set(attrs)}
|
||||
distutils.core.Distribution.__init__(self, filtered)
|
||||
super().__init__(filtered)
|
||||
# Prevent accidentally triggering discovery with incomplete set of attrs
|
||||
self.set_defaults._disable()
|
||||
|
||||
def _get_project_config_files(self, filenames=None):
|
||||
"""Ignore ``pyproject.toml``, they are not related to setup_requires"""
|
||||
try:
|
||||
cfg, toml = super()._split_standard_project_metadata(filenames)
|
||||
return cfg, ()
|
||||
except Exception:
|
||||
return filenames, ()
|
||||
|
||||
def finalize_options(self):
|
||||
"""
|
||||
@@ -149,6 +82,7 @@ def _install_setup_requires(attrs):
|
||||
|
||||
def setup(**attrs):
|
||||
# Make sure we have any requirements needed to interpret 'attrs'.
|
||||
logging.configure()
|
||||
_install_setup_requires(attrs)
|
||||
return distutils.core.setup(**attrs)
|
||||
|
||||
@@ -160,7 +94,59 @@ _Command = monkey.get_unpatched(distutils.core.Command)
|
||||
|
||||
|
||||
class Command(_Command):
|
||||
__doc__ = _Command.__doc__
|
||||
"""
|
||||
Setuptools internal actions are organized using a *command design pattern*.
|
||||
This means that each action (or group of closely related actions) executed during
|
||||
the build should be implemented as a ``Command`` subclass.
|
||||
|
||||
These commands are abstractions and do not necessarily correspond to a command that
|
||||
can (or should) be executed via a terminal, in a CLI fashion (although historically
|
||||
they would).
|
||||
|
||||
When creating a new command from scratch, custom defined classes **SHOULD** inherit
|
||||
from ``setuptools.Command`` and implement a few mandatory methods.
|
||||
Between these mandatory methods, are listed:
|
||||
|
||||
.. method:: initialize_options(self)
|
||||
|
||||
Set or (reset) all options/attributes/caches used by the command
|
||||
to their default values. Note that these values may be overwritten during
|
||||
the build.
|
||||
|
||||
.. method:: finalize_options(self)
|
||||
|
||||
Set final values for all options/attributes used by the command.
|
||||
Most of the time, each option/attribute/cache should only be set if it does not
|
||||
have any value yet (e.g. ``if self.attr is None: self.attr = val``).
|
||||
|
||||
.. method:: run(self)
|
||||
|
||||
Execute the actions intended by the command.
|
||||
(Side effects **SHOULD** only take place when ``run`` is executed,
|
||||
for example, creating new files or writing to the terminal output).
|
||||
|
||||
A useful analogy for command classes is to think of them as subroutines with local
|
||||
variables called "options". The options are "declared" in ``initialize_options()``
|
||||
and "defined" (given their final values, aka "finalized") in ``finalize_options()``,
|
||||
both of which must be defined by every command class. The "body" of the subroutine,
|
||||
(where it does all the work) is the ``run()`` method.
|
||||
Between ``initialize_options()`` and ``finalize_options()``, ``setuptools`` may set
|
||||
the values for options/attributes based on user's input (or circumstance),
|
||||
which means that the implementation should be careful to not overwrite values in
|
||||
``finalize_options`` unless necessary.
|
||||
|
||||
Please note that other commands (or other parts of setuptools) may also overwrite
|
||||
the values of the command's options/attributes multiple times during the build
|
||||
process.
|
||||
Therefore it is important to consistently implement ``initialize_options()`` and
|
||||
``finalize_options()``. For example, all derived attributes (or attributes that
|
||||
depend on the value of other attributes) **SHOULD** be recomputed in
|
||||
``finalize_options``.
|
||||
|
||||
When overwriting existing commands, custom defined classes **MUST** abide by the
|
||||
same APIs implemented by the original class. They also **SHOULD** inherit from the
|
||||
original class.
|
||||
"""
|
||||
|
||||
command_consumes_arguments = False
|
||||
|
||||
@@ -169,7 +155,7 @@ class Command(_Command):
|
||||
Construct the command for dist, updating
|
||||
vars(self) with any keyword parameters.
|
||||
"""
|
||||
_Command.__init__(self, dist)
|
||||
super().__init__(dist)
|
||||
vars(self).update(kw)
|
||||
|
||||
def _ensure_stringlike(self, option, what, default=None):
|
||||
@@ -188,6 +174,12 @@ class Command(_Command):
|
||||
currently a string, we split it either on /,\s*/ or /\s+/, so
|
||||
"foo bar baz", "foo,bar,baz", and "foo, bar baz" all become
|
||||
["foo", "bar", "baz"].
|
||||
|
||||
..
|
||||
TODO: This method seems to be similar to the one in ``distutils.cmd``
|
||||
Probably it is just here for backward compatibility with old Python versions?
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
val = getattr(self, option)
|
||||
if val is None:
|
||||
@@ -234,6 +226,19 @@ def findall(dir=os.curdir):
|
||||
return list(files)
|
||||
|
||||
|
||||
@functools.wraps(_convert_path)
|
||||
def convert_path(pathname):
|
||||
from inspect import cleandoc
|
||||
|
||||
msg = """
|
||||
The function `convert_path` is considered internal and not part of the public API.
|
||||
Its direct usage by 3rd-party packages is considered deprecated and the function
|
||||
may be removed in the future.
|
||||
"""
|
||||
warnings.warn(cleandoc(msg), SetuptoolsDeprecationWarning)
|
||||
return _convert_path(pathname)
|
||||
|
||||
|
||||
class sic(str):
|
||||
"""Treat this string as-is (https://en.wikipedia.org/wiki/Sic)"""
|
||||
|
||||
|
||||
@@ -9,7 +9,16 @@ used from a setup script as
|
||||
"""
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
__version__ = sys.version[:sys.version.index(' ')]
|
||||
__version__ = sys.version[: sys.version.index(' ')]
|
||||
|
||||
local = True
|
||||
|
||||
try:
|
||||
# Allow Debian and pkgsrc (only) to customize system
|
||||
# behavior. Ref pypa/distutils#2 and pypa/distutils#16.
|
||||
# This hook is deprecated and no other environments
|
||||
# should use it.
|
||||
importlib.import_module('_distutils_system_mod')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -17,24 +17,31 @@ import os
|
||||
import subprocess
|
||||
import contextlib
|
||||
import warnings
|
||||
import unittest.mock
|
||||
import unittest.mock as mock
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
import winreg
|
||||
|
||||
from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
|
||||
CompileError, LibError, LinkError
|
||||
from distutils.errors import (
|
||||
DistutilsExecError,
|
||||
DistutilsPlatformError,
|
||||
CompileError,
|
||||
LibError,
|
||||
LinkError,
|
||||
)
|
||||
from distutils.ccompiler import CCompiler, gen_lib_options
|
||||
from distutils import log
|
||||
from distutils.util import get_platform
|
||||
|
||||
from itertools import count
|
||||
|
||||
|
||||
def _find_vc2015():
|
||||
try:
|
||||
key = winreg.OpenKeyEx(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
r"Software\Microsoft\VisualStudio\SxS\VC7",
|
||||
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY
|
||||
access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY,
|
||||
)
|
||||
except OSError:
|
||||
log.debug("Visual C++ is not registered")
|
||||
@@ -57,6 +64,7 @@ def _find_vc2015():
|
||||
best_version, best_dir = version, vc_dir
|
||||
return best_version, best_dir
|
||||
|
||||
|
||||
def _find_vc2017():
|
||||
"""Returns "15, path" based on the result of invoking vswhere.exe
|
||||
If no install is found, returns "None, None"
|
||||
@@ -72,14 +80,23 @@ def _find_vc2017():
|
||||
return None, None
|
||||
|
||||
try:
|
||||
path = subprocess.check_output([
|
||||
os.path.join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),
|
||||
"-latest",
|
||||
"-prerelease",
|
||||
"-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"-property", "installationPath",
|
||||
"-products", "*",
|
||||
], encoding="mbcs", errors="strict").strip()
|
||||
path = subprocess.check_output(
|
||||
[
|
||||
os.path.join(
|
||||
root, "Microsoft Visual Studio", "Installer", "vswhere.exe"
|
||||
),
|
||||
"-latest",
|
||||
"-prerelease",
|
||||
"-requires",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"-property",
|
||||
"installationPath",
|
||||
"-products",
|
||||
"*",
|
||||
],
|
||||
encoding="mbcs",
|
||||
errors="strict",
|
||||
).strip()
|
||||
except (subprocess.CalledProcessError, OSError, UnicodeDecodeError):
|
||||
return None, None
|
||||
|
||||
@@ -89,13 +106,15 @@ def _find_vc2017():
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
PLAT_SPEC_TO_RUNTIME = {
|
||||
'x86' : 'x86',
|
||||
'x86_amd64' : 'x64',
|
||||
'x86_arm' : 'arm',
|
||||
'x86_arm64' : 'arm64'
|
||||
'x86': 'x86',
|
||||
'x86_amd64': 'x64',
|
||||
'x86_arm': 'arm',
|
||||
'x86_arm64': 'arm64',
|
||||
}
|
||||
|
||||
|
||||
def _find_vcvarsall(plat_spec):
|
||||
# bpo-38597: Removed vcruntime return value
|
||||
_, best_dir = _find_vc2017()
|
||||
@@ -114,12 +133,10 @@ def _find_vcvarsall(plat_spec):
|
||||
|
||||
return vcvarsall, None
|
||||
|
||||
|
||||
def _get_vc_env(plat_spec):
|
||||
if os.getenv("DISTUTILS_USE_SDK"):
|
||||
return {
|
||||
key.lower(): value
|
||||
for key, value in os.environ.items()
|
||||
}
|
||||
return {key.lower(): value for key, value in os.environ.items()}
|
||||
|
||||
vcvarsall, _ = _find_vcvarsall(plat_spec)
|
||||
if not vcvarsall:
|
||||
@@ -127,23 +144,22 @@ def _get_vc_env(plat_spec):
|
||||
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),
|
||||
f'cmd /u /c "{vcvarsall}" {plat_spec} && set',
|
||||
stderr=subprocess.STDOUT,
|
||||
).decode('utf-16le', errors='replace')
|
||||
except subprocess.CalledProcessError as exc:
|
||||
log.error(exc.output)
|
||||
raise DistutilsPlatformError("Error executing {}"
|
||||
.format(exc.cmd))
|
||||
raise DistutilsPlatformError(f"Error executing {exc.cmd}")
|
||||
|
||||
env = {
|
||||
key.lower(): value
|
||||
for key, _, value in
|
||||
(line.partition('=') for line in out.splitlines())
|
||||
for key, _, value in (line.partition('=') for line in out.splitlines())
|
||||
if key and value
|
||||
}
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def _find_exe(exe, paths=None):
|
||||
"""Return path to an MSVC executable program.
|
||||
|
||||
@@ -161,19 +177,21 @@ def _find_exe(exe, paths=None):
|
||||
return fn
|
||||
return exe
|
||||
|
||||
|
||||
# A map keyed by get_platform() return values to values accepted by
|
||||
# 'vcvarsall.bat'. Always cross-compile from x86 to work with the
|
||||
# lighter-weight MSVC installs that do not include native 64-bit tools.
|
||||
PLAT_TO_VCVARS = {
|
||||
'win32' : 'x86',
|
||||
'win-amd64' : 'x86_amd64',
|
||||
'win-arm32' : 'x86_arm',
|
||||
'win-arm64' : 'x86_arm64'
|
||||
'win32': 'x86',
|
||||
'win-amd64': 'x86_amd64',
|
||||
'win-arm32': 'x86_arm',
|
||||
'win-arm64': 'x86_arm64',
|
||||
}
|
||||
|
||||
class MSVCCompiler(CCompiler) :
|
||||
|
||||
class MSVCCompiler(CCompiler):
|
||||
"""Concrete class that implements an interface to Microsoft Visual C++,
|
||||
as defined by the CCompiler abstract class."""
|
||||
as defined by the CCompiler abstract class."""
|
||||
|
||||
compiler_type = 'msvc'
|
||||
|
||||
@@ -192,8 +210,7 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
# Needed for the filename generation methods provided by the
|
||||
# base class, CCompiler.
|
||||
src_extensions = (_c_extensions + _cpp_extensions +
|
||||
_rc_extensions + _mc_extensions)
|
||||
src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions
|
||||
res_extension = '.res'
|
||||
obj_extension = '.obj'
|
||||
static_lib_extension = '.lib'
|
||||
@@ -201,13 +218,24 @@ class MSVCCompiler(CCompiler) :
|
||||
static_lib_format = shared_lib_format = '%s%s'
|
||||
exe_extension = '.exe'
|
||||
|
||||
|
||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
||||
CCompiler.__init__ (self, verbose, dry_run, force)
|
||||
super().__init__(verbose, dry_run, force)
|
||||
# target platform (.plat_name is consistent with 'bdist')
|
||||
self.plat_name = None
|
||||
self.initialized = False
|
||||
|
||||
@classmethod
|
||||
def _configure(cls, vc_env):
|
||||
"""
|
||||
Set class-level include/lib dirs.
|
||||
"""
|
||||
cls.include_dirs = cls._parse_path(vc_env.get('include', ''))
|
||||
cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))
|
||||
|
||||
@staticmethod
|
||||
def _parse_path(val):
|
||||
return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]
|
||||
|
||||
def initialize(self, plat_name=None):
|
||||
# multi-init means we would need to check platform same each time...
|
||||
assert not self.initialized, "don't init multiple times"
|
||||
@@ -215,58 +243,62 @@ class MSVCCompiler(CCompiler) :
|
||||
plat_name = get_platform()
|
||||
# sanity check for platforms to prevent obscure errors later.
|
||||
if plat_name not in PLAT_TO_VCVARS:
|
||||
raise DistutilsPlatformError("--plat-name must be one of {}"
|
||||
.format(tuple(PLAT_TO_VCVARS)))
|
||||
raise DistutilsPlatformError(
|
||||
f"--plat-name must be one of {tuple(PLAT_TO_VCVARS)}"
|
||||
)
|
||||
|
||||
# Get the vcvarsall.bat spec for the requested platform.
|
||||
plat_spec = PLAT_TO_VCVARS[plat_name]
|
||||
|
||||
vc_env = _get_vc_env(plat_spec)
|
||||
if not vc_env:
|
||||
raise DistutilsPlatformError("Unable to find a compatible "
|
||||
"Visual Studio installation.")
|
||||
raise DistutilsPlatformError(
|
||||
"Unable to find a compatible " "Visual Studio installation."
|
||||
)
|
||||
self._configure(vc_env)
|
||||
|
||||
self._paths = vc_env.get('path', '')
|
||||
paths = self._paths.split(os.pathsep)
|
||||
self.cc = _find_exe("cl.exe", paths)
|
||||
self.linker = _find_exe("link.exe", paths)
|
||||
self.lib = _find_exe("lib.exe", paths)
|
||||
self.rc = _find_exe("rc.exe", paths) # resource compiler
|
||||
self.mc = _find_exe("mc.exe", paths) # message compiler
|
||||
self.mt = _find_exe("mt.exe", paths) # message compiler
|
||||
|
||||
for dir in vc_env.get('include', '').split(os.pathsep):
|
||||
if dir:
|
||||
self.add_include_dir(dir.rstrip(os.sep))
|
||||
|
||||
for dir in vc_env.get('lib', '').split(os.pathsep):
|
||||
if dir:
|
||||
self.add_library_dir(dir.rstrip(os.sep))
|
||||
self.rc = _find_exe("rc.exe", paths) # resource compiler
|
||||
self.mc = _find_exe("mc.exe", paths) # message compiler
|
||||
self.mt = _find_exe("mt.exe", paths) # message compiler
|
||||
|
||||
self.preprocess_options = None
|
||||
# bpo-38597: Always compile with dynamic linking
|
||||
# Future releases of Python 3.x will include all past
|
||||
# versions of vcruntime*.dll for compatibility.
|
||||
self.compile_options = [
|
||||
'/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD'
|
||||
]
|
||||
self.compile_options = ['/nologo', '/O2', '/W3', '/GL', '/DNDEBUG', '/MD']
|
||||
|
||||
self.compile_options_debug = [
|
||||
'/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG'
|
||||
'/nologo',
|
||||
'/Od',
|
||||
'/MDd',
|
||||
'/Zi',
|
||||
'/W3',
|
||||
'/D_DEBUG',
|
||||
]
|
||||
|
||||
ldflags = [
|
||||
'/nologo', '/INCREMENTAL:NO', '/LTCG'
|
||||
]
|
||||
ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']
|
||||
|
||||
ldflags_debug = [
|
||||
'/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL'
|
||||
]
|
||||
ldflags_debug = ['/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL']
|
||||
|
||||
self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1']
|
||||
self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1']
|
||||
self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
|
||||
self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO']
|
||||
self.ldflags_shared = [
|
||||
*ldflags,
|
||||
'/DLL',
|
||||
'/MANIFEST:EMBED,ID=2',
|
||||
'/MANIFESTUAC:NO',
|
||||
]
|
||||
self.ldflags_shared_debug = [
|
||||
*ldflags_debug,
|
||||
'/DLL',
|
||||
'/MANIFEST:EMBED,ID=2',
|
||||
'/MANIFESTUAC:NO',
|
||||
]
|
||||
self.ldflags_static = [*ldflags]
|
||||
self.ldflags_static_debug = [*ldflags_debug]
|
||||
|
||||
@@ -286,47 +318,33 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
# -- Worker methods ------------------------------------------------
|
||||
|
||||
def object_filenames(self,
|
||||
source_filenames,
|
||||
strip_dir=0,
|
||||
output_dir=''):
|
||||
ext_map = {
|
||||
**{ext: self.obj_extension for ext in self.src_extensions},
|
||||
**{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions},
|
||||
@property
|
||||
def out_extensions(self):
|
||||
return {
|
||||
**super().out_extensions,
|
||||
**{
|
||||
ext: self.res_extension
|
||||
for ext in self._rc_extensions + self._mc_extensions
|
||||
},
|
||||
}
|
||||
|
||||
output_dir = output_dir or ''
|
||||
|
||||
def make_out_path(p):
|
||||
base, ext = os.path.splitext(p)
|
||||
if strip_dir:
|
||||
base = os.path.basename(base)
|
||||
else:
|
||||
_, base = os.path.splitdrive(base)
|
||||
if base.startswith((os.path.sep, os.path.altsep)):
|
||||
base = base[1:]
|
||||
try:
|
||||
# XXX: This may produce absurdly long paths. We should check
|
||||
# the length of the result and trim base until we fit within
|
||||
# 260 characters.
|
||||
return os.path.join(output_dir, base + ext_map[ext])
|
||||
except LookupError:
|
||||
# Better to raise an exception instead of silently continuing
|
||||
# and later complain about sources and targets having
|
||||
# different lengths
|
||||
raise CompileError("Don't know how to compile {}".format(p))
|
||||
|
||||
return list(map(make_out_path, source_filenames))
|
||||
|
||||
|
||||
def compile(self, sources,
|
||||
output_dir=None, macros=None, include_dirs=None, debug=0,
|
||||
extra_preargs=None, extra_postargs=None, depends=None):
|
||||
def compile( # noqa: C901
|
||||
self,
|
||||
sources,
|
||||
output_dir=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
depends=None,
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
compile_info = self._setup_compile(output_dir, macros, include_dirs,
|
||||
sources, depends, extra_postargs)
|
||||
compile_info = self._setup_compile(
|
||||
output_dir, macros, include_dirs, sources, depends, extra_postargs
|
||||
)
|
||||
macros, objects, extra_postargs, pp_opts, build = compile_info
|
||||
|
||||
compile_opts = extra_preargs or []
|
||||
@@ -336,7 +354,6 @@ class MSVCCompiler(CCompiler) :
|
||||
else:
|
||||
compile_opts.extend(self.compile_options)
|
||||
|
||||
|
||||
add_cpp_opts = False
|
||||
|
||||
for obj in objects:
|
||||
@@ -381,7 +398,7 @@ class MSVCCompiler(CCompiler) :
|
||||
try:
|
||||
# first compile .MC to .RC and .H file
|
||||
self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src])
|
||||
base, _ = os.path.splitext(os.path.basename (src))
|
||||
base, _ = os.path.splitext(os.path.basename(src))
|
||||
rc_file = os.path.join(rc_dir, base + '.rc')
|
||||
# then compile .RC to .RES file
|
||||
self.spawn([self.rc, "/fo" + obj, rc_file])
|
||||
@@ -391,8 +408,7 @@ class MSVCCompiler(CCompiler) :
|
||||
continue
|
||||
else:
|
||||
# how to handle this file?
|
||||
raise CompileError("Don't know how to compile {} to {}"
|
||||
.format(src, obj))
|
||||
raise CompileError(f"Don't know how to compile {src} to {obj}")
|
||||
|
||||
args = [self.cc] + compile_opts + pp_opts
|
||||
if add_cpp_opts:
|
||||
@@ -408,24 +424,19 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def create_static_lib(self,
|
||||
objects,
|
||||
output_libname,
|
||||
output_dir=None,
|
||||
debug=0,
|
||||
target_lang=None):
|
||||
def create_static_lib(
|
||||
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
objects, output_dir = self._fix_object_args(objects, output_dir)
|
||||
output_filename = self.library_filename(output_libname,
|
||||
output_dir=output_dir)
|
||||
output_filename = self.library_filename(output_libname, output_dir=output_dir)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
lib_args = objects + ['/OUT:' + output_filename]
|
||||
if debug:
|
||||
pass # XXX what goes here?
|
||||
pass # XXX what goes here?
|
||||
try:
|
||||
log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))
|
||||
self.spawn([self.lib] + lib_args)
|
||||
@@ -434,36 +445,36 @@ class MSVCCompiler(CCompiler) :
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
|
||||
def link(self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None):
|
||||
def link(
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
objects, output_dir = self._fix_object_args(objects, output_dir)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs,
|
||||
runtime_library_dirs)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
|
||||
libraries, library_dirs, runtime_library_dirs = fixed_args
|
||||
|
||||
if runtime_library_dirs:
|
||||
self.warn("I don't know what to do with 'runtime_library_dirs': "
|
||||
+ str(runtime_library_dirs))
|
||||
self.warn(
|
||||
"I don't know what to do with 'runtime_library_dirs': "
|
||||
+ str(runtime_library_dirs)
|
||||
)
|
||||
|
||||
lib_opts = gen_lib_options(self,
|
||||
library_dirs, runtime_library_dirs,
|
||||
libraries)
|
||||
lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
|
||||
if output_dir is not None:
|
||||
output_filename = os.path.join(output_dir, output_filename)
|
||||
|
||||
@@ -472,8 +483,9 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]
|
||||
|
||||
ld_args = (ldflags + lib_opts + export_opts +
|
||||
objects + ['/OUT:' + output_filename])
|
||||
ld_args = (
|
||||
ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]
|
||||
)
|
||||
|
||||
# The MSVC linker generates .lib and .exp files, which cannot be
|
||||
# suppressed by any linker switches. The .lib files may even be
|
||||
@@ -483,11 +495,10 @@ class MSVCCompiler(CCompiler) :
|
||||
build_temp = os.path.dirname(objects[0])
|
||||
if export_symbols is not None:
|
||||
(dll_name, dll_ext) = os.path.splitext(
|
||||
os.path.basename(output_filename))
|
||||
implib_file = os.path.join(
|
||||
build_temp,
|
||||
self.library_filename(dll_name))
|
||||
ld_args.append ('/IMPLIB:' + implib_file)
|
||||
os.path.basename(output_filename)
|
||||
)
|
||||
implib_file = os.path.join(build_temp, self.library_filename(dll_name))
|
||||
ld_args.append('/IMPLIB:' + implib_file)
|
||||
|
||||
if extra_preargs:
|
||||
ld_args[:0] = extra_preargs
|
||||
@@ -525,9 +536,8 @@ class MSVCCompiler(CCompiler) :
|
||||
raise
|
||||
else:
|
||||
return
|
||||
warnings.warn(
|
||||
"Fallback spawn triggered. Please update distutils monkeypatch.")
|
||||
with unittest.mock.patch('os.environ', env):
|
||||
warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.")
|
||||
with mock.patch.dict('os.environ', env):
|
||||
bag.value = super().spawn(cmd)
|
||||
|
||||
# -- Miscellaneous methods -----------------------------------------
|
||||
@@ -539,7 +549,8 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
raise DistutilsPlatformError(
|
||||
"don't know how to set runtime library search path for MSVC")
|
||||
"don't know how to set runtime library search path for MSVC"
|
||||
)
|
||||
|
||||
def library_option(self, lib):
|
||||
return self.library_filename(lib)
|
||||
|
||||
@@ -28,6 +28,7 @@ try:
|
||||
except ImportError:
|
||||
getgrnam = None
|
||||
|
||||
|
||||
def _get_gid(name):
|
||||
"""Returns a gid, given a group name."""
|
||||
if getgrnam is None or name is None:
|
||||
@@ -40,6 +41,7 @@ def _get_gid(name):
|
||||
return result[2]
|
||||
return None
|
||||
|
||||
|
||||
def _get_uid(name):
|
||||
"""Returns an uid, given a user name."""
|
||||
if getpwnam is None or name is None:
|
||||
@@ -52,8 +54,10 @@ def _get_uid(name):
|
||||
return result[2]
|
||||
return None
|
||||
|
||||
def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
|
||||
owner=None, group=None):
|
||||
|
||||
def make_tarball(
|
||||
base_name, base_dir, compress="gzip", verbose=0, dry_run=0, owner=None, group=None
|
||||
):
|
||||
"""Create a (possibly compressed) tar file from all the files under
|
||||
'base_dir'.
|
||||
|
||||
@@ -69,16 +73,21 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
|
||||
|
||||
Returns the output filename.
|
||||
"""
|
||||
tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '',
|
||||
'compress': ''}
|
||||
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz',
|
||||
'compress': '.Z'}
|
||||
tar_compression = {
|
||||
'gzip': 'gz',
|
||||
'bzip2': 'bz2',
|
||||
'xz': 'xz',
|
||||
None: '',
|
||||
'compress': '',
|
||||
}
|
||||
compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz', 'compress': '.Z'}
|
||||
|
||||
# flags for compression program, each element of list will be an argument
|
||||
if compress is not None and compress not in compress_ext.keys():
|
||||
raise ValueError(
|
||||
"bad value for 'compress': must be None, 'gzip', 'bzip2', "
|
||||
"'xz' or 'compress'")
|
||||
"bad value for 'compress': must be None, 'gzip', 'bzip2', "
|
||||
"'xz' or 'compress'"
|
||||
)
|
||||
|
||||
archive_name = base_name + '.tar'
|
||||
if compress != 'compress':
|
||||
@@ -112,7 +121,7 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
|
||||
|
||||
# compression using `compress`
|
||||
if compress == 'compress':
|
||||
warn("'compress' will be deprecated.", PendingDeprecationWarning)
|
||||
warn("'compress' is deprecated.", DeprecationWarning)
|
||||
# the option varies depending on the platform
|
||||
compressed_name = archive_name + compress_ext[compress]
|
||||
if sys.platform == 'win32':
|
||||
@@ -124,7 +133,8 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
|
||||
|
||||
return archive_name
|
||||
|
||||
def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
|
||||
|
||||
def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): # noqa: C901
|
||||
"""Create a zip file from all the files under 'base_dir'.
|
||||
|
||||
The output zip file will be named 'base_name' + ".zip". Uses either the
|
||||
@@ -145,26 +155,29 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
|
||||
zipoptions = "-rq"
|
||||
|
||||
try:
|
||||
spawn(["zip", zipoptions, zip_filename, base_dir],
|
||||
dry_run=dry_run)
|
||||
spawn(["zip", zipoptions, zip_filename, base_dir], dry_run=dry_run)
|
||||
except DistutilsExecError:
|
||||
# XXX really should distinguish between "couldn't find
|
||||
# external 'zip' command" and "zip failed".
|
||||
raise DistutilsExecError(("unable to create zip file '%s': "
|
||||
"could neither import the 'zipfile' module nor "
|
||||
"find a standalone zip utility") % zip_filename)
|
||||
raise DistutilsExecError(
|
||||
(
|
||||
"unable to create zip file '%s': "
|
||||
"could neither import the 'zipfile' module nor "
|
||||
"find a standalone zip utility"
|
||||
)
|
||||
% zip_filename
|
||||
)
|
||||
|
||||
else:
|
||||
log.info("creating '%s' and adding '%s' to it",
|
||||
zip_filename, base_dir)
|
||||
log.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)
|
||||
|
||||
if not dry_run:
|
||||
try:
|
||||
zip = zipfile.ZipFile(zip_filename, "w",
|
||||
compression=zipfile.ZIP_DEFLATED)
|
||||
zip = zipfile.ZipFile(
|
||||
zip_filename, "w", compression=zipfile.ZIP_DEFLATED
|
||||
)
|
||||
except RuntimeError:
|
||||
zip = zipfile.ZipFile(zip_filename, "w",
|
||||
compression=zipfile.ZIP_STORED)
|
||||
zip = zipfile.ZipFile(zip_filename, "w", compression=zipfile.ZIP_STORED)
|
||||
|
||||
with zip:
|
||||
if base_dir != os.curdir:
|
||||
@@ -184,14 +197,16 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0):
|
||||
|
||||
return zip_filename
|
||||
|
||||
|
||||
ARCHIVE_FORMATS = {
|
||||
'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
|
||||
'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
|
||||
'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"),
|
||||
'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"),
|
||||
'tar': (make_tarball, [('compress', None)], "uncompressed tar file"),
|
||||
'zip': (make_zipfile, [],"ZIP file")
|
||||
}
|
||||
'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"),
|
||||
'tar': (make_tarball, [('compress', None)], "uncompressed tar file"),
|
||||
'zip': (make_zipfile, [], "ZIP file"),
|
||||
}
|
||||
|
||||
|
||||
def check_archive_formats(formats):
|
||||
"""Returns the first format from the 'format' list that is unknown.
|
||||
@@ -203,8 +218,17 @@ def check_archive_formats(formats):
|
||||
return format
|
||||
return None
|
||||
|
||||
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
|
||||
dry_run=0, owner=None, group=None):
|
||||
|
||||
def make_archive(
|
||||
base_name,
|
||||
format,
|
||||
root_dir=None,
|
||||
base_dir=None,
|
||||
verbose=0,
|
||||
dry_run=0,
|
||||
owner=None,
|
||||
group=None,
|
||||
):
|
||||
"""Create an archive file (eg. zip or tar).
|
||||
|
||||
'base_name' is the name of the file to create, minus any format-specific
|
||||
|
||||
@@ -13,16 +13,30 @@ for the Borland C++ compiler.
|
||||
|
||||
|
||||
import os
|
||||
from distutils.errors import \
|
||||
DistutilsExecError, \
|
||||
CompileError, LibError, LinkError, UnknownFileError
|
||||
from distutils.ccompiler import \
|
||||
CCompiler, gen_preprocess_options
|
||||
import warnings
|
||||
|
||||
from distutils.errors import (
|
||||
DistutilsExecError,
|
||||
CompileError,
|
||||
LibError,
|
||||
LinkError,
|
||||
UnknownFileError,
|
||||
)
|
||||
from distutils.ccompiler import CCompiler, gen_preprocess_options
|
||||
from distutils.file_util import write_file
|
||||
from distutils.dep_util import newer
|
||||
from distutils import log
|
||||
|
||||
class BCPPCompiler(CCompiler) :
|
||||
|
||||
warnings.warn(
|
||||
"bcppcompiler is deprecated and slated to be removed "
|
||||
"in the future. Please discontinue use or file an issue "
|
||||
"with pypa/distutils describing your use case.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
class BCPPCompiler(CCompiler):
|
||||
"""Concrete class that implements an interface to the Borland C/C++
|
||||
compiler, as defined by the CCompiler abstract class.
|
||||
"""
|
||||
@@ -49,13 +63,9 @@ class BCPPCompiler(CCompiler) :
|
||||
static_lib_format = shared_lib_format = '%s%s'
|
||||
exe_extension = '.exe'
|
||||
|
||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
||||
|
||||
def __init__ (self,
|
||||
verbose=0,
|
||||
dry_run=0,
|
||||
force=0):
|
||||
|
||||
CCompiler.__init__ (self, verbose, dry_run, force)
|
||||
super().__init__(verbose, dry_run, force)
|
||||
|
||||
# These executables are assumed to all be in the path.
|
||||
# Borland doesn't seem to use any special registry settings to
|
||||
@@ -73,24 +83,31 @@ class BCPPCompiler(CCompiler) :
|
||||
self.ldflags_shared_debug = ['/Tpd', '/Gn', '/q', '/x']
|
||||
self.ldflags_static = []
|
||||
self.ldflags_exe = ['/Gn', '/q', '/x']
|
||||
self.ldflags_exe_debug = ['/Gn', '/q', '/x','/r']
|
||||
|
||||
self.ldflags_exe_debug = ['/Gn', '/q', '/x', '/r']
|
||||
|
||||
# -- Worker methods ------------------------------------------------
|
||||
|
||||
def compile(self, sources,
|
||||
output_dir=None, macros=None, include_dirs=None, debug=0,
|
||||
extra_preargs=None, extra_postargs=None, depends=None):
|
||||
def compile( # noqa: C901
|
||||
self,
|
||||
sources,
|
||||
output_dir=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
depends=None,
|
||||
):
|
||||
|
||||
macros, objects, extra_postargs, pp_opts, build = \
|
||||
self._setup_compile(output_dir, macros, include_dirs, sources,
|
||||
depends, extra_postargs)
|
||||
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
|
||||
output_dir, macros, include_dirs, sources, depends, extra_postargs
|
||||
)
|
||||
compile_opts = extra_preargs or []
|
||||
compile_opts.append ('-c')
|
||||
compile_opts.append('-c')
|
||||
if debug:
|
||||
compile_opts.extend (self.compile_options_debug)
|
||||
compile_opts.extend(self.compile_options_debug)
|
||||
else:
|
||||
compile_opts.extend (self.compile_options)
|
||||
compile_opts.extend(self.compile_options)
|
||||
|
||||
for obj in objects:
|
||||
try:
|
||||
@@ -106,14 +123,14 @@ class BCPPCompiler(CCompiler) :
|
||||
|
||||
if ext == '.res':
|
||||
# This is already a binary file -- skip it.
|
||||
continue # the 'for' loop
|
||||
continue # the 'for' loop
|
||||
if ext == '.rc':
|
||||
# This needs to be compiled to a .res file -- do it now.
|
||||
try:
|
||||
self.spawn (["brcc32", "-fo", obj, src])
|
||||
self.spawn(["brcc32", "-fo", obj, src])
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
continue # the 'for' loop
|
||||
continue # the 'for' loop
|
||||
|
||||
# The next two are both for the real compiler.
|
||||
if ext in self._c_extensions:
|
||||
@@ -132,9 +149,14 @@ class BCPPCompiler(CCompiler) :
|
||||
# Note that the source file names must appear at the end of
|
||||
# the command line.
|
||||
try:
|
||||
self.spawn ([self.cc] + compile_opts + pp_opts +
|
||||
[input_opt, output_opt] +
|
||||
extra_postargs + [src])
|
||||
self.spawn(
|
||||
[self.cc]
|
||||
+ compile_opts
|
||||
+ pp_opts
|
||||
+ [input_opt, output_opt]
|
||||
+ extra_postargs
|
||||
+ [src]
|
||||
)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
@@ -142,24 +164,19 @@ class BCPPCompiler(CCompiler) :
|
||||
|
||||
# compile ()
|
||||
|
||||
def create_static_lib(
|
||||
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
|
||||
):
|
||||
|
||||
def create_static_lib (self,
|
||||
objects,
|
||||
output_libname,
|
||||
output_dir=None,
|
||||
debug=0,
|
||||
target_lang=None):
|
||||
(objects, output_dir) = self._fix_object_args(objects, output_dir)
|
||||
output_filename = self.library_filename(output_libname, output_dir=output_dir)
|
||||
|
||||
(objects, output_dir) = self._fix_object_args (objects, output_dir)
|
||||
output_filename = \
|
||||
self.library_filename (output_libname, output_dir=output_dir)
|
||||
|
||||
if self._need_link (objects, output_filename):
|
||||
if self._need_link(objects, output_filename):
|
||||
lib_args = [output_filename, '/u'] + objects
|
||||
if debug:
|
||||
pass # XXX what goes here?
|
||||
pass # XXX what goes here?
|
||||
try:
|
||||
self.spawn ([self.lib] + lib_args)
|
||||
self.spawn([self.lib] + lib_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise LibError(msg)
|
||||
else:
|
||||
@@ -167,37 +184,41 @@ class BCPPCompiler(CCompiler) :
|
||||
|
||||
# create_static_lib ()
|
||||
|
||||
|
||||
def link (self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None):
|
||||
def link( # noqa: C901
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
|
||||
# XXX this ignores 'build_temp'! should follow the lead of
|
||||
# msvccompiler.py
|
||||
|
||||
(objects, output_dir) = self._fix_object_args (objects, output_dir)
|
||||
(libraries, library_dirs, runtime_library_dirs) = \
|
||||
self._fix_lib_args (libraries, library_dirs, runtime_library_dirs)
|
||||
(objects, output_dir) = self._fix_object_args(objects, output_dir)
|
||||
(libraries, library_dirs, runtime_library_dirs) = self._fix_lib_args(
|
||||
libraries, library_dirs, runtime_library_dirs
|
||||
)
|
||||
|
||||
if runtime_library_dirs:
|
||||
log.warn("I don't know what to do with 'runtime_library_dirs': %s",
|
||||
str(runtime_library_dirs))
|
||||
log.warn(
|
||||
"I don't know what to do with 'runtime_library_dirs': %s",
|
||||
str(runtime_library_dirs),
|
||||
)
|
||||
|
||||
if output_dir is not None:
|
||||
output_filename = os.path.join (output_dir, output_filename)
|
||||
output_filename = os.path.join(output_dir, output_filename)
|
||||
|
||||
if self._need_link (objects, output_filename):
|
||||
if self._need_link(objects, output_filename):
|
||||
|
||||
# Figure out linker args based on type of target.
|
||||
if target_desc == CCompiler.EXECUTABLE:
|
||||
@@ -213,20 +234,18 @@ class BCPPCompiler(CCompiler) :
|
||||
else:
|
||||
ld_args = self.ldflags_shared[:]
|
||||
|
||||
|
||||
# Create a temporary exports file for use by the linker
|
||||
if export_symbols is None:
|
||||
def_file = ''
|
||||
else:
|
||||
head, tail = os.path.split (output_filename)
|
||||
modname, ext = os.path.splitext (tail)
|
||||
temp_dir = os.path.dirname(objects[0]) # preserve tree structure
|
||||
def_file = os.path.join (temp_dir, '%s.def' % modname)
|
||||
head, tail = os.path.split(output_filename)
|
||||
modname, ext = os.path.splitext(tail)
|
||||
temp_dir = os.path.dirname(objects[0]) # preserve tree structure
|
||||
def_file = os.path.join(temp_dir, '%s.def' % modname)
|
||||
contents = ['EXPORTS']
|
||||
for sym in (export_symbols or []):
|
||||
contents.append(' %s=_%s' % (sym, sym))
|
||||
self.execute(write_file, (def_file, contents),
|
||||
"writing %s" % def_file)
|
||||
for sym in export_symbols or []:
|
||||
contents.append(' {}=_{}'.format(sym, sym))
|
||||
self.execute(write_file, (def_file, contents), "writing %s" % def_file)
|
||||
|
||||
# Borland C++ has problems with '/' in paths
|
||||
objects2 = map(os.path.normpath, objects)
|
||||
@@ -241,10 +260,9 @@ class BCPPCompiler(CCompiler) :
|
||||
else:
|
||||
objects.append(file)
|
||||
|
||||
|
||||
for l in library_dirs:
|
||||
ld_args.append("/L%s" % os.path.normpath(l))
|
||||
ld_args.append("/L.") # we sometimes use relative paths
|
||||
for ell in library_dirs:
|
||||
ld_args.append("/L%s" % os.path.normpath(ell))
|
||||
ld_args.append("/L.") # we sometimes use relative paths
|
||||
|
||||
# list of object files
|
||||
ld_args.extend(objects)
|
||||
@@ -260,7 +278,7 @@ class BCPPCompiler(CCompiler) :
|
||||
# them. Arghghh!. Apparently it works fine as coded...
|
||||
|
||||
# name of dll/exe file
|
||||
ld_args.extend([',',output_filename])
|
||||
ld_args.extend([',', output_filename])
|
||||
# no map file and start libraries
|
||||
ld_args.append(',,')
|
||||
|
||||
@@ -276,24 +294,23 @@ class BCPPCompiler(CCompiler) :
|
||||
ld_args.append(libfile)
|
||||
|
||||
# some default libraries
|
||||
ld_args.append ('import32')
|
||||
ld_args.append ('cw32mt')
|
||||
ld_args.append('import32')
|
||||
ld_args.append('cw32mt')
|
||||
|
||||
# def file for export symbols
|
||||
ld_args.extend([',',def_file])
|
||||
ld_args.extend([',', def_file])
|
||||
# add resource files
|
||||
ld_args.append(',')
|
||||
ld_args.extend(resources)
|
||||
|
||||
|
||||
if extra_preargs:
|
||||
ld_args[:0] = extra_preargs
|
||||
if extra_postargs:
|
||||
ld_args.extend(extra_postargs)
|
||||
|
||||
self.mkpath (os.path.dirname (output_filename))
|
||||
self.mkpath(os.path.dirname(output_filename))
|
||||
try:
|
||||
self.spawn ([self.linker] + ld_args)
|
||||
self.spawn([self.linker] + ld_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise LinkError(msg)
|
||||
|
||||
@@ -304,8 +321,7 @@ class BCPPCompiler(CCompiler) :
|
||||
|
||||
# -- Miscellaneous methods -----------------------------------------
|
||||
|
||||
|
||||
def find_library_file (self, dirs, lib, debug=0):
|
||||
def find_library_file(self, dirs, lib, debug=0):
|
||||
# List of effective library names to try, in order of preference:
|
||||
# xxx_bcpp.lib is better than xxx.lib
|
||||
# and xxx_d.lib is better than xxx.lib if debug is set
|
||||
@@ -316,7 +332,7 @@ class BCPPCompiler(CCompiler) :
|
||||
# compiler they care about, since (almost?) every Windows compiler
|
||||
# seems to have a different format for static libraries.
|
||||
if debug:
|
||||
dlib = (lib + "_d")
|
||||
dlib = lib + "_d"
|
||||
try_names = (dlib + "_bcpp", lib + "_bcpp", dlib, lib)
|
||||
else:
|
||||
try_names = (lib + "_bcpp", lib)
|
||||
@@ -331,43 +347,42 @@ class BCPPCompiler(CCompiler) :
|
||||
return None
|
||||
|
||||
# overwrite the one from CCompiler to support rc and res-files
|
||||
def object_filenames (self,
|
||||
source_filenames,
|
||||
strip_dir=0,
|
||||
output_dir=''):
|
||||
if output_dir is None: output_dir = ''
|
||||
def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
|
||||
if output_dir is None:
|
||||
output_dir = ''
|
||||
obj_names = []
|
||||
for src_name in source_filenames:
|
||||
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
|
||||
(base, ext) = os.path.splitext (os.path.normcase(src_name))
|
||||
if ext not in (self.src_extensions + ['.rc','.res']):
|
||||
raise UnknownFileError("unknown file type '%s' (from '%s')" % \
|
||||
(ext, src_name))
|
||||
(base, ext) = os.path.splitext(os.path.normcase(src_name))
|
||||
if ext not in (self.src_extensions + ['.rc', '.res']):
|
||||
raise UnknownFileError(
|
||||
"unknown file type '{}' (from '{}')".format(ext, src_name)
|
||||
)
|
||||
if strip_dir:
|
||||
base = os.path.basename (base)
|
||||
base = os.path.basename(base)
|
||||
if ext == '.res':
|
||||
# these can go unchanged
|
||||
obj_names.append (os.path.join (output_dir, base + ext))
|
||||
obj_names.append(os.path.join(output_dir, base + ext))
|
||||
elif ext == '.rc':
|
||||
# these need to be compiled to .res-files
|
||||
obj_names.append (os.path.join (output_dir, base + '.res'))
|
||||
obj_names.append(os.path.join(output_dir, base + '.res'))
|
||||
else:
|
||||
obj_names.append (os.path.join (output_dir,
|
||||
base + self.obj_extension))
|
||||
obj_names.append(os.path.join(output_dir, base + self.obj_extension))
|
||||
return obj_names
|
||||
|
||||
# object_filenames ()
|
||||
|
||||
def preprocess (self,
|
||||
source,
|
||||
output_file=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None):
|
||||
def preprocess(
|
||||
self,
|
||||
source,
|
||||
output_file=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
):
|
||||
|
||||
(_, macros, include_dirs) = \
|
||||
self._fix_compile_args(None, macros, include_dirs)
|
||||
(_, macros, include_dirs) = self._fix_compile_args(None, macros, include_dirs)
|
||||
pp_opts = gen_preprocess_options(macros, include_dirs)
|
||||
pp_args = ['cpp32.exe'] + pp_opts
|
||||
if output_file is not None:
|
||||
|
||||
@@ -3,8 +3,17 @@
|
||||
Contains CCompiler, an abstract base class that defines the interface
|
||||
for the Distutils compiler abstraction model."""
|
||||
|
||||
import sys, os, re
|
||||
from distutils.errors import *
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
from distutils.errors import (
|
||||
CompileError,
|
||||
LinkError,
|
||||
UnknownFileError,
|
||||
DistutilsPlatformError,
|
||||
DistutilsModuleError,
|
||||
)
|
||||
from distutils.spawn import spawn
|
||||
from distutils.file_util import move_file
|
||||
from distutils.dir_util import mkpath
|
||||
@@ -12,6 +21,7 @@ from distutils.dep_util import newer_group
|
||||
from distutils.util import split_quoted, execute
|
||||
from distutils import log
|
||||
|
||||
|
||||
class CCompiler:
|
||||
"""Abstract base class to define the interface that must be implemented
|
||||
by real compiler classes. Also has some utility methods used by
|
||||
@@ -56,17 +66,16 @@ class CCompiler:
|
||||
# think this is useless without the ability to null out the
|
||||
# library search path anyways.
|
||||
|
||||
|
||||
# Subclasses that rely on the standard filename generation methods
|
||||
# implemented below should override these; see the comment near
|
||||
# those methods ('object_filenames()' et. al.) for details:
|
||||
src_extensions = None # list of strings
|
||||
obj_extension = None # string
|
||||
src_extensions = None # list of strings
|
||||
obj_extension = None # string
|
||||
static_lib_extension = None
|
||||
shared_lib_extension = None # string
|
||||
static_lib_format = None # format string
|
||||
shared_lib_format = None # prob. same as static_lib_format
|
||||
exe_extension = None # string
|
||||
shared_lib_extension = None # string
|
||||
static_lib_format = None # format string
|
||||
shared_lib_format = None # prob. same as static_lib_format
|
||||
exe_extension = None # string
|
||||
|
||||
# Default language settings. language_map is used to detect a source
|
||||
# file or Extension target language, checking source filenames.
|
||||
@@ -74,14 +83,25 @@ class CCompiler:
|
||||
# what language to use when mixing source types. For example, if some
|
||||
# extension has two files with ".c" extension, and one with ".cpp", it
|
||||
# is still linked as c++.
|
||||
language_map = {".c" : "c",
|
||||
".cc" : "c++",
|
||||
".cpp" : "c++",
|
||||
".cxx" : "c++",
|
||||
".m" : "objc",
|
||||
}
|
||||
language_map = {
|
||||
".c": "c",
|
||||
".cc": "c++",
|
||||
".cpp": "c++",
|
||||
".cxx": "c++",
|
||||
".m": "objc",
|
||||
}
|
||||
language_order = ["c++", "objc", "c"]
|
||||
|
||||
include_dirs = []
|
||||
"""
|
||||
include dirs specific to this compiler class
|
||||
"""
|
||||
|
||||
library_dirs = []
|
||||
"""
|
||||
library dirs specific to this compiler class
|
||||
"""
|
||||
|
||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
||||
self.dry_run = dry_run
|
||||
self.force = force
|
||||
@@ -146,8 +166,10 @@ class CCompiler:
|
||||
|
||||
for key in kwargs:
|
||||
if key not in self.executables:
|
||||
raise ValueError("unknown executable '%s' for class %s" %
|
||||
(key, self.__class__.__name__))
|
||||
raise ValueError(
|
||||
"unknown executable '%s' for class %s"
|
||||
% (key, self.__class__.__name__)
|
||||
)
|
||||
self.set_executable(key, kwargs[key])
|
||||
|
||||
def set_executable(self, key, value):
|
||||
@@ -170,14 +192,19 @@ class CCompiler:
|
||||
nothing if all definitions are OK, raise TypeError otherwise.
|
||||
"""
|
||||
for defn in definitions:
|
||||
if not (isinstance(defn, tuple) and
|
||||
(len(defn) in (1, 2) and
|
||||
(isinstance (defn[1], str) or defn[1] is None)) and
|
||||
isinstance (defn[0], str)):
|
||||
raise TypeError(("invalid macro definition '%s': " % defn) + \
|
||||
"must be tuple (string,), (string, string), or " + \
|
||||
"(string, None)")
|
||||
|
||||
if not (
|
||||
isinstance(defn, tuple)
|
||||
and (
|
||||
len(defn) in (1, 2)
|
||||
and (isinstance(defn[1], str) or defn[1] is None)
|
||||
)
|
||||
and isinstance(defn[0], str)
|
||||
):
|
||||
raise TypeError(
|
||||
("invalid macro definition '%s': " % defn)
|
||||
+ "must be tuple (string,), (string, string), or "
|
||||
+ "(string, None)"
|
||||
)
|
||||
|
||||
# -- Bookkeeping methods -------------------------------------------
|
||||
|
||||
@@ -190,7 +217,7 @@ class CCompiler:
|
||||
"""
|
||||
# Delete from the list of macro definitions/undefinitions if
|
||||
# already there (so that this one will take precedence).
|
||||
i = self._find_macro (name)
|
||||
i = self._find_macro(name)
|
||||
if i is not None:
|
||||
del self.macros[i]
|
||||
|
||||
@@ -207,7 +234,7 @@ class CCompiler:
|
||||
"""
|
||||
# Delete from the list of macro definitions/undefinitions if
|
||||
# already there (so that this one will take precedence).
|
||||
i = self._find_macro (name)
|
||||
i = self._find_macro(name)
|
||||
if i is not None:
|
||||
del self.macros[i]
|
||||
|
||||
@@ -301,41 +328,20 @@ class CCompiler:
|
||||
"""
|
||||
self.objects = objects[:]
|
||||
|
||||
|
||||
# -- Private utility methods --------------------------------------
|
||||
# (here for the convenience of subclasses)
|
||||
|
||||
# Helper method to prep compiler in subclass compile() methods
|
||||
|
||||
def _setup_compile(self, outdir, macros, incdirs, sources, depends,
|
||||
extra):
|
||||
def _setup_compile(self, outdir, macros, incdirs, sources, depends, extra):
|
||||
"""Process arguments and decide which source files to compile."""
|
||||
if outdir is None:
|
||||
outdir = self.output_dir
|
||||
elif not isinstance(outdir, str):
|
||||
raise TypeError("'output_dir' must be a string or None")
|
||||
|
||||
if macros is None:
|
||||
macros = self.macros
|
||||
elif isinstance(macros, list):
|
||||
macros = macros + (self.macros or [])
|
||||
else:
|
||||
raise TypeError("'macros' (if supplied) must be a list of tuples")
|
||||
|
||||
if incdirs is None:
|
||||
incdirs = self.include_dirs
|
||||
elif isinstance(incdirs, (list, tuple)):
|
||||
incdirs = list(incdirs) + (self.include_dirs or [])
|
||||
else:
|
||||
raise TypeError(
|
||||
"'include_dirs' (if supplied) must be a list of strings")
|
||||
outdir, macros, incdirs = self._fix_compile_args(outdir, macros, incdirs)
|
||||
|
||||
if extra is None:
|
||||
extra = []
|
||||
|
||||
# Get the list of expected output (object) files
|
||||
objects = self.object_filenames(sources, strip_dir=0,
|
||||
output_dir=outdir)
|
||||
objects = self.object_filenames(sources, strip_dir=0, output_dir=outdir)
|
||||
assert len(objects) == len(sources)
|
||||
|
||||
pp_opts = gen_preprocess_options(macros, incdirs)
|
||||
@@ -386,8 +392,10 @@ class CCompiler:
|
||||
elif isinstance(include_dirs, (list, tuple)):
|
||||
include_dirs = list(include_dirs) + (self.include_dirs or [])
|
||||
else:
|
||||
raise TypeError(
|
||||
"'include_dirs' (if supplied) must be a list of strings")
|
||||
raise TypeError("'include_dirs' (if supplied) must be a list of strings")
|
||||
|
||||
# add include dirs for class
|
||||
include_dirs += self.__class__.include_dirs
|
||||
|
||||
return output_dir, macros, include_dirs
|
||||
|
||||
@@ -434,27 +442,30 @@ class CCompiler:
|
||||
if libraries is None:
|
||||
libraries = self.libraries
|
||||
elif isinstance(libraries, (list, tuple)):
|
||||
libraries = list (libraries) + (self.libraries or [])
|
||||
libraries = list(libraries) + (self.libraries or [])
|
||||
else:
|
||||
raise TypeError(
|
||||
"'libraries' (if supplied) must be a list of strings")
|
||||
raise TypeError("'libraries' (if supplied) must be a list of strings")
|
||||
|
||||
if library_dirs is None:
|
||||
library_dirs = self.library_dirs
|
||||
elif isinstance(library_dirs, (list, tuple)):
|
||||
library_dirs = list (library_dirs) + (self.library_dirs or [])
|
||||
library_dirs = list(library_dirs) + (self.library_dirs or [])
|
||||
else:
|
||||
raise TypeError(
|
||||
"'library_dirs' (if supplied) must be a list of strings")
|
||||
raise TypeError("'library_dirs' (if supplied) must be a list of strings")
|
||||
|
||||
# add library dirs for class
|
||||
library_dirs += self.__class__.library_dirs
|
||||
|
||||
if runtime_library_dirs is None:
|
||||
runtime_library_dirs = self.runtime_library_dirs
|
||||
elif isinstance(runtime_library_dirs, (list, tuple)):
|
||||
runtime_library_dirs = (list(runtime_library_dirs) +
|
||||
(self.runtime_library_dirs or []))
|
||||
runtime_library_dirs = list(runtime_library_dirs) + (
|
||||
self.runtime_library_dirs or []
|
||||
)
|
||||
else:
|
||||
raise TypeError("'runtime_library_dirs' (if supplied) "
|
||||
"must be a list of strings")
|
||||
raise TypeError(
|
||||
"'runtime_library_dirs' (if supplied) " "must be a list of strings"
|
||||
)
|
||||
|
||||
return (libraries, library_dirs, runtime_library_dirs)
|
||||
|
||||
@@ -466,9 +477,9 @@ class CCompiler:
|
||||
return True
|
||||
else:
|
||||
if self.dry_run:
|
||||
newer = newer_group (objects, output_file, missing='newer')
|
||||
newer = newer_group(objects, output_file, missing='newer')
|
||||
else:
|
||||
newer = newer_group (objects, output_file)
|
||||
newer = newer_group(objects, output_file)
|
||||
return newer
|
||||
|
||||
def detect_language(self, sources):
|
||||
@@ -491,12 +502,18 @@ class CCompiler:
|
||||
pass
|
||||
return lang
|
||||
|
||||
|
||||
# -- Worker methods ------------------------------------------------
|
||||
# (must be implemented by subclasses)
|
||||
|
||||
def preprocess(self, source, output_file=None, macros=None,
|
||||
include_dirs=None, extra_preargs=None, extra_postargs=None):
|
||||
def preprocess(
|
||||
self,
|
||||
source,
|
||||
output_file=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
):
|
||||
"""Preprocess a single C/C++ source file, named in 'source'.
|
||||
Output will be written to file named 'output_file', or stdout if
|
||||
'output_file' not supplied. 'macros' is a list of macro
|
||||
@@ -508,9 +525,17 @@ class CCompiler:
|
||||
"""
|
||||
pass
|
||||
|
||||
def compile(self, sources, output_dir=None, macros=None,
|
||||
include_dirs=None, debug=0, extra_preargs=None,
|
||||
extra_postargs=None, depends=None):
|
||||
def compile(
|
||||
self,
|
||||
sources,
|
||||
output_dir=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
depends=None,
|
||||
):
|
||||
"""Compile one or more source files.
|
||||
|
||||
'sources' must be a list of filenames, most likely C/C++
|
||||
@@ -561,9 +586,9 @@ class CCompiler:
|
||||
"""
|
||||
# A concrete compiler class can either override this method
|
||||
# entirely or implement _compile().
|
||||
macros, objects, extra_postargs, pp_opts, build = \
|
||||
self._setup_compile(output_dir, macros, include_dirs, sources,
|
||||
depends, extra_postargs)
|
||||
macros, objects, extra_postargs, pp_opts, build = self._setup_compile(
|
||||
output_dir, macros, include_dirs, sources, depends, extra_postargs
|
||||
)
|
||||
cc_args = self._get_cc_args(pp_opts, debug, extra_preargs)
|
||||
|
||||
for obj in objects:
|
||||
@@ -582,8 +607,9 @@ class CCompiler:
|
||||
# should implement _compile().
|
||||
pass
|
||||
|
||||
def create_static_lib(self, objects, output_libname, output_dir=None,
|
||||
debug=0, target_lang=None):
|
||||
def create_static_lib(
|
||||
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
|
||||
):
|
||||
"""Link a bunch of stuff together to create a static library file.
|
||||
The "bunch of stuff" consists of the list of object files supplied
|
||||
as 'objects', the extra object files supplied to
|
||||
@@ -608,26 +634,27 @@ class CCompiler:
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# values for target_desc parameter in link()
|
||||
SHARED_OBJECT = "shared_object"
|
||||
SHARED_LIBRARY = "shared_library"
|
||||
EXECUTABLE = "executable"
|
||||
|
||||
def link(self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None):
|
||||
def link(
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
"""Link a bunch of stuff together to create an executable or
|
||||
shared library file.
|
||||
|
||||
@@ -673,66 +700,98 @@ class CCompiler:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# Old 'link_*()' methods, rewritten to use the new 'link()' method.
|
||||
|
||||
def link_shared_lib(self,
|
||||
objects,
|
||||
output_libname,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None):
|
||||
self.link(CCompiler.SHARED_LIBRARY, objects,
|
||||
self.library_filename(output_libname, lib_type='shared'),
|
||||
output_dir,
|
||||
libraries, library_dirs, runtime_library_dirs,
|
||||
export_symbols, debug,
|
||||
extra_preargs, extra_postargs, build_temp, target_lang)
|
||||
def link_shared_lib(
|
||||
self,
|
||||
objects,
|
||||
output_libname,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
self.link(
|
||||
CCompiler.SHARED_LIBRARY,
|
||||
objects,
|
||||
self.library_filename(output_libname, lib_type='shared'),
|
||||
output_dir,
|
||||
libraries,
|
||||
library_dirs,
|
||||
runtime_library_dirs,
|
||||
export_symbols,
|
||||
debug,
|
||||
extra_preargs,
|
||||
extra_postargs,
|
||||
build_temp,
|
||||
target_lang,
|
||||
)
|
||||
|
||||
def link_shared_object(
|
||||
self,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
self.link(
|
||||
CCompiler.SHARED_OBJECT,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir,
|
||||
libraries,
|
||||
library_dirs,
|
||||
runtime_library_dirs,
|
||||
export_symbols,
|
||||
debug,
|
||||
extra_preargs,
|
||||
extra_postargs,
|
||||
build_temp,
|
||||
target_lang,
|
||||
)
|
||||
|
||||
def link_shared_object(self,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None):
|
||||
self.link(CCompiler.SHARED_OBJECT, objects,
|
||||
output_filename, output_dir,
|
||||
libraries, library_dirs, runtime_library_dirs,
|
||||
export_symbols, debug,
|
||||
extra_preargs, extra_postargs, build_temp, target_lang)
|
||||
|
||||
|
||||
def link_executable(self,
|
||||
objects,
|
||||
output_progname,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
target_lang=None):
|
||||
self.link(CCompiler.EXECUTABLE, objects,
|
||||
self.executable_filename(output_progname), output_dir,
|
||||
libraries, library_dirs, runtime_library_dirs, None,
|
||||
debug, extra_preargs, extra_postargs, None, target_lang)
|
||||
|
||||
def link_executable(
|
||||
self,
|
||||
objects,
|
||||
output_progname,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
target_lang=None,
|
||||
):
|
||||
self.link(
|
||||
CCompiler.EXECUTABLE,
|
||||
objects,
|
||||
self.executable_filename(output_progname),
|
||||
output_dir,
|
||||
libraries,
|
||||
library_dirs,
|
||||
runtime_library_dirs,
|
||||
None,
|
||||
debug,
|
||||
extra_preargs,
|
||||
extra_postargs,
|
||||
None,
|
||||
target_lang,
|
||||
)
|
||||
|
||||
# -- Miscellaneous methods -----------------------------------------
|
||||
# These are all used by the 'gen_lib_options() function; there is
|
||||
@@ -757,8 +816,14 @@ class CCompiler:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def has_function(self, funcname, includes=None, include_dirs=None,
|
||||
libraries=None, library_dirs=None):
|
||||
def has_function( # noqa: C901
|
||||
self,
|
||||
funcname,
|
||||
includes=None,
|
||||
include_dirs=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
):
|
||||
"""Return a boolean indicating whether funcname is supported on
|
||||
the current platform. The optional arguments can be used to
|
||||
augment the compilation environment.
|
||||
@@ -767,6 +832,7 @@ class CCompiler:
|
||||
# import math which might not be available at that point - maybe
|
||||
# the necessary logic should just be inlined?
|
||||
import tempfile
|
||||
|
||||
if includes is None:
|
||||
includes = []
|
||||
if include_dirs is None:
|
||||
@@ -780,12 +846,15 @@ class CCompiler:
|
||||
try:
|
||||
for incl in includes:
|
||||
f.write("""#include "%s"\n""" % incl)
|
||||
f.write("""\
|
||||
f.write(
|
||||
"""\
|
||||
int main (int argc, char **argv) {
|
||||
%s();
|
||||
return 0;
|
||||
}
|
||||
""" % funcname)
|
||||
"""
|
||||
% funcname
|
||||
)
|
||||
finally:
|
||||
f.close()
|
||||
try:
|
||||
@@ -796,19 +865,19 @@ int main (int argc, char **argv) {
|
||||
os.remove(fname)
|
||||
|
||||
try:
|
||||
self.link_executable(objects, "a.out",
|
||||
libraries=libraries,
|
||||
library_dirs=library_dirs)
|
||||
self.link_executable(
|
||||
objects, "a.out", libraries=libraries, library_dirs=library_dirs
|
||||
)
|
||||
except (LinkError, TypeError):
|
||||
return False
|
||||
else:
|
||||
os.remove("a.out")
|
||||
os.remove(os.path.join(self.output_dir or '', "a.out"))
|
||||
finally:
|
||||
for fn in objects:
|
||||
os.remove(fn)
|
||||
return True
|
||||
|
||||
def find_library_file (self, dirs, lib, debug=0):
|
||||
def find_library_file(self, dirs, lib, debug=0):
|
||||
"""Search the specified list of directories for a static or shared
|
||||
library file 'lib' and return the full path to that file. If
|
||||
'debug' true, look for a debugging version (if that makes sense on
|
||||
@@ -854,19 +923,39 @@ int main (int argc, char **argv) {
|
||||
def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
|
||||
if output_dir is None:
|
||||
output_dir = ''
|
||||
obj_names = []
|
||||
for src_name in source_filenames:
|
||||
base, ext = os.path.splitext(src_name)
|
||||
base = os.path.splitdrive(base)[1] # Chop off the drive
|
||||
base = base[os.path.isabs(base):] # If abs, chop off leading /
|
||||
if ext not in self.src_extensions:
|
||||
raise UnknownFileError(
|
||||
"unknown file type '%s' (from '%s')" % (ext, src_name))
|
||||
if strip_dir:
|
||||
base = os.path.basename(base)
|
||||
obj_names.append(os.path.join(output_dir,
|
||||
base + self.obj_extension))
|
||||
return obj_names
|
||||
return list(
|
||||
self._make_out_path(output_dir, strip_dir, src_name)
|
||||
for src_name in source_filenames
|
||||
)
|
||||
|
||||
@property
|
||||
def out_extensions(self):
|
||||
return dict.fromkeys(self.src_extensions, self.obj_extension)
|
||||
|
||||
def _make_out_path(self, output_dir, strip_dir, src_name):
|
||||
base, ext = os.path.splitext(src_name)
|
||||
base = self._make_relative(base)
|
||||
try:
|
||||
new_ext = self.out_extensions[ext]
|
||||
except LookupError:
|
||||
raise UnknownFileError(
|
||||
"unknown file type '{}' (from '{}')".format(ext, src_name)
|
||||
)
|
||||
if strip_dir:
|
||||
base = os.path.basename(base)
|
||||
return os.path.join(output_dir, base + new_ext)
|
||||
|
||||
@staticmethod
|
||||
def _make_relative(base):
|
||||
"""
|
||||
In order to ensure that a filename always honors the
|
||||
indicated output_dir, make sure it's relative.
|
||||
Ref python/cpython#37775.
|
||||
"""
|
||||
# Chop off the drive
|
||||
no_drive = os.path.splitdrive(base)[1]
|
||||
# If abs, chop off leading /
|
||||
return no_drive[os.path.isabs(no_drive) :]
|
||||
|
||||
def shared_object_filename(self, basename, strip_dir=0, output_dir=''):
|
||||
assert output_dir is not None
|
||||
@@ -880,12 +969,13 @@ int main (int argc, char **argv) {
|
||||
basename = os.path.basename(basename)
|
||||
return os.path.join(output_dir, basename + (self.exe_extension or ''))
|
||||
|
||||
def library_filename(self, libname, lib_type='static', # or 'shared'
|
||||
strip_dir=0, output_dir=''):
|
||||
def library_filename(
|
||||
self, libname, lib_type='static', strip_dir=0, output_dir='' # or 'shared'
|
||||
):
|
||||
assert output_dir is not None
|
||||
if lib_type not in ("static", "shared", "dylib", "xcode_stub"):
|
||||
raise ValueError(
|
||||
"'lib_type' must be \"static\", \"shared\", \"dylib\", or \"xcode_stub\"")
|
||||
expected = '"static", "shared", "dylib", "xcode_stub"'
|
||||
if lib_type not in eval(expected):
|
||||
raise ValueError(f"'lib_type' must be {expected}")
|
||||
fmt = getattr(self, lib_type + "_lib_format")
|
||||
ext = getattr(self, lib_type + "_lib_extension")
|
||||
|
||||
@@ -896,7 +986,6 @@ int main (int argc, char **argv) {
|
||||
|
||||
return os.path.join(output_dir, dir, filename)
|
||||
|
||||
|
||||
# -- Utility methods -----------------------------------------------
|
||||
|
||||
def announce(self, msg, level=1):
|
||||
@@ -904,6 +993,7 @@ int main (int argc, char **argv) {
|
||||
|
||||
def debug_print(self, msg):
|
||||
from distutils.debug import DEBUG
|
||||
|
||||
if DEBUG:
|
||||
print(msg)
|
||||
|
||||
@@ -919,7 +1009,7 @@ int main (int argc, char **argv) {
|
||||
def move_file(self, src, dst):
|
||||
return move_file(src, dst, dry_run=self.dry_run)
|
||||
|
||||
def mkpath (self, name, mode=0o777):
|
||||
def mkpath(self, name, mode=0o777):
|
||||
mkpath(name, mode, dry_run=self.dry_run)
|
||||
|
||||
|
||||
@@ -928,54 +1018,59 @@ int main (int argc, char **argv) {
|
||||
# patterns. Order is important; platform mappings are preferred over
|
||||
# OS names.
|
||||
_default_compilers = (
|
||||
|
||||
# Platform string mappings
|
||||
|
||||
# on a cygwin built python we can use gcc like an ordinary UNIXish
|
||||
# compiler
|
||||
('cygwin.*', 'unix'),
|
||||
|
||||
# OS name mappings
|
||||
('posix', 'unix'),
|
||||
('nt', 'msvc'),
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
def get_default_compiler(osname=None, platform=None):
|
||||
"""Determine the default compiler to use for the given platform.
|
||||
|
||||
osname should be one of the standard Python OS names (i.e. the
|
||||
ones returned by os.name) and platform the common value
|
||||
returned by sys.platform for the platform in question.
|
||||
osname should be one of the standard Python OS names (i.e. the
|
||||
ones returned by os.name) and platform the common value
|
||||
returned by sys.platform for the platform in question.
|
||||
|
||||
The default values are os.name and sys.platform in case the
|
||||
parameters are not given.
|
||||
The default values are os.name and sys.platform in case the
|
||||
parameters are not given.
|
||||
"""
|
||||
if osname is None:
|
||||
osname = os.name
|
||||
if platform is None:
|
||||
platform = sys.platform
|
||||
for pattern, compiler in _default_compilers:
|
||||
if re.match(pattern, platform) is not None or \
|
||||
re.match(pattern, osname) is not None:
|
||||
if (
|
||||
re.match(pattern, platform) is not None
|
||||
or re.match(pattern, osname) is not None
|
||||
):
|
||||
return compiler
|
||||
# Default to Unix compiler
|
||||
return 'unix'
|
||||
|
||||
|
||||
# Map compiler types to (module_name, class_name) pairs -- ie. where to
|
||||
# find the code that implements an interface to this compiler. (The module
|
||||
# is assumed to be in the 'distutils' package.)
|
||||
compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler',
|
||||
"standard UNIX-style compiler"),
|
||||
'msvc': ('_msvccompiler', 'MSVCCompiler',
|
||||
"Microsoft Visual C++"),
|
||||
'cygwin': ('cygwinccompiler', 'CygwinCCompiler',
|
||||
"Cygwin port of GNU C Compiler for Win32"),
|
||||
'mingw32': ('cygwinccompiler', 'Mingw32CCompiler',
|
||||
"Mingw32 port of GNU C Compiler for Win32"),
|
||||
'bcpp': ('bcppcompiler', 'BCPPCompiler',
|
||||
"Borland C++ Compiler"),
|
||||
}
|
||||
compiler_class = {
|
||||
'unix': ('unixccompiler', 'UnixCCompiler', "standard UNIX-style compiler"),
|
||||
'msvc': ('_msvccompiler', 'MSVCCompiler', "Microsoft Visual C++"),
|
||||
'cygwin': (
|
||||
'cygwinccompiler',
|
||||
'CygwinCCompiler',
|
||||
"Cygwin port of GNU C Compiler for Win32",
|
||||
),
|
||||
'mingw32': (
|
||||
'cygwinccompiler',
|
||||
'Mingw32CCompiler',
|
||||
"Mingw32 port of GNU C Compiler for Win32",
|
||||
),
|
||||
'bcpp': ('bcppcompiler', 'BCPPCompiler', "Borland C++ Compiler"),
|
||||
}
|
||||
|
||||
|
||||
def show_compilers():
|
||||
"""Print list of available compilers (used by the "--help-compiler"
|
||||
@@ -985,10 +1080,10 @@ def show_compilers():
|
||||
# "--compiler", which just happens to be the case for the three
|
||||
# commands that use it.
|
||||
from distutils.fancy_getopt import FancyGetopt
|
||||
|
||||
compilers = []
|
||||
for compiler in compiler_class.keys():
|
||||
compilers.append(("compiler="+compiler, None,
|
||||
compiler_class[compiler][2]))
|
||||
compilers.append(("compiler=" + compiler, None, compiler_class[compiler][2]))
|
||||
compilers.sort()
|
||||
pretty_printer = FancyGetopt(compilers)
|
||||
pretty_printer.print_help("List of available compilers:")
|
||||
@@ -1021,17 +1116,18 @@ def new_compiler(plat=None, compiler=None, verbose=0, dry_run=0, force=0):
|
||||
|
||||
try:
|
||||
module_name = "distutils." + module_name
|
||||
__import__ (module_name)
|
||||
__import__(module_name)
|
||||
module = sys.modules[module_name]
|
||||
klass = vars(module)[class_name]
|
||||
except ImportError:
|
||||
raise DistutilsModuleError(
|
||||
"can't compile C/C++ code: unable to load module '%s'" % \
|
||||
module_name)
|
||||
"can't compile C/C++ code: unable to load module '%s'" % module_name
|
||||
)
|
||||
except KeyError:
|
||||
raise DistutilsModuleError(
|
||||
"can't compile C/C++ code: unable to find class '%s' "
|
||||
"in module '%s'" % (class_name, module_name))
|
||||
"can't compile C/C++ code: unable to find class '%s' "
|
||||
"in module '%s'" % (class_name, module_name)
|
||||
)
|
||||
|
||||
# XXX The None is necessary to preserve backwards compatibility
|
||||
# with classes that expect verbose to be the first positional
|
||||
@@ -1064,14 +1160,14 @@ def gen_preprocess_options(macros, include_dirs):
|
||||
for macro in macros:
|
||||
if not (isinstance(macro, tuple) and 1 <= len(macro) <= 2):
|
||||
raise TypeError(
|
||||
"bad macro definition '%s': "
|
||||
"each element of 'macros' list must be a 1- or 2-tuple"
|
||||
% macro)
|
||||
"bad macro definition '%s': "
|
||||
"each element of 'macros' list must be a 1- or 2-tuple" % macro
|
||||
)
|
||||
|
||||
if len(macro) == 1: # undefine this macro
|
||||
if len(macro) == 1: # undefine this macro
|
||||
pp_opts.append("-U%s" % macro[0])
|
||||
elif len(macro) == 2:
|
||||
if macro[1] is None: # define with no explicit value
|
||||
if macro[1] is None: # define with no explicit value
|
||||
pp_opts.append("-D%s" % macro[0])
|
||||
else:
|
||||
# XXX *don't* need to be clever about quoting the
|
||||
@@ -1084,7 +1180,7 @@ def gen_preprocess_options(macros, include_dirs):
|
||||
return pp_opts
|
||||
|
||||
|
||||
def gen_lib_options (compiler, library_dirs, runtime_library_dirs, libraries):
|
||||
def gen_lib_options(compiler, library_dirs, runtime_library_dirs, libraries):
|
||||
"""Generate linker options for searching library directories and
|
||||
linking with specific libraries. 'libraries' and 'library_dirs' are,
|
||||
respectively, lists of library names (not filenames!) and search
|
||||
@@ -1116,8 +1212,9 @@ def gen_lib_options (compiler, library_dirs, runtime_library_dirs, libraries):
|
||||
if lib_file:
|
||||
lib_opts.append(lib_file)
|
||||
else:
|
||||
compiler.warn("no library file corresponding to "
|
||||
"'%s' found (skipping)" % lib)
|
||||
compiler.warn(
|
||||
"no library file corresponding to " "'%s' found (skipping)" % lib
|
||||
)
|
||||
else:
|
||||
lib_opts.append(compiler.library_option (lib))
|
||||
lib_opts.append(compiler.library_option(lib))
|
||||
return lib_opts
|
||||
|
||||
@@ -4,11 +4,14 @@ Provides the Command class, the base class for the command classes
|
||||
in the distutils.command package.
|
||||
"""
|
||||
|
||||
import sys, os, re
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from distutils.errors import DistutilsOptionError
|
||||
from distutils import util, dir_util, file_util, archive_util, dep_util
|
||||
from distutils import log
|
||||
|
||||
|
||||
class Command:
|
||||
"""Abstract base class for defining command classes, the "worker bees"
|
||||
of the Distutils. A useful analogy for command classes is to think of
|
||||
@@ -41,7 +44,6 @@ class Command:
|
||||
# defined. The canonical example is the "install" command.
|
||||
sub_commands = []
|
||||
|
||||
|
||||
# -- Creation/initialization methods -------------------------------
|
||||
|
||||
def __init__(self, dist):
|
||||
@@ -130,8 +132,9 @@ class Command:
|
||||
|
||||
This method must be implemented by all command classes.
|
||||
"""
|
||||
raise RuntimeError("abstract method -- subclass %s must override"
|
||||
% self.__class__)
|
||||
raise RuntimeError(
|
||||
"abstract method -- subclass %s must override" % self.__class__
|
||||
)
|
||||
|
||||
def finalize_options(self):
|
||||
"""Set final values for all the options that this command supports.
|
||||
@@ -144,12 +147,13 @@ class Command:
|
||||
|
||||
This method must be implemented by all command classes.
|
||||
"""
|
||||
raise RuntimeError("abstract method -- subclass %s must override"
|
||||
% self.__class__)
|
||||
|
||||
raise RuntimeError(
|
||||
"abstract method -- subclass %s must override" % self.__class__
|
||||
)
|
||||
|
||||
def dump_options(self, header=None, indent=""):
|
||||
from distutils.fancy_getopt import longopt_xlate
|
||||
|
||||
if header is None:
|
||||
header = "command options for '%s':" % self.get_command_name()
|
||||
self.announce(indent + header, level=log.INFO)
|
||||
@@ -159,8 +163,7 @@ class Command:
|
||||
if option[-1] == "=":
|
||||
option = option[:-1]
|
||||
value = getattr(self, option)
|
||||
self.announce(indent + "%s = %s" % (option, value),
|
||||
level=log.INFO)
|
||||
self.announce(indent + "{} = {}".format(option, value), level=log.INFO)
|
||||
|
||||
def run(self):
|
||||
"""A command's raison d'etre: carry out the action it exists to
|
||||
@@ -172,8 +175,9 @@ class Command:
|
||||
|
||||
This method must be implemented by all command classes.
|
||||
"""
|
||||
raise RuntimeError("abstract method -- subclass %s must override"
|
||||
% self.__class__)
|
||||
raise RuntimeError(
|
||||
"abstract method -- subclass %s must override" % self.__class__
|
||||
)
|
||||
|
||||
def announce(self, msg, level=1):
|
||||
"""If the current verbosity level is of greater than or equal to
|
||||
@@ -186,11 +190,11 @@ class Command:
|
||||
DISTUTILS_DEBUG environment variable) flag is true.
|
||||
"""
|
||||
from distutils.debug import DEBUG
|
||||
|
||||
if DEBUG:
|
||||
print(msg)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
# -- Option validation methods -------------------------------------
|
||||
# (these are very handy in writing the 'finalize_options()' method)
|
||||
#
|
||||
@@ -210,8 +214,9 @@ class Command:
|
||||
setattr(self, option, default)
|
||||
return default
|
||||
elif not isinstance(val, str):
|
||||
raise DistutilsOptionError("'%s' must be a %s (got `%s`)"
|
||||
% (option, what, val))
|
||||
raise DistutilsOptionError(
|
||||
"'{}' must be a {} (got `{}`)".format(option, what, val)
|
||||
)
|
||||
return val
|
||||
|
||||
def ensure_string(self, option, default=None):
|
||||
@@ -238,27 +243,29 @@ class Command:
|
||||
ok = False
|
||||
if not ok:
|
||||
raise DistutilsOptionError(
|
||||
"'%s' must be a list of strings (got %r)"
|
||||
% (option, val))
|
||||
"'{}' must be a list of strings (got {!r})".format(option, val)
|
||||
)
|
||||
|
||||
def _ensure_tested_string(self, option, tester, what, error_fmt,
|
||||
default=None):
|
||||
def _ensure_tested_string(self, option, tester, what, error_fmt, default=None):
|
||||
val = self._ensure_stringlike(option, what, default)
|
||||
if val is not None and not tester(val):
|
||||
raise DistutilsOptionError(("error in '%s' option: " + error_fmt)
|
||||
% (option, val))
|
||||
raise DistutilsOptionError(
|
||||
("error in '%s' option: " + error_fmt) % (option, val)
|
||||
)
|
||||
|
||||
def ensure_filename(self, option):
|
||||
"""Ensure that 'option' is the name of an existing file."""
|
||||
self._ensure_tested_string(option, os.path.isfile,
|
||||
"filename",
|
||||
"'%s' does not exist or is not a file")
|
||||
self._ensure_tested_string(
|
||||
option, os.path.isfile, "filename", "'%s' does not exist or is not a file"
|
||||
)
|
||||
|
||||
def ensure_dirname(self, option):
|
||||
self._ensure_tested_string(option, os.path.isdir,
|
||||
"directory name",
|
||||
"'%s' does not exist or is not a directory")
|
||||
|
||||
self._ensure_tested_string(
|
||||
option,
|
||||
os.path.isdir,
|
||||
"directory name",
|
||||
"'%s' does not exist or is not a directory",
|
||||
)
|
||||
|
||||
# -- Convenience methods for commands ------------------------------
|
||||
|
||||
@@ -302,8 +309,7 @@ class Command:
|
||||
# XXX rename to 'get_reinitialized_command()'? (should do the
|
||||
# same in dist.py, if so)
|
||||
def reinitialize_command(self, command, reinit_subcommands=0):
|
||||
return self.distribution.reinitialize_command(command,
|
||||
reinit_subcommands)
|
||||
return self.distribution.reinitialize_command(command, reinit_subcommands)
|
||||
|
||||
def run_command(self, command):
|
||||
"""Run some other command: uses the 'run_command()' method of
|
||||
@@ -325,7 +331,6 @@ class Command:
|
||||
commands.append(cmd_name)
|
||||
return commands
|
||||
|
||||
|
||||
# -- External world manipulation -----------------------------------
|
||||
|
||||
def warn(self, msg):
|
||||
@@ -337,41 +342,70 @@ class Command:
|
||||
def mkpath(self, name, mode=0o777):
|
||||
dir_util.mkpath(name, mode, dry_run=self.dry_run)
|
||||
|
||||
def copy_file(self, infile, outfile, preserve_mode=1, preserve_times=1,
|
||||
link=None, level=1):
|
||||
def copy_file(
|
||||
self, infile, outfile, preserve_mode=1, preserve_times=1, link=None, level=1
|
||||
):
|
||||
"""Copy a file respecting verbose, dry-run and force flags. (The
|
||||
former two default to whatever is in the Distribution object, and
|
||||
the latter defaults to false for commands that don't define it.)"""
|
||||
return file_util.copy_file(infile, outfile, preserve_mode,
|
||||
preserve_times, not self.force, link,
|
||||
dry_run=self.dry_run)
|
||||
return file_util.copy_file(
|
||||
infile,
|
||||
outfile,
|
||||
preserve_mode,
|
||||
preserve_times,
|
||||
not self.force,
|
||||
link,
|
||||
dry_run=self.dry_run,
|
||||
)
|
||||
|
||||
def copy_tree(self, infile, outfile, preserve_mode=1, preserve_times=1,
|
||||
preserve_symlinks=0, level=1):
|
||||
def copy_tree(
|
||||
self,
|
||||
infile,
|
||||
outfile,
|
||||
preserve_mode=1,
|
||||
preserve_times=1,
|
||||
preserve_symlinks=0,
|
||||
level=1,
|
||||
):
|
||||
"""Copy an entire directory tree respecting verbose, dry-run,
|
||||
and force flags.
|
||||
"""
|
||||
return dir_util.copy_tree(infile, outfile, preserve_mode,
|
||||
preserve_times, preserve_symlinks,
|
||||
not self.force, dry_run=self.dry_run)
|
||||
return dir_util.copy_tree(
|
||||
infile,
|
||||
outfile,
|
||||
preserve_mode,
|
||||
preserve_times,
|
||||
preserve_symlinks,
|
||||
not self.force,
|
||||
dry_run=self.dry_run,
|
||||
)
|
||||
|
||||
def move_file (self, src, dst, level=1):
|
||||
def move_file(self, src, dst, level=1):
|
||||
"""Move a file respecting dry-run flag."""
|
||||
return file_util.move_file(src, dst, dry_run=self.dry_run)
|
||||
|
||||
def spawn(self, cmd, search_path=1, level=1):
|
||||
"""Spawn an external command respecting dry-run flag."""
|
||||
from distutils.spawn import spawn
|
||||
|
||||
spawn(cmd, search_path, dry_run=self.dry_run)
|
||||
|
||||
def make_archive(self, base_name, format, root_dir=None, base_dir=None,
|
||||
owner=None, group=None):
|
||||
return archive_util.make_archive(base_name, format, root_dir, base_dir,
|
||||
dry_run=self.dry_run,
|
||||
owner=owner, group=group)
|
||||
def make_archive(
|
||||
self, base_name, format, root_dir=None, base_dir=None, owner=None, group=None
|
||||
):
|
||||
return archive_util.make_archive(
|
||||
base_name,
|
||||
format,
|
||||
root_dir,
|
||||
base_dir,
|
||||
dry_run=self.dry_run,
|
||||
owner=owner,
|
||||
group=group,
|
||||
)
|
||||
|
||||
def make_file(self, infiles, outfile, func, args,
|
||||
exec_msg=None, skip_msg=None, level=1):
|
||||
def make_file(
|
||||
self, infiles, outfile, func, args, exec_msg=None, skip_msg=None, level=1
|
||||
):
|
||||
"""Special case of 'execute()' for operations that process one or
|
||||
more input files and generate one output file. Works just like
|
||||
'execute()', except the operation is skipped and a different
|
||||
@@ -387,11 +421,10 @@ class Command:
|
||||
if isinstance(infiles, str):
|
||||
infiles = (infiles,)
|
||||
elif not isinstance(infiles, (list, tuple)):
|
||||
raise TypeError(
|
||||
"'infiles' must be a string, or a list or tuple of strings")
|
||||
raise TypeError("'infiles' must be a string, or a list or tuple of strings")
|
||||
|
||||
if exec_msg is None:
|
||||
exec_msg = "generating %s from %s" % (outfile, ', '.join(infiles))
|
||||
exec_msg = "generating {} from {}".format(outfile, ', '.join(infiles))
|
||||
|
||||
# If 'outfile' must be regenerated (either because it doesn't
|
||||
# exist, is out-of-date, or the 'force' flag is true) then
|
||||
|
||||
@@ -3,29 +3,23 @@
|
||||
Package containing implementation of all the standard Distutils
|
||||
commands."""
|
||||
|
||||
__all__ = ['build',
|
||||
'build_py',
|
||||
'build_ext',
|
||||
'build_clib',
|
||||
'build_scripts',
|
||||
'clean',
|
||||
'install',
|
||||
'install_lib',
|
||||
'install_headers',
|
||||
'install_scripts',
|
||||
'install_data',
|
||||
'sdist',
|
||||
'register',
|
||||
'bdist',
|
||||
'bdist_dumb',
|
||||
'bdist_rpm',
|
||||
'bdist_wininst',
|
||||
'check',
|
||||
'upload',
|
||||
# These two are reserved for future use:
|
||||
#'bdist_sdux',
|
||||
#'bdist_pkgtool',
|
||||
# Note:
|
||||
# bdist_packager is not included because it only provides
|
||||
# an abstract base class
|
||||
]
|
||||
__all__ = [ # noqa: F822
|
||||
'build',
|
||||
'build_py',
|
||||
'build_ext',
|
||||
'build_clib',
|
||||
'build_scripts',
|
||||
'clean',
|
||||
'install',
|
||||
'install_lib',
|
||||
'install_headers',
|
||||
'install_scripts',
|
||||
'install_data',
|
||||
'sdist',
|
||||
'register',
|
||||
'bdist',
|
||||
'bdist_dumb',
|
||||
'bdist_rpm',
|
||||
'check',
|
||||
'upload',
|
||||
]
|
||||
|
||||
@@ -4,79 +4,93 @@ Implements the Distutils 'bdist' command (create a built [binary]
|
||||
distribution)."""
|
||||
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from distutils.core import Command
|
||||
from distutils.errors import *
|
||||
from distutils.errors import DistutilsPlatformError, DistutilsOptionError
|
||||
from distutils.util import get_platform
|
||||
|
||||
|
||||
def show_formats():
|
||||
"""Print list of available formats (arguments to "--format" option).
|
||||
"""
|
||||
"""Print list of available formats (arguments to "--format" option)."""
|
||||
from distutils.fancy_getopt import FancyGetopt
|
||||
|
||||
formats = []
|
||||
for format in bdist.format_commands:
|
||||
formats.append(("formats=" + format, None,
|
||||
bdist.format_command[format][1]))
|
||||
formats.append(("formats=" + format, None, bdist.format_commands[format][1]))
|
||||
pretty_printer = FancyGetopt(formats)
|
||||
pretty_printer.print_help("List of available distribution formats:")
|
||||
|
||||
|
||||
class ListCompat(dict):
|
||||
# adapter to allow for Setuptools compatibility in format_commands
|
||||
def append(self, item):
|
||||
warnings.warn(
|
||||
"""format_commands is now a dict. append is deprecated.""",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
class bdist(Command):
|
||||
|
||||
description = "create a built (binary) distribution"
|
||||
|
||||
user_options = [('bdist-base=', 'b',
|
||||
"temporary directory for creating built distributions"),
|
||||
('plat-name=', 'p',
|
||||
"platform name to embed in generated filenames "
|
||||
"(default: %s)" % get_platform()),
|
||||
('formats=', None,
|
||||
"formats for distribution (comma-separated list)"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put final built distributions in "
|
||||
"[default: dist]"),
|
||||
('skip-build', None,
|
||||
"skip rebuilding everything (for testing/debugging)"),
|
||||
('owner=', 'u',
|
||||
"Owner name used when creating a tar file"
|
||||
" [default: current user]"),
|
||||
('group=', 'g',
|
||||
"Group name used when creating a tar file"
|
||||
" [default: current group]"),
|
||||
]
|
||||
user_options = [
|
||||
('bdist-base=', 'b', "temporary directory for creating built distributions"),
|
||||
(
|
||||
'plat-name=',
|
||||
'p',
|
||||
"platform name to embed in generated filenames "
|
||||
"(default: %s)" % get_platform(),
|
||||
),
|
||||
('formats=', None, "formats for distribution (comma-separated list)"),
|
||||
(
|
||||
'dist-dir=',
|
||||
'd',
|
||||
"directory to put final built distributions in " "[default: dist]",
|
||||
),
|
||||
('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
|
||||
(
|
||||
'owner=',
|
||||
'u',
|
||||
"Owner name used when creating a tar file" " [default: current user]",
|
||||
),
|
||||
(
|
||||
'group=',
|
||||
'g',
|
||||
"Group name used when creating a tar file" " [default: current group]",
|
||||
),
|
||||
]
|
||||
|
||||
boolean_options = ['skip-build']
|
||||
|
||||
help_options = [
|
||||
('help-formats', None,
|
||||
"lists available distribution formats", show_formats),
|
||||
]
|
||||
('help-formats', None, "lists available distribution formats", show_formats),
|
||||
]
|
||||
|
||||
# The following commands do not take a format option from bdist
|
||||
no_format_option = ('bdist_rpm',)
|
||||
|
||||
# This won't do in reality: will need to distinguish RPM-ish Linux,
|
||||
# Debian-ish Linux, Solaris, FreeBSD, ..., Windows, Mac OS.
|
||||
default_format = {'posix': 'gztar',
|
||||
'nt': 'zip'}
|
||||
default_format = {'posix': 'gztar', 'nt': 'zip'}
|
||||
|
||||
# Establish the preferred order (for the --help-formats option).
|
||||
format_commands = ['rpm', 'gztar', 'bztar', 'xztar', 'ztar', 'tar',
|
||||
'wininst', 'zip', 'msi']
|
||||
|
||||
# And the real information.
|
||||
format_command = {'rpm': ('bdist_rpm', "RPM distribution"),
|
||||
'gztar': ('bdist_dumb', "gzip'ed tar file"),
|
||||
'bztar': ('bdist_dumb', "bzip2'ed tar file"),
|
||||
'xztar': ('bdist_dumb', "xz'ed tar file"),
|
||||
'ztar': ('bdist_dumb', "compressed tar file"),
|
||||
'tar': ('bdist_dumb', "tar file"),
|
||||
'wininst': ('bdist_wininst',
|
||||
"Windows executable installer"),
|
||||
'zip': ('bdist_dumb', "ZIP file"),
|
||||
'msi': ('bdist_msi', "Microsoft Installer")
|
||||
}
|
||||
# Define commands in preferred order for the --help-formats option
|
||||
format_commands = ListCompat(
|
||||
{
|
||||
'rpm': ('bdist_rpm', "RPM distribution"),
|
||||
'gztar': ('bdist_dumb', "gzip'ed tar file"),
|
||||
'bztar': ('bdist_dumb', "bzip2'ed tar file"),
|
||||
'xztar': ('bdist_dumb', "xz'ed tar file"),
|
||||
'ztar': ('bdist_dumb', "compressed tar file"),
|
||||
'tar': ('bdist_dumb', "tar file"),
|
||||
'zip': ('bdist_dumb', "ZIP file"),
|
||||
}
|
||||
)
|
||||
|
||||
# for compatibility until consumers only reference format_commands
|
||||
format_command = format_commands
|
||||
|
||||
def initialize_options(self):
|
||||
self.bdist_base = None
|
||||
@@ -100,8 +114,7 @@ class bdist(Command):
|
||||
# "build/bdist.<plat>/dumb", "build/bdist.<plat>/rpm", etc.)
|
||||
if self.bdist_base is None:
|
||||
build_base = self.get_finalized_command('build').build_base
|
||||
self.bdist_base = os.path.join(build_base,
|
||||
'bdist.' + self.plat_name)
|
||||
self.bdist_base = os.path.join(build_base, 'bdist.' + self.plat_name)
|
||||
|
||||
self.ensure_string_list('formats')
|
||||
if self.formats is None:
|
||||
@@ -109,8 +122,9 @@ class bdist(Command):
|
||||
self.formats = [self.default_format[os.name]]
|
||||
except KeyError:
|
||||
raise DistutilsPlatformError(
|
||||
"don't know how to create built distributions "
|
||||
"on platform %s" % os.name)
|
||||
"don't know how to create built distributions "
|
||||
"on platform %s" % os.name
|
||||
)
|
||||
|
||||
if self.dist_dir is None:
|
||||
self.dist_dir = "dist"
|
||||
@@ -120,7 +134,7 @@ class bdist(Command):
|
||||
commands = []
|
||||
for format in self.formats:
|
||||
try:
|
||||
commands.append(self.format_command[format][0])
|
||||
commands.append(self.format_commands[format][0])
|
||||
except KeyError:
|
||||
raise DistutilsOptionError("invalid format '%s'" % format)
|
||||
|
||||
@@ -138,6 +152,6 @@ class bdist(Command):
|
||||
|
||||
# If we're going to need to run this command again, tell it to
|
||||
# keep its temporary files around so subsequent runs go faster.
|
||||
if cmd_name in commands[i+1:]:
|
||||
if cmd_name in commands[i + 1 :]:
|
||||
sub_cmd.keep_temp = 1
|
||||
self.run_command(cmd_name)
|
||||
|
||||
@@ -8,44 +8,56 @@ import os
|
||||
from distutils.core import Command
|
||||
from distutils.util import get_platform
|
||||
from distutils.dir_util import remove_tree, ensure_relative
|
||||
from distutils.errors import *
|
||||
from distutils.errors import DistutilsPlatformError
|
||||
from distutils.sysconfig import get_python_version
|
||||
from distutils import log
|
||||
|
||||
|
||||
class bdist_dumb(Command):
|
||||
|
||||
description = "create a \"dumb\" built distribution"
|
||||
|
||||
user_options = [('bdist-dir=', 'd',
|
||||
"temporary directory for creating the distribution"),
|
||||
('plat-name=', 'p',
|
||||
"platform name to embed in generated filenames "
|
||||
"(default: %s)" % get_platform()),
|
||||
('format=', 'f',
|
||||
"archive format to create (tar, gztar, bztar, xztar, "
|
||||
"ztar, zip)"),
|
||||
('keep-temp', 'k',
|
||||
"keep the pseudo-installation tree around after " +
|
||||
"creating the distribution archive"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put final built distributions in"),
|
||||
('skip-build', None,
|
||||
"skip rebuilding everything (for testing/debugging)"),
|
||||
('relative', None,
|
||||
"build the archive using relative paths "
|
||||
"(default: false)"),
|
||||
('owner=', 'u',
|
||||
"Owner name used when creating a tar file"
|
||||
" [default: current user]"),
|
||||
('group=', 'g',
|
||||
"Group name used when creating a tar file"
|
||||
" [default: current group]"),
|
||||
]
|
||||
user_options = [
|
||||
('bdist-dir=', 'd', "temporary directory for creating the distribution"),
|
||||
(
|
||||
'plat-name=',
|
||||
'p',
|
||||
"platform name to embed in generated filenames "
|
||||
"(default: %s)" % get_platform(),
|
||||
),
|
||||
(
|
||||
'format=',
|
||||
'f',
|
||||
"archive format to create (tar, gztar, bztar, xztar, " "ztar, zip)",
|
||||
),
|
||||
(
|
||||
'keep-temp',
|
||||
'k',
|
||||
"keep the pseudo-installation tree around after "
|
||||
+ "creating the distribution archive",
|
||||
),
|
||||
('dist-dir=', 'd', "directory to put final built distributions in"),
|
||||
('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
|
||||
(
|
||||
'relative',
|
||||
None,
|
||||
"build the archive using relative paths " "(default: false)",
|
||||
),
|
||||
(
|
||||
'owner=',
|
||||
'u',
|
||||
"Owner name used when creating a tar file" " [default: current user]",
|
||||
),
|
||||
(
|
||||
'group=',
|
||||
'g',
|
||||
"Group name used when creating a tar file" " [default: current group]",
|
||||
),
|
||||
]
|
||||
|
||||
boolean_options = ['keep-temp', 'skip-build', 'relative']
|
||||
|
||||
default_format = { 'posix': 'gztar',
|
||||
'nt': 'zip' }
|
||||
default_format = {'posix': 'gztar', 'nt': 'zip'}
|
||||
|
||||
def initialize_options(self):
|
||||
self.bdist_dir = None
|
||||
@@ -68,13 +80,16 @@ class bdist_dumb(Command):
|
||||
self.format = self.default_format[os.name]
|
||||
except KeyError:
|
||||
raise DistutilsPlatformError(
|
||||
"don't know how to create dumb built distributions "
|
||||
"on platform %s" % os.name)
|
||||
"don't know how to create dumb built distributions "
|
||||
"on platform %s" % os.name
|
||||
)
|
||||
|
||||
self.set_undefined_options('bdist',
|
||||
('dist_dir', 'dist_dir'),
|
||||
('plat_name', 'plat_name'),
|
||||
('skip_build', 'skip_build'))
|
||||
self.set_undefined_options(
|
||||
'bdist',
|
||||
('dist_dir', 'dist_dir'),
|
||||
('plat_name', 'plat_name'),
|
||||
('skip_build', 'skip_build'),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
if not self.skip_build:
|
||||
@@ -90,34 +105,40 @@ class bdist_dumb(Command):
|
||||
|
||||
# And make an archive relative to the root of the
|
||||
# pseudo-installation tree.
|
||||
archive_basename = "%s.%s" % (self.distribution.get_fullname(),
|
||||
self.plat_name)
|
||||
archive_basename = "{}.{}".format(
|
||||
self.distribution.get_fullname(), self.plat_name
|
||||
)
|
||||
|
||||
pseudoinstall_root = os.path.join(self.dist_dir, archive_basename)
|
||||
if not self.relative:
|
||||
archive_root = self.bdist_dir
|
||||
else:
|
||||
if (self.distribution.has_ext_modules() and
|
||||
(install.install_base != install.install_platbase)):
|
||||
if self.distribution.has_ext_modules() and (
|
||||
install.install_base != install.install_platbase
|
||||
):
|
||||
raise DistutilsPlatformError(
|
||||
"can't make a dumb built distribution where "
|
||||
"base and platbase are different (%s, %s)"
|
||||
% (repr(install.install_base),
|
||||
repr(install.install_platbase)))
|
||||
"can't make a dumb built distribution where "
|
||||
"base and platbase are different (%s, %s)"
|
||||
% (repr(install.install_base), repr(install.install_platbase))
|
||||
)
|
||||
else:
|
||||
archive_root = os.path.join(self.bdist_dir,
|
||||
ensure_relative(install.install_base))
|
||||
archive_root = os.path.join(
|
||||
self.bdist_dir, ensure_relative(install.install_base)
|
||||
)
|
||||
|
||||
# Make the archive
|
||||
filename = self.make_archive(pseudoinstall_root,
|
||||
self.format, root_dir=archive_root,
|
||||
owner=self.owner, group=self.group)
|
||||
filename = self.make_archive(
|
||||
pseudoinstall_root,
|
||||
self.format,
|
||||
root_dir=archive_root,
|
||||
owner=self.owner,
|
||||
group=self.group,
|
||||
)
|
||||
if self.distribution.has_ext_modules():
|
||||
pyversion = get_python_version()
|
||||
else:
|
||||
pyversion = 'any'
|
||||
self.distribution.dist_files.append(('bdist_dumb', pyversion,
|
||||
filename))
|
||||
self.distribution.dist_files.append(('bdist_dumb', pyversion, filename))
|
||||
|
||||
if not self.keep_temp:
|
||||
remove_tree(self.bdist_dir, dry_run=self.dry_run)
|
||||
|
||||
@@ -1,749 +0,0 @@
|
||||
# Copyright (C) 2005, 2006 Martin von Löwis
|
||||
# Licensed to PSF under a Contributor Agreement.
|
||||
# The bdist_wininst command proper
|
||||
# based on bdist_wininst
|
||||
"""
|
||||
Implements the bdist_msi command.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from distutils.core import Command
|
||||
from distutils.dir_util import remove_tree
|
||||
from distutils.sysconfig import get_python_version
|
||||
from distutils.version import StrictVersion
|
||||
from distutils.errors import DistutilsOptionError
|
||||
from distutils.util import get_platform
|
||||
from distutils import log
|
||||
import msilib
|
||||
from msilib import schema, sequence, text
|
||||
from msilib import Directory, Feature, Dialog, add_data
|
||||
|
||||
class PyDialog(Dialog):
|
||||
"""Dialog class with a fixed layout: controls at the top, then a ruler,
|
||||
then a list of buttons: back, next, cancel. Optionally a bitmap at the
|
||||
left."""
|
||||
def __init__(self, *args, **kw):
|
||||
"""Dialog(database, name, x, y, w, h, attributes, title, first,
|
||||
default, cancel, bitmap=true)"""
|
||||
Dialog.__init__(self, *args)
|
||||
ruler = self.h - 36
|
||||
bmwidth = 152*ruler/328
|
||||
#if kw.get("bitmap", True):
|
||||
# self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin")
|
||||
self.line("BottomLine", 0, ruler, self.w, 0)
|
||||
|
||||
def title(self, title):
|
||||
"Set the title text of the dialog at the top."
|
||||
# name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix,
|
||||
# text, in VerdanaBold10
|
||||
self.text("Title", 15, 10, 320, 60, 0x30003,
|
||||
r"{\VerdanaBold10}%s" % title)
|
||||
|
||||
def back(self, title, next, name = "Back", active = 1):
|
||||
"""Add a back button with a given title, the tab-next button,
|
||||
its name in the Control table, possibly initially disabled.
|
||||
|
||||
Return the button, so that events can be associated"""
|
||||
if active:
|
||||
flags = 3 # Visible|Enabled
|
||||
else:
|
||||
flags = 1 # Visible
|
||||
return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next)
|
||||
|
||||
def cancel(self, title, next, name = "Cancel", active = 1):
|
||||
"""Add a cancel button with a given title, the tab-next button,
|
||||
its name in the Control table, possibly initially disabled.
|
||||
|
||||
Return the button, so that events can be associated"""
|
||||
if active:
|
||||
flags = 3 # Visible|Enabled
|
||||
else:
|
||||
flags = 1 # Visible
|
||||
return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next)
|
||||
|
||||
def next(self, title, next, name = "Next", active = 1):
|
||||
"""Add a Next button with a given title, the tab-next button,
|
||||
its name in the Control table, possibly initially disabled.
|
||||
|
||||
Return the button, so that events can be associated"""
|
||||
if active:
|
||||
flags = 3 # Visible|Enabled
|
||||
else:
|
||||
flags = 1 # Visible
|
||||
return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next)
|
||||
|
||||
def xbutton(self, name, title, next, xpos):
|
||||
"""Add a button with a given title, the tab-next button,
|
||||
its name in the Control table, giving its x position; the
|
||||
y-position is aligned with the other buttons.
|
||||
|
||||
Return the button, so that events can be associated"""
|
||||
return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next)
|
||||
|
||||
class bdist_msi(Command):
|
||||
|
||||
description = "create a Microsoft Installer (.msi) binary distribution"
|
||||
|
||||
user_options = [('bdist-dir=', None,
|
||||
"temporary directory for creating the distribution"),
|
||||
('plat-name=', 'p',
|
||||
"platform name to embed in generated filenames "
|
||||
"(default: %s)" % get_platform()),
|
||||
('keep-temp', 'k',
|
||||
"keep the pseudo-installation tree around after " +
|
||||
"creating the distribution archive"),
|
||||
('target-version=', None,
|
||||
"require a specific python version" +
|
||||
" on the target system"),
|
||||
('no-target-compile', 'c',
|
||||
"do not compile .py to .pyc on the target system"),
|
||||
('no-target-optimize', 'o',
|
||||
"do not compile .py to .pyo (optimized) "
|
||||
"on the target system"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put final built distributions in"),
|
||||
('skip-build', None,
|
||||
"skip rebuilding everything (for testing/debugging)"),
|
||||
('install-script=', None,
|
||||
"basename of installation script to be run after "
|
||||
"installation or before deinstallation"),
|
||||
('pre-install-script=', None,
|
||||
"Fully qualified filename of a script to be run before "
|
||||
"any files are installed. This script need not be in the "
|
||||
"distribution"),
|
||||
]
|
||||
|
||||
boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
|
||||
'skip-build']
|
||||
|
||||
all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4',
|
||||
'2.5', '2.6', '2.7', '2.8', '2.9',
|
||||
'3.0', '3.1', '3.2', '3.3', '3.4',
|
||||
'3.5', '3.6', '3.7', '3.8', '3.9']
|
||||
other_version = 'X'
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
warnings.warn("bdist_msi command is deprecated since Python 3.9, "
|
||||
"use bdist_wheel (wheel packages) instead",
|
||||
DeprecationWarning, 2)
|
||||
|
||||
def initialize_options(self):
|
||||
self.bdist_dir = None
|
||||
self.plat_name = None
|
||||
self.keep_temp = 0
|
||||
self.no_target_compile = 0
|
||||
self.no_target_optimize = 0
|
||||
self.target_version = None
|
||||
self.dist_dir = None
|
||||
self.skip_build = None
|
||||
self.install_script = None
|
||||
self.pre_install_script = None
|
||||
self.versions = None
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('bdist', ('skip_build', 'skip_build'))
|
||||
|
||||
if self.bdist_dir is None:
|
||||
bdist_base = self.get_finalized_command('bdist').bdist_base
|
||||
self.bdist_dir = os.path.join(bdist_base, 'msi')
|
||||
|
||||
short_version = get_python_version()
|
||||
if (not self.target_version) and self.distribution.has_ext_modules():
|
||||
self.target_version = short_version
|
||||
|
||||
if self.target_version:
|
||||
self.versions = [self.target_version]
|
||||
if not self.skip_build and self.distribution.has_ext_modules()\
|
||||
and self.target_version != short_version:
|
||||
raise DistutilsOptionError(
|
||||
"target version can only be %s, or the '--skip-build'"
|
||||
" option must be specified" % (short_version,))
|
||||
else:
|
||||
self.versions = list(self.all_versions)
|
||||
|
||||
self.set_undefined_options('bdist',
|
||||
('dist_dir', 'dist_dir'),
|
||||
('plat_name', 'plat_name'),
|
||||
)
|
||||
|
||||
if self.pre_install_script:
|
||||
raise DistutilsOptionError(
|
||||
"the pre-install-script feature is not yet implemented")
|
||||
|
||||
if self.install_script:
|
||||
for script in self.distribution.scripts:
|
||||
if self.install_script == os.path.basename(script):
|
||||
break
|
||||
else:
|
||||
raise DistutilsOptionError(
|
||||
"install_script '%s' not found in scripts"
|
||||
% self.install_script)
|
||||
self.install_script_key = None
|
||||
|
||||
def run(self):
|
||||
if not self.skip_build:
|
||||
self.run_command('build')
|
||||
|
||||
install = self.reinitialize_command('install', reinit_subcommands=1)
|
||||
install.prefix = self.bdist_dir
|
||||
install.skip_build = self.skip_build
|
||||
install.warn_dir = 0
|
||||
|
||||
install_lib = self.reinitialize_command('install_lib')
|
||||
# we do not want to include pyc or pyo files
|
||||
install_lib.compile = 0
|
||||
install_lib.optimize = 0
|
||||
|
||||
if self.distribution.has_ext_modules():
|
||||
# If we are building an installer for a Python version other
|
||||
# than the one we are currently running, then we need to ensure
|
||||
# our build_lib reflects the other Python version rather than ours.
|
||||
# Note that for target_version!=sys.version, we must have skipped the
|
||||
# build step, so there is no issue with enforcing the build of this
|
||||
# version.
|
||||
target_version = self.target_version
|
||||
if not target_version:
|
||||
assert self.skip_build, "Should have already checked this"
|
||||
target_version = '%d.%d' % sys.version_info[:2]
|
||||
plat_specifier = ".%s-%s" % (self.plat_name, target_version)
|
||||
build = self.get_finalized_command('build')
|
||||
build.build_lib = os.path.join(build.build_base,
|
||||
'lib' + plat_specifier)
|
||||
|
||||
log.info("installing to %s", self.bdist_dir)
|
||||
install.ensure_finalized()
|
||||
|
||||
# avoid warning of 'install_lib' about installing
|
||||
# into a directory not in sys.path
|
||||
sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB'))
|
||||
|
||||
install.run()
|
||||
|
||||
del sys.path[0]
|
||||
|
||||
self.mkpath(self.dist_dir)
|
||||
fullname = self.distribution.get_fullname()
|
||||
installer_name = self.get_installer_filename(fullname)
|
||||
installer_name = os.path.abspath(installer_name)
|
||||
if os.path.exists(installer_name): os.unlink(installer_name)
|
||||
|
||||
metadata = self.distribution.metadata
|
||||
author = metadata.author
|
||||
if not author:
|
||||
author = metadata.maintainer
|
||||
if not author:
|
||||
author = "UNKNOWN"
|
||||
version = metadata.get_version()
|
||||
# ProductVersion must be strictly numeric
|
||||
# XXX need to deal with prerelease versions
|
||||
sversion = "%d.%d.%d" % StrictVersion(version).version
|
||||
# Prefix ProductName with Python x.y, so that
|
||||
# it sorts together with the other Python packages
|
||||
# in Add-Remove-Programs (APR)
|
||||
fullname = self.distribution.get_fullname()
|
||||
if self.target_version:
|
||||
product_name = "Python %s %s" % (self.target_version, fullname)
|
||||
else:
|
||||
product_name = "Python %s" % (fullname)
|
||||
self.db = msilib.init_database(installer_name, schema,
|
||||
product_name, msilib.gen_uuid(),
|
||||
sversion, author)
|
||||
msilib.add_tables(self.db, sequence)
|
||||
props = [('DistVersion', version)]
|
||||
email = metadata.author_email or metadata.maintainer_email
|
||||
if email:
|
||||
props.append(("ARPCONTACT", email))
|
||||
if metadata.url:
|
||||
props.append(("ARPURLINFOABOUT", metadata.url))
|
||||
if props:
|
||||
add_data(self.db, 'Property', props)
|
||||
|
||||
self.add_find_python()
|
||||
self.add_files()
|
||||
self.add_scripts()
|
||||
self.add_ui()
|
||||
self.db.Commit()
|
||||
|
||||
if hasattr(self.distribution, 'dist_files'):
|
||||
tup = 'bdist_msi', self.target_version or 'any', fullname
|
||||
self.distribution.dist_files.append(tup)
|
||||
|
||||
if not self.keep_temp:
|
||||
remove_tree(self.bdist_dir, dry_run=self.dry_run)
|
||||
|
||||
def add_files(self):
|
||||
db = self.db
|
||||
cab = msilib.CAB("distfiles")
|
||||
rootdir = os.path.abspath(self.bdist_dir)
|
||||
|
||||
root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir")
|
||||
f = Feature(db, "Python", "Python", "Everything",
|
||||
0, 1, directory="TARGETDIR")
|
||||
|
||||
items = [(f, root, '')]
|
||||
for version in self.versions + [self.other_version]:
|
||||
target = "TARGETDIR" + version
|
||||
name = default = "Python" + version
|
||||
desc = "Everything"
|
||||
if version is self.other_version:
|
||||
title = "Python from another location"
|
||||
level = 2
|
||||
else:
|
||||
title = "Python %s from registry" % version
|
||||
level = 1
|
||||
f = Feature(db, name, title, desc, 1, level, directory=target)
|
||||
dir = Directory(db, cab, root, rootdir, target, default)
|
||||
items.append((f, dir, version))
|
||||
db.Commit()
|
||||
|
||||
seen = {}
|
||||
for feature, dir, version in items:
|
||||
todo = [dir]
|
||||
while todo:
|
||||
dir = todo.pop()
|
||||
for file in os.listdir(dir.absolute):
|
||||
afile = os.path.join(dir.absolute, file)
|
||||
if os.path.isdir(afile):
|
||||
short = "%s|%s" % (dir.make_short(file), file)
|
||||
default = file + version
|
||||
newdir = Directory(db, cab, dir, file, default, short)
|
||||
todo.append(newdir)
|
||||
else:
|
||||
if not dir.component:
|
||||
dir.start_component(dir.logical, feature, 0)
|
||||
if afile not in seen:
|
||||
key = seen[afile] = dir.add_file(file)
|
||||
if file==self.install_script:
|
||||
if self.install_script_key:
|
||||
raise DistutilsOptionError(
|
||||
"Multiple files with name %s" % file)
|
||||
self.install_script_key = '[#%s]' % key
|
||||
else:
|
||||
key = seen[afile]
|
||||
add_data(self.db, "DuplicateFile",
|
||||
[(key + version, dir.component, key, None, dir.logical)])
|
||||
db.Commit()
|
||||
cab.commit(db)
|
||||
|
||||
def add_find_python(self):
|
||||
"""Adds code to the installer to compute the location of Python.
|
||||
|
||||
Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the
|
||||
registry for each version of Python.
|
||||
|
||||
Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined,
|
||||
else from PYTHON.MACHINE.X.Y.
|
||||
|
||||
Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe"""
|
||||
|
||||
start = 402
|
||||
for ver in self.versions:
|
||||
install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver
|
||||
machine_reg = "python.machine." + ver
|
||||
user_reg = "python.user." + ver
|
||||
machine_prop = "PYTHON.MACHINE." + ver
|
||||
user_prop = "PYTHON.USER." + ver
|
||||
machine_action = "PythonFromMachine" + ver
|
||||
user_action = "PythonFromUser" + ver
|
||||
exe_action = "PythonExe" + ver
|
||||
target_dir_prop = "TARGETDIR" + ver
|
||||
exe_prop = "PYTHON" + ver
|
||||
if msilib.Win64:
|
||||
# type: msidbLocatorTypeRawValue + msidbLocatorType64bit
|
||||
Type = 2+16
|
||||
else:
|
||||
Type = 2
|
||||
add_data(self.db, "RegLocator",
|
||||
[(machine_reg, 2, install_path, None, Type),
|
||||
(user_reg, 1, install_path, None, Type)])
|
||||
add_data(self.db, "AppSearch",
|
||||
[(machine_prop, machine_reg),
|
||||
(user_prop, user_reg)])
|
||||
add_data(self.db, "CustomAction",
|
||||
[(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"),
|
||||
(user_action, 51+256, target_dir_prop, "[" + user_prop + "]"),
|
||||
(exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"),
|
||||
])
|
||||
add_data(self.db, "InstallExecuteSequence",
|
||||
[(machine_action, machine_prop, start),
|
||||
(user_action, user_prop, start + 1),
|
||||
(exe_action, None, start + 2),
|
||||
])
|
||||
add_data(self.db, "InstallUISequence",
|
||||
[(machine_action, machine_prop, start),
|
||||
(user_action, user_prop, start + 1),
|
||||
(exe_action, None, start + 2),
|
||||
])
|
||||
add_data(self.db, "Condition",
|
||||
[("Python" + ver, 0, "NOT TARGETDIR" + ver)])
|
||||
start += 4
|
||||
assert start < 500
|
||||
|
||||
def add_scripts(self):
|
||||
if self.install_script:
|
||||
start = 6800
|
||||
for ver in self.versions + [self.other_version]:
|
||||
install_action = "install_script." + ver
|
||||
exe_prop = "PYTHON" + ver
|
||||
add_data(self.db, "CustomAction",
|
||||
[(install_action, 50, exe_prop, self.install_script_key)])
|
||||
add_data(self.db, "InstallExecuteSequence",
|
||||
[(install_action, "&Python%s=3" % ver, start)])
|
||||
start += 1
|
||||
# XXX pre-install scripts are currently refused in finalize_options()
|
||||
# but if this feature is completed, it will also need to add
|
||||
# entries for each version as the above code does
|
||||
if self.pre_install_script:
|
||||
scriptfn = os.path.join(self.bdist_dir, "preinstall.bat")
|
||||
with open(scriptfn, "w") as f:
|
||||
# The batch file will be executed with [PYTHON], so that %1
|
||||
# is the path to the Python interpreter; %0 will be the path
|
||||
# of the batch file.
|
||||
# rem ="""
|
||||
# %1 %0
|
||||
# exit
|
||||
# """
|
||||
# <actual script>
|
||||
f.write('rem ="""\n%1 %0\nexit\n"""\n')
|
||||
with open(self.pre_install_script) as fin:
|
||||
f.write(fin.read())
|
||||
add_data(self.db, "Binary",
|
||||
[("PreInstall", msilib.Binary(scriptfn))
|
||||
])
|
||||
add_data(self.db, "CustomAction",
|
||||
[("PreInstall", 2, "PreInstall", None)
|
||||
])
|
||||
add_data(self.db, "InstallExecuteSequence",
|
||||
[("PreInstall", "NOT Installed", 450)])
|
||||
|
||||
|
||||
def add_ui(self):
|
||||
db = self.db
|
||||
x = y = 50
|
||||
w = 370
|
||||
h = 300
|
||||
title = "[ProductName] Setup"
|
||||
|
||||
# see "Dialog Style Bits"
|
||||
modal = 3 # visible | modal
|
||||
modeless = 1 # visible
|
||||
track_disk_space = 32
|
||||
|
||||
# UI customization properties
|
||||
add_data(db, "Property",
|
||||
# See "DefaultUIFont Property"
|
||||
[("DefaultUIFont", "DlgFont8"),
|
||||
# See "ErrorDialog Style Bit"
|
||||
("ErrorDialog", "ErrorDlg"),
|
||||
("Progress1", "Install"), # modified in maintenance type dlg
|
||||
("Progress2", "installs"),
|
||||
("MaintenanceForm_Action", "Repair"),
|
||||
# possible values: ALL, JUSTME
|
||||
("WhichUsers", "ALL")
|
||||
])
|
||||
|
||||
# Fonts, see "TextStyle Table"
|
||||
add_data(db, "TextStyle",
|
||||
[("DlgFont8", "Tahoma", 9, None, 0),
|
||||
("DlgFontBold8", "Tahoma", 8, None, 1), #bold
|
||||
("VerdanaBold10", "Verdana", 10, None, 1),
|
||||
("VerdanaRed9", "Verdana", 9, 255, 0),
|
||||
])
|
||||
|
||||
# UI Sequences, see "InstallUISequence Table", "Using a Sequence Table"
|
||||
# Numbers indicate sequence; see sequence.py for how these action integrate
|
||||
add_data(db, "InstallUISequence",
|
||||
[("PrepareDlg", "Not Privileged or Windows9x or Installed", 140),
|
||||
("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141),
|
||||
# In the user interface, assume all-users installation if privileged.
|
||||
("SelectFeaturesDlg", "Not Installed", 1230),
|
||||
# XXX no support for resume installations yet
|
||||
#("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240),
|
||||
("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250),
|
||||
("ProgressDlg", None, 1280)])
|
||||
|
||||
add_data(db, 'ActionText', text.ActionText)
|
||||
add_data(db, 'UIText', text.UIText)
|
||||
#####################################################################
|
||||
# Standard dialogs: FatalError, UserExit, ExitDialog
|
||||
fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title,
|
||||
"Finish", "Finish", "Finish")
|
||||
fatal.title("[ProductName] Installer ended prematurely")
|
||||
fatal.back("< Back", "Finish", active = 0)
|
||||
fatal.cancel("Cancel", "Back", active = 0)
|
||||
fatal.text("Description1", 15, 70, 320, 80, 0x30003,
|
||||
"[ProductName] setup ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.")
|
||||
fatal.text("Description2", 15, 155, 320, 20, 0x30003,
|
||||
"Click the Finish button to exit the Installer.")
|
||||
c=fatal.next("Finish", "Cancel", name="Finish")
|
||||
c.event("EndDialog", "Exit")
|
||||
|
||||
user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title,
|
||||
"Finish", "Finish", "Finish")
|
||||
user_exit.title("[ProductName] Installer was interrupted")
|
||||
user_exit.back("< Back", "Finish", active = 0)
|
||||
user_exit.cancel("Cancel", "Back", active = 0)
|
||||
user_exit.text("Description1", 15, 70, 320, 80, 0x30003,
|
||||
"[ProductName] setup was interrupted. Your system has not been modified. "
|
||||
"To install this program at a later time, please run the installation again.")
|
||||
user_exit.text("Description2", 15, 155, 320, 20, 0x30003,
|
||||
"Click the Finish button to exit the Installer.")
|
||||
c = user_exit.next("Finish", "Cancel", name="Finish")
|
||||
c.event("EndDialog", "Exit")
|
||||
|
||||
exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title,
|
||||
"Finish", "Finish", "Finish")
|
||||
exit_dialog.title("Completing the [ProductName] Installer")
|
||||
exit_dialog.back("< Back", "Finish", active = 0)
|
||||
exit_dialog.cancel("Cancel", "Back", active = 0)
|
||||
exit_dialog.text("Description", 15, 235, 320, 20, 0x30003,
|
||||
"Click the Finish button to exit the Installer.")
|
||||
c = exit_dialog.next("Finish", "Cancel", name="Finish")
|
||||
c.event("EndDialog", "Return")
|
||||
|
||||
#####################################################################
|
||||
# Required dialog: FilesInUse, ErrorDlg
|
||||
inuse = PyDialog(db, "FilesInUse",
|
||||
x, y, w, h,
|
||||
19, # KeepModeless|Modal|Visible
|
||||
title,
|
||||
"Retry", "Retry", "Retry", bitmap=False)
|
||||
inuse.text("Title", 15, 6, 200, 15, 0x30003,
|
||||
r"{\DlgFontBold8}Files in Use")
|
||||
inuse.text("Description", 20, 23, 280, 20, 0x30003,
|
||||
"Some files that need to be updated are currently in use.")
|
||||
inuse.text("Text", 20, 55, 330, 50, 3,
|
||||
"The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.")
|
||||
inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess",
|
||||
None, None, None)
|
||||
c=inuse.back("Exit", "Ignore", name="Exit")
|
||||
c.event("EndDialog", "Exit")
|
||||
c=inuse.next("Ignore", "Retry", name="Ignore")
|
||||
c.event("EndDialog", "Ignore")
|
||||
c=inuse.cancel("Retry", "Exit", name="Retry")
|
||||
c.event("EndDialog","Retry")
|
||||
|
||||
# See "Error Dialog". See "ICE20" for the required names of the controls.
|
||||
error = Dialog(db, "ErrorDlg",
|
||||
50, 10, 330, 101,
|
||||
65543, # Error|Minimize|Modal|Visible
|
||||
title,
|
||||
"ErrorText", None, None)
|
||||
error.text("ErrorText", 50,9,280,48,3, "")
|
||||
#error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None)
|
||||
error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo")
|
||||
error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes")
|
||||
error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort")
|
||||
error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel")
|
||||
error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore")
|
||||
error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk")
|
||||
error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry")
|
||||
|
||||
#####################################################################
|
||||
# Global "Query Cancel" dialog
|
||||
cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title,
|
||||
"No", "No", "No")
|
||||
cancel.text("Text", 48, 15, 194, 30, 3,
|
||||
"Are you sure you want to cancel [ProductName] installation?")
|
||||
#cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None,
|
||||
# "py.ico", None, None)
|
||||
c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No")
|
||||
c.event("EndDialog", "Exit")
|
||||
|
||||
c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes")
|
||||
c.event("EndDialog", "Return")
|
||||
|
||||
#####################################################################
|
||||
# Global "Wait for costing" dialog
|
||||
costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title,
|
||||
"Return", "Return", "Return")
|
||||
costing.text("Text", 48, 15, 194, 30, 3,
|
||||
"Please wait while the installer finishes determining your disk space requirements.")
|
||||
c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None)
|
||||
c.event("EndDialog", "Exit")
|
||||
|
||||
#####################################################################
|
||||
# Preparation dialog: no user input except cancellation
|
||||
prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title,
|
||||
"Cancel", "Cancel", "Cancel")
|
||||
prep.text("Description", 15, 70, 320, 40, 0x30003,
|
||||
"Please wait while the Installer prepares to guide you through the installation.")
|
||||
prep.title("Welcome to the [ProductName] Installer")
|
||||
c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...")
|
||||
c.mapping("ActionText", "Text")
|
||||
c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None)
|
||||
c.mapping("ActionData", "Text")
|
||||
prep.back("Back", None, active=0)
|
||||
prep.next("Next", None, active=0)
|
||||
c=prep.cancel("Cancel", None)
|
||||
c.event("SpawnDialog", "CancelDlg")
|
||||
|
||||
#####################################################################
|
||||
# Feature (Python directory) selection
|
||||
seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title,
|
||||
"Next", "Next", "Cancel")
|
||||
seldlg.title("Select Python Installations")
|
||||
|
||||
seldlg.text("Hint", 15, 30, 300, 20, 3,
|
||||
"Select the Python locations where %s should be installed."
|
||||
% self.distribution.get_fullname())
|
||||
|
||||
seldlg.back("< Back", None, active=0)
|
||||
c = seldlg.next("Next >", "Cancel")
|
||||
order = 1
|
||||
c.event("[TARGETDIR]", "[SourceDir]", ordering=order)
|
||||
for version in self.versions + [self.other_version]:
|
||||
order += 1
|
||||
c.event("[TARGETDIR]", "[TARGETDIR%s]" % version,
|
||||
"FEATURE_SELECTED AND &Python%s=3" % version,
|
||||
ordering=order)
|
||||
c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1)
|
||||
c.event("EndDialog", "Return", ordering=order + 2)
|
||||
c = seldlg.cancel("Cancel", "Features")
|
||||
c.event("SpawnDialog", "CancelDlg")
|
||||
|
||||
c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3,
|
||||
"FEATURE", None, "PathEdit", None)
|
||||
c.event("[FEATURE_SELECTED]", "1")
|
||||
ver = self.other_version
|
||||
install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver
|
||||
dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver
|
||||
|
||||
c = seldlg.text("Other", 15, 200, 300, 15, 3,
|
||||
"Provide an alternate Python location")
|
||||
c.condition("Enable", install_other_cond)
|
||||
c.condition("Show", install_other_cond)
|
||||
c.condition("Disable", dont_install_other_cond)
|
||||
c.condition("Hide", dont_install_other_cond)
|
||||
|
||||
c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1,
|
||||
"TARGETDIR" + ver, None, "Next", None)
|
||||
c.condition("Enable", install_other_cond)
|
||||
c.condition("Show", install_other_cond)
|
||||
c.condition("Disable", dont_install_other_cond)
|
||||
c.condition("Hide", dont_install_other_cond)
|
||||
|
||||
#####################################################################
|
||||
# Disk cost
|
||||
cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title,
|
||||
"OK", "OK", "OK", bitmap=False)
|
||||
cost.text("Title", 15, 6, 200, 15, 0x30003,
|
||||
r"{\DlgFontBold8}Disk Space Requirements")
|
||||
cost.text("Description", 20, 20, 280, 20, 0x30003,
|
||||
"The disk space required for the installation of the selected features.")
|
||||
cost.text("Text", 20, 53, 330, 60, 3,
|
||||
"The highlighted volumes (if any) do not have enough disk space "
|
||||
"available for the currently selected features. You can either "
|
||||
"remove some files from the highlighted volumes, or choose to "
|
||||
"install less features onto local drive(s), or select different "
|
||||
"destination drive(s).")
|
||||
cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223,
|
||||
None, "{120}{70}{70}{70}{70}", None, None)
|
||||
cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return")
|
||||
|
||||
#####################################################################
|
||||
# WhichUsers Dialog. Only available on NT, and for privileged users.
|
||||
# This must be run before FindRelatedProducts, because that will
|
||||
# take into account whether the previous installation was per-user
|
||||
# or per-machine. We currently don't support going back to this
|
||||
# dialog after "Next" was selected; to support this, we would need to
|
||||
# find how to reset the ALLUSERS property, and how to re-run
|
||||
# FindRelatedProducts.
|
||||
# On Windows9x, the ALLUSERS property is ignored on the command line
|
||||
# and in the Property table, but installer fails according to the documentation
|
||||
# if a dialog attempts to set ALLUSERS.
|
||||
whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title,
|
||||
"AdminInstall", "Next", "Cancel")
|
||||
whichusers.title("Select whether to install [ProductName] for all users of this computer.")
|
||||
# A radio group with two options: allusers, justme
|
||||
g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3,
|
||||
"WhichUsers", "", "Next")
|
||||
g.add("ALL", 0, 5, 150, 20, "Install for all users")
|
||||
g.add("JUSTME", 0, 25, 150, 20, "Install just for me")
|
||||
|
||||
whichusers.back("Back", None, active=0)
|
||||
|
||||
c = whichusers.next("Next >", "Cancel")
|
||||
c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1)
|
||||
c.event("EndDialog", "Return", ordering = 2)
|
||||
|
||||
c = whichusers.cancel("Cancel", "AdminInstall")
|
||||
c.event("SpawnDialog", "CancelDlg")
|
||||
|
||||
#####################################################################
|
||||
# Installation Progress dialog (modeless)
|
||||
progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title,
|
||||
"Cancel", "Cancel", "Cancel", bitmap=False)
|
||||
progress.text("Title", 20, 15, 200, 15, 0x30003,
|
||||
r"{\DlgFontBold8}[Progress1] [ProductName]")
|
||||
progress.text("Text", 35, 65, 300, 30, 3,
|
||||
"Please wait while the Installer [Progress2] [ProductName]. "
|
||||
"This may take several minutes.")
|
||||
progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:")
|
||||
|
||||
c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...")
|
||||
c.mapping("ActionText", "Text")
|
||||
|
||||
#c=progress.text("ActionData", 35, 140, 300, 20, 3, None)
|
||||
#c.mapping("ActionData", "Text")
|
||||
|
||||
c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537,
|
||||
None, "Progress done", None, None)
|
||||
c.mapping("SetProgress", "Progress")
|
||||
|
||||
progress.back("< Back", "Next", active=False)
|
||||
progress.next("Next >", "Cancel", active=False)
|
||||
progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg")
|
||||
|
||||
###################################################################
|
||||
# Maintenance type: repair/uninstall
|
||||
maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title,
|
||||
"Next", "Next", "Cancel")
|
||||
maint.title("Welcome to the [ProductName] Setup Wizard")
|
||||
maint.text("BodyText", 15, 63, 330, 42, 3,
|
||||
"Select whether you want to repair or remove [ProductName].")
|
||||
g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3,
|
||||
"MaintenanceForm_Action", "", "Next")
|
||||
#g.add("Change", 0, 0, 200, 17, "&Change [ProductName]")
|
||||
g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]")
|
||||
g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]")
|
||||
|
||||
maint.back("< Back", None, active=False)
|
||||
c=maint.next("Finish", "Cancel")
|
||||
# Change installation: Change progress dialog to "Change", then ask
|
||||
# for feature selection
|
||||
#c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1)
|
||||
#c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2)
|
||||
|
||||
# Reinstall: Change progress dialog to "Repair", then invoke reinstall
|
||||
# Also set list of reinstalled features to "ALL"
|
||||
c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5)
|
||||
c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6)
|
||||
c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7)
|
||||
c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8)
|
||||
|
||||
# Uninstall: Change progress to "Remove", then invoke uninstall
|
||||
# Also set list of removed features to "ALL"
|
||||
c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11)
|
||||
c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12)
|
||||
c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13)
|
||||
c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14)
|
||||
|
||||
# Close dialog when maintenance action scheduled
|
||||
c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20)
|
||||
#c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21)
|
||||
|
||||
maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg")
|
||||
|
||||
def get_installer_filename(self, fullname):
|
||||
# Factored out to allow overriding in subclasses
|
||||
if self.target_version:
|
||||
base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name,
|
||||
self.target_version)
|
||||
else:
|
||||
base_name = "%s.%s.msi" % (fullname, self.plat_name)
|
||||
installer_name = os.path.join(self.dist_dir, base_name)
|
||||
return installer_name
|
||||
@@ -3,134 +3,153 @@
|
||||
Implements the Distutils 'bdist_rpm' command (create RPM source and binary
|
||||
distributions)."""
|
||||
|
||||
import subprocess, sys, os
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
from distutils.core import Command
|
||||
from distutils.debug import DEBUG
|
||||
from distutils.file_util import write_file
|
||||
from distutils.errors import *
|
||||
from distutils.errors import (
|
||||
DistutilsOptionError,
|
||||
DistutilsPlatformError,
|
||||
DistutilsFileError,
|
||||
DistutilsExecError,
|
||||
)
|
||||
from distutils.sysconfig import get_python_version
|
||||
from distutils import log
|
||||
|
||||
|
||||
class bdist_rpm(Command):
|
||||
|
||||
description = "create an RPM distribution"
|
||||
|
||||
user_options = [
|
||||
('bdist-base=', None,
|
||||
"base directory for creating built distributions"),
|
||||
('rpm-base=', None,
|
||||
"base directory for creating RPMs (defaults to \"rpm\" under "
|
||||
"--bdist-base; must be specified for RPM 2)"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put final RPM files in "
|
||||
"(and .spec files if --spec-only)"),
|
||||
('python=', None,
|
||||
"path to Python interpreter to hard-code in the .spec file "
|
||||
"(default: \"python\")"),
|
||||
('fix-python', None,
|
||||
"hard-code the exact path to the current Python interpreter in "
|
||||
"the .spec file"),
|
||||
('spec-only', None,
|
||||
"only regenerate spec file"),
|
||||
('source-only', None,
|
||||
"only generate source RPM"),
|
||||
('binary-only', None,
|
||||
"only generate binary RPM"),
|
||||
('use-bzip2', None,
|
||||
"use bzip2 instead of gzip to create source distribution"),
|
||||
|
||||
('bdist-base=', None, "base directory for creating built distributions"),
|
||||
(
|
||||
'rpm-base=',
|
||||
None,
|
||||
"base directory for creating RPMs (defaults to \"rpm\" under "
|
||||
"--bdist-base; must be specified for RPM 2)",
|
||||
),
|
||||
(
|
||||
'dist-dir=',
|
||||
'd',
|
||||
"directory to put final RPM files in " "(and .spec files if --spec-only)",
|
||||
),
|
||||
(
|
||||
'python=',
|
||||
None,
|
||||
"path to Python interpreter to hard-code in the .spec file "
|
||||
"(default: \"python\")",
|
||||
),
|
||||
(
|
||||
'fix-python',
|
||||
None,
|
||||
"hard-code the exact path to the current Python interpreter in "
|
||||
"the .spec file",
|
||||
),
|
||||
('spec-only', None, "only regenerate spec file"),
|
||||
('source-only', None, "only generate source RPM"),
|
||||
('binary-only', None, "only generate binary RPM"),
|
||||
('use-bzip2', None, "use bzip2 instead of gzip to create source distribution"),
|
||||
# More meta-data: too RPM-specific to put in the setup script,
|
||||
# but needs to go in the .spec file -- so we make these options
|
||||
# to "bdist_rpm". The idea is that packagers would put this
|
||||
# info in setup.cfg, although they are of course free to
|
||||
# supply it on the command line.
|
||||
('distribution-name=', None,
|
||||
"name of the (Linux) distribution to which this "
|
||||
"RPM applies (*not* the name of the module distribution!)"),
|
||||
('group=', None,
|
||||
"package classification [default: \"Development/Libraries\"]"),
|
||||
('release=', None,
|
||||
"RPM release number"),
|
||||
('serial=', None,
|
||||
"RPM serial number"),
|
||||
('vendor=', None,
|
||||
"RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
|
||||
"[default: maintainer or author from setup script]"),
|
||||
('packager=', None,
|
||||
"RPM packager (eg. \"Jane Doe <jane@example.net>\") "
|
||||
"[default: vendor]"),
|
||||
('doc-files=', None,
|
||||
"list of documentation files (space or comma-separated)"),
|
||||
('changelog=', None,
|
||||
"RPM changelog"),
|
||||
('icon=', None,
|
||||
"name of icon file"),
|
||||
('provides=', None,
|
||||
"capabilities provided by this package"),
|
||||
('requires=', None,
|
||||
"capabilities required by this package"),
|
||||
('conflicts=', None,
|
||||
"capabilities which conflict with this package"),
|
||||
('build-requires=', None,
|
||||
"capabilities required to build this package"),
|
||||
('obsoletes=', None,
|
||||
"capabilities made obsolete by this package"),
|
||||
('no-autoreq', None,
|
||||
"do not automatically calculate dependencies"),
|
||||
|
||||
(
|
||||
'distribution-name=',
|
||||
None,
|
||||
"name of the (Linux) distribution to which this "
|
||||
"RPM applies (*not* the name of the module distribution!)",
|
||||
),
|
||||
('group=', None, "package classification [default: \"Development/Libraries\"]"),
|
||||
('release=', None, "RPM release number"),
|
||||
('serial=', None, "RPM serial number"),
|
||||
(
|
||||
'vendor=',
|
||||
None,
|
||||
"RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
|
||||
"[default: maintainer or author from setup script]",
|
||||
),
|
||||
(
|
||||
'packager=',
|
||||
None,
|
||||
"RPM packager (eg. \"Jane Doe <jane@example.net>\") " "[default: vendor]",
|
||||
),
|
||||
('doc-files=', None, "list of documentation files (space or comma-separated)"),
|
||||
('changelog=', None, "RPM changelog"),
|
||||
('icon=', None, "name of icon file"),
|
||||
('provides=', None, "capabilities provided by this package"),
|
||||
('requires=', None, "capabilities required by this package"),
|
||||
('conflicts=', None, "capabilities which conflict with this package"),
|
||||
('build-requires=', None, "capabilities required to build this package"),
|
||||
('obsoletes=', None, "capabilities made obsolete by this package"),
|
||||
('no-autoreq', None, "do not automatically calculate dependencies"),
|
||||
# Actions to take when building RPM
|
||||
('keep-temp', 'k',
|
||||
"don't clean up RPM build directory"),
|
||||
('no-keep-temp', None,
|
||||
"clean up RPM build directory [default]"),
|
||||
('use-rpm-opt-flags', None,
|
||||
"compile with RPM_OPT_FLAGS when building from source RPM"),
|
||||
('no-rpm-opt-flags', None,
|
||||
"do not pass any RPM CFLAGS to compiler"),
|
||||
('rpm3-mode', None,
|
||||
"RPM 3 compatibility mode (default)"),
|
||||
('rpm2-mode', None,
|
||||
"RPM 2 compatibility mode"),
|
||||
|
||||
('keep-temp', 'k', "don't clean up RPM build directory"),
|
||||
('no-keep-temp', None, "clean up RPM build directory [default]"),
|
||||
(
|
||||
'use-rpm-opt-flags',
|
||||
None,
|
||||
"compile with RPM_OPT_FLAGS when building from source RPM",
|
||||
),
|
||||
('no-rpm-opt-flags', None, "do not pass any RPM CFLAGS to compiler"),
|
||||
('rpm3-mode', None, "RPM 3 compatibility mode (default)"),
|
||||
('rpm2-mode', None, "RPM 2 compatibility mode"),
|
||||
# Add the hooks necessary for specifying custom scripts
|
||||
('prep-script=', None,
|
||||
"Specify a script for the PREP phase of RPM building"),
|
||||
('build-script=', None,
|
||||
"Specify a script for the BUILD phase of RPM building"),
|
||||
|
||||
('pre-install=', None,
|
||||
"Specify a script for the pre-INSTALL phase of RPM building"),
|
||||
('install-script=', None,
|
||||
"Specify a script for the INSTALL phase of RPM building"),
|
||||
('post-install=', None,
|
||||
"Specify a script for the post-INSTALL phase of RPM building"),
|
||||
|
||||
('pre-uninstall=', None,
|
||||
"Specify a script for the pre-UNINSTALL phase of RPM building"),
|
||||
('post-uninstall=', None,
|
||||
"Specify a script for the post-UNINSTALL phase of RPM building"),
|
||||
|
||||
('clean-script=', None,
|
||||
"Specify a script for the CLEAN phase of RPM building"),
|
||||
|
||||
('verify-script=', None,
|
||||
"Specify a script for the VERIFY phase of the RPM build"),
|
||||
|
||||
('prep-script=', None, "Specify a script for the PREP phase of RPM building"),
|
||||
('build-script=', None, "Specify a script for the BUILD phase of RPM building"),
|
||||
(
|
||||
'pre-install=',
|
||||
None,
|
||||
"Specify a script for the pre-INSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
'install-script=',
|
||||
None,
|
||||
"Specify a script for the INSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
'post-install=',
|
||||
None,
|
||||
"Specify a script for the post-INSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
'pre-uninstall=',
|
||||
None,
|
||||
"Specify a script for the pre-UNINSTALL phase of RPM building",
|
||||
),
|
||||
(
|
||||
'post-uninstall=',
|
||||
None,
|
||||
"Specify a script for the post-UNINSTALL phase of RPM building",
|
||||
),
|
||||
('clean-script=', None, "Specify a script for the CLEAN phase of RPM building"),
|
||||
(
|
||||
'verify-script=',
|
||||
None,
|
||||
"Specify a script for the VERIFY phase of the RPM build",
|
||||
),
|
||||
# Allow a packager to explicitly force an architecture
|
||||
('force-arch=', None,
|
||||
"Force an architecture onto the RPM build process"),
|
||||
('force-arch=', None, "Force an architecture onto the RPM build process"),
|
||||
('quiet', 'q', "Run the INSTALL phase of RPM building in quiet mode"),
|
||||
]
|
||||
|
||||
('quiet', 'q',
|
||||
"Run the INSTALL phase of RPM building in quiet mode"),
|
||||
]
|
||||
|
||||
boolean_options = ['keep-temp', 'use-rpm-opt-flags', 'rpm3-mode',
|
||||
'no-autoreq', 'quiet']
|
||||
|
||||
negative_opt = {'no-keep-temp': 'keep-temp',
|
||||
'no-rpm-opt-flags': 'use-rpm-opt-flags',
|
||||
'rpm2-mode': 'rpm3-mode'}
|
||||
boolean_options = [
|
||||
'keep-temp',
|
||||
'use-rpm-opt-flags',
|
||||
'rpm3-mode',
|
||||
'no-autoreq',
|
||||
'quiet',
|
||||
]
|
||||
|
||||
negative_opt = {
|
||||
'no-keep-temp': 'keep-temp',
|
||||
'no-rpm-opt-flags': 'use-rpm-opt-flags',
|
||||
'rpm2-mode': 'rpm3-mode',
|
||||
}
|
||||
|
||||
def initialize_options(self):
|
||||
self.bdist_base = None
|
||||
@@ -181,8 +200,7 @@ class bdist_rpm(Command):
|
||||
self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
|
||||
if self.rpm_base is None:
|
||||
if not self.rpm3_mode:
|
||||
raise DistutilsOptionError(
|
||||
"you must specify --rpm-base in RPM 2 mode")
|
||||
raise DistutilsOptionError("you must specify --rpm-base in RPM 2 mode")
|
||||
self.rpm_base = os.path.join(self.bdist_base, "rpm")
|
||||
|
||||
if self.python is None:
|
||||
@@ -192,14 +210,17 @@ class bdist_rpm(Command):
|
||||
self.python = "python3"
|
||||
elif self.fix_python:
|
||||
raise DistutilsOptionError(
|
||||
"--python and --fix-python are mutually exclusive options")
|
||||
"--python and --fix-python are mutually exclusive options"
|
||||
)
|
||||
|
||||
if os.name != 'posix':
|
||||
raise DistutilsPlatformError("don't know how to create RPM "
|
||||
"distributions on platform %s" % os.name)
|
||||
raise DistutilsPlatformError(
|
||||
"don't know how to create RPM " "distributions on platform %s" % os.name
|
||||
)
|
||||
if self.binary_only and self.source_only:
|
||||
raise DistutilsOptionError(
|
||||
"cannot supply both '--source-only' and '--binary-only'")
|
||||
"cannot supply both '--source-only' and '--binary-only'"
|
||||
)
|
||||
|
||||
# don't pass CFLAGS to pure python distributions
|
||||
if not self.distribution.has_ext_modules():
|
||||
@@ -210,9 +231,11 @@ class bdist_rpm(Command):
|
||||
|
||||
def finalize_package_data(self):
|
||||
self.ensure_string('group', "Development/Libraries")
|
||||
self.ensure_string('vendor',
|
||||
"%s <%s>" % (self.distribution.get_contact(),
|
||||
self.distribution.get_contact_email()))
|
||||
self.ensure_string(
|
||||
'vendor',
|
||||
"%s <%s>"
|
||||
% (self.distribution.get_contact(), self.distribution.get_contact_email()),
|
||||
)
|
||||
self.ensure_string('packager')
|
||||
self.ensure_string_list('doc_files')
|
||||
if isinstance(self.doc_files, list):
|
||||
@@ -221,12 +244,12 @@ class bdist_rpm(Command):
|
||||
self.doc_files.append(readme)
|
||||
|
||||
self.ensure_string('release', "1")
|
||||
self.ensure_string('serial') # should it be an int?
|
||||
self.ensure_string('serial') # should it be an int?
|
||||
|
||||
self.ensure_string('distribution_name')
|
||||
|
||||
self.ensure_string('changelog')
|
||||
# Format changelog correctly
|
||||
# Format changelog correctly
|
||||
self.changelog = self._format_changelog(self.changelog)
|
||||
|
||||
self.ensure_filename('icon')
|
||||
@@ -253,7 +276,7 @@ class bdist_rpm(Command):
|
||||
|
||||
self.ensure_string('force_arch')
|
||||
|
||||
def run(self):
|
||||
def run(self): # noqa: C901
|
||||
if DEBUG:
|
||||
print("before _get_package_data():")
|
||||
print("vendor =", self.vendor)
|
||||
@@ -274,14 +297,12 @@ class bdist_rpm(Command):
|
||||
|
||||
# Spec file goes into 'dist_dir' if '--spec-only specified',
|
||||
# build/rpm.<plat> otherwise.
|
||||
spec_path = os.path.join(spec_dir,
|
||||
"%s.spec" % self.distribution.get_name())
|
||||
self.execute(write_file,
|
||||
(spec_path,
|
||||
self._make_spec_file()),
|
||||
"writing '%s'" % spec_path)
|
||||
spec_path = os.path.join(spec_dir, "%s.spec" % self.distribution.get_name())
|
||||
self.execute(
|
||||
write_file, (spec_path, self._make_spec_file()), "writing '%s'" % spec_path
|
||||
)
|
||||
|
||||
if self.spec_only: # stop if requested
|
||||
if self.spec_only: # stop if requested
|
||||
return
|
||||
|
||||
# Make a source distribution and copy to SOURCES directory with
|
||||
@@ -303,14 +324,13 @@ class bdist_rpm(Command):
|
||||
if os.path.exists(self.icon):
|
||||
self.copy_file(self.icon, source_dir)
|
||||
else:
|
||||
raise DistutilsFileError(
|
||||
"icon file '%s' does not exist" % self.icon)
|
||||
raise DistutilsFileError("icon file '%s' does not exist" % self.icon)
|
||||
|
||||
# build package
|
||||
log.info("building RPMs")
|
||||
rpm_cmd = ['rpmbuild']
|
||||
|
||||
if self.source_only: # what kind of RPMs?
|
||||
if self.source_only: # what kind of RPMs?
|
||||
rpm_cmd.append('-bs')
|
||||
elif self.binary_only:
|
||||
rpm_cmd.append('-bb')
|
||||
@@ -318,8 +338,7 @@ class bdist_rpm(Command):
|
||||
rpm_cmd.append('-ba')
|
||||
rpm_cmd.extend(['--define', '__python %s' % self.python])
|
||||
if self.rpm3_mode:
|
||||
rpm_cmd.extend(['--define',
|
||||
'_topdir %s' % os.path.abspath(self.rpm_base)])
|
||||
rpm_cmd.extend(['--define', '_topdir %s' % os.path.abspath(self.rpm_base)])
|
||||
if not self.keep_temp:
|
||||
rpm_cmd.append('--clean')
|
||||
|
||||
@@ -334,8 +353,11 @@ class bdist_rpm(Command):
|
||||
nvr_string = "%{name}-%{version}-%{release}"
|
||||
src_rpm = nvr_string + ".src.rpm"
|
||||
non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm"
|
||||
q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % (
|
||||
src_rpm, non_src_rpm, spec_path)
|
||||
q_cmd = r"rpm -q --qf '{} {}\n' --specfile '{}'".format(
|
||||
src_rpm,
|
||||
non_src_rpm,
|
||||
spec_path,
|
||||
)
|
||||
|
||||
out = os.popen(q_cmd)
|
||||
try:
|
||||
@@ -345,12 +367,12 @@ class bdist_rpm(Command):
|
||||
line = out.readline()
|
||||
if not line:
|
||||
break
|
||||
l = line.strip().split()
|
||||
assert(len(l) == 2)
|
||||
binary_rpms.append(l[1])
|
||||
ell = line.strip().split()
|
||||
assert len(ell) == 2
|
||||
binary_rpms.append(ell[1])
|
||||
# The source rpm is named after the first entry in the spec file
|
||||
if source_rpm is None:
|
||||
source_rpm = l[0]
|
||||
source_rpm = ell[0]
|
||||
|
||||
status = out.close()
|
||||
if status:
|
||||
@@ -369,38 +391,37 @@ class bdist_rpm(Command):
|
||||
|
||||
if not self.binary_only:
|
||||
srpm = os.path.join(rpm_dir['SRPMS'], source_rpm)
|
||||
assert(os.path.exists(srpm))
|
||||
assert os.path.exists(srpm)
|
||||
self.move_file(srpm, self.dist_dir)
|
||||
filename = os.path.join(self.dist_dir, source_rpm)
|
||||
self.distribution.dist_files.append(
|
||||
('bdist_rpm', pyversion, filename))
|
||||
self.distribution.dist_files.append(('bdist_rpm', pyversion, filename))
|
||||
|
||||
if not self.source_only:
|
||||
for rpm in binary_rpms:
|
||||
rpm = os.path.join(rpm_dir['RPMS'], rpm)
|
||||
if os.path.exists(rpm):
|
||||
self.move_file(rpm, self.dist_dir)
|
||||
filename = os.path.join(self.dist_dir,
|
||||
os.path.basename(rpm))
|
||||
filename = os.path.join(self.dist_dir, os.path.basename(rpm))
|
||||
self.distribution.dist_files.append(
|
||||
('bdist_rpm', pyversion, filename))
|
||||
('bdist_rpm', pyversion, filename)
|
||||
)
|
||||
|
||||
def _dist_path(self, path):
|
||||
return os.path.join(self.dist_dir, os.path.basename(path))
|
||||
|
||||
def _make_spec_file(self):
|
||||
def _make_spec_file(self): # noqa: C901
|
||||
"""Generate the text of an RPM spec file and return it as a
|
||||
list of strings (one per line).
|
||||
"""
|
||||
# definitions and headers
|
||||
spec_file = [
|
||||
'%define name ' + self.distribution.get_name(),
|
||||
'%define version ' + self.distribution.get_version().replace('-','_'),
|
||||
'%define version ' + self.distribution.get_version().replace('-', '_'),
|
||||
'%define unmangled_version ' + self.distribution.get_version(),
|
||||
'%define release ' + self.release.replace('-','_'),
|
||||
'%define release ' + self.release.replace('-', '_'),
|
||||
'',
|
||||
'Summary: ' + self.distribution.get_description(),
|
||||
]
|
||||
'Summary: ' + (self.distribution.get_description() or "UNKNOWN"),
|
||||
]
|
||||
|
||||
# Workaround for #14443 which affects some RPM based systems such as
|
||||
# RHEL6 (and probably derivatives)
|
||||
@@ -408,8 +429,9 @@ class bdist_rpm(Command):
|
||||
# Generate a potential replacement value for __os_install_post (whilst
|
||||
# normalizing the whitespace to simplify the test for whether the
|
||||
# invocation of brp-python-bytecompile passes in __python):
|
||||
vendor_hook = '\n'.join([' %s \\' % line.strip()
|
||||
for line in vendor_hook.splitlines()])
|
||||
vendor_hook = '\n'.join(
|
||||
[' %s \\' % line.strip() for line in vendor_hook.splitlines()]
|
||||
)
|
||||
problem = "brp-python-bytecompile \\\n"
|
||||
fixed = "brp-python-bytecompile %{__python} \\\n"
|
||||
fixed_hook = vendor_hook.replace(problem, fixed)
|
||||
@@ -420,14 +442,17 @@ class bdist_rpm(Command):
|
||||
# put locale summaries into spec file
|
||||
# XXX not supported for now (hard to put a dictionary
|
||||
# in a config file -- arg!)
|
||||
#for locale in self.summaries.keys():
|
||||
# for locale in self.summaries.keys():
|
||||
# spec_file.append('Summary(%s): %s' % (locale,
|
||||
# self.summaries[locale]))
|
||||
|
||||
spec_file.extend([
|
||||
'Name: %{name}',
|
||||
'Version: %{version}',
|
||||
'Release: %{release}',])
|
||||
spec_file.extend(
|
||||
[
|
||||
'Name: %{name}',
|
||||
'Version: %{version}',
|
||||
'Release: %{release}',
|
||||
]
|
||||
)
|
||||
|
||||
# XXX yuck! this filename is available from the "sdist" command,
|
||||
# but only after it has run: and we create the spec file before
|
||||
@@ -437,42 +462,44 @@ class bdist_rpm(Command):
|
||||
else:
|
||||
spec_file.append('Source0: %{name}-%{unmangled_version}.tar.gz')
|
||||
|
||||
spec_file.extend([
|
||||
'License: ' + self.distribution.get_license(),
|
||||
'Group: ' + self.group,
|
||||
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
|
||||
'Prefix: %{_prefix}', ])
|
||||
spec_file.extend(
|
||||
[
|
||||
'License: ' + (self.distribution.get_license() or "UNKNOWN"),
|
||||
'Group: ' + self.group,
|
||||
'BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot',
|
||||
'Prefix: %{_prefix}',
|
||||
]
|
||||
)
|
||||
|
||||
if not self.force_arch:
|
||||
# noarch if no extension modules
|
||||
if not self.distribution.has_ext_modules():
|
||||
spec_file.append('BuildArch: noarch')
|
||||
else:
|
||||
spec_file.append( 'BuildArch: %s' % self.force_arch )
|
||||
spec_file.append('BuildArch: %s' % self.force_arch)
|
||||
|
||||
for field in ('Vendor',
|
||||
'Packager',
|
||||
'Provides',
|
||||
'Requires',
|
||||
'Conflicts',
|
||||
'Obsoletes',
|
||||
):
|
||||
for field in (
|
||||
'Vendor',
|
||||
'Packager',
|
||||
'Provides',
|
||||
'Requires',
|
||||
'Conflicts',
|
||||
'Obsoletes',
|
||||
):
|
||||
val = getattr(self, field.lower())
|
||||
if isinstance(val, list):
|
||||
spec_file.append('%s: %s' % (field, ' '.join(val)))
|
||||
spec_file.append('{}: {}'.format(field, ' '.join(val)))
|
||||
elif val is not None:
|
||||
spec_file.append('%s: %s' % (field, val))
|
||||
spec_file.append('{}: {}'.format(field, val))
|
||||
|
||||
|
||||
if self.distribution.get_url() != 'UNKNOWN':
|
||||
if self.distribution.get_url():
|
||||
spec_file.append('Url: ' + self.distribution.get_url())
|
||||
|
||||
if self.distribution_name:
|
||||
spec_file.append('Distribution: ' + self.distribution_name)
|
||||
|
||||
if self.build_requires:
|
||||
spec_file.append('BuildRequires: ' +
|
||||
' '.join(self.build_requires))
|
||||
spec_file.append('BuildRequires: ' + ' '.join(self.build_requires))
|
||||
|
||||
if self.icon:
|
||||
spec_file.append('Icon: ' + os.path.basename(self.icon))
|
||||
@@ -480,16 +507,18 @@ class bdist_rpm(Command):
|
||||
if self.no_autoreq:
|
||||
spec_file.append('AutoReq: 0')
|
||||
|
||||
spec_file.extend([
|
||||
'',
|
||||
'%description',
|
||||
self.distribution.get_long_description()
|
||||
])
|
||||
spec_file.extend(
|
||||
[
|
||||
'',
|
||||
'%description',
|
||||
self.distribution.get_long_description() or "",
|
||||
]
|
||||
)
|
||||
|
||||
# put locale descriptions into spec file
|
||||
# XXX again, suppressed because config file syntax doesn't
|
||||
# easily support this ;-(
|
||||
#for locale in self.descriptions.keys():
|
||||
# for locale in self.descriptions.keys():
|
||||
# spec_file.extend([
|
||||
# '',
|
||||
# '%description -l ' + locale,
|
||||
@@ -498,7 +527,7 @@ class bdist_rpm(Command):
|
||||
|
||||
# rpm scripts
|
||||
# figure out default build script
|
||||
def_setup_call = "%s %s" % (self.python,os.path.basename(sys.argv[0]))
|
||||
def_setup_call = "{} {}".format(self.python, os.path.basename(sys.argv[0]))
|
||||
def_build = "%s build" % def_setup_call
|
||||
if self.use_rpm_opt_flags:
|
||||
def_build = 'env CFLAGS="$RPM_OPT_FLAGS" ' + def_build
|
||||
@@ -509,8 +538,9 @@ class bdist_rpm(Command):
|
||||
# that we open and interpolate into the spec file, but the defaults
|
||||
# are just text that we drop in as-is. Hmmm.
|
||||
|
||||
install_cmd = ('%s install -O1 --root=$RPM_BUILD_ROOT '
|
||||
'--record=INSTALLED_FILES') % def_setup_call
|
||||
install_cmd = (
|
||||
'%s install -O1 --root=$RPM_BUILD_ROOT ' '--record=INSTALLED_FILES'
|
||||
) % def_setup_call
|
||||
|
||||
script_options = [
|
||||
('prep', 'prep_script', "%setup -n %{name}-%{unmangled_version}"),
|
||||
@@ -529,37 +559,43 @@ class bdist_rpm(Command):
|
||||
# use 'default' as contents of script
|
||||
val = getattr(self, attr)
|
||||
if val or default:
|
||||
spec_file.extend([
|
||||
'',
|
||||
'%' + rpm_opt,])
|
||||
spec_file.extend(
|
||||
[
|
||||
'',
|
||||
'%' + rpm_opt,
|
||||
]
|
||||
)
|
||||
if val:
|
||||
with open(val) as f:
|
||||
spec_file.extend(f.read().split('\n'))
|
||||
else:
|
||||
spec_file.append(default)
|
||||
|
||||
|
||||
# files section
|
||||
spec_file.extend([
|
||||
'',
|
||||
'%files -f INSTALLED_FILES',
|
||||
'%defattr(-,root,root)',
|
||||
])
|
||||
spec_file.extend(
|
||||
[
|
||||
'',
|
||||
'%files -f INSTALLED_FILES',
|
||||
'%defattr(-,root,root)',
|
||||
]
|
||||
)
|
||||
|
||||
if self.doc_files:
|
||||
spec_file.append('%doc ' + ' '.join(self.doc_files))
|
||||
|
||||
if self.changelog:
|
||||
spec_file.extend([
|
||||
'',
|
||||
'%changelog',])
|
||||
spec_file.extend(
|
||||
[
|
||||
'',
|
||||
'%changelog',
|
||||
]
|
||||
)
|
||||
spec_file.extend(self.changelog)
|
||||
|
||||
return spec_file
|
||||
|
||||
def _format_changelog(self, changelog):
|
||||
"""Format the changelog correctly and convert it to a list of strings
|
||||
"""
|
||||
"""Format the changelog correctly and convert it to a list of strings"""
|
||||
if not changelog:
|
||||
return changelog
|
||||
new_changelog = []
|
||||
|
||||
@@ -1,377 +0,0 @@
|
||||
"""distutils.command.bdist_wininst
|
||||
|
||||
Implements the Distutils 'bdist_wininst' command: create a windows installer
|
||||
exe-program."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from distutils.core import Command
|
||||
from distutils.util import get_platform
|
||||
from distutils.dir_util import remove_tree
|
||||
from distutils.errors import *
|
||||
from distutils.sysconfig import get_python_version
|
||||
from distutils import log
|
||||
|
||||
class bdist_wininst(Command):
|
||||
|
||||
description = "create an executable installer for MS Windows"
|
||||
|
||||
user_options = [('bdist-dir=', None,
|
||||
"temporary directory for creating the distribution"),
|
||||
('plat-name=', 'p',
|
||||
"platform name to embed in generated filenames "
|
||||
"(default: %s)" % get_platform()),
|
||||
('keep-temp', 'k',
|
||||
"keep the pseudo-installation tree around after " +
|
||||
"creating the distribution archive"),
|
||||
('target-version=', None,
|
||||
"require a specific python version" +
|
||||
" on the target system"),
|
||||
('no-target-compile', 'c',
|
||||
"do not compile .py to .pyc on the target system"),
|
||||
('no-target-optimize', 'o',
|
||||
"do not compile .py to .pyo (optimized) "
|
||||
"on the target system"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put final built distributions in"),
|
||||
('bitmap=', 'b',
|
||||
"bitmap to use for the installer instead of python-powered logo"),
|
||||
('title=', 't',
|
||||
"title to display on the installer background instead of default"),
|
||||
('skip-build', None,
|
||||
"skip rebuilding everything (for testing/debugging)"),
|
||||
('install-script=', None,
|
||||
"basename of installation script to be run after "
|
||||
"installation or before deinstallation"),
|
||||
('pre-install-script=', None,
|
||||
"Fully qualified filename of a script to be run before "
|
||||
"any files are installed. This script need not be in the "
|
||||
"distribution"),
|
||||
('user-access-control=', None,
|
||||
"specify Vista's UAC handling - 'none'/default=no "
|
||||
"handling, 'auto'=use UAC if target Python installed for "
|
||||
"all users, 'force'=always use UAC"),
|
||||
]
|
||||
|
||||
boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize',
|
||||
'skip-build']
|
||||
|
||||
# bpo-10945: bdist_wininst requires mbcs encoding only available on Windows
|
||||
_unsupported = (sys.platform != "win32")
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super().__init__(*args, **kw)
|
||||
warnings.warn("bdist_wininst command is deprecated since Python 3.8, "
|
||||
"use bdist_wheel (wheel packages) instead",
|
||||
DeprecationWarning, 2)
|
||||
|
||||
def initialize_options(self):
|
||||
self.bdist_dir = None
|
||||
self.plat_name = None
|
||||
self.keep_temp = 0
|
||||
self.no_target_compile = 0
|
||||
self.no_target_optimize = 0
|
||||
self.target_version = None
|
||||
self.dist_dir = None
|
||||
self.bitmap = None
|
||||
self.title = None
|
||||
self.skip_build = None
|
||||
self.install_script = None
|
||||
self.pre_install_script = None
|
||||
self.user_access_control = None
|
||||
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('bdist', ('skip_build', 'skip_build'))
|
||||
|
||||
if self.bdist_dir is None:
|
||||
if self.skip_build and self.plat_name:
|
||||
# If build is skipped and plat_name is overridden, bdist will
|
||||
# not see the correct 'plat_name' - so set that up manually.
|
||||
bdist = self.distribution.get_command_obj('bdist')
|
||||
bdist.plat_name = self.plat_name
|
||||
# next the command will be initialized using that name
|
||||
bdist_base = self.get_finalized_command('bdist').bdist_base
|
||||
self.bdist_dir = os.path.join(bdist_base, 'wininst')
|
||||
|
||||
if not self.target_version:
|
||||
self.target_version = ""
|
||||
|
||||
if not self.skip_build and self.distribution.has_ext_modules():
|
||||
short_version = get_python_version()
|
||||
if self.target_version and self.target_version != short_version:
|
||||
raise DistutilsOptionError(
|
||||
"target version can only be %s, or the '--skip-build'" \
|
||||
" option must be specified" % (short_version,))
|
||||
self.target_version = short_version
|
||||
|
||||
self.set_undefined_options('bdist',
|
||||
('dist_dir', 'dist_dir'),
|
||||
('plat_name', 'plat_name'),
|
||||
)
|
||||
|
||||
if self.install_script:
|
||||
for script in self.distribution.scripts:
|
||||
if self.install_script == os.path.basename(script):
|
||||
break
|
||||
else:
|
||||
raise DistutilsOptionError(
|
||||
"install_script '%s' not found in scripts"
|
||||
% self.install_script)
|
||||
|
||||
def run(self):
|
||||
if (sys.platform != "win32" and
|
||||
(self.distribution.has_ext_modules() or
|
||||
self.distribution.has_c_libraries())):
|
||||
raise DistutilsPlatformError \
|
||||
("distribution contains extensions and/or C libraries; "
|
||||
"must be compiled on a Windows 32 platform")
|
||||
|
||||
if not self.skip_build:
|
||||
self.run_command('build')
|
||||
|
||||
install = self.reinitialize_command('install', reinit_subcommands=1)
|
||||
install.root = self.bdist_dir
|
||||
install.skip_build = self.skip_build
|
||||
install.warn_dir = 0
|
||||
install.plat_name = self.plat_name
|
||||
|
||||
install_lib = self.reinitialize_command('install_lib')
|
||||
# we do not want to include pyc or pyo files
|
||||
install_lib.compile = 0
|
||||
install_lib.optimize = 0
|
||||
|
||||
if self.distribution.has_ext_modules():
|
||||
# If we are building an installer for a Python version other
|
||||
# than the one we are currently running, then we need to ensure
|
||||
# our build_lib reflects the other Python version rather than ours.
|
||||
# Note that for target_version!=sys.version, we must have skipped the
|
||||
# build step, so there is no issue with enforcing the build of this
|
||||
# version.
|
||||
target_version = self.target_version
|
||||
if not target_version:
|
||||
assert self.skip_build, "Should have already checked this"
|
||||
target_version = '%d.%d' % sys.version_info[:2]
|
||||
plat_specifier = ".%s-%s" % (self.plat_name, target_version)
|
||||
build = self.get_finalized_command('build')
|
||||
build.build_lib = os.path.join(build.build_base,
|
||||
'lib' + plat_specifier)
|
||||
|
||||
# Use a custom scheme for the zip-file, because we have to decide
|
||||
# at installation time which scheme to use.
|
||||
for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'):
|
||||
value = key.upper()
|
||||
if key == 'headers':
|
||||
value = value + '/Include/$dist_name'
|
||||
setattr(install,
|
||||
'install_' + key,
|
||||
value)
|
||||
|
||||
log.info("installing to %s", self.bdist_dir)
|
||||
install.ensure_finalized()
|
||||
|
||||
# avoid warning of 'install_lib' about installing
|
||||
# into a directory not in sys.path
|
||||
sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB'))
|
||||
|
||||
install.run()
|
||||
|
||||
del sys.path[0]
|
||||
|
||||
# And make an archive relative to the root of the
|
||||
# pseudo-installation tree.
|
||||
from tempfile import mktemp
|
||||
archive_basename = mktemp()
|
||||
fullname = self.distribution.get_fullname()
|
||||
arcname = self.make_archive(archive_basename, "zip",
|
||||
root_dir=self.bdist_dir)
|
||||
# create an exe containing the zip-file
|
||||
self.create_exe(arcname, fullname, self.bitmap)
|
||||
if self.distribution.has_ext_modules():
|
||||
pyversion = get_python_version()
|
||||
else:
|
||||
pyversion = 'any'
|
||||
self.distribution.dist_files.append(('bdist_wininst', pyversion,
|
||||
self.get_installer_filename(fullname)))
|
||||
# remove the zip-file again
|
||||
log.debug("removing temporary file '%s'", arcname)
|
||||
os.remove(arcname)
|
||||
|
||||
if not self.keep_temp:
|
||||
remove_tree(self.bdist_dir, dry_run=self.dry_run)
|
||||
|
||||
def get_inidata(self):
|
||||
# Return data describing the installation.
|
||||
lines = []
|
||||
metadata = self.distribution.metadata
|
||||
|
||||
# Write the [metadata] section.
|
||||
lines.append("[metadata]")
|
||||
|
||||
# 'info' will be displayed in the installer's dialog box,
|
||||
# describing the items to be installed.
|
||||
info = (metadata.long_description or '') + '\n'
|
||||
|
||||
# Escape newline characters
|
||||
def escape(s):
|
||||
return s.replace("\n", "\\n")
|
||||
|
||||
for name in ["author", "author_email", "description", "maintainer",
|
||||
"maintainer_email", "name", "url", "version"]:
|
||||
data = getattr(metadata, name, "")
|
||||
if data:
|
||||
info = info + ("\n %s: %s" % \
|
||||
(name.capitalize(), escape(data)))
|
||||
lines.append("%s=%s" % (name, escape(data)))
|
||||
|
||||
# The [setup] section contains entries controlling
|
||||
# the installer runtime.
|
||||
lines.append("\n[Setup]")
|
||||
if self.install_script:
|
||||
lines.append("install_script=%s" % self.install_script)
|
||||
lines.append("info=%s" % escape(info))
|
||||
lines.append("target_compile=%d" % (not self.no_target_compile))
|
||||
lines.append("target_optimize=%d" % (not self.no_target_optimize))
|
||||
if self.target_version:
|
||||
lines.append("target_version=%s" % self.target_version)
|
||||
if self.user_access_control:
|
||||
lines.append("user_access_control=%s" % self.user_access_control)
|
||||
|
||||
title = self.title or self.distribution.get_fullname()
|
||||
lines.append("title=%s" % escape(title))
|
||||
import time
|
||||
import distutils
|
||||
build_info = "Built %s with distutils-%s" % \
|
||||
(time.ctime(time.time()), distutils.__version__)
|
||||
lines.append("build_info=%s" % build_info)
|
||||
return "\n".join(lines)
|
||||
|
||||
def create_exe(self, arcname, fullname, bitmap=None):
|
||||
import struct
|
||||
|
||||
self.mkpath(self.dist_dir)
|
||||
|
||||
cfgdata = self.get_inidata()
|
||||
|
||||
installer_name = self.get_installer_filename(fullname)
|
||||
self.announce("creating %s" % installer_name)
|
||||
|
||||
if bitmap:
|
||||
with open(bitmap, "rb") as f:
|
||||
bitmapdata = f.read()
|
||||
bitmaplen = len(bitmapdata)
|
||||
else:
|
||||
bitmaplen = 0
|
||||
|
||||
with open(installer_name, "wb") as file:
|
||||
file.write(self.get_exe_bytes())
|
||||
if bitmap:
|
||||
file.write(bitmapdata)
|
||||
|
||||
# Convert cfgdata from unicode to ascii, mbcs encoded
|
||||
if isinstance(cfgdata, str):
|
||||
cfgdata = cfgdata.encode("mbcs")
|
||||
|
||||
# Append the pre-install script
|
||||
cfgdata = cfgdata + b"\0"
|
||||
if self.pre_install_script:
|
||||
# We need to normalize newlines, so we open in text mode and
|
||||
# convert back to bytes. "latin-1" simply avoids any possible
|
||||
# failures.
|
||||
with open(self.pre_install_script, "r",
|
||||
encoding="latin-1") as script:
|
||||
script_data = script.read().encode("latin-1")
|
||||
cfgdata = cfgdata + script_data + b"\n\0"
|
||||
else:
|
||||
# empty pre-install script
|
||||
cfgdata = cfgdata + b"\0"
|
||||
file.write(cfgdata)
|
||||
|
||||
# The 'magic number' 0x1234567B is used to make sure that the
|
||||
# binary layout of 'cfgdata' is what the wininst.exe binary
|
||||
# expects. If the layout changes, increment that number, make
|
||||
# the corresponding changes to the wininst.exe sources, and
|
||||
# recompile them.
|
||||
header = struct.pack("<iii",
|
||||
0x1234567B, # tag
|
||||
len(cfgdata), # length
|
||||
bitmaplen, # number of bytes in bitmap
|
||||
)
|
||||
file.write(header)
|
||||
with open(arcname, "rb") as f:
|
||||
file.write(f.read())
|
||||
|
||||
def get_installer_filename(self, fullname):
|
||||
# Factored out to allow overriding in subclasses
|
||||
if self.target_version:
|
||||
# if we create an installer for a specific python version,
|
||||
# it's better to include this in the name
|
||||
installer_name = os.path.join(self.dist_dir,
|
||||
"%s.%s-py%s.exe" %
|
||||
(fullname, self.plat_name, self.target_version))
|
||||
else:
|
||||
installer_name = os.path.join(self.dist_dir,
|
||||
"%s.%s.exe" % (fullname, self.plat_name))
|
||||
return installer_name
|
||||
|
||||
def get_exe_bytes(self):
|
||||
# If a target-version other than the current version has been
|
||||
# specified, then using the MSVC version from *this* build is no good.
|
||||
# Without actually finding and executing the target version and parsing
|
||||
# its sys.version, we just hard-code our knowledge of old versions.
|
||||
# NOTE: Possible alternative is to allow "--target-version" to
|
||||
# specify a Python executable rather than a simple version string.
|
||||
# We can then execute this program to obtain any info we need, such
|
||||
# as the real sys.version string for the build.
|
||||
cur_version = get_python_version()
|
||||
|
||||
# If the target version is *later* than us, then we assume they
|
||||
# use what we use
|
||||
# string compares seem wrong, but are what sysconfig.py itself uses
|
||||
if self.target_version and self.target_version < cur_version:
|
||||
if self.target_version < "2.4":
|
||||
bv = '6.0'
|
||||
elif self.target_version == "2.4":
|
||||
bv = '7.1'
|
||||
elif self.target_version == "2.5":
|
||||
bv = '8.0'
|
||||
elif self.target_version <= "3.2":
|
||||
bv = '9.0'
|
||||
elif self.target_version <= "3.4":
|
||||
bv = '10.0'
|
||||
else:
|
||||
bv = '14.0'
|
||||
else:
|
||||
# for current version - use authoritative check.
|
||||
try:
|
||||
from msvcrt import CRT_ASSEMBLY_VERSION
|
||||
except ImportError:
|
||||
# cross-building, so assume the latest version
|
||||
bv = '14.0'
|
||||
else:
|
||||
# as far as we know, CRT is binary compatible based on
|
||||
# the first field, so assume 'x.0' until proven otherwise
|
||||
major = CRT_ASSEMBLY_VERSION.partition('.')[0]
|
||||
bv = major + '.0'
|
||||
|
||||
|
||||
# wininst-x.y.exe is in the same directory as this file
|
||||
directory = os.path.dirname(__file__)
|
||||
# we must use a wininst-x.y.exe built with the same C compiler
|
||||
# used for python. XXX What about mingw, borland, and so on?
|
||||
|
||||
# if plat_name starts with "win" but is not "win32"
|
||||
# we want to strip "win" and leave the rest (e.g. -amd64)
|
||||
# for all other cases, we don't want any suffix
|
||||
if self.plat_name != 'win32' and self.plat_name[:3] == 'win':
|
||||
sfix = self.plat_name[3:]
|
||||
else:
|
||||
sfix = ''
|
||||
|
||||
filename = os.path.join(directory, "wininst-%s%s.exe" % (bv, sfix))
|
||||
f = open(filename, "rb")
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
Implements the Distutils 'build' command."""
|
||||
|
||||
import sys, os
|
||||
import sys
|
||||
import os
|
||||
from distutils.core import Command
|
||||
from distutils.errors import DistutilsOptionError
|
||||
from distutils.util import get_platform
|
||||
@@ -10,6 +11,7 @@ from distutils.util import get_platform
|
||||
|
||||
def show_compilers():
|
||||
from distutils.ccompiler import show_compilers
|
||||
|
||||
show_compilers()
|
||||
|
||||
|
||||
@@ -18,40 +20,35 @@ class build(Command):
|
||||
description = "build everything needed to install"
|
||||
|
||||
user_options = [
|
||||
('build-base=', 'b',
|
||||
"base directory for build library"),
|
||||
('build-purelib=', None,
|
||||
"build directory for platform-neutral distributions"),
|
||||
('build-platlib=', None,
|
||||
"build directory for platform-specific distributions"),
|
||||
('build-lib=', None,
|
||||
"build directory for all distribution (defaults to either " +
|
||||
"build-purelib or build-platlib"),
|
||||
('build-scripts=', None,
|
||||
"build directory for scripts"),
|
||||
('build-temp=', 't',
|
||||
"temporary build directory"),
|
||||
('plat-name=', 'p',
|
||||
"platform name to build for, if supported "
|
||||
"(default: %s)" % get_platform()),
|
||||
('compiler=', 'c',
|
||||
"specify the compiler type"),
|
||||
('parallel=', 'j',
|
||||
"number of parallel build jobs"),
|
||||
('debug', 'g',
|
||||
"compile extensions and libraries with debugging information"),
|
||||
('force', 'f',
|
||||
"forcibly build everything (ignore file timestamps)"),
|
||||
('executable=', 'e',
|
||||
"specify final destination interpreter path (build.py)"),
|
||||
]
|
||||
('build-base=', 'b', "base directory for build library"),
|
||||
('build-purelib=', None, "build directory for platform-neutral distributions"),
|
||||
('build-platlib=', None, "build directory for platform-specific distributions"),
|
||||
(
|
||||
'build-lib=',
|
||||
None,
|
||||
"build directory for all distribution (defaults to either "
|
||||
+ "build-purelib or build-platlib",
|
||||
),
|
||||
('build-scripts=', None, "build directory for scripts"),
|
||||
('build-temp=', 't', "temporary build directory"),
|
||||
(
|
||||
'plat-name=',
|
||||
'p',
|
||||
"platform name to build for, if supported "
|
||||
"(default: %s)" % get_platform(),
|
||||
),
|
||||
('compiler=', 'c', "specify the compiler type"),
|
||||
('parallel=', 'j', "number of parallel build jobs"),
|
||||
('debug', 'g', "compile extensions and libraries with debugging information"),
|
||||
('force', 'f', "forcibly build everything (ignore file timestamps)"),
|
||||
('executable=', 'e', "specify final destination interpreter path (build.py)"),
|
||||
]
|
||||
|
||||
boolean_options = ['debug', 'force']
|
||||
|
||||
help_options = [
|
||||
('help-compiler', None,
|
||||
"list available compilers", show_compilers),
|
||||
]
|
||||
('help-compiler', None, "list available compilers", show_compilers),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.build_base = 'build'
|
||||
@@ -69,7 +66,7 @@ class build(Command):
|
||||
self.executable = None
|
||||
self.parallel = None
|
||||
|
||||
def finalize_options(self):
|
||||
def finalize_options(self): # noqa: C901
|
||||
if self.plat_name is None:
|
||||
self.plat_name = get_platform()
|
||||
else:
|
||||
@@ -78,10 +75,11 @@ class build(Command):
|
||||
# other platforms.
|
||||
if os.name != 'nt':
|
||||
raise DistutilsOptionError(
|
||||
"--plat-name only supported on Windows (try "
|
||||
"using './configure --help' on your platform)")
|
||||
"--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 = ".{}-{}".format(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
|
||||
@@ -95,8 +93,7 @@ class build(Command):
|
||||
if self.build_purelib is None:
|
||||
self.build_purelib = os.path.join(self.build_base, 'lib')
|
||||
if self.build_platlib is None:
|
||||
self.build_platlib = os.path.join(self.build_base,
|
||||
'lib' + plat_specifier)
|
||||
self.build_platlib = os.path.join(self.build_base, 'lib' + plat_specifier)
|
||||
|
||||
# 'build_lib' is the actual directory that we will use for this
|
||||
# particular module distribution -- if user didn't supply it, pick
|
||||
@@ -110,11 +107,11 @@ class build(Command):
|
||||
# 'build_temp' -- temporary directory for compiler turds,
|
||||
# "build/temp.<plat>"
|
||||
if self.build_temp is None:
|
||||
self.build_temp = os.path.join(self.build_base,
|
||||
'temp' + plat_specifier)
|
||||
self.build_temp = os.path.join(self.build_base, 'temp' + plat_specifier)
|
||||
if self.build_scripts is None:
|
||||
self.build_scripts = os.path.join(self.build_base,
|
||||
'scripts-%d.%d' % sys.version_info[:2])
|
||||
self.build_scripts = os.path.join(
|
||||
self.build_base, 'scripts-%d.%d' % sys.version_info[:2]
|
||||
)
|
||||
|
||||
if self.executable is None and sys.executable:
|
||||
self.executable = os.path.normpath(sys.executable)
|
||||
@@ -134,7 +131,6 @@ class build(Command):
|
||||
for cmd_name in self.get_sub_commands():
|
||||
self.run_command(cmd_name)
|
||||
|
||||
|
||||
# -- Predicates for the sub-command list ---------------------------
|
||||
|
||||
def has_pure_modules(self):
|
||||
@@ -149,9 +145,9 @@ class build(Command):
|
||||
def has_scripts(self):
|
||||
return self.distribution.has_scripts()
|
||||
|
||||
|
||||
sub_commands = [('build_py', has_pure_modules),
|
||||
('build_clib', has_c_libraries),
|
||||
('build_ext', has_ext_modules),
|
||||
('build_scripts', has_scripts),
|
||||
]
|
||||
sub_commands = [
|
||||
('build_py', has_pure_modules),
|
||||
('build_clib', has_c_libraries),
|
||||
('build_ext', has_ext_modules),
|
||||
('build_scripts', has_scripts),
|
||||
]
|
||||
|
||||
@@ -16,12 +16,14 @@ module."""
|
||||
|
||||
import os
|
||||
from distutils.core import Command
|
||||
from distutils.errors import *
|
||||
from distutils.errors import DistutilsSetupError
|
||||
from distutils.sysconfig import customize_compiler
|
||||
from distutils import log
|
||||
|
||||
|
||||
def show_compilers():
|
||||
from distutils.ccompiler import show_compilers
|
||||
|
||||
show_compilers()
|
||||
|
||||
|
||||
@@ -30,24 +32,18 @@ class build_clib(Command):
|
||||
description = "build C/C++ libraries used by Python extensions"
|
||||
|
||||
user_options = [
|
||||
('build-clib=', 'b',
|
||||
"directory to build C/C++ libraries to"),
|
||||
('build-temp=', 't',
|
||||
"directory to put temporary build by-products"),
|
||||
('debug', 'g',
|
||||
"compile with debugging information"),
|
||||
('force', 'f',
|
||||
"forcibly build everything (ignore file timestamps)"),
|
||||
('compiler=', 'c',
|
||||
"specify the compiler type"),
|
||||
]
|
||||
('build-clib=', 'b', "directory to build C/C++ libraries to"),
|
||||
('build-temp=', 't', "directory to put temporary build by-products"),
|
||||
('debug', 'g', "compile with debugging information"),
|
||||
('force', 'f', "forcibly build everything (ignore file timestamps)"),
|
||||
('compiler=', 'c', "specify the compiler type"),
|
||||
]
|
||||
|
||||
boolean_options = ['debug', 'force']
|
||||
|
||||
help_options = [
|
||||
('help-compiler', None,
|
||||
"list available compilers", show_compilers),
|
||||
]
|
||||
('help-compiler', None, "list available compilers", show_compilers),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.build_clib = None
|
||||
@@ -64,19 +60,20 @@ class build_clib(Command):
|
||||
self.force = 0
|
||||
self.compiler = None
|
||||
|
||||
|
||||
def finalize_options(self):
|
||||
# This might be confusing: both build-clib and build-temp default
|
||||
# to build-temp as defined by the "build" command. This is because
|
||||
# I think that C libraries are really just temporary build
|
||||
# by-products, at least from the point of view of building Python
|
||||
# extensions -- but I want to keep my options open.
|
||||
self.set_undefined_options('build',
|
||||
('build_temp', 'build_clib'),
|
||||
('build_temp', 'build_temp'),
|
||||
('compiler', 'compiler'),
|
||||
('debug', 'debug'),
|
||||
('force', 'force'))
|
||||
self.set_undefined_options(
|
||||
'build',
|
||||
('build_temp', 'build_clib'),
|
||||
('build_temp', 'build_temp'),
|
||||
('compiler', 'compiler'),
|
||||
('debug', 'debug'),
|
||||
('force', 'force'),
|
||||
)
|
||||
|
||||
self.libraries = self.distribution.libraries
|
||||
if self.libraries:
|
||||
@@ -90,23 +87,23 @@ class build_clib(Command):
|
||||
# XXX same as for build_ext -- what about 'self.define' and
|
||||
# 'self.undef' ?
|
||||
|
||||
|
||||
def run(self):
|
||||
if not self.libraries:
|
||||
return
|
||||
|
||||
# Yech -- this is cut 'n pasted from build_ext.py!
|
||||
from distutils.ccompiler import new_compiler
|
||||
self.compiler = new_compiler(compiler=self.compiler,
|
||||
dry_run=self.dry_run,
|
||||
force=self.force)
|
||||
|
||||
self.compiler = new_compiler(
|
||||
compiler=self.compiler, dry_run=self.dry_run, force=self.force
|
||||
)
|
||||
customize_compiler(self.compiler)
|
||||
|
||||
if self.include_dirs is not None:
|
||||
self.compiler.set_include_dirs(self.include_dirs)
|
||||
if self.define is not None:
|
||||
# 'define' option is a list of (name,value) tuples
|
||||
for (name,value) in self.define:
|
||||
for (name, value) in self.define:
|
||||
self.compiler.define_macro(name, value)
|
||||
if self.undef is not None:
|
||||
for macro in self.undef:
|
||||
@@ -114,7 +111,6 @@ class build_clib(Command):
|
||||
|
||||
self.build_libraries(self.libraries)
|
||||
|
||||
|
||||
def check_library_list(self, libraries):
|
||||
"""Ensure that the list of libraries is valid.
|
||||
|
||||
@@ -126,30 +122,31 @@ class build_clib(Command):
|
||||
just returns otherwise.
|
||||
"""
|
||||
if not isinstance(libraries, list):
|
||||
raise DistutilsSetupError(
|
||||
"'libraries' option must be a list of tuples")
|
||||
raise DistutilsSetupError("'libraries' option must be a list of tuples")
|
||||
|
||||
for lib in libraries:
|
||||
if not isinstance(lib, tuple) and len(lib) != 2:
|
||||
raise DistutilsSetupError(
|
||||
"each element of 'libraries' must a 2-tuple")
|
||||
raise DistutilsSetupError("each element of 'libraries' must a 2-tuple")
|
||||
|
||||
name, build_info = lib
|
||||
|
||||
if not isinstance(name, str):
|
||||
raise DistutilsSetupError(
|
||||
"first element of each tuple in 'libraries' "
|
||||
"must be a string (the library name)")
|
||||
"first element of each tuple in 'libraries' "
|
||||
"must be a string (the library name)"
|
||||
)
|
||||
|
||||
if '/' in name or (os.sep != '/' and os.sep in name):
|
||||
raise DistutilsSetupError("bad library name '%s': "
|
||||
"may not contain directory separators" % lib[0])
|
||||
raise DistutilsSetupError(
|
||||
"bad library name '%s': "
|
||||
"may not contain directory separators" % lib[0]
|
||||
)
|
||||
|
||||
if not isinstance(build_info, dict):
|
||||
raise DistutilsSetupError(
|
||||
"second element of each tuple in 'libraries' "
|
||||
"must be a dictionary (build info)")
|
||||
|
||||
"second element of each tuple in 'libraries' "
|
||||
"must be a dictionary (build info)"
|
||||
)
|
||||
|
||||
def get_library_names(self):
|
||||
# Assume the library list is valid -- 'check_library_list()' is
|
||||
@@ -162,7 +159,6 @@ class build_clib(Command):
|
||||
lib_names.append(lib_name)
|
||||
return lib_names
|
||||
|
||||
|
||||
def get_source_files(self):
|
||||
self.check_library_list(self.libraries)
|
||||
filenames = []
|
||||
@@ -170,22 +166,23 @@ class build_clib(Command):
|
||||
sources = build_info.get('sources')
|
||||
if sources is None or not isinstance(sources, (list, tuple)):
|
||||
raise DistutilsSetupError(
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'sources' must be present and must be "
|
||||
"a list of source filenames" % lib_name)
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'sources' must be present and must be "
|
||||
"a list of source filenames" % lib_name
|
||||
)
|
||||
|
||||
filenames.extend(sources)
|
||||
return filenames
|
||||
|
||||
|
||||
def build_libraries(self, libraries):
|
||||
for (lib_name, build_info) in libraries:
|
||||
sources = build_info.get('sources')
|
||||
if sources is None or not isinstance(sources, (list, tuple)):
|
||||
raise DistutilsSetupError(
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'sources' must be present and must be "
|
||||
"a list of source filenames" % lib_name)
|
||||
"in 'libraries' option (library '%s'), "
|
||||
"'sources' must be present and must be "
|
||||
"a list of source filenames" % lib_name
|
||||
)
|
||||
sources = list(sources)
|
||||
|
||||
log.info("building '%s' library", lib_name)
|
||||
@@ -195,15 +192,17 @@ class build_clib(Command):
|
||||
# files in a temporary build directory.)
|
||||
macros = build_info.get('macros')
|
||||
include_dirs = build_info.get('include_dirs')
|
||||
objects = self.compiler.compile(sources,
|
||||
output_dir=self.build_temp,
|
||||
macros=macros,
|
||||
include_dirs=include_dirs,
|
||||
debug=self.debug)
|
||||
objects = self.compiler.compile(
|
||||
sources,
|
||||
output_dir=self.build_temp,
|
||||
macros=macros,
|
||||
include_dirs=include_dirs,
|
||||
debug=self.debug,
|
||||
)
|
||||
|
||||
# Now "link" the object files together into a static library.
|
||||
# (On Unix at least, this isn't really linking -- it just
|
||||
# builds an archive. Whatever.)
|
||||
self.compiler.create_static_lib(objects, lib_name,
|
||||
output_dir=self.build_clib,
|
||||
debug=self.debug)
|
||||
self.compiler.create_static_lib(
|
||||
objects, lib_name, output_dir=self.build_clib, debug=self.debug
|
||||
)
|
||||
|
||||
@@ -9,7 +9,14 @@ import os
|
||||
import re
|
||||
import sys
|
||||
from distutils.core import Command
|
||||
from distutils.errors import *
|
||||
from distutils.errors import (
|
||||
DistutilsOptionError,
|
||||
DistutilsSetupError,
|
||||
CCompilerError,
|
||||
DistutilsError,
|
||||
CompileError,
|
||||
DistutilsPlatformError,
|
||||
)
|
||||
from distutils.sysconfig import customize_compiler, get_python_version
|
||||
from distutils.sysconfig import get_config_h_filename
|
||||
from distutils.dep_util import newer_group
|
||||
@@ -22,12 +29,12 @@ from site import USER_BASE
|
||||
|
||||
# An extension name is just a dot-separated list of Python NAMEs (ie.
|
||||
# the same as a fully-qualified module name).
|
||||
extension_name_re = re.compile \
|
||||
(r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$')
|
||||
extension_name_re = re.compile(r'^[a-zA-Z_][a-zA-Z_0-9]*(\.[a-zA-Z_][a-zA-Z_0-9]*)*$')
|
||||
|
||||
|
||||
def show_compilers ():
|
||||
def show_compilers():
|
||||
from distutils.ccompiler import show_compilers
|
||||
|
||||
show_compilers()
|
||||
|
||||
|
||||
@@ -55,54 +62,50 @@ class build_ext(Command):
|
||||
|
||||
sep_by = " (separated by '%s')" % os.pathsep
|
||||
user_options = [
|
||||
('build-lib=', 'b',
|
||||
"directory for compiled extension modules"),
|
||||
('build-temp=', 't',
|
||||
"directory for temporary files (build by-products)"),
|
||||
('plat-name=', 'p',
|
||||
"platform name to cross-compile for, if supported "
|
||||
"(default: %s)" % get_platform()),
|
||||
('inplace', 'i',
|
||||
"ignore build-lib and put compiled extensions into the source " +
|
||||
"directory alongside your pure Python modules"),
|
||||
('include-dirs=', 'I',
|
||||
"list of directories to search for header files" + sep_by),
|
||||
('define=', 'D',
|
||||
"C preprocessor macros to define"),
|
||||
('undef=', 'U',
|
||||
"C preprocessor macros to undefine"),
|
||||
('libraries=', 'l',
|
||||
"external C libraries to link with"),
|
||||
('library-dirs=', 'L',
|
||||
"directories to search for external C libraries" + sep_by),
|
||||
('rpath=', 'R',
|
||||
"directories to search for shared C libraries at runtime"),
|
||||
('link-objects=', 'O',
|
||||
"extra explicit link objects to include in the link"),
|
||||
('debug', 'g',
|
||||
"compile/link with debugging information"),
|
||||
('force', 'f',
|
||||
"forcibly build everything (ignore file timestamps)"),
|
||||
('compiler=', 'c',
|
||||
"specify the compiler type"),
|
||||
('parallel=', 'j',
|
||||
"number of parallel build jobs"),
|
||||
('swig-cpp', None,
|
||||
"make SWIG create C++ files (default is C)"),
|
||||
('swig-opts=', None,
|
||||
"list of SWIG command line options"),
|
||||
('swig=', None,
|
||||
"path to the SWIG executable"),
|
||||
('user', None,
|
||||
"add user include, library and rpath")
|
||||
]
|
||||
('build-lib=', 'b', "directory for compiled extension modules"),
|
||||
('build-temp=', 't', "directory for temporary files (build by-products)"),
|
||||
(
|
||||
'plat-name=',
|
||||
'p',
|
||||
"platform name to cross-compile for, if supported "
|
||||
"(default: %s)" % get_platform(),
|
||||
),
|
||||
(
|
||||
'inplace',
|
||||
'i',
|
||||
"ignore build-lib and put compiled extensions into the source "
|
||||
+ "directory alongside your pure Python modules",
|
||||
),
|
||||
(
|
||||
'include-dirs=',
|
||||
'I',
|
||||
"list of directories to search for header files" + sep_by,
|
||||
),
|
||||
('define=', 'D', "C preprocessor macros to define"),
|
||||
('undef=', 'U', "C preprocessor macros to undefine"),
|
||||
('libraries=', 'l', "external C libraries to link with"),
|
||||
(
|
||||
'library-dirs=',
|
||||
'L',
|
||||
"directories to search for external C libraries" + sep_by,
|
||||
),
|
||||
('rpath=', 'R', "directories to search for shared C libraries at runtime"),
|
||||
('link-objects=', 'O', "extra explicit link objects to include in the link"),
|
||||
('debug', 'g', "compile/link with debugging information"),
|
||||
('force', 'f', "forcibly build everything (ignore file timestamps)"),
|
||||
('compiler=', 'c', "specify the compiler type"),
|
||||
('parallel=', 'j', "number of parallel build jobs"),
|
||||
('swig-cpp', None, "make SWIG create C++ files (default is C)"),
|
||||
('swig-opts=', None, "list of SWIG command line options"),
|
||||
('swig=', None, "path to the SWIG executable"),
|
||||
('user', None, "add user include, library and rpath"),
|
||||
]
|
||||
|
||||
boolean_options = ['inplace', 'debug', 'force', 'swig-cpp', 'user']
|
||||
|
||||
help_options = [
|
||||
('help-compiler', None,
|
||||
"list available compilers", show_compilers),
|
||||
]
|
||||
('help-compiler', None, "list available compilers", show_compilers),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.extensions = None
|
||||
@@ -128,18 +131,19 @@ class build_ext(Command):
|
||||
self.user = None
|
||||
self.parallel = None
|
||||
|
||||
def finalize_options(self):
|
||||
def finalize_options(self): # noqa: C901
|
||||
from distutils import sysconfig
|
||||
|
||||
self.set_undefined_options('build',
|
||||
('build_lib', 'build_lib'),
|
||||
('build_temp', 'build_temp'),
|
||||
('compiler', 'compiler'),
|
||||
('debug', 'debug'),
|
||||
('force', 'force'),
|
||||
('parallel', 'parallel'),
|
||||
('plat_name', 'plat_name'),
|
||||
)
|
||||
self.set_undefined_options(
|
||||
'build',
|
||||
('build_lib', 'build_lib'),
|
||||
('build_temp', 'build_temp'),
|
||||
('compiler', 'compiler'),
|
||||
('debug', 'debug'),
|
||||
('force', 'force'),
|
||||
('parallel', 'parallel'),
|
||||
('plat_name', 'plat_name'),
|
||||
)
|
||||
|
||||
if self.package is None:
|
||||
self.package = self.distribution.ext_package
|
||||
@@ -164,8 +168,7 @@ class build_ext(Command):
|
||||
# any local include dirs take precedence.
|
||||
self.include_dirs.extend(py_include.split(os.path.pathsep))
|
||||
if plat_py_include != py_include:
|
||||
self.include_dirs.extend(
|
||||
plat_py_include.split(os.path.pathsep))
|
||||
self.include_dirs.extend(plat_py_include.split(os.path.pathsep))
|
||||
|
||||
self.ensure_string_list('libraries')
|
||||
self.ensure_string_list('link_objects')
|
||||
@@ -202,9 +205,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':
|
||||
@@ -222,9 +223,11 @@ class build_ext(Command):
|
||||
if sys.platform[:6] == 'cygwin':
|
||||
if not sysconfig.python_build:
|
||||
# building third party extensions
|
||||
self.library_dirs.append(os.path.join(sys.prefix, "lib",
|
||||
"python" + get_python_version(),
|
||||
"config"))
|
||||
self.library_dirs.append(
|
||||
os.path.join(
|
||||
sys.prefix, "lib", "python" + get_python_version(), "config"
|
||||
)
|
||||
)
|
||||
else:
|
||||
# building python standard extensions
|
||||
self.library_dirs.append('.')
|
||||
@@ -232,7 +235,7 @@ class build_ext(Command):
|
||||
# For building extensions with a shared Python library,
|
||||
# Python's library directory must be appended to library_dirs
|
||||
# See Issues: #1600860, #4366
|
||||
if (sysconfig.get_config_var('Py_ENABLE_SHARED')):
|
||||
if sysconfig.get_config_var('Py_ENABLE_SHARED'):
|
||||
if not sysconfig.python_build:
|
||||
# building third party extensions
|
||||
self.library_dirs.append(sysconfig.get_config_var('LIBDIR'))
|
||||
@@ -276,7 +279,7 @@ class build_ext(Command):
|
||||
except ValueError:
|
||||
raise DistutilsOptionError("parallel should be an integer")
|
||||
|
||||
def run(self):
|
||||
def run(self): # noqa: C901
|
||||
from distutils.ccompiler import new_compiler
|
||||
|
||||
# 'self.extensions', as supplied by setup.py, is a list of
|
||||
@@ -304,10 +307,12 @@ class build_ext(Command):
|
||||
|
||||
# Setup the CCompiler object that we'll use to do all the
|
||||
# compiling and linking
|
||||
self.compiler = new_compiler(compiler=self.compiler,
|
||||
verbose=self.verbose,
|
||||
dry_run=self.dry_run,
|
||||
force=self.force)
|
||||
self.compiler = new_compiler(
|
||||
compiler=self.compiler,
|
||||
verbose=self.verbose,
|
||||
dry_run=self.dry_run,
|
||||
force=self.force,
|
||||
)
|
||||
customize_compiler(self.compiler)
|
||||
# If we are cross-compiling, init the compiler now (if we are not
|
||||
# cross-compiling, init would not hurt, but people may rely on
|
||||
@@ -340,7 +345,7 @@ class build_ext(Command):
|
||||
# Now actually compile and link everything.
|
||||
self.build_extensions()
|
||||
|
||||
def check_extensions_list(self, extensions):
|
||||
def check_extensions_list(self, extensions): # noqa: C901
|
||||
"""Ensure that the list of extensions (presumably provided as a
|
||||
command option 'extensions') is valid, i.e. it is a list of
|
||||
Extension objects. We also support the old-style list of 2-tuples,
|
||||
@@ -352,34 +357,40 @@ class build_ext(Command):
|
||||
"""
|
||||
if not isinstance(extensions, list):
|
||||
raise DistutilsSetupError(
|
||||
"'ext_modules' option must be a list of Extension instances")
|
||||
"'ext_modules' option must be a list of Extension instances"
|
||||
)
|
||||
|
||||
for i, ext in enumerate(extensions):
|
||||
if isinstance(ext, Extension):
|
||||
continue # OK! (assume type-checking done
|
||||
# by Extension constructor)
|
||||
continue # OK! (assume type-checking done
|
||||
# by Extension constructor)
|
||||
|
||||
if not isinstance(ext, tuple) or len(ext) != 2:
|
||||
raise DistutilsSetupError(
|
||||
"each element of 'ext_modules' option must be an "
|
||||
"Extension instance or 2-tuple")
|
||||
"each element of 'ext_modules' option must be an "
|
||||
"Extension instance or 2-tuple"
|
||||
)
|
||||
|
||||
ext_name, build_info = ext
|
||||
|
||||
log.warn("old-style (ext_name, build_info) tuple found in "
|
||||
"ext_modules for extension '%s' "
|
||||
"-- please convert to Extension instance", ext_name)
|
||||
log.warn(
|
||||
"old-style (ext_name, build_info) tuple found in "
|
||||
"ext_modules for extension '%s' "
|
||||
"-- please convert to Extension instance",
|
||||
ext_name,
|
||||
)
|
||||
|
||||
if not (isinstance(ext_name, str) and
|
||||
extension_name_re.match(ext_name)):
|
||||
if not (isinstance(ext_name, str) and extension_name_re.match(ext_name)):
|
||||
raise DistutilsSetupError(
|
||||
"first element of each tuple in 'ext_modules' "
|
||||
"must be the extension name (a string)")
|
||||
"first element of each tuple in 'ext_modules' "
|
||||
"must be the extension name (a string)"
|
||||
)
|
||||
|
||||
if not isinstance(build_info, dict):
|
||||
raise DistutilsSetupError(
|
||||
"second element of each tuple in 'ext_modules' "
|
||||
"must be a dictionary (build info)")
|
||||
"second element of each tuple in 'ext_modules' "
|
||||
"must be a dictionary (build info)"
|
||||
)
|
||||
|
||||
# OK, the (ext_name, build_info) dict is type-safe: convert it
|
||||
# to an Extension instance.
|
||||
@@ -387,9 +398,14 @@ class build_ext(Command):
|
||||
|
||||
# Easy stuff: one-to-one mapping from dict elements to
|
||||
# instance attributes.
|
||||
for key in ('include_dirs', 'library_dirs', 'libraries',
|
||||
'extra_objects', 'extra_compile_args',
|
||||
'extra_link_args'):
|
||||
for key in (
|
||||
'include_dirs',
|
||||
'library_dirs',
|
||||
'libraries',
|
||||
'extra_objects',
|
||||
'extra_compile_args',
|
||||
'extra_link_args',
|
||||
):
|
||||
val = build_info.get(key)
|
||||
if val is not None:
|
||||
setattr(ext, key, val)
|
||||
@@ -397,8 +413,7 @@ class build_ext(Command):
|
||||
# Medium-easy stuff: same syntax/semantics, different names.
|
||||
ext.runtime_library_dirs = build_info.get('rpath')
|
||||
if 'def_file' in build_info:
|
||||
log.warn("'def_file' element of build info dict "
|
||||
"no longer supported")
|
||||
log.warn("'def_file' element of build info dict " "no longer supported")
|
||||
|
||||
# Non-trivial stuff: 'macros' split into 'define_macros'
|
||||
# and 'undef_macros'.
|
||||
@@ -409,8 +424,9 @@ class build_ext(Command):
|
||||
for macro in macros:
|
||||
if not (isinstance(macro, tuple) and len(macro) in (1, 2)):
|
||||
raise DistutilsSetupError(
|
||||
"'macros' element of build info dict "
|
||||
"must be 1- or 2-tuple")
|
||||
"'macros' element of build info dict "
|
||||
"must be 1- or 2-tuple"
|
||||
)
|
||||
if len(macro) == 1:
|
||||
ext.undef_macros.append(macro[0])
|
||||
elif len(macro) == 2:
|
||||
@@ -463,8 +479,9 @@ class build_ext(Command):
|
||||
return
|
||||
|
||||
with ThreadPoolExecutor(max_workers=workers) as executor:
|
||||
futures = [executor.submit(self.build_extension, ext)
|
||||
for ext in self.extensions]
|
||||
futures = [
|
||||
executor.submit(self.build_extension, ext) for ext in self.extensions
|
||||
]
|
||||
for ext, fut in zip(self.extensions, futures):
|
||||
with self._filter_build_errors(ext):
|
||||
fut.result()
|
||||
@@ -481,16 +498,16 @@ class build_ext(Command):
|
||||
except (CCompilerError, DistutilsError, CompileError) as e:
|
||||
if not ext.optional:
|
||||
raise
|
||||
self.warn('building extension "%s" failed: %s' %
|
||||
(ext.name, e))
|
||||
self.warn('building extension "{}" failed: {}'.format(ext.name, e))
|
||||
|
||||
def build_extension(self, ext):
|
||||
sources = ext.sources
|
||||
if sources is None or not isinstance(sources, (list, tuple)):
|
||||
raise DistutilsSetupError(
|
||||
"in 'ext_modules' option (extension '%s'), "
|
||||
"'sources' must be present and must be "
|
||||
"a list of source filenames" % ext.name)
|
||||
"in 'ext_modules' option (extension '%s'), "
|
||||
"'sources' must be present and must be "
|
||||
"a list of source filenames" % ext.name
|
||||
)
|
||||
# sort to make the resulting .so file build reproducible
|
||||
sources = sorted(sources)
|
||||
|
||||
@@ -527,13 +544,15 @@ class build_ext(Command):
|
||||
for undef in ext.undef_macros:
|
||||
macros.append((undef,))
|
||||
|
||||
objects = self.compiler.compile(sources,
|
||||
output_dir=self.build_temp,
|
||||
macros=macros,
|
||||
include_dirs=ext.include_dirs,
|
||||
debug=self.debug,
|
||||
extra_postargs=extra_args,
|
||||
depends=ext.depends)
|
||||
objects = self.compiler.compile(
|
||||
sources,
|
||||
output_dir=self.build_temp,
|
||||
macros=macros,
|
||||
include_dirs=ext.include_dirs,
|
||||
debug=self.debug,
|
||||
extra_postargs=extra_args,
|
||||
depends=ext.depends,
|
||||
)
|
||||
|
||||
# XXX outdated variable, kept here in case third-part code
|
||||
# needs it.
|
||||
@@ -550,7 +569,8 @@ class build_ext(Command):
|
||||
language = ext.language or self.compiler.detect_language(sources)
|
||||
|
||||
self.compiler.link_shared_object(
|
||||
objects, ext_path,
|
||||
objects,
|
||||
ext_path,
|
||||
libraries=self.get_libraries(ext),
|
||||
library_dirs=ext.library_dirs,
|
||||
runtime_library_dirs=ext.runtime_library_dirs,
|
||||
@@ -558,7 +578,8 @@ class build_ext(Command):
|
||||
export_symbols=self.get_export_symbols(ext),
|
||||
debug=self.debug,
|
||||
build_temp=self.build_temp,
|
||||
target_lang=language)
|
||||
target_lang=language,
|
||||
)
|
||||
|
||||
def swig_sources(self, sources, extension):
|
||||
"""Walk the list of source files in 'sources', looking for SWIG
|
||||
@@ -578,15 +599,18 @@ class build_ext(Command):
|
||||
if self.swig_cpp:
|
||||
log.warn("--swig-cpp is deprecated - use --swig-opts=-c++")
|
||||
|
||||
if self.swig_cpp or ('-c++' in self.swig_opts) or \
|
||||
('-c++' in extension.swig_opts):
|
||||
if (
|
||||
self.swig_cpp
|
||||
or ('-c++' in self.swig_opts)
|
||||
or ('-c++' in extension.swig_opts)
|
||||
):
|
||||
target_ext = '.cpp'
|
||||
else:
|
||||
target_ext = '.c'
|
||||
|
||||
for source in sources:
|
||||
(base, ext) = os.path.splitext(source)
|
||||
if ext == ".i": # SWIG interface file
|
||||
if ext == ".i": # SWIG interface file
|
||||
new_sources.append(base + '_wrap' + target_ext)
|
||||
swig_sources.append(source)
|
||||
swig_targets[source] = new_sources[-1]
|
||||
@@ -633,8 +657,9 @@ class build_ext(Command):
|
||||
return "swig.exe"
|
||||
else:
|
||||
raise DistutilsPlatformError(
|
||||
"I don't know how to find (much less run) SWIG "
|
||||
"on platform '%s'" % os.name)
|
||||
"I don't know how to find (much less run) SWIG "
|
||||
"on platform '%s'" % os.name
|
||||
)
|
||||
|
||||
# -- Name generators -----------------------------------------------
|
||||
# (extension names, filenames, whatever)
|
||||
@@ -652,7 +677,7 @@ class build_ext(Command):
|
||||
# no further work needed
|
||||
# returning :
|
||||
# build_dir/package/path/filename
|
||||
filename = os.path.join(*modpath[:-1]+[filename])
|
||||
filename = os.path.join(*modpath[:-1] + [filename])
|
||||
return os.path.join(self.build_lib, filename)
|
||||
|
||||
# the inplace option requires to find the package directory
|
||||
@@ -680,6 +705,7 @@ class build_ext(Command):
|
||||
"foo\bar.pyd").
|
||||
"""
|
||||
from distutils.sysconfig import get_config_var
|
||||
|
||||
ext_path = ext_name.split('.')
|
||||
ext_suffix = get_config_var('EXT_SUFFIX')
|
||||
return os.path.join(*ext_path) + ext_suffix
|
||||
@@ -705,7 +731,7 @@ class build_ext(Command):
|
||||
ext.export_symbols.append(initfunc_name)
|
||||
return ext.export_symbols
|
||||
|
||||
def get_libraries(self, ext):
|
||||
def get_libraries(self, ext): # noqa: C901
|
||||
"""Return the list of libraries to link against when building a
|
||||
shared extension. On most platforms, this is just 'ext.libraries';
|
||||
on Windows, we add the Python library (eg. python20.dll).
|
||||
@@ -717,12 +743,15 @@ class build_ext(Command):
|
||||
# Append '_d' to the python import library on debug builds.
|
||||
if sys.platform == "win32":
|
||||
from distutils._msvccompiler import MSVCCompiler
|
||||
|
||||
if not isinstance(self.compiler, MSVCCompiler):
|
||||
template = "python%d%d"
|
||||
if self.debug:
|
||||
template = template + '_d'
|
||||
pythonlib = (template %
|
||||
(sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff))
|
||||
pythonlib = template % (
|
||||
sys.hexversion >> 24,
|
||||
(sys.hexversion >> 16) & 0xFF,
|
||||
)
|
||||
# don't extend ext.libraries, it may be shared with other
|
||||
# extensions, it is a reference to the original list
|
||||
return ext.libraries + [pythonlib]
|
||||
@@ -736,6 +765,7 @@ class build_ext(Command):
|
||||
# Windows like MinGW) it is simply necessary that all symbols in
|
||||
# shared libraries are resolved at link time.
|
||||
from distutils.sysconfig import get_config_var
|
||||
|
||||
link_libpython = False
|
||||
if get_config_var('Py_ENABLE_SHARED'):
|
||||
# A native build on an Android device or on Cygwin
|
||||
|
||||
@@ -8,11 +8,12 @@ import sys
|
||||
import glob
|
||||
|
||||
from distutils.core import Command
|
||||
from distutils.errors import *
|
||||
from distutils.errors import DistutilsOptionError, DistutilsFileError
|
||||
from distutils.util import convert_path
|
||||
from distutils import log
|
||||
|
||||
class build_py (Command):
|
||||
|
||||
class build_py(Command):
|
||||
|
||||
description = "\"build\" pure Python modules (copy to build directory)"
|
||||
|
||||
@@ -20,14 +21,17 @@ class build_py (Command):
|
||||
('build-lib=', 'd', "directory to \"build\" (copy) to"),
|
||||
('compile', 'c', "compile .py to .pyc"),
|
||||
('no-compile', None, "don't compile .py files [default]"),
|
||||
('optimize=', 'O',
|
||||
"also compile with optimization: -O1 for \"python -O\", "
|
||||
"-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
|
||||
(
|
||||
'optimize=',
|
||||
'O',
|
||||
"also compile with optimization: -O1 for \"python -O\", "
|
||||
"-O2 for \"python -OO\", and -O0 to disable [default: -O0]",
|
||||
),
|
||||
('force', 'f', "forcibly build everything (ignore file timestamps)"),
|
||||
]
|
||||
]
|
||||
|
||||
boolean_options = ['compile', 'force']
|
||||
negative_opt = {'no-compile' : 'compile'}
|
||||
negative_opt = {'no-compile': 'compile'}
|
||||
|
||||
def initialize_options(self):
|
||||
self.build_lib = None
|
||||
@@ -40,9 +44,9 @@ class build_py (Command):
|
||||
self.force = None
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('build',
|
||||
('build_lib', 'build_lib'),
|
||||
('force', 'force'))
|
||||
self.set_undefined_options(
|
||||
'build', ('build_lib', 'build_lib'), ('force', 'force')
|
||||
)
|
||||
|
||||
# Get the distribution options that are aliases for build_py
|
||||
# options -- list of packages and list of modules.
|
||||
@@ -109,42 +113,42 @@ class build_py (Command):
|
||||
# Length of path to strip from found files
|
||||
plen = 0
|
||||
if src_dir:
|
||||
plen = len(src_dir)+1
|
||||
plen = len(src_dir) + 1
|
||||
|
||||
# Strip directory from globbed filenames
|
||||
filenames = [
|
||||
file[plen:] for file in self.find_data_files(package, src_dir)
|
||||
]
|
||||
filenames = [file[plen:] for file in self.find_data_files(package, src_dir)]
|
||||
data.append((package, src_dir, build_dir, filenames))
|
||||
return data
|
||||
|
||||
def find_data_files(self, package, src_dir):
|
||||
"""Return filenames for package's data files in 'src_dir'"""
|
||||
globs = (self.package_data.get('', [])
|
||||
+ self.package_data.get(package, []))
|
||||
globs = self.package_data.get('', []) + self.package_data.get(package, [])
|
||||
files = []
|
||||
for pattern in globs:
|
||||
# Each pattern has to be converted to a platform-specific path
|
||||
filelist = glob.glob(os.path.join(glob.escape(src_dir), convert_path(pattern)))
|
||||
filelist = glob.glob(
|
||||
os.path.join(glob.escape(src_dir), convert_path(pattern))
|
||||
)
|
||||
# Files that match more than one pattern are only added once
|
||||
files.extend([fn for fn in filelist if fn not in files
|
||||
and os.path.isfile(fn)])
|
||||
files.extend(
|
||||
[fn for fn in filelist if fn not in files and os.path.isfile(fn)]
|
||||
)
|
||||
return files
|
||||
|
||||
def build_package_data(self):
|
||||
"""Copy data files into build directory"""
|
||||
lastdir = None
|
||||
for package, src_dir, build_dir, filenames in self.data_files:
|
||||
for filename in filenames:
|
||||
target = os.path.join(build_dir, filename)
|
||||
self.mkpath(os.path.dirname(target))
|
||||
self.copy_file(os.path.join(src_dir, filename), target,
|
||||
preserve_mode=False)
|
||||
self.copy_file(
|
||||
os.path.join(src_dir, filename), target, preserve_mode=False
|
||||
)
|
||||
|
||||
def get_package_dir(self, package):
|
||||
"""Return the directory, relative to the top of the source
|
||||
distribution, where package 'package' should be found
|
||||
(at least according to the 'package_dir' option, if any)."""
|
||||
distribution, where package 'package' should be found
|
||||
(at least according to the 'package_dir' option, if any)."""
|
||||
path = package.split('.')
|
||||
|
||||
if not self.package_dir:
|
||||
@@ -188,20 +192,19 @@ class build_py (Command):
|
||||
if package_dir != "":
|
||||
if not os.path.exists(package_dir):
|
||||
raise DistutilsFileError(
|
||||
"package directory '%s' does not exist" % package_dir)
|
||||
"package directory '%s' does not exist" % package_dir
|
||||
)
|
||||
if not os.path.isdir(package_dir):
|
||||
raise DistutilsFileError(
|
||||
"supposed package directory '%s' exists, "
|
||||
"but is not a directory" % package_dir)
|
||||
"supposed package directory '%s' exists, "
|
||||
"but is not a directory" % package_dir
|
||||
)
|
||||
|
||||
# Require __init__.py for all but the "root package"
|
||||
# Directories without __init__.py are namespace packages (PEP 420).
|
||||
if package:
|
||||
init_py = os.path.join(package_dir, "__init__.py")
|
||||
if os.path.isfile(init_py):
|
||||
return init_py
|
||||
else:
|
||||
log.warn(("package init file '%s' not found " +
|
||||
"(or not a regular file)"), init_py)
|
||||
|
||||
# Either not in a package at all (__init__.py not expected), or
|
||||
# __init__.py doesn't exist -- so don't return the filename.
|
||||
@@ -313,17 +316,21 @@ class build_py (Command):
|
||||
outputs.append(filename)
|
||||
if include_bytecode:
|
||||
if self.compile:
|
||||
outputs.append(importlib.util.cache_from_source(
|
||||
filename, optimization=''))
|
||||
outputs.append(
|
||||
importlib.util.cache_from_source(filename, optimization='')
|
||||
)
|
||||
if self.optimize > 0:
|
||||
outputs.append(importlib.util.cache_from_source(
|
||||
filename, optimization=self.optimize))
|
||||
outputs.append(
|
||||
importlib.util.cache_from_source(
|
||||
filename, optimization=self.optimize
|
||||
)
|
||||
)
|
||||
|
||||
outputs += [
|
||||
os.path.join(build_dir, filename)
|
||||
for package, src_dir, build_dir, filenames in self.data_files
|
||||
for filename in filenames
|
||||
]
|
||||
]
|
||||
|
||||
return outputs
|
||||
|
||||
@@ -332,7 +339,8 @@ class build_py (Command):
|
||||
package = package.split('.')
|
||||
elif not isinstance(package, (list, tuple)):
|
||||
raise TypeError(
|
||||
"'package' must be a string (dot-separated), list, or tuple")
|
||||
"'package' must be a string (dot-separated), list, or tuple"
|
||||
)
|
||||
|
||||
# Now put the module source file into the "build" area -- this is
|
||||
# easy, we just copy it somewhere under self.build_lib (the build
|
||||
@@ -377,6 +385,7 @@ class build_py (Command):
|
||||
return
|
||||
|
||||
from distutils.util import byte_compile
|
||||
|
||||
prefix = self.build_lib
|
||||
if prefix[-1] != os.sep:
|
||||
prefix = prefix + os.sep
|
||||
@@ -385,8 +394,14 @@ class build_py (Command):
|
||||
# method of the "install_lib" command, except for the determination
|
||||
# of the 'prefix' string. Hmmm.
|
||||
if self.compile:
|
||||
byte_compile(files, optimize=0,
|
||||
force=self.force, prefix=prefix, dry_run=self.dry_run)
|
||||
byte_compile(
|
||||
files, optimize=0, force=self.force, prefix=prefix, dry_run=self.dry_run
|
||||
)
|
||||
if self.optimize > 0:
|
||||
byte_compile(files, optimize=self.optimize,
|
||||
force=self.force, prefix=prefix, dry_run=self.dry_run)
|
||||
byte_compile(
|
||||
files,
|
||||
optimize=self.optimize,
|
||||
force=self.force,
|
||||
prefix=prefix,
|
||||
dry_run=self.dry_run,
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -22,23 +29,23 @@ class build_scripts(Command):
|
||||
('build-dir=', 'd', "directory to \"build\" (copy) to"),
|
||||
('force', 'f', "forcibly build everything (ignore file timestamps"),
|
||||
('executable=', 'e', "specify final destination interpreter path"),
|
||||
]
|
||||
]
|
||||
|
||||
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',
|
||||
('build_scripts', 'build_dir'),
|
||||
('force', 'force'),
|
||||
('executable', 'executable'))
|
||||
self.set_undefined_options(
|
||||
'build',
|
||||
('build_scripts', 'build_dir'),
|
||||
('force', 'force'),
|
||||
('executable', 'executable'),
|
||||
)
|
||||
self.scripts = self.distribution.scripts
|
||||
|
||||
def get_source_files(self):
|
||||
@@ -49,104 +56,118 @@ 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)
|
||||
self._copy_script(script, outfiles, updated_files)
|
||||
|
||||
if not self.force and not newer(script, outfile):
|
||||
log.debug("not copying %s (up-to-date)", script)
|
||||
continue
|
||||
self._change_modes(outfiles)
|
||||
|
||||
# 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?
|
||||
return outfiles, updated_files
|
||||
|
||||
def _copy_script(self, script, outfiles, updated_files): # noqa: C901
|
||||
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)
|
||||
)
|
||||
|
||||
@@ -2,46 +2,56 @@
|
||||
|
||||
Implements the Distutils 'check' command.
|
||||
"""
|
||||
import contextlib
|
||||
|
||||
from distutils.core import Command
|
||||
from distutils.errors import DistutilsSetupError
|
||||
|
||||
try:
|
||||
# docutils is installed
|
||||
from docutils.utils import Reporter
|
||||
from docutils.parsers.rst import Parser
|
||||
from docutils import frontend
|
||||
from docutils import nodes
|
||||
with contextlib.suppress(ImportError):
|
||||
import docutils.utils
|
||||
import docutils.parsers.rst
|
||||
import docutils.frontend
|
||||
import docutils.nodes
|
||||
|
||||
class SilentReporter(Reporter):
|
||||
|
||||
def __init__(self, source, report_level, halt_level, stream=None,
|
||||
debug=0, encoding='ascii', error_handler='replace'):
|
||||
class SilentReporter(docutils.utils.Reporter):
|
||||
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,
|
||||
debug, encoding, error_handler)
|
||||
super().__init__(
|
||||
source, report_level, halt_level, stream, debug, encoding, error_handler
|
||||
)
|
||||
|
||||
def system_message(self, level, message, *children, **kwargs):
|
||||
self.messages.append((level, message, children, kwargs))
|
||||
return nodes.system_message(message, level=level,
|
||||
type=self.levels[level],
|
||||
*children, **kwargs)
|
||||
return docutils.nodes.system_message(
|
||||
message, level=level, type=self.levels[level], *children, **kwargs
|
||||
)
|
||||
|
||||
HAS_DOCUTILS = True
|
||||
except Exception:
|
||||
# Catch all exceptions because exceptions besides ImportError probably
|
||||
# indicate that docutils is not ported to Py3k.
|
||||
HAS_DOCUTILS = False
|
||||
|
||||
class check(Command):
|
||||
"""This command checks the meta-data of the package.
|
||||
"""
|
||||
description = ("perform some checks on the package")
|
||||
user_options = [('metadata', 'm', 'Verify meta-data'),
|
||||
('restructuredtext', 'r',
|
||||
('Checks if long string meta-data syntax '
|
||||
'are reStructuredText-compliant')),
|
||||
('strict', 's',
|
||||
'Will exit with an error if a check fails')]
|
||||
"""This command checks the meta-data of the package."""
|
||||
|
||||
description = "perform some checks on the package"
|
||||
user_options = [
|
||||
('metadata', 'm', 'Verify meta-data'),
|
||||
(
|
||||
'restructuredtext',
|
||||
'r',
|
||||
(
|
||||
'Checks if long string meta-data syntax '
|
||||
'are reStructuredText-compliant'
|
||||
),
|
||||
),
|
||||
('strict', 's', 'Will exit with an error if a check fails'),
|
||||
]
|
||||
|
||||
boolean_options = ['metadata', 'restructuredtext', 'strict']
|
||||
|
||||
@@ -66,8 +76,11 @@ class check(Command):
|
||||
if self.metadata:
|
||||
self.check_metadata()
|
||||
if self.restructuredtext:
|
||||
if HAS_DOCUTILS:
|
||||
self.check_restructuredtext()
|
||||
if 'docutils' in globals():
|
||||
try:
|
||||
self.check_restructuredtext()
|
||||
except TypeError as exc:
|
||||
raise DistutilsSetupError(str(exc))
|
||||
elif self.strict:
|
||||
raise DistutilsSetupError('The docutils package is needed.')
|
||||
|
||||
@@ -80,34 +93,19 @@ class check(Command):
|
||||
"""Ensures that all required elements of meta-data are supplied.
|
||||
|
||||
Required fields:
|
||||
name, version, URL
|
||||
|
||||
Recommended fields:
|
||||
(author and author_email) or (maintainer and maintainer_email))
|
||||
name, version
|
||||
|
||||
Warns if any are missing.
|
||||
"""
|
||||
metadata = self.distribution.metadata
|
||||
|
||||
missing = []
|
||||
for attr in ('name', 'version', 'url'):
|
||||
if not (hasattr(metadata, attr) and getattr(metadata, attr)):
|
||||
for attr in 'name', 'version':
|
||||
if not getattr(metadata, attr, None):
|
||||
missing.append(attr)
|
||||
|
||||
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:
|
||||
self.warn("missing meta-data: either (author and author_email) " +
|
||||
"or (maintainer and maintainer_email) " +
|
||||
"should be supplied")
|
||||
self.warn("missing required meta-data: %s" % ', '.join(missing))
|
||||
|
||||
def check_restructuredtext(self):
|
||||
"""Checks if the long string fields are reST-compliant."""
|
||||
@@ -117,32 +115,37 @@ class check(Command):
|
||||
if line is None:
|
||||
warning = warning[1]
|
||||
else:
|
||||
warning = '%s (line %s)' % (warning[1], line)
|
||||
warning = '{} (line {})'.format(warning[1], line)
|
||||
self.warn(warning)
|
||||
|
||||
def _check_rst_data(self, data):
|
||||
"""Returns warnings when the provided data doesn't compile."""
|
||||
# the include and csv_table directives need this to be a path
|
||||
source_path = self.distribution.script_name or 'setup.py'
|
||||
parser = Parser()
|
||||
settings = frontend.OptionParser(components=(Parser,)).get_default_values()
|
||||
parser = docutils.parsers.rst.Parser()
|
||||
settings = docutils.frontend.OptionParser(
|
||||
components=(docutils.parsers.rst.Parser,)
|
||||
).get_default_values()
|
||||
settings.tab_width = 4
|
||||
settings.pep_references = None
|
||||
settings.rfc_references = None
|
||||
reporter = SilentReporter(source_path,
|
||||
settings.report_level,
|
||||
settings.halt_level,
|
||||
stream=settings.warning_stream,
|
||||
debug=settings.debug,
|
||||
encoding=settings.error_encoding,
|
||||
error_handler=settings.error_encoding_error_handler)
|
||||
reporter = SilentReporter(
|
||||
source_path,
|
||||
settings.report_level,
|
||||
settings.halt_level,
|
||||
stream=settings.warning_stream,
|
||||
debug=settings.debug,
|
||||
encoding=settings.error_encoding,
|
||||
error_handler=settings.error_encoding_error_handler,
|
||||
)
|
||||
|
||||
document = nodes.document(settings, reporter, source=source_path)
|
||||
document = docutils.nodes.document(settings, reporter, source=source_path)
|
||||
document.note_source(source_path, -1)
|
||||
try:
|
||||
parser.parse(data, document)
|
||||
except AttributeError as e:
|
||||
reporter.messages.append(
|
||||
(-1, 'Could not finish the parsing: %s.' % e, '', {}))
|
||||
(-1, 'Could not finish the parsing: %s.' % e, '', {})
|
||||
)
|
||||
|
||||
return reporter.messages
|
||||
|
||||
@@ -9,22 +9,25 @@ from distutils.core import Command
|
||||
from distutils.dir_util import remove_tree
|
||||
from distutils import log
|
||||
|
||||
|
||||
class clean(Command):
|
||||
|
||||
description = "clean up temporary files from 'build' command"
|
||||
user_options = [
|
||||
('build-base=', 'b',
|
||||
"base build directory (default: 'build.build-base')"),
|
||||
('build-lib=', None,
|
||||
"build directory for all modules (default: 'build.build-lib')"),
|
||||
('build-temp=', 't',
|
||||
"temporary build directory (default: 'build.build-temp')"),
|
||||
('build-scripts=', None,
|
||||
"build directory for scripts (default: 'build.build-scripts')"),
|
||||
('bdist-base=', None,
|
||||
"temporary directory for built distributions"),
|
||||
('all', 'a',
|
||||
"remove all build output, not just temporary by-products")
|
||||
('build-base=', 'b', "base build directory (default: 'build.build-base')"),
|
||||
(
|
||||
'build-lib=',
|
||||
None,
|
||||
"build directory for all modules (default: 'build.build-lib')",
|
||||
),
|
||||
('build-temp=', 't', "temporary build directory (default: 'build.build-temp')"),
|
||||
(
|
||||
'build-scripts=',
|
||||
None,
|
||||
"build directory for scripts (default: 'build.build-scripts')",
|
||||
),
|
||||
('bdist-base=', None, "temporary directory for built distributions"),
|
||||
('all', 'a', "remove all build output, not just temporary by-products"),
|
||||
]
|
||||
|
||||
boolean_options = ['all']
|
||||
@@ -38,13 +41,14 @@ class clean(Command):
|
||||
self.all = None
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('build',
|
||||
('build_base', 'build_base'),
|
||||
('build_lib', 'build_lib'),
|
||||
('build_scripts', 'build_scripts'),
|
||||
('build_temp', 'build_temp'))
|
||||
self.set_undefined_options('bdist',
|
||||
('bdist_base', 'bdist_base'))
|
||||
self.set_undefined_options(
|
||||
'build',
|
||||
('build_base', 'build_base'),
|
||||
('build_lib', 'build_lib'),
|
||||
('build_scripts', 'build_scripts'),
|
||||
('build_temp', 'build_temp'),
|
||||
)
|
||||
self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
|
||||
|
||||
def run(self):
|
||||
# remove the build/temp.<plat> directory (unless it's already
|
||||
@@ -52,19 +56,15 @@ class clean(Command):
|
||||
if os.path.exists(self.build_temp):
|
||||
remove_tree(self.build_temp, dry_run=self.dry_run)
|
||||
else:
|
||||
log.debug("'%s' does not exist -- can't clean it",
|
||||
self.build_temp)
|
||||
log.debug("'%s' does not exist -- can't clean it", self.build_temp)
|
||||
|
||||
if self.all:
|
||||
# remove build directories
|
||||
for directory in (self.build_lib,
|
||||
self.bdist_base,
|
||||
self.build_scripts):
|
||||
for directory in (self.build_lib, self.bdist_base, self.build_scripts):
|
||||
if os.path.exists(directory):
|
||||
remove_tree(directory, dry_run=self.dry_run)
|
||||
else:
|
||||
log.warn("'%s' does not exist -- can't clean it",
|
||||
directory)
|
||||
log.warn("'%s' does not exist -- can't clean it", directory)
|
||||
|
||||
# just for the heck of it, try to remove the base build directory:
|
||||
# we might have emptied it right now, but if not we don't care
|
||||
|
||||
@@ -9,7 +9,8 @@ configure-like tasks: "try to compile this C code", or "figure out where
|
||||
this header file lives".
|
||||
"""
|
||||
|
||||
import os, re
|
||||
import os
|
||||
import re
|
||||
|
||||
from distutils.core import Command
|
||||
from distutils.errors import DistutilsExecError
|
||||
@@ -18,32 +19,26 @@ from distutils import log
|
||||
|
||||
LANG_EXT = {"c": ".c", "c++": ".cxx"}
|
||||
|
||||
|
||||
class config(Command):
|
||||
|
||||
description = "prepare to build"
|
||||
|
||||
user_options = [
|
||||
('compiler=', None,
|
||||
"specify the compiler type"),
|
||||
('cc=', None,
|
||||
"specify the compiler executable"),
|
||||
('include-dirs=', 'I',
|
||||
"list of directories to search for header files"),
|
||||
('define=', 'D',
|
||||
"C preprocessor macros to define"),
|
||||
('undef=', 'U',
|
||||
"C preprocessor macros to undefine"),
|
||||
('libraries=', 'l',
|
||||
"external C libraries to link with"),
|
||||
('library-dirs=', 'L',
|
||||
"directories to search for external C libraries"),
|
||||
|
||||
('noisy', None,
|
||||
"show every action (compile, link, run, ...) taken"),
|
||||
('dump-source', None,
|
||||
"dump generated source files before attempting to compile them"),
|
||||
]
|
||||
|
||||
('compiler=', None, "specify the compiler type"),
|
||||
('cc=', None, "specify the compiler executable"),
|
||||
('include-dirs=', 'I', "list of directories to search for header files"),
|
||||
('define=', 'D', "C preprocessor macros to define"),
|
||||
('undef=', 'U', "C preprocessor macros to undefine"),
|
||||
('libraries=', 'l', "external C libraries to link with"),
|
||||
('library-dirs=', 'L', "directories to search for external C libraries"),
|
||||
('noisy', None, "show every action (compile, link, run, ...) taken"),
|
||||
(
|
||||
'dump-source',
|
||||
None,
|
||||
"dump generated source files before attempting to compile them",
|
||||
),
|
||||
]
|
||||
|
||||
# The three standard command methods: since the "config" command
|
||||
# does nothing by default, these are empty.
|
||||
@@ -93,9 +88,11 @@ class config(Command):
|
||||
# We do this late, and only on-demand, because this is an expensive
|
||||
# import.
|
||||
from distutils.ccompiler import CCompiler, new_compiler
|
||||
|
||||
if not isinstance(self.compiler, CCompiler):
|
||||
self.compiler = new_compiler(compiler=self.compiler,
|
||||
dry_run=self.dry_run, force=1)
|
||||
self.compiler = new_compiler(
|
||||
compiler=self.compiler, dry_run=self.dry_run, force=1
|
||||
)
|
||||
customize_compiler(self.compiler)
|
||||
if self.include_dirs:
|
||||
self.compiler.set_include_dirs(self.include_dirs)
|
||||
@@ -132,14 +129,16 @@ class config(Command):
|
||||
self.compiler.compile([src], include_dirs=include_dirs)
|
||||
return (src, obj)
|
||||
|
||||
def _link(self, body, headers, include_dirs, libraries, library_dirs,
|
||||
lang):
|
||||
def _link(self, body, headers, include_dirs, libraries, library_dirs, lang):
|
||||
(src, obj) = self._compile(body, headers, include_dirs, lang)
|
||||
prog = os.path.splitext(os.path.basename(src))[0]
|
||||
self.compiler.link_executable([obj], prog,
|
||||
libraries=libraries,
|
||||
library_dirs=library_dirs,
|
||||
target_lang=lang)
|
||||
self.compiler.link_executable(
|
||||
[obj],
|
||||
prog,
|
||||
libraries=libraries,
|
||||
library_dirs=library_dirs,
|
||||
target_lang=lang,
|
||||
)
|
||||
|
||||
if self.compiler.exe_extension is not None:
|
||||
prog = prog + self.compiler.exe_extension
|
||||
@@ -158,7 +157,6 @@ class config(Command):
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
# XXX these ignore the dry-run flag: what to do, what to do? even if
|
||||
# you want a dry-run build, you still need some sort of configuration
|
||||
# info. My inclination is to make it up to the real config command to
|
||||
@@ -177,6 +175,7 @@ class config(Command):
|
||||
('body' probably isn't of much use, but what the heck.)
|
||||
"""
|
||||
from distutils.ccompiler import CompileError
|
||||
|
||||
self._check_compiler()
|
||||
ok = True
|
||||
try:
|
||||
@@ -187,8 +186,7 @@ class config(Command):
|
||||
self._clean()
|
||||
return ok
|
||||
|
||||
def search_cpp(self, pattern, body=None, headers=None, include_dirs=None,
|
||||
lang="c"):
|
||||
def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, lang="c"):
|
||||
"""Construct a source file (just like 'try_cpp()'), run it through
|
||||
the preprocessor, and return true if any line of the output matches
|
||||
'pattern'. 'pattern' should either be a compiled regex object or a
|
||||
@@ -220,6 +218,7 @@ class config(Command):
|
||||
Return true on success, false otherwise.
|
||||
"""
|
||||
from distutils.ccompiler import CompileError
|
||||
|
||||
self._check_compiler()
|
||||
try:
|
||||
self._compile(body, headers, include_dirs, lang)
|
||||
@@ -231,17 +230,24 @@ class config(Command):
|
||||
self._clean()
|
||||
return ok
|
||||
|
||||
def try_link(self, body, headers=None, include_dirs=None, libraries=None,
|
||||
library_dirs=None, lang="c"):
|
||||
def try_link(
|
||||
self,
|
||||
body,
|
||||
headers=None,
|
||||
include_dirs=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
lang="c",
|
||||
):
|
||||
"""Try to compile and link a source file, built from 'body' and
|
||||
'headers', to executable form. Return true on success, false
|
||||
otherwise.
|
||||
"""
|
||||
from distutils.ccompiler import CompileError, LinkError
|
||||
|
||||
self._check_compiler()
|
||||
try:
|
||||
self._link(body, headers, include_dirs,
|
||||
libraries, library_dirs, lang)
|
||||
self._link(body, headers, include_dirs, libraries, library_dirs, lang)
|
||||
ok = True
|
||||
except (CompileError, LinkError):
|
||||
ok = False
|
||||
@@ -250,17 +256,26 @@ class config(Command):
|
||||
self._clean()
|
||||
return ok
|
||||
|
||||
def try_run(self, body, headers=None, include_dirs=None, libraries=None,
|
||||
library_dirs=None, lang="c"):
|
||||
def try_run(
|
||||
self,
|
||||
body,
|
||||
headers=None,
|
||||
include_dirs=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
lang="c",
|
||||
):
|
||||
"""Try to compile, link to an executable, and run a program
|
||||
built from 'body' and 'headers'. Return true on success, false
|
||||
otherwise.
|
||||
"""
|
||||
from distutils.ccompiler import CompileError, LinkError
|
||||
|
||||
self._check_compiler()
|
||||
try:
|
||||
src, obj, exe = self._link(body, headers, include_dirs,
|
||||
libraries, library_dirs, lang)
|
||||
src, obj, exe = self._link(
|
||||
body, headers, include_dirs, libraries, library_dirs, lang
|
||||
)
|
||||
self.spawn([exe])
|
||||
ok = True
|
||||
except (CompileError, LinkError, DistutilsExecError):
|
||||
@@ -270,13 +285,20 @@ class config(Command):
|
||||
self._clean()
|
||||
return ok
|
||||
|
||||
|
||||
# -- High-level methods --------------------------------------------
|
||||
# (these are the ones that are actually likely to be useful
|
||||
# when implementing a real-world config command!)
|
||||
|
||||
def check_func(self, func, headers=None, include_dirs=None,
|
||||
libraries=None, library_dirs=None, decl=0, call=0):
|
||||
def check_func(
|
||||
self,
|
||||
func,
|
||||
headers=None,
|
||||
include_dirs=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
decl=0,
|
||||
call=0,
|
||||
):
|
||||
"""Determine if function 'func' is available by constructing a
|
||||
source file that refers to 'func', and compiles and links it.
|
||||
If everything succeeds, returns true; otherwise returns false.
|
||||
@@ -302,11 +324,16 @@ class config(Command):
|
||||
body.append("}")
|
||||
body = "\n".join(body) + "\n"
|
||||
|
||||
return self.try_link(body, headers, include_dirs,
|
||||
libraries, library_dirs)
|
||||
return self.try_link(body, headers, include_dirs, libraries, library_dirs)
|
||||
|
||||
def check_lib(self, library, library_dirs=None, headers=None,
|
||||
include_dirs=None, other_libraries=[]):
|
||||
def check_lib(
|
||||
self,
|
||||
library,
|
||||
library_dirs=None,
|
||||
headers=None,
|
||||
include_dirs=None,
|
||||
other_libraries=[],
|
||||
):
|
||||
"""Determine if 'library' is available to be linked against,
|
||||
without actually checking that any particular symbols are provided
|
||||
by it. 'headers' will be used in constructing the source file to
|
||||
@@ -316,17 +343,23 @@ class config(Command):
|
||||
has symbols that depend on other libraries.
|
||||
"""
|
||||
self._check_compiler()
|
||||
return self.try_link("int main (void) { }", headers, include_dirs,
|
||||
[library] + other_libraries, library_dirs)
|
||||
return self.try_link(
|
||||
"int main (void) { }",
|
||||
headers,
|
||||
include_dirs,
|
||||
[library] + other_libraries,
|
||||
library_dirs,
|
||||
)
|
||||
|
||||
def check_header(self, header, include_dirs=None, library_dirs=None,
|
||||
lang="c"):
|
||||
def check_header(self, header, include_dirs=None, library_dirs=None, lang="c"):
|
||||
"""Determine if the system header file named by 'header_file'
|
||||
exists and can be found by the preprocessor; return true if so,
|
||||
false otherwise.
|
||||
"""
|
||||
return self.try_cpp(body="/* No body */", headers=[header],
|
||||
include_dirs=include_dirs)
|
||||
return self.try_cpp(
|
||||
body="/* No body */", headers=[header], include_dirs=include_dirs
|
||||
)
|
||||
|
||||
|
||||
def dump_file(filename, head=None):
|
||||
"""Dumps a file content into log.info.
|
||||
|
||||
@@ -4,79 +4,91 @@ Implements the Distutils 'install' command."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import contextlib
|
||||
import sysconfig
|
||||
import itertools
|
||||
|
||||
from distutils import log
|
||||
from distutils.core import Command
|
||||
from distutils.debug import DEBUG
|
||||
from distutils.sysconfig import get_config_vars
|
||||
from distutils.errors import DistutilsPlatformError
|
||||
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 distutils.errors import DistutilsOptionError, DistutilsPlatformError
|
||||
from . import _framework_compat as fw
|
||||
from .. import _collections
|
||||
|
||||
from site import USER_BASE
|
||||
from site import USER_SITE
|
||||
|
||||
HAS_USER_SITE = True
|
||||
|
||||
WINDOWS_SCHEME = {
|
||||
'purelib': '$base/Lib/site-packages',
|
||||
'platlib': '$base/Lib/site-packages',
|
||||
'headers': '$base/Include/$dist_name',
|
||||
'scripts': '$base/Scripts',
|
||||
'data' : '$base',
|
||||
'purelib': '{base}/Lib/site-packages',
|
||||
'platlib': '{base}/Lib/site-packages',
|
||||
'headers': '{base}/Include/{dist_name}',
|
||||
'scripts': '{base}/Scripts',
|
||||
'data': '{base}',
|
||||
}
|
||||
|
||||
INSTALL_SCHEMES = {
|
||||
'unix_prefix': {
|
||||
'purelib': '$base/lib/python$py_version_short/site-packages',
|
||||
'platlib': '$platbase/$platlibdir/python$py_version_short/site-packages',
|
||||
'headers': '$base/include/python$py_version_short$abiflags/$dist_name',
|
||||
'scripts': '$base/bin',
|
||||
'data' : '$base',
|
||||
},
|
||||
'unix_home': {
|
||||
'purelib': '$base/lib/python',
|
||||
'platlib': '$base/$platlibdir/python',
|
||||
'headers': '$base/include/python/$dist_name',
|
||||
'scripts': '$base/bin',
|
||||
'data' : '$base',
|
||||
},
|
||||
'posix_prefix': {
|
||||
'purelib': '{base}/lib/{implementation_lower}{py_version_short}/site-packages',
|
||||
'platlib': '{platbase}/{platlibdir}/{implementation_lower}'
|
||||
'{py_version_short}/site-packages',
|
||||
'headers': '{base}/include/{implementation_lower}'
|
||||
'{py_version_short}{abiflags}/{dist_name}',
|
||||
'scripts': '{base}/bin',
|
||||
'data': '{base}',
|
||||
},
|
||||
'posix_home': {
|
||||
'purelib': '{base}/lib/{implementation_lower}',
|
||||
'platlib': '{base}/{platlibdir}/{implementation_lower}',
|
||||
'headers': '{base}/include/{implementation_lower}/{dist_name}',
|
||||
'scripts': '{base}/bin',
|
||||
'data': '{base}',
|
||||
},
|
||||
'nt': WINDOWS_SCHEME,
|
||||
'pypy': {
|
||||
'purelib': '$base/site-packages',
|
||||
'platlib': '$base/site-packages',
|
||||
'headers': '$base/include/$dist_name',
|
||||
'scripts': '$base/bin',
|
||||
'data' : '$base',
|
||||
},
|
||||
'purelib': '{base}/site-packages',
|
||||
'platlib': '{base}/site-packages',
|
||||
'headers': '{base}/include/{dist_name}',
|
||||
'scripts': '{base}/bin',
|
||||
'data': '{base}',
|
||||
},
|
||||
'pypy_nt': {
|
||||
'purelib': '$base/site-packages',
|
||||
'platlib': '$base/site-packages',
|
||||
'headers': '$base/include/$dist_name',
|
||||
'scripts': '$base/Scripts',
|
||||
'data' : '$base',
|
||||
},
|
||||
}
|
||||
'purelib': '{base}/site-packages',
|
||||
'platlib': '{base}/site-packages',
|
||||
'headers': '{base}/include/{dist_name}',
|
||||
'scripts': '{base}/Scripts',
|
||||
'data': '{base}',
|
||||
},
|
||||
}
|
||||
|
||||
# user site schemes
|
||||
if HAS_USER_SITE:
|
||||
INSTALL_SCHEMES['nt_user'] = {
|
||||
'purelib': '$usersite',
|
||||
'platlib': '$usersite',
|
||||
'headers': '$userbase/Python$py_version_nodot/Include/$dist_name',
|
||||
'scripts': '$userbase/Python$py_version_nodot/Scripts',
|
||||
'data' : '$userbase',
|
||||
}
|
||||
'purelib': '{usersite}',
|
||||
'platlib': '{usersite}',
|
||||
'headers': '{userbase}/{implementation}{py_version_nodot_plat}'
|
||||
'/Include/{dist_name}',
|
||||
'scripts': '{userbase}/{implementation}{py_version_nodot_plat}/Scripts',
|
||||
'data': '{userbase}',
|
||||
}
|
||||
|
||||
INSTALL_SCHEMES['posix_user'] = {
|
||||
'purelib': '{usersite}',
|
||||
'platlib': '{usersite}',
|
||||
'headers': '{userbase}/include/{implementation_lower}'
|
||||
'{py_version_short}{abiflags}/{dist_name}',
|
||||
'scripts': '{userbase}/bin',
|
||||
'data': '{userbase}',
|
||||
}
|
||||
|
||||
|
||||
INSTALL_SCHEMES.update(fw.schemes)
|
||||
|
||||
INSTALL_SCHEMES['unix_user'] = {
|
||||
'purelib': '$usersite',
|
||||
'platlib': '$usersite',
|
||||
'headers':
|
||||
'$userbase/include/python$py_version_short$abiflags/$dist_name',
|
||||
'scripts': '$userbase/bin',
|
||||
'data' : '$userbase',
|
||||
}
|
||||
|
||||
# The keys to an installation scheme; if any new types of files are to be
|
||||
# installed, be sure to add an entry to every installation scheme above,
|
||||
@@ -84,78 +96,162 @@ if HAS_USER_SITE:
|
||||
SCHEME_KEYS = ('purelib', 'platlib', 'headers', 'scripts', 'data')
|
||||
|
||||
|
||||
def _load_sysconfig_schemes():
|
||||
with contextlib.suppress(AttributeError):
|
||||
return {
|
||||
scheme: sysconfig.get_paths(scheme, expand=False)
|
||||
for scheme in sysconfig.get_scheme_names()
|
||||
}
|
||||
|
||||
|
||||
def _load_schemes():
|
||||
"""
|
||||
Extend default schemes with schemes from sysconfig.
|
||||
"""
|
||||
|
||||
sysconfig_schemes = _load_sysconfig_schemes() or {}
|
||||
|
||||
return {
|
||||
scheme: {
|
||||
**INSTALL_SCHEMES.get(scheme, {}),
|
||||
**sysconfig_schemes.get(scheme, {}),
|
||||
}
|
||||
for scheme in set(itertools.chain(INSTALL_SCHEMES, sysconfig_schemes))
|
||||
}
|
||||
|
||||
|
||||
def _get_implementation():
|
||||
if hasattr(sys, 'pypy_version_info'):
|
||||
return 'PyPy'
|
||||
else:
|
||||
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 = fw.scheme(_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"
|
||||
|
||||
user_options = [
|
||||
# Select installation scheme and set base director(y|ies)
|
||||
('prefix=', None,
|
||||
"installation prefix"),
|
||||
('exec-prefix=', None,
|
||||
"(Unix only) prefix for platform-specific files"),
|
||||
('home=', None,
|
||||
"(Unix only) home directory to install under"),
|
||||
|
||||
('prefix=', None, "installation prefix"),
|
||||
('exec-prefix=', None, "(Unix only) prefix for platform-specific files"),
|
||||
('home=', None, "(Unix only) home directory to install under"),
|
||||
# Or, just set the base director(y|ies)
|
||||
('install-base=', None,
|
||||
"base installation directory (instead of --prefix or --home)"),
|
||||
('install-platbase=', None,
|
||||
"base installation directory for platform-specific files " +
|
||||
"(instead of --exec-prefix or --home)"),
|
||||
('root=', None,
|
||||
"install everything relative to this alternate root directory"),
|
||||
|
||||
(
|
||||
'install-base=',
|
||||
None,
|
||||
"base installation directory (instead of --prefix or --home)",
|
||||
),
|
||||
(
|
||||
'install-platbase=',
|
||||
None,
|
||||
"base installation directory for platform-specific files "
|
||||
+ "(instead of --exec-prefix or --home)",
|
||||
),
|
||||
('root=', None, "install everything relative to this alternate root directory"),
|
||||
# Or, explicitly set the installation scheme
|
||||
('install-purelib=', None,
|
||||
"installation directory for pure Python module distributions"),
|
||||
('install-platlib=', None,
|
||||
"installation directory for non-pure module distributions"),
|
||||
('install-lib=', None,
|
||||
"installation directory for all module distributions " +
|
||||
"(overrides --install-purelib and --install-platlib)"),
|
||||
|
||||
('install-headers=', None,
|
||||
"installation directory for C/C++ headers"),
|
||||
('install-scripts=', None,
|
||||
"installation directory for Python scripts"),
|
||||
('install-data=', None,
|
||||
"installation directory for data files"),
|
||||
|
||||
(
|
||||
'install-purelib=',
|
||||
None,
|
||||
"installation directory for pure Python module distributions",
|
||||
),
|
||||
(
|
||||
'install-platlib=',
|
||||
None,
|
||||
"installation directory for non-pure module distributions",
|
||||
),
|
||||
(
|
||||
'install-lib=',
|
||||
None,
|
||||
"installation directory for all module distributions "
|
||||
+ "(overrides --install-purelib and --install-platlib)",
|
||||
),
|
||||
('install-headers=', None, "installation directory for C/C++ headers"),
|
||||
('install-scripts=', None, "installation directory for Python scripts"),
|
||||
('install-data=', None, "installation directory for data files"),
|
||||
# Byte-compilation options -- see install_lib.py for details, as
|
||||
# these are duplicated from there (but only install_lib does
|
||||
# anything with them).
|
||||
('compile', 'c', "compile .py to .pyc [default]"),
|
||||
('no-compile', None, "don't compile .py files"),
|
||||
('optimize=', 'O',
|
||||
"also compile with optimization: -O1 for \"python -O\", "
|
||||
"-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
|
||||
|
||||
(
|
||||
'optimize=',
|
||||
'O',
|
||||
"also compile with optimization: -O1 for \"python -O\", "
|
||||
"-O2 for \"python -OO\", and -O0 to disable [default: -O0]",
|
||||
),
|
||||
# Miscellaneous control options
|
||||
('force', 'f',
|
||||
"force installation (overwrite any existing files)"),
|
||||
('skip-build', None,
|
||||
"skip rebuilding everything (for testing/debugging)"),
|
||||
|
||||
('force', 'f', "force installation (overwrite any existing files)"),
|
||||
('skip-build', None, "skip rebuilding everything (for testing/debugging)"),
|
||||
# Where to install documentation (eventually!)
|
||||
#('doc-format=', None, "format of documentation to generate"),
|
||||
#('install-man=', None, "directory for Unix man pages"),
|
||||
#('install-html=', None, "directory for HTML documentation"),
|
||||
#('install-info=', None, "directory for GNU info files"),
|
||||
|
||||
('record=', None,
|
||||
"filename in which to record list of installed files"),
|
||||
]
|
||||
# ('doc-format=', None, "format of documentation to generate"),
|
||||
# ('install-man=', None, "directory for Unix man pages"),
|
||||
# ('install-html=', None, "directory for HTML documentation"),
|
||||
# ('install-info=', None, "directory for GNU info files"),
|
||||
('record=', None, "filename in which to record list of installed files"),
|
||||
]
|
||||
|
||||
boolean_options = ['compile', 'force', 'skip-build']
|
||||
|
||||
if HAS_USER_SITE:
|
||||
user_options.append(('user', None,
|
||||
"install in user site-package '%s'" % USER_SITE))
|
||||
user_options.append(
|
||||
('user', None, "install in user site-package '%s'" % USER_SITE)
|
||||
)
|
||||
boolean_options.append('user')
|
||||
|
||||
negative_opt = {'no-compile' : 'compile'}
|
||||
|
||||
negative_opt = {'no-compile': 'compile'}
|
||||
|
||||
def initialize_options(self):
|
||||
"""Initializes options."""
|
||||
@@ -177,10 +273,10 @@ class install(Command):
|
||||
# supplied by the user, they are filled in using the installation
|
||||
# scheme implied by prefix/exec-prefix/home and the contents of
|
||||
# that installation scheme.
|
||||
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_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_userbase = USER_BASE
|
||||
@@ -222,20 +318,19 @@ class install(Command):
|
||||
|
||||
# Not defined yet because we don't know anything about
|
||||
# documentation yet.
|
||||
#self.install_man = None
|
||||
#self.install_html = None
|
||||
#self.install_info = None
|
||||
# self.install_man = None
|
||||
# self.install_html = None
|
||||
# self.install_info = None
|
||||
|
||||
self.record = None
|
||||
|
||||
|
||||
# -- Option finalizing methods -------------------------------------
|
||||
# (This is rather more involved than for most commands,
|
||||
# because this is where the policy for installing third-
|
||||
# party Python modules on various platforms given a wide
|
||||
# array of user input is decided. Yes, it's quite complex!)
|
||||
|
||||
def finalize_options(self):
|
||||
def finalize_options(self): # noqa: C901
|
||||
"""Finalizes options."""
|
||||
# This method (and its helpers, like 'finalize_unix()',
|
||||
# 'finalize_other()', and 'select_scheme()') is where the default
|
||||
@@ -251,20 +346,30 @@ class install(Command):
|
||||
# Check for errors/inconsistencies in the options; first, stuff
|
||||
# that's wrong on any platform.
|
||||
|
||||
if ((self.prefix or self.exec_prefix or self.home) and
|
||||
(self.install_base or self.install_platbase)):
|
||||
if (self.prefix or self.exec_prefix or self.home) and (
|
||||
self.install_base or self.install_platbase
|
||||
):
|
||||
raise DistutilsOptionError(
|
||||
"must supply either prefix/exec-prefix/home or " +
|
||||
"install-base/install-platbase -- not both")
|
||||
"must supply either prefix/exec-prefix/home or "
|
||||
+ "install-base/install-platbase -- not both"
|
||||
)
|
||||
|
||||
if self.home and (self.prefix or self.exec_prefix):
|
||||
raise DistutilsOptionError(
|
||||
"must supply either home or prefix/exec-prefix -- not both")
|
||||
"must supply either home or prefix/exec-prefix -- not both"
|
||||
)
|
||||
|
||||
if self.user and (self.prefix or self.exec_prefix or self.home or
|
||||
self.install_base or self.install_platbase):
|
||||
raise DistutilsOptionError("can't combine user with prefix, "
|
||||
"exec_prefix/home, or install_(plat)base")
|
||||
if self.user and (
|
||||
self.prefix
|
||||
or self.exec_prefix
|
||||
or self.home
|
||||
or self.install_base
|
||||
or self.install_platbase
|
||||
):
|
||||
raise DistutilsOptionError(
|
||||
"can't combine user with prefix, "
|
||||
"exec_prefix/home, or install_(plat)base"
|
||||
)
|
||||
|
||||
# Next, stuff that's wrong (or dubious) only on certain platforms.
|
||||
if os.name != "posix":
|
||||
@@ -278,7 +383,7 @@ class install(Command):
|
||||
# input a heady brew of prefix, exec_prefix, home, install_base,
|
||||
# install_platbase, user-supplied versions of
|
||||
# install_{purelib,platlib,lib,scripts,data,...}, and the
|
||||
# INSTALL_SCHEME dictionary above. Phew!
|
||||
# install schemes. Phew!
|
||||
|
||||
self.dump_dirs("pre-finalize_{unix,other}")
|
||||
|
||||
@@ -301,23 +406,36 @@ 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'),
|
||||
}
|
||||
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(
|
||||
[fw.vars(), compat_vars, sysconfig.get_config_vars(), local_vars]
|
||||
)
|
||||
|
||||
self.expand_basedirs()
|
||||
|
||||
@@ -325,13 +443,14 @@ 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
|
||||
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.
|
||||
@@ -348,17 +467,23 @@ class install(Command):
|
||||
# module distribution is pure or not. Of course, if the user
|
||||
# already specified install_lib, use their selection.
|
||||
if self.install_lib is None:
|
||||
if self.distribution.has_ext_modules(): # has extensions: non-pure
|
||||
if self.distribution.has_ext_modules(): # has extensions: non-pure
|
||||
self.install_lib = self.install_platlib
|
||||
else:
|
||||
self.install_lib = self.install_purelib
|
||||
|
||||
|
||||
# Convert directories from Unix /-separated syntax to the local
|
||||
# convention.
|
||||
self.convert_paths('lib', 'purelib', 'platlib',
|
||||
'scripts', 'data', 'headers',
|
||||
'userbase', 'usersite')
|
||||
self.convert_paths(
|
||||
'lib',
|
||||
'purelib',
|
||||
'platlib',
|
||||
'scripts',
|
||||
'data',
|
||||
'headers',
|
||||
'userbase',
|
||||
'usersite',
|
||||
)
|
||||
|
||||
# Deprecated
|
||||
# Well, we're not actually fully completely finalized yet: we still
|
||||
@@ -366,21 +491,22 @@ class install(Command):
|
||||
# non-packagized module distributions (hello, Numerical Python!) to
|
||||
# get their own directories.
|
||||
self.handle_extra_path()
|
||||
self.install_libbase = self.install_lib # needed for .pth file
|
||||
self.install_libbase = self.install_lib # needed for .pth file
|
||||
self.install_lib = os.path.join(self.install_lib, self.extra_dirs)
|
||||
|
||||
# If a new root directory was supplied, make all the installation
|
||||
# dirs relative to it.
|
||||
if self.root is not None:
|
||||
self.change_roots('libbase', 'lib', 'purelib', 'platlib',
|
||||
'scripts', 'data', 'headers')
|
||||
self.change_roots(
|
||||
'libbase', 'lib', 'purelib', 'platlib', 'scripts', 'data', 'headers'
|
||||
)
|
||||
|
||||
self.dump_dirs("after prepending root")
|
||||
|
||||
# Find out the build directories, ie. where to install from.
|
||||
self.set_undefined_options('build',
|
||||
('build_base', 'build_base'),
|
||||
('build_lib', 'build_lib'))
|
||||
self.set_undefined_options(
|
||||
'build', ('build_base', 'build_base'), ('build_lib', 'build_lib')
|
||||
)
|
||||
|
||||
# Punt on doc directories for now -- after all, we're punting on
|
||||
# documentation completely!
|
||||
@@ -390,6 +516,7 @@ class install(Command):
|
||||
if not DEBUG:
|
||||
return
|
||||
from distutils.fancy_getopt import longopt_xlate
|
||||
|
||||
log.debug(msg + ":")
|
||||
for opt in self.user_options:
|
||||
opt_name = opt[0]
|
||||
@@ -407,34 +534,43 @@ 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
|
||||
self.install_headers is None or
|
||||
self.install_scripts is None or
|
||||
self.install_data is None):
|
||||
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
|
||||
)
|
||||
if incomplete_scheme:
|
||||
raise DistutilsOptionError(
|
||||
"install-base or install-platbase supplied, but "
|
||||
"installation scheme is incomplete")
|
||||
"install-base or install-platbase supplied, but "
|
||||
"installation scheme is incomplete"
|
||||
)
|
||||
return
|
||||
|
||||
if self.user:
|
||||
if self.install_userbase is None:
|
||||
raise DistutilsPlatformError(
|
||||
"User base directory is not specified")
|
||||
raise DistutilsPlatformError("User base directory is not specified")
|
||||
self.install_base = self.install_platbase = self.install_userbase
|
||||
self.select_scheme("unix_user")
|
||||
self.select_scheme("posix_user")
|
||||
elif self.home is not None:
|
||||
self.install_base = self.install_platbase = self.home
|
||||
self.select_scheme("unix_home")
|
||||
self.select_scheme("posix_home")
|
||||
else:
|
||||
if self.prefix is None:
|
||||
if self.exec_prefix is not None:
|
||||
raise DistutilsOptionError(
|
||||
"must not supply exec-prefix without prefix")
|
||||
"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:
|
||||
@@ -442,19 +578,18 @@ class install(Command):
|
||||
|
||||
self.install_base = self.prefix
|
||||
self.install_platbase = self.exec_prefix
|
||||
self.select_scheme("unix_prefix")
|
||||
self.select_scheme("posix_prefix")
|
||||
|
||||
def finalize_other(self):
|
||||
"""Finalizes options for non-posix platforms"""
|
||||
if self.user:
|
||||
if self.install_userbase is None:
|
||||
raise DistutilsPlatformError(
|
||||
"User base directory is not specified")
|
||||
raise DistutilsPlatformError("User base directory is not specified")
|
||||
self.install_base = self.install_platbase = self.install_userbase
|
||||
self.select_scheme(os.name + "_user")
|
||||
elif self.home is not None:
|
||||
self.install_base = self.install_platbase = self.home
|
||||
self.select_scheme("unix_home")
|
||||
self.select_scheme("posix_home")
|
||||
else:
|
||||
if self.prefix is None:
|
||||
self.prefix = os.path.normpath(sys.prefix)
|
||||
@@ -464,23 +599,11 @@ class install(Command):
|
||||
self.select_scheme(os.name)
|
||||
except KeyError:
|
||||
raise DistutilsPlatformError(
|
||||
"I don't know how to install stuff on '%s'" % os.name)
|
||||
"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 = INSTALL_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:
|
||||
@@ -498,9 +621,16 @@ class install(Command):
|
||||
|
||||
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',])
|
||||
self._expand_attrs(
|
||||
[
|
||||
'install_purelib',
|
||||
'install_platlib',
|
||||
'install_lib',
|
||||
'install_headers',
|
||||
'install_scripts',
|
||||
'install_data',
|
||||
]
|
||||
)
|
||||
|
||||
def convert_paths(self, *names):
|
||||
"""Call `convert_path` over `names`."""
|
||||
@@ -527,8 +657,9 @@ class install(Command):
|
||||
path_file, extra_dirs = self.extra_path
|
||||
else:
|
||||
raise DistutilsOptionError(
|
||||
"'extra_path' option must be a list, tuple, or "
|
||||
"comma-separated string with 1 or 2 elements")
|
||||
"'extra_path' option must be a list, tuple, or "
|
||||
"comma-separated string with 1 or 2 elements"
|
||||
)
|
||||
|
||||
# convert to local form in case Unix notation used (as it
|
||||
# should be in setup scripts)
|
||||
@@ -554,7 +685,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)
|
||||
|
||||
@@ -571,8 +702,7 @@ class install(Command):
|
||||
# internally, and not to sys.path, so we don't check the platform
|
||||
# matches what we are running.
|
||||
if self.warn_dir and build_plat != get_platform():
|
||||
raise DistutilsPlatformError("Can't install when "
|
||||
"cross-compiling")
|
||||
raise DistutilsPlatformError("Can't install when " "cross-compiling")
|
||||
|
||||
# Run all sub-commands (at least those that need to be run)
|
||||
for cmd_name in self.get_sub_commands():
|
||||
@@ -584,38 +714,43 @@ class install(Command):
|
||||
# write list of installed files, if requested.
|
||||
if self.record:
|
||||
outputs = self.get_outputs()
|
||||
if self.root: # strip any package prefix
|
||||
if self.root: # strip any package prefix
|
||||
root_len = len(self.root)
|
||||
for counter in range(len(outputs)):
|
||||
outputs[counter] = outputs[counter][root_len:]
|
||||
self.execute(write_file,
|
||||
(self.record, outputs),
|
||||
"writing list of installed files to '%s'" %
|
||||
self.record)
|
||||
self.execute(
|
||||
write_file,
|
||||
(self.record, outputs),
|
||||
"writing list of installed files to '%s'" % self.record,
|
||||
)
|
||||
|
||||
sys_path = map(os.path.normpath, sys.path)
|
||||
sys_path = map(os.path.normcase, sys_path)
|
||||
install_lib = os.path.normcase(os.path.normpath(self.install_lib))
|
||||
if (self.warn_dir and
|
||||
not (self.path_file and self.install_path_file) and
|
||||
install_lib not in sys_path):
|
||||
log.debug(("modules installed to '%s', which is not in "
|
||||
"Python's module search path (sys.path) -- "
|
||||
"you'll have to change the search path yourself"),
|
||||
self.install_lib)
|
||||
if (
|
||||
self.warn_dir
|
||||
and not (self.path_file and self.install_path_file)
|
||||
and install_lib not in sys_path
|
||||
):
|
||||
log.debug(
|
||||
(
|
||||
"modules installed to '%s', which is not in "
|
||||
"Python's module search path (sys.path) -- "
|
||||
"you'll have to change the search path yourself"
|
||||
),
|
||||
self.install_lib,
|
||||
)
|
||||
|
||||
def create_path_file(self):
|
||||
"""Creates the .pth file"""
|
||||
filename = os.path.join(self.install_libbase,
|
||||
self.path_file + ".pth")
|
||||
filename = os.path.join(self.install_libbase, self.path_file + ".pth")
|
||||
if self.install_path_file:
|
||||
self.execute(write_file,
|
||||
(filename, [self.extra_dirs]),
|
||||
"creating %s" % filename)
|
||||
self.execute(
|
||||
write_file, (filename, [self.extra_dirs]), "creating %s" % filename
|
||||
)
|
||||
else:
|
||||
self.warn("path file '%s' not created" % filename)
|
||||
|
||||
|
||||
# -- Reporting methods ---------------------------------------------
|
||||
|
||||
def get_outputs(self):
|
||||
@@ -630,8 +765,7 @@ class install(Command):
|
||||
outputs.append(filename)
|
||||
|
||||
if self.path_file and self.install_path_file:
|
||||
outputs.append(os.path.join(self.install_libbase,
|
||||
self.path_file + ".pth"))
|
||||
outputs.append(os.path.join(self.install_libbase, self.path_file + ".pth"))
|
||||
|
||||
return outputs
|
||||
|
||||
@@ -650,8 +784,9 @@ class install(Command):
|
||||
def has_lib(self):
|
||||
"""Returns true if the current distribution has any Python
|
||||
modules to install."""
|
||||
return (self.distribution.has_pure_modules() or
|
||||
self.distribution.has_ext_modules())
|
||||
return (
|
||||
self.distribution.has_pure_modules() or self.distribution.has_ext_modules()
|
||||
)
|
||||
|
||||
def has_headers(self):
|
||||
"""Returns true if the current distribution has any headers to
|
||||
@@ -670,9 +805,10 @@ class install(Command):
|
||||
|
||||
# 'sub_commands': a list of commands this command might have to run to
|
||||
# get its work done. See cmd.py for more info.
|
||||
sub_commands = [('install_lib', has_lib),
|
||||
('install_headers', has_headers),
|
||||
('install_scripts', has_scripts),
|
||||
('install_data', has_data),
|
||||
('install_egg_info', lambda self:True),
|
||||
]
|
||||
sub_commands = [
|
||||
('install_lib', has_lib),
|
||||
('install_headers', has_headers),
|
||||
('install_scripts', has_scripts),
|
||||
('install_data', has_data),
|
||||
('install_egg_info', lambda self: True),
|
||||
]
|
||||
|
||||
@@ -9,18 +9,21 @@ import os
|
||||
from distutils.core import Command
|
||||
from distutils.util import change_root, convert_path
|
||||
|
||||
|
||||
class install_data(Command):
|
||||
|
||||
description = "install data files"
|
||||
|
||||
user_options = [
|
||||
('install-dir=', 'd',
|
||||
"base directory for installing data files "
|
||||
"(default: installation base dir)"),
|
||||
('root=', None,
|
||||
"install everything relative to this alternate root directory"),
|
||||
(
|
||||
'install-dir=',
|
||||
'd',
|
||||
"base directory for installing data files "
|
||||
"(default: installation base dir)",
|
||||
),
|
||||
('root=', None, "install everything relative to this alternate root directory"),
|
||||
('force', 'f', "force installation (overwrite existing files)"),
|
||||
]
|
||||
]
|
||||
|
||||
boolean_options = ['force']
|
||||
|
||||
@@ -33,11 +36,12 @@ class install_data(Command):
|
||||
self.warn_dir = 1
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('install',
|
||||
('install_data', 'install_dir'),
|
||||
('root', 'root'),
|
||||
('force', 'force'),
|
||||
)
|
||||
self.set_undefined_options(
|
||||
'install',
|
||||
('install_data', 'install_dir'),
|
||||
('root', 'root'),
|
||||
('force', 'force'),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
self.mkpath(self.install_dir)
|
||||
@@ -46,9 +50,10 @@ class install_data(Command):
|
||||
# it's a simple file, so copy it
|
||||
f = convert_path(f)
|
||||
if self.warn_dir:
|
||||
self.warn("setup script did not provide a directory for "
|
||||
"'%s' -- installing right in '%s'" %
|
||||
(f, self.install_dir))
|
||||
self.warn(
|
||||
"setup script did not provide a directory for "
|
||||
"'%s' -- installing right in '%s'" % (f, self.install_dir)
|
||||
)
|
||||
(out, _) = self.copy_file(f, self.install_dir)
|
||||
self.outfiles.append(out)
|
||||
else:
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
"""distutils.command.install_egg_info
|
||||
"""
|
||||
distutils.command.install_egg_info
|
||||
|
||||
Implements the Distutils 'install_egg_info' command, for installing
|
||||
a package's PKG-INFO metadata."""
|
||||
a package's PKG-INFO metadata.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
from distutils.cmd import Command
|
||||
from distutils import log, dir_util
|
||||
import os, sys, re
|
||||
|
||||
|
||||
class install_egg_info(Command):
|
||||
"""Install an .egg-info file for the package"""
|
||||
@@ -19,14 +24,21 @@ class install_egg_info(Command):
|
||||
def initialize_options(self):
|
||||
self.install_dir = None
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('install_lib',('install_dir','install_dir'))
|
||||
basename = "%s-%s-py%d.%d.egg-info" % (
|
||||
@property
|
||||
def basename(self):
|
||||
"""
|
||||
Allow basename to be overridden by child class.
|
||||
Ref pypa/distutils#2.
|
||||
"""
|
||||
return "%s-%s-py%d.%d.egg-info" % (
|
||||
to_filename(safe_name(self.distribution.get_name())),
|
||||
to_filename(safe_version(self.distribution.get_version())),
|
||||
*sys.version_info[:2]
|
||||
*sys.version_info[:2],
|
||||
)
|
||||
self.target = os.path.join(self.install_dir, basename)
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('install_lib', ('install_dir', 'install_dir'))
|
||||
self.target = os.path.join(self.install_dir, self.basename)
|
||||
self.outputs = [self.target]
|
||||
|
||||
def run(self):
|
||||
@@ -34,10 +46,11 @@ class install_egg_info(Command):
|
||||
if os.path.isdir(target) and not os.path.islink(target):
|
||||
dir_util.remove_tree(target, dry_run=self.dry_run)
|
||||
elif os.path.exists(target):
|
||||
self.execute(os.unlink,(self.target,),"Removing "+target)
|
||||
self.execute(os.unlink, (self.target,), "Removing " + target)
|
||||
elif not os.path.isdir(self.install_dir):
|
||||
self.execute(os.makedirs, (self.install_dir,),
|
||||
"Creating "+self.install_dir)
|
||||
self.execute(
|
||||
os.makedirs, (self.install_dir,), "Creating " + self.install_dir
|
||||
)
|
||||
log.info("Writing %s", target)
|
||||
if not self.dry_run:
|
||||
with open(target, 'w', encoding='UTF-8') as f:
|
||||
@@ -51,6 +64,7 @@ class install_egg_info(Command):
|
||||
# can be replaced by importing them from pkg_resources once it is included
|
||||
# in the stdlib.
|
||||
|
||||
|
||||
def safe_name(name):
|
||||
"""Convert an arbitrary string to a standard distribution name
|
||||
|
||||
@@ -65,7 +79,7 @@ def safe_version(version):
|
||||
Spaces become dots, and all other non-alphanumeric characters become
|
||||
dashes, with runs of multiple dashes condensed to a single dash.
|
||||
"""
|
||||
version = version.replace(' ','.')
|
||||
version = version.replace(' ', '.')
|
||||
return re.sub('[^A-Za-z0-9.]+', '-', version)
|
||||
|
||||
|
||||
@@ -74,4 +88,4 @@ def to_filename(name):
|
||||
|
||||
Any '-' characters are currently replaced with '_'.
|
||||
"""
|
||||
return name.replace('-','_')
|
||||
return name.replace('-', '_')
|
||||
|
||||
@@ -11,11 +11,10 @@ class install_headers(Command):
|
||||
|
||||
description = "install C/C++ header files"
|
||||
|
||||
user_options = [('install-dir=', 'd',
|
||||
"directory to install header files to"),
|
||||
('force', 'f',
|
||||
"force installation (overwrite existing files)"),
|
||||
]
|
||||
user_options = [
|
||||
('install-dir=', 'd', "directory to install header files to"),
|
||||
('force', 'f', "force installation (overwrite existing files)"),
|
||||
]
|
||||
|
||||
boolean_options = ['force']
|
||||
|
||||
@@ -25,10 +24,9 @@ class install_headers(Command):
|
||||
self.outfiles = []
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('install',
|
||||
('install_headers', 'install_dir'),
|
||||
('force', 'force'))
|
||||
|
||||
self.set_undefined_options(
|
||||
'install', ('install_headers', 'install_dir'), ('force', 'force')
|
||||
)
|
||||
|
||||
def run(self):
|
||||
headers = self.distribution.headers
|
||||
|
||||
@@ -14,6 +14,7 @@ from distutils.errors import DistutilsOptionError
|
||||
# Extension for Python source files.
|
||||
PYTHON_SOURCE_EXTENSION = ".py"
|
||||
|
||||
|
||||
class install_lib(Command):
|
||||
|
||||
description = "install all Python modules (extensions and pure Python)"
|
||||
@@ -35,18 +36,21 @@ class install_lib(Command):
|
||||
|
||||
user_options = [
|
||||
('install-dir=', 'd', "directory to install to"),
|
||||
('build-dir=','b', "build directory (where to install from)"),
|
||||
('build-dir=', 'b', "build directory (where to install from)"),
|
||||
('force', 'f', "force installation (overwrite existing files)"),
|
||||
('compile', 'c', "compile .py to .pyc [default]"),
|
||||
('no-compile', None, "don't compile .py files"),
|
||||
('optimize=', 'O',
|
||||
"also compile with optimization: -O1 for \"python -O\", "
|
||||
"-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
|
||||
(
|
||||
'optimize=',
|
||||
'O',
|
||||
"also compile with optimization: -O1 for \"python -O\", "
|
||||
"-O2 for \"python -OO\", and -O0 to disable [default: -O0]",
|
||||
),
|
||||
('skip-build', None, "skip the build steps"),
|
||||
]
|
||||
]
|
||||
|
||||
boolean_options = ['force', 'compile', 'skip-build']
|
||||
negative_opt = {'no-compile' : 'compile'}
|
||||
negative_opt = {'no-compile': 'compile'}
|
||||
|
||||
def initialize_options(self):
|
||||
# let the 'install' command dictate our installation directory
|
||||
@@ -61,14 +65,15 @@ class install_lib(Command):
|
||||
# Get all the information we need to install pure Python modules
|
||||
# from the umbrella 'install' command -- build (source) directory,
|
||||
# install (target) directory, and whether to compile .py files.
|
||||
self.set_undefined_options('install',
|
||||
('build_lib', 'build_dir'),
|
||||
('install_lib', 'install_dir'),
|
||||
('force', 'force'),
|
||||
('compile', 'compile'),
|
||||
('optimize', 'optimize'),
|
||||
('skip_build', 'skip_build'),
|
||||
)
|
||||
self.set_undefined_options(
|
||||
'install',
|
||||
('build_lib', 'build_dir'),
|
||||
('install_lib', 'install_dir'),
|
||||
('force', 'force'),
|
||||
('compile', 'compile'),
|
||||
('optimize', 'optimize'),
|
||||
('skip_build', 'skip_build'),
|
||||
)
|
||||
|
||||
if self.compile is None:
|
||||
self.compile = True
|
||||
@@ -110,8 +115,9 @@ class install_lib(Command):
|
||||
if os.path.isdir(self.build_dir):
|
||||
outfiles = self.copy_tree(self.build_dir, self.install_dir)
|
||||
else:
|
||||
self.warn("'%s' does not exist -- no Python modules to install" %
|
||||
self.build_dir)
|
||||
self.warn(
|
||||
"'%s' does not exist -- no Python modules to install" % self.build_dir
|
||||
)
|
||||
return
|
||||
return outfiles
|
||||
|
||||
@@ -129,14 +135,22 @@ class install_lib(Command):
|
||||
install_root = self.get_finalized_command('install').root
|
||||
|
||||
if self.compile:
|
||||
byte_compile(files, optimize=0,
|
||||
force=self.force, prefix=install_root,
|
||||
dry_run=self.dry_run)
|
||||
byte_compile(
|
||||
files,
|
||||
optimize=0,
|
||||
force=self.force,
|
||||
prefix=install_root,
|
||||
dry_run=self.dry_run,
|
||||
)
|
||||
if self.optimize > 0:
|
||||
byte_compile(files, optimize=self.optimize,
|
||||
force=self.force, prefix=install_root,
|
||||
verbose=self.verbose, dry_run=self.dry_run)
|
||||
|
||||
byte_compile(
|
||||
files,
|
||||
optimize=self.optimize,
|
||||
force=self.force,
|
||||
prefix=install_root,
|
||||
verbose=self.verbose,
|
||||
dry_run=self.dry_run,
|
||||
)
|
||||
|
||||
# -- Utility methods -----------------------------------------------
|
||||
|
||||
@@ -165,15 +179,18 @@ class install_lib(Command):
|
||||
if ext != PYTHON_SOURCE_EXTENSION:
|
||||
continue
|
||||
if self.compile:
|
||||
bytecode_files.append(importlib.util.cache_from_source(
|
||||
py_file, optimization=''))
|
||||
bytecode_files.append(
|
||||
importlib.util.cache_from_source(py_file, optimization='')
|
||||
)
|
||||
if self.optimize > 0:
|
||||
bytecode_files.append(importlib.util.cache_from_source(
|
||||
py_file, optimization=self.optimize))
|
||||
bytecode_files.append(
|
||||
importlib.util.cache_from_source(
|
||||
py_file, optimization=self.optimize
|
||||
)
|
||||
)
|
||||
|
||||
return bytecode_files
|
||||
|
||||
|
||||
# -- External interface --------------------------------------------
|
||||
# (called by outsiders)
|
||||
|
||||
@@ -182,19 +199,23 @@ class install_lib(Command):
|
||||
were actually run. Not affected by the "dry-run" flag or whether
|
||||
modules have actually been built yet.
|
||||
"""
|
||||
pure_outputs = \
|
||||
self._mutate_outputs(self.distribution.has_pure_modules(),
|
||||
'build_py', 'build_lib',
|
||||
self.install_dir)
|
||||
pure_outputs = self._mutate_outputs(
|
||||
self.distribution.has_pure_modules(),
|
||||
'build_py',
|
||||
'build_lib',
|
||||
self.install_dir,
|
||||
)
|
||||
if self.compile:
|
||||
bytecode_outputs = self._bytecode_filenames(pure_outputs)
|
||||
else:
|
||||
bytecode_outputs = []
|
||||
|
||||
ext_outputs = \
|
||||
self._mutate_outputs(self.distribution.has_ext_modules(),
|
||||
'build_ext', 'build_lib',
|
||||
self.install_dir)
|
||||
ext_outputs = self._mutate_outputs(
|
||||
self.distribution.has_ext_modules(),
|
||||
'build_ext',
|
||||
'build_lib',
|
||||
self.install_dir,
|
||||
)
|
||||
|
||||
return pure_outputs + bytecode_outputs + ext_outputs
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class install_scripts(Command):
|
||||
|
||||
user_options = [
|
||||
('install-dir=', 'd', "directory to install scripts to"),
|
||||
('build-dir=','b', "build directory (where to install from)"),
|
||||
('build-dir=', 'b', "build directory (where to install from)"),
|
||||
('force', 'f', "force installation (overwrite existing files)"),
|
||||
('skip-build', None, "skip the build steps"),
|
||||
]
|
||||
@@ -32,11 +32,12 @@ class install_scripts(Command):
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options('build', ('build_scripts', 'build_dir'))
|
||||
self.set_undefined_options('install',
|
||||
('install_scripts', 'install_dir'),
|
||||
('force', 'force'),
|
||||
('skip_build', 'skip_build'),
|
||||
)
|
||||
self.set_undefined_options(
|
||||
'install',
|
||||
('install_scripts', 'install_dir'),
|
||||
('force', 'force'),
|
||||
('skip_build', 'skip_build'),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
if not self.skip_build:
|
||||
|
||||
@@ -7,12 +7,13 @@ def _pythonlib_compat():
|
||||
library. See pypa/distutils#9.
|
||||
"""
|
||||
from distutils import sysconfig
|
||||
|
||||
if not sysconfig.get_config_var('Py_ENABLED_SHARED'):
|
||||
return
|
||||
|
||||
yield 'python{}.{}{}'.format(
|
||||
sys.hexversion >> 24,
|
||||
(sys.hexversion >> 16) & 0xff,
|
||||
(sys.hexversion >> 16) & 0xFF,
|
||||
sysconfig.get_config_var('ABIFLAGS'),
|
||||
)
|
||||
|
||||
|
||||
@@ -7,24 +7,30 @@ Implements the Distutils 'register' command (register with the repository).
|
||||
|
||||
import getpass
|
||||
import io
|
||||
import urllib.parse, urllib.request
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from warnings import warn
|
||||
|
||||
from distutils.core import PyPIRCCommand
|
||||
from distutils.errors import *
|
||||
from distutils import log
|
||||
|
||||
|
||||
class register(PyPIRCCommand):
|
||||
|
||||
description = ("register the distribution with the Python package index")
|
||||
description = "register the distribution with the Python package index"
|
||||
user_options = PyPIRCCommand.user_options + [
|
||||
('list-classifiers', None,
|
||||
'list the valid Trove classifiers'),
|
||||
('strict', None ,
|
||||
'Will stop the registering if the meta-data are not fully compliant')
|
||||
]
|
||||
('list-classifiers', None, 'list the valid Trove classifiers'),
|
||||
(
|
||||
'strict',
|
||||
None,
|
||||
'Will stop the registering if the meta-data are not fully compliant',
|
||||
),
|
||||
]
|
||||
boolean_options = PyPIRCCommand.boolean_options + [
|
||||
'verify', 'list-classifiers', 'strict']
|
||||
'verify',
|
||||
'list-classifiers',
|
||||
'strict',
|
||||
]
|
||||
|
||||
sub_commands = [('check', lambda self: True)]
|
||||
|
||||
@@ -36,8 +42,10 @@ class register(PyPIRCCommand):
|
||||
def finalize_options(self):
|
||||
PyPIRCCommand.finalize_options(self)
|
||||
# setting options for the `check` subcommand
|
||||
check_options = {'strict': ('register', self.strict),
|
||||
'restructuredtext': ('register', 1)}
|
||||
check_options = {
|
||||
'strict': ('register', self.strict),
|
||||
'restructuredtext': ('register', 1),
|
||||
}
|
||||
self.distribution.command_options['check'] = check_options
|
||||
|
||||
def run(self):
|
||||
@@ -57,8 +65,11 @@ class register(PyPIRCCommand):
|
||||
|
||||
def check_metadata(self):
|
||||
"""Deprecated API."""
|
||||
warn("distutils.command.register.check_metadata is deprecated, \
|
||||
use the check command instead", PendingDeprecationWarning)
|
||||
warn(
|
||||
"distutils.command.register.check_metadata is deprecated; "
|
||||
"use the check command instead",
|
||||
DeprecationWarning,
|
||||
)
|
||||
check = self.distribution.get_command_obj('check')
|
||||
check.ensure_finalized()
|
||||
check.strict = self.strict
|
||||
@@ -66,8 +77,7 @@ class register(PyPIRCCommand):
|
||||
check.run()
|
||||
|
||||
def _set_config(self):
|
||||
''' Reads the configuration file and set attributes.
|
||||
'''
|
||||
'''Reads the configuration file and set attributes.'''
|
||||
config = self._read_pypirc()
|
||||
if config != {}:
|
||||
self.username = config['username']
|
||||
@@ -83,45 +93,43 @@ class register(PyPIRCCommand):
|
||||
self.has_config = False
|
||||
|
||||
def classifiers(self):
|
||||
''' Fetch the list of classifiers from the server.
|
||||
'''
|
||||
url = self.repository+'?:action=list_classifiers'
|
||||
'''Fetch the list of classifiers from the server.'''
|
||||
url = self.repository + '?:action=list_classifiers'
|
||||
response = urllib.request.urlopen(url)
|
||||
log.info(self._read_pypi_response(response))
|
||||
|
||||
def verify_metadata(self):
|
||||
''' Send the metadata to the package index server to be checked.
|
||||
'''
|
||||
'''Send the metadata to the package index server to be checked.'''
|
||||
# send the info to the server and report the result
|
||||
(code, result) = self.post_to_server(self.build_post_data('verify'))
|
||||
log.info('Server response (%s): %s', code, result)
|
||||
|
||||
def send_metadata(self):
|
||||
''' Send the metadata to the package index server.
|
||||
def send_metadata(self): # noqa: C901
|
||||
'''Send the metadata to the package index server.
|
||||
|
||||
Well, do the following:
|
||||
1. figure who the user is, and then
|
||||
2. send the data as a Basic auth'ed POST.
|
||||
Well, do the following:
|
||||
1. figure who the user is, and then
|
||||
2. send the data as a Basic auth'ed POST.
|
||||
|
||||
First we try to read the username/password from $HOME/.pypirc,
|
||||
which is a ConfigParser-formatted file with a section
|
||||
[distutils] containing username and password entries (both
|
||||
in clear text). Eg:
|
||||
First we try to read the username/password from $HOME/.pypirc,
|
||||
which is a ConfigParser-formatted file with a section
|
||||
[distutils] containing username and password entries (both
|
||||
in clear text). Eg:
|
||||
|
||||
[distutils]
|
||||
index-servers =
|
||||
pypi
|
||||
[distutils]
|
||||
index-servers =
|
||||
pypi
|
||||
|
||||
[pypi]
|
||||
username: fred
|
||||
password: sekrit
|
||||
[pypi]
|
||||
username: fred
|
||||
password: sekrit
|
||||
|
||||
Otherwise, to figure who the user is, we offer the user three
|
||||
choices:
|
||||
Otherwise, to figure who the user is, we offer the user three
|
||||
choices:
|
||||
|
||||
1. use existing login,
|
||||
2. register as a new user, or
|
||||
3. set the password to a random string and email the user.
|
||||
1. use existing login,
|
||||
2. register as a new user, or
|
||||
3. set the password to a random string and email the user.
|
||||
|
||||
'''
|
||||
# see if we can short-cut and get the username/password from the
|
||||
@@ -137,13 +145,16 @@ class register(PyPIRCCommand):
|
||||
# get the user's login info
|
||||
choices = '1 2 3 4'.split()
|
||||
while choice not in choices:
|
||||
self.announce('''\
|
||||
self.announce(
|
||||
'''\
|
||||
We need to know who you are, so please choose either:
|
||||
1. use your existing login,
|
||||
2. register as a new user,
|
||||
3. have the server generate a new password for you (and email it to you), or
|
||||
4. quit
|
||||
Your selection [default 1]: ''', log.INFO)
|
||||
Your selection [default 1]: ''',
|
||||
log.INFO,
|
||||
)
|
||||
choice = input()
|
||||
if not choice:
|
||||
choice = '1'
|
||||
@@ -162,10 +173,8 @@ Your selection [default 1]: ''', log.INFO)
|
||||
host = urllib.parse.urlparse(self.repository)[1]
|
||||
auth.add_password(self.realm, host, username, password)
|
||||
# send the info to the server and report the result
|
||||
code, result = self.post_to_server(self.build_post_data('submit'),
|
||||
auth)
|
||||
self.announce('Server response (%s): %s' % (code, result),
|
||||
log.INFO)
|
||||
code, result = self.post_to_server(self.build_post_data('submit'), auth)
|
||||
self.announce('Server response ({}): {}'.format(code, result), log.INFO)
|
||||
|
||||
# possibly save the login
|
||||
if code == 200:
|
||||
@@ -174,10 +183,17 @@ Your selection [default 1]: ''', log.INFO)
|
||||
# so the upload command can reuse it
|
||||
self.distribution.password = password
|
||||
else:
|
||||
self.announce(('I can store your PyPI login so future '
|
||||
'submissions will be faster.'), log.INFO)
|
||||
self.announce('(the login will be stored in %s)' % \
|
||||
self._get_rc_file(), log.INFO)
|
||||
self.announce(
|
||||
(
|
||||
'I can store your PyPI login so future '
|
||||
'submissions will be faster.'
|
||||
),
|
||||
log.INFO,
|
||||
)
|
||||
self.announce(
|
||||
'(the login will be stored in %s)' % self._get_rc_file(),
|
||||
log.INFO,
|
||||
)
|
||||
choice = 'X'
|
||||
while choice.lower() not in 'yn':
|
||||
choice = input('Save your login (y/N)?')
|
||||
@@ -208,8 +224,7 @@ Your selection [default 1]: ''', log.INFO)
|
||||
log.info('Server response (%s): %s', code, result)
|
||||
else:
|
||||
log.info('You will receive an email shortly.')
|
||||
log.info(('Follow the instructions in it to '
|
||||
'complete registration.'))
|
||||
log.info('Follow the instructions in it to ' 'complete registration.')
|
||||
elif choice == '3':
|
||||
data = {':action': 'password_reset'}
|
||||
data['email'] = ''
|
||||
@@ -224,7 +239,7 @@ Your selection [default 1]: ''', log.INFO)
|
||||
meta = self.distribution.metadata
|
||||
data = {
|
||||
':action': action,
|
||||
'metadata_version' : '1.0',
|
||||
'metadata_version': '1.0',
|
||||
'name': meta.get_name(),
|
||||
'version': meta.get_version(),
|
||||
'summary': meta.get_description(),
|
||||
@@ -246,13 +261,12 @@ Your selection [default 1]: ''', log.INFO)
|
||||
data['metadata_version'] = '1.1'
|
||||
return data
|
||||
|
||||
def post_to_server(self, data, auth=None):
|
||||
''' Post a query to the server, and return a string response.
|
||||
'''
|
||||
def post_to_server(self, data, auth=None): # noqa: C901
|
||||
'''Post a query to the server, and return a string response.'''
|
||||
if 'name' in data:
|
||||
self.announce('Registering %s to %s' % (data['name'],
|
||||
self.repository),
|
||||
log.INFO)
|
||||
self.announce(
|
||||
'Registering {} to {}'.format(data['name'], self.repository), log.INFO
|
||||
)
|
||||
# Build up the MIME payload for the urllib2 POST data
|
||||
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
|
||||
sep_boundary = '\n--' + boundary
|
||||
@@ -260,12 +274,12 @@ Your selection [default 1]: ''', log.INFO)
|
||||
body = io.StringIO()
|
||||
for key, value in data.items():
|
||||
# handle multiple entries for the same name
|
||||
if type(value) not in (type([]), type( () )):
|
||||
if type(value) not in (type([]), type(())):
|
||||
value = [value]
|
||||
for value in value:
|
||||
value = str(value)
|
||||
body.write(sep_boundary)
|
||||
body.write('\nContent-Disposition: form-data; name="%s"'%key)
|
||||
body.write('\nContent-Disposition: form-data; name="%s"' % key)
|
||||
body.write("\n\n")
|
||||
body.write(value)
|
||||
if value and value[-1] == '\r':
|
||||
@@ -276,8 +290,9 @@ Your selection [default 1]: ''', log.INFO)
|
||||
|
||||
# build the Request
|
||||
headers = {
|
||||
'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary,
|
||||
'Content-length': str(len(body))
|
||||
'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'
|
||||
% boundary,
|
||||
'Content-length': str(len(body)),
|
||||
}
|
||||
req = urllib.request.Request(self.repository, body, headers)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from distutils.text_file import TextFile
|
||||
from distutils.filelist import FileList
|
||||
from distutils import log
|
||||
from distutils.util import convert_path
|
||||
from distutils.errors import DistutilsTemplateError, DistutilsOptionError
|
||||
from distutils.errors import DistutilsOptionError, DistutilsTemplateError
|
||||
|
||||
|
||||
def show_formats():
|
||||
@@ -24,13 +24,12 @@ def show_formats():
|
||||
"""
|
||||
from distutils.fancy_getopt import FancyGetopt
|
||||
from distutils.archive_util import ARCHIVE_FORMATS
|
||||
|
||||
formats = []
|
||||
for format in ARCHIVE_FORMATS.keys():
|
||||
formats.append(("formats=" + format, None,
|
||||
ARCHIVE_FORMATS[format][2]))
|
||||
formats.append(("formats=" + format, None, ARCHIVE_FORMATS[format][2]))
|
||||
formats.sort()
|
||||
FancyGetopt(formats).print_help(
|
||||
"List of available source distribution formats:")
|
||||
FancyGetopt(formats).print_help("List of available source distribution formats:")
|
||||
|
||||
|
||||
class sdist(Command):
|
||||
@@ -44,55 +43,77 @@ class sdist(Command):
|
||||
return self.metadata_check
|
||||
|
||||
user_options = [
|
||||
('template=', 't',
|
||||
"name of manifest template file [default: MANIFEST.in]"),
|
||||
('manifest=', 'm',
|
||||
"name of manifest file [default: MANIFEST]"),
|
||||
('use-defaults', None,
|
||||
"include the default file set in the manifest "
|
||||
"[default; disable with --no-defaults]"),
|
||||
('no-defaults', None,
|
||||
"don't include the default file set"),
|
||||
('prune', None,
|
||||
"specifically exclude files/directories that should not be "
|
||||
"distributed (build tree, RCS/CVS dirs, etc.) "
|
||||
"[default; disable with --no-prune]"),
|
||||
('no-prune', None,
|
||||
"don't automatically exclude anything"),
|
||||
('manifest-only', 'o',
|
||||
"just regenerate the manifest and then stop "
|
||||
"(implies --force-manifest)"),
|
||||
('force-manifest', 'f',
|
||||
"forcibly regenerate the manifest and carry on as usual. "
|
||||
"Deprecated: now the manifest is always regenerated."),
|
||||
('formats=', None,
|
||||
"formats for source distribution (comma-separated list)"),
|
||||
('keep-temp', 'k',
|
||||
"keep the distribution tree around after creating " +
|
||||
"archive file(s)"),
|
||||
('dist-dir=', 'd',
|
||||
"directory to put the source distribution archive(s) in "
|
||||
"[default: dist]"),
|
||||
('metadata-check', None,
|
||||
"Ensure that all required elements of meta-data "
|
||||
"are supplied. Warn if any missing. [default]"),
|
||||
('owner=', 'u',
|
||||
"Owner name used when creating a tar file [default: current user]"),
|
||||
('group=', 'g',
|
||||
"Group name used when creating a tar file [default: current group]"),
|
||||
]
|
||||
('template=', 't', "name of manifest template file [default: MANIFEST.in]"),
|
||||
('manifest=', 'm', "name of manifest file [default: MANIFEST]"),
|
||||
(
|
||||
'use-defaults',
|
||||
None,
|
||||
"include the default file set in the manifest "
|
||||
"[default; disable with --no-defaults]",
|
||||
),
|
||||
('no-defaults', None, "don't include the default file set"),
|
||||
(
|
||||
'prune',
|
||||
None,
|
||||
"specifically exclude files/directories that should not be "
|
||||
"distributed (build tree, RCS/CVS dirs, etc.) "
|
||||
"[default; disable with --no-prune]",
|
||||
),
|
||||
('no-prune', None, "don't automatically exclude anything"),
|
||||
(
|
||||
'manifest-only',
|
||||
'o',
|
||||
"just regenerate the manifest and then stop " "(implies --force-manifest)",
|
||||
),
|
||||
(
|
||||
'force-manifest',
|
||||
'f',
|
||||
"forcibly regenerate the manifest and carry on as usual. "
|
||||
"Deprecated: now the manifest is always regenerated.",
|
||||
),
|
||||
('formats=', None, "formats for source distribution (comma-separated list)"),
|
||||
(
|
||||
'keep-temp',
|
||||
'k',
|
||||
"keep the distribution tree around after creating " + "archive file(s)",
|
||||
),
|
||||
(
|
||||
'dist-dir=',
|
||||
'd',
|
||||
"directory to put the source distribution archive(s) in " "[default: dist]",
|
||||
),
|
||||
(
|
||||
'metadata-check',
|
||||
None,
|
||||
"Ensure that all required elements of meta-data "
|
||||
"are supplied. Warn if any missing. [default]",
|
||||
),
|
||||
(
|
||||
'owner=',
|
||||
'u',
|
||||
"Owner name used when creating a tar file [default: current user]",
|
||||
),
|
||||
(
|
||||
'group=',
|
||||
'g',
|
||||
"Group name used when creating a tar file [default: current group]",
|
||||
),
|
||||
]
|
||||
|
||||
boolean_options = ['use-defaults', 'prune',
|
||||
'manifest-only', 'force-manifest',
|
||||
'keep-temp', 'metadata-check']
|
||||
boolean_options = [
|
||||
'use-defaults',
|
||||
'prune',
|
||||
'manifest-only',
|
||||
'force-manifest',
|
||||
'keep-temp',
|
||||
'metadata-check',
|
||||
]
|
||||
|
||||
help_options = [
|
||||
('help-formats', None,
|
||||
"list available distribution formats", show_formats),
|
||||
]
|
||||
('help-formats', None, "list available distribution formats", show_formats),
|
||||
]
|
||||
|
||||
negative_opt = {'no-defaults': 'use-defaults',
|
||||
'no-prune': 'prune' }
|
||||
negative_opt = {'no-defaults': 'use-defaults', 'no-prune': 'prune'}
|
||||
|
||||
sub_commands = [('check', checking_metadata)]
|
||||
|
||||
@@ -131,8 +152,7 @@ class sdist(Command):
|
||||
|
||||
bad_format = archive_util.check_archive_formats(self.formats)
|
||||
if bad_format:
|
||||
raise DistutilsOptionError(
|
||||
"unknown archive format '%s'" % bad_format)
|
||||
raise DistutilsOptionError("unknown archive format '%s'" % bad_format)
|
||||
|
||||
if self.dist_dir is None:
|
||||
self.dist_dir = "dist"
|
||||
@@ -161,8 +181,11 @@ class sdist(Command):
|
||||
|
||||
def check_metadata(self):
|
||||
"""Deprecated API."""
|
||||
warn("distutils.command.sdist.check_metadata is deprecated, \
|
||||
use the check command instead", PendingDeprecationWarning)
|
||||
warn(
|
||||
"distutils.command.sdist.check_metadata is deprecated, \
|
||||
use the check command instead",
|
||||
PendingDeprecationWarning,
|
||||
)
|
||||
check = self.distribution.get_command_obj('check')
|
||||
check.ensure_finalized()
|
||||
check.run()
|
||||
@@ -189,9 +212,10 @@ class sdist(Command):
|
||||
return
|
||||
|
||||
if not template_exists:
|
||||
self.warn(("manifest template '%s' does not exist " +
|
||||
"(using default file list)") %
|
||||
self.template)
|
||||
self.warn(
|
||||
("manifest template '%s' does not exist " + "(using default file list)")
|
||||
% self.template
|
||||
)
|
||||
self.filelist.findall()
|
||||
|
||||
if self.use_defaults:
|
||||
@@ -259,8 +283,9 @@ class sdist(Command):
|
||||
break
|
||||
|
||||
if not got_it:
|
||||
self.warn("standard file not found: should have one of " +
|
||||
', '.join(alts))
|
||||
self.warn(
|
||||
"standard file not found: should have one of " + ', '.join(alts)
|
||||
)
|
||||
else:
|
||||
if self._cs_path_exists(fn):
|
||||
self.filelist.append(fn)
|
||||
@@ -328,14 +353,20 @@ class sdist(Command):
|
||||
'self.filelist', which updates itself accordingly.
|
||||
"""
|
||||
log.info("reading manifest template '%s'", self.template)
|
||||
template = TextFile(self.template, strip_comments=1, skip_blanks=1,
|
||||
join_lines=1, lstrip_ws=1, rstrip_ws=1,
|
||||
collapse_join=1)
|
||||
template = TextFile(
|
||||
self.template,
|
||||
strip_comments=1,
|
||||
skip_blanks=1,
|
||||
join_lines=1,
|
||||
lstrip_ws=1,
|
||||
rstrip_ws=1,
|
||||
collapse_join=1,
|
||||
)
|
||||
|
||||
try:
|
||||
while True:
|
||||
line = template.readline()
|
||||
if line is None: # end of file
|
||||
if line is None: # end of file
|
||||
break
|
||||
|
||||
try:
|
||||
@@ -344,9 +375,10 @@ class sdist(Command):
|
||||
# malformed lines, or a ValueError from the lower-level
|
||||
# convert_path function
|
||||
except (DistutilsTemplateError, ValueError) as msg:
|
||||
self.warn("%s, line %d: %s" % (template.filename,
|
||||
template.current_line,
|
||||
msg))
|
||||
self.warn(
|
||||
"%s, line %d: %s"
|
||||
% (template.filename, template.current_line, msg)
|
||||
)
|
||||
finally:
|
||||
template.close()
|
||||
|
||||
@@ -369,9 +401,8 @@ class sdist(Command):
|
||||
else:
|
||||
seps = '/'
|
||||
|
||||
vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
|
||||
'_darcs']
|
||||
vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
|
||||
vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr', '_darcs']
|
||||
vcs_ptrn = r'(^|{})({})({}).*'.format(seps, '|'.join(vcs_dirs), seps)
|
||||
self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
|
||||
|
||||
def write_manifest(self):
|
||||
@@ -380,14 +411,19 @@ class sdist(Command):
|
||||
named by 'self.manifest'.
|
||||
"""
|
||||
if self._manifest_is_not_generated():
|
||||
log.info("not writing to manually maintained "
|
||||
"manifest file '%s'" % self.manifest)
|
||||
log.info(
|
||||
"not writing to manually maintained "
|
||||
"manifest file '%s'" % self.manifest
|
||||
)
|
||||
return
|
||||
|
||||
content = self.filelist.files[:]
|
||||
content.insert(0, '# file GENERATED by distutils, do NOT edit')
|
||||
self.execute(file_util.write_file, (self.manifest, content),
|
||||
"writing manifest file '%s'" % self.manifest)
|
||||
self.execute(
|
||||
file_util.write_file,
|
||||
(self.manifest, content),
|
||||
"writing manifest file '%s'" % self.manifest,
|
||||
)
|
||||
|
||||
def _manifest_is_not_generated(self):
|
||||
# check for special comment used in 3.1.3 and higher
|
||||
@@ -437,10 +473,10 @@ class sdist(Command):
|
||||
# out-of-date, because by default we blow away 'base_dir' when
|
||||
# we're done making the distribution archives.)
|
||||
|
||||
if hasattr(os, 'link'): # can make hard links on this system
|
||||
if hasattr(os, 'link'): # can make hard links on this system
|
||||
link = 'hard'
|
||||
msg = "making hard links in %s..." % base_dir
|
||||
else: # nope, have to copy
|
||||
else: # nope, have to copy
|
||||
link = None
|
||||
msg = "copying files to %s..." % base_dir
|
||||
|
||||
@@ -471,14 +507,15 @@ class sdist(Command):
|
||||
base_name = os.path.join(self.dist_dir, base_dir)
|
||||
|
||||
self.make_release_tree(base_dir, self.filelist.files)
|
||||
archive_files = [] # remember names of files we create
|
||||
archive_files = [] # remember names of files we create
|
||||
# tar archive must be created last to avoid overwrite and remove
|
||||
if 'tar' in self.formats:
|
||||
self.formats.append(self.formats.pop(self.formats.index('tar')))
|
||||
|
||||
for fmt in self.formats:
|
||||
file = self.make_archive(base_name, fmt, base_dir=base_dir,
|
||||
owner=self.owner, group=self.group)
|
||||
file = self.make_archive(
|
||||
base_name, fmt, base_dir=base_dir, owner=self.owner, group=self.group
|
||||
)
|
||||
archive_files.append(file)
|
||||
self.distribution.dist_files.append(('sdist', '', file))
|
||||
|
||||
|
||||
@@ -31,10 +31,9 @@ class upload(PyPIRCCommand):
|
||||
description = "upload binary package to PyPI"
|
||||
|
||||
user_options = PyPIRCCommand.user_options + [
|
||||
('sign', 's',
|
||||
'sign files to upload using gpg'),
|
||||
('sign', 's', 'sign files to upload using gpg'),
|
||||
('identity=', 'i', 'GPG identity used to sign files'),
|
||||
]
|
||||
]
|
||||
|
||||
boolean_options = PyPIRCCommand.boolean_options + ['sign']
|
||||
|
||||
@@ -49,9 +48,7 @@ class upload(PyPIRCCommand):
|
||||
def finalize_options(self):
|
||||
PyPIRCCommand.finalize_options(self)
|
||||
if self.identity and not self.sign:
|
||||
raise DistutilsOptionError(
|
||||
"Must use --sign for --identity to have meaning"
|
||||
)
|
||||
raise DistutilsOptionError("Must use --sign for --identity to have meaning")
|
||||
config = self._read_pypirc()
|
||||
if config != {}:
|
||||
self.username = config['username']
|
||||
@@ -66,16 +63,17 @@ class upload(PyPIRCCommand):
|
||||
|
||||
def run(self):
|
||||
if not self.distribution.dist_files:
|
||||
msg = ("Must create and upload files in one command "
|
||||
"(e.g. setup.py sdist upload)")
|
||||
msg = (
|
||||
"Must create and upload files in one command "
|
||||
"(e.g. setup.py sdist upload)"
|
||||
)
|
||||
raise DistutilsOptionError(msg)
|
||||
for command, pyversion, filename in self.distribution.dist_files:
|
||||
self.upload_file(command, pyversion, filename)
|
||||
|
||||
def upload_file(self, command, pyversion, filename):
|
||||
def upload_file(self, command, pyversion, filename): # noqa: C901
|
||||
# Makes sure the repository URL is compliant
|
||||
schema, netloc, url, params, query, fragments = \
|
||||
urlparse(self.repository)
|
||||
schema, netloc, url, params, query, fragments = urlparse(self.repository)
|
||||
if params or query or fragments:
|
||||
raise AssertionError("Incompatible url %s" % self.repository)
|
||||
|
||||
@@ -87,12 +85,11 @@ class upload(PyPIRCCommand):
|
||||
gpg_args = ["gpg", "--detach-sign", "-a", filename]
|
||||
if self.identity:
|
||||
gpg_args[2:2] = ["--local-user", self.identity]
|
||||
spawn(gpg_args,
|
||||
dry_run=self.dry_run)
|
||||
spawn(gpg_args, dry_run=self.dry_run)
|
||||
|
||||
# Fill in the data - send all the meta-data in case we need to
|
||||
# register a new release
|
||||
f = open(filename,'rb')
|
||||
f = open(filename, 'rb')
|
||||
try:
|
||||
content = f.read()
|
||||
finally:
|
||||
@@ -103,16 +100,13 @@ class upload(PyPIRCCommand):
|
||||
# action
|
||||
':action': 'file_upload',
|
||||
'protocol_version': '1',
|
||||
|
||||
# identify release
|
||||
'name': meta.get_name(),
|
||||
'version': meta.get_version(),
|
||||
|
||||
# file content
|
||||
'content': (os.path.basename(filename),content),
|
||||
'content': (os.path.basename(filename), content),
|
||||
'filetype': command,
|
||||
'pyversion': pyversion,
|
||||
|
||||
# additional meta-data
|
||||
'metadata_version': '1.0',
|
||||
'summary': meta.get_description(),
|
||||
@@ -129,7 +123,7 @@ class upload(PyPIRCCommand):
|
||||
'provides': meta.get_provides(),
|
||||
'requires': meta.get_requires(),
|
||||
'obsoletes': meta.get_obsoletes(),
|
||||
}
|
||||
}
|
||||
|
||||
data['comment'] = ''
|
||||
|
||||
@@ -145,8 +139,7 @@ class upload(PyPIRCCommand):
|
||||
|
||||
if self.sign:
|
||||
with open(filename + ".asc", "rb") as f:
|
||||
data['gpg_signature'] = (os.path.basename(filename) + ".asc",
|
||||
f.read())
|
||||
data['gpg_signature'] = (os.path.basename(filename) + ".asc", f.read())
|
||||
|
||||
# set up the authentication
|
||||
user_pass = (self.username + ":" + self.password).encode('ascii')
|
||||
@@ -177,7 +170,7 @@ class upload(PyPIRCCommand):
|
||||
body.write(end_boundary)
|
||||
body = body.getvalue()
|
||||
|
||||
msg = "Submitting %s to %s" % (filename, self.repository)
|
||||
msg = "Submitting {} to {}".format(filename, self.repository)
|
||||
self.announce(msg, log.INFO)
|
||||
|
||||
# build the Request
|
||||
@@ -187,8 +180,7 @@ class upload(PyPIRCCommand):
|
||||
'Authorization': auth,
|
||||
}
|
||||
|
||||
request = Request(self.repository, data=body,
|
||||
headers=headers)
|
||||
request = Request(self.repository, data=body, headers=headers)
|
||||
# send the data
|
||||
try:
|
||||
result = urlopen(request)
|
||||
@@ -202,13 +194,12 @@ class upload(PyPIRCCommand):
|
||||
raise
|
||||
|
||||
if status == 200:
|
||||
self.announce('Server response (%s): %s' % (status, reason),
|
||||
log.INFO)
|
||||
self.announce('Server response ({}): {}'.format(status, reason), log.INFO)
|
||||
if self.show_response:
|
||||
text = self._read_pypi_response(result)
|
||||
msg = '\n'.join(('-' * 75, text, '-' * 75))
|
||||
self.announce(msg, log.INFO)
|
||||
else:
|
||||
msg = 'Upload failed (%s): %s' % (status, reason)
|
||||
msg = 'Upload failed ({}): {}'.format(status, reason)
|
||||
self.announce(msg, log.ERROR)
|
||||
raise DistutilsError(msg)
|
||||
|
||||
@@ -18,20 +18,19 @@ username:%s
|
||||
password:%s
|
||||
"""
|
||||
|
||||
|
||||
class PyPIRCCommand(Command):
|
||||
"""Base command that knows how to handle the .pypirc file
|
||||
"""
|
||||
"""Base command that knows how to handle the .pypirc file"""
|
||||
|
||||
DEFAULT_REPOSITORY = 'https://upload.pypi.org/legacy/'
|
||||
DEFAULT_REALM = 'pypi'
|
||||
repository = None
|
||||
realm = None
|
||||
|
||||
user_options = [
|
||||
('repository=', 'r',
|
||||
"url of repository [default: %s]" % \
|
||||
DEFAULT_REPOSITORY),
|
||||
('show-response', None,
|
||||
'display full response text from server')]
|
||||
('repository=', 'r', "url of repository [default: %s]" % DEFAULT_REPOSITORY),
|
||||
('show-response', None, 'display full response text from server'),
|
||||
]
|
||||
|
||||
boolean_options = ['show-response']
|
||||
|
||||
@@ -45,7 +44,7 @@ class PyPIRCCommand(Command):
|
||||
with os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f:
|
||||
f.write(DEFAULT_PYPIRC % (username, password))
|
||||
|
||||
def _read_pypirc(self):
|
||||
def _read_pypirc(self): # noqa: C901
|
||||
"""Reads the .pypirc file."""
|
||||
rc = self._get_rc_file()
|
||||
if os.path.exists(rc):
|
||||
@@ -58,9 +57,11 @@ class PyPIRCCommand(Command):
|
||||
if 'distutils' in sections:
|
||||
# let's get the list of servers
|
||||
index_servers = config.get('distutils', 'index-servers')
|
||||
_servers = [server.strip() for server in
|
||||
index_servers.split('\n')
|
||||
if server.strip() != '']
|
||||
_servers = [
|
||||
server.strip()
|
||||
for server in index_servers.split('\n')
|
||||
if server.strip() != ''
|
||||
]
|
||||
if _servers == []:
|
||||
# nothing set, let's try to get the default pypi
|
||||
if 'pypi' in sections:
|
||||
@@ -74,10 +75,11 @@ class PyPIRCCommand(Command):
|
||||
current['username'] = config.get(server, 'username')
|
||||
|
||||
# optional params
|
||||
for key, default in (('repository',
|
||||
self.DEFAULT_REPOSITORY),
|
||||
('realm', self.DEFAULT_REALM),
|
||||
('password', None)):
|
||||
for key, default in (
|
||||
('repository', self.DEFAULT_REPOSITORY),
|
||||
('realm', self.DEFAULT_REALM),
|
||||
('password', None),
|
||||
):
|
||||
if config.has_option(server, key):
|
||||
current[key] = config.get(server, key)
|
||||
else:
|
||||
@@ -86,13 +88,17 @@ class PyPIRCCommand(Command):
|
||||
# work around people having "repository" for the "pypi"
|
||||
# section of their config set to the HTTP (rather than
|
||||
# HTTPS) URL
|
||||
if (server == 'pypi' and
|
||||
repository in (self.DEFAULT_REPOSITORY, 'pypi')):
|
||||
if server == 'pypi' and repository in (
|
||||
self.DEFAULT_REPOSITORY,
|
||||
'pypi',
|
||||
):
|
||||
current['repository'] = self.DEFAULT_REPOSITORY
|
||||
return current
|
||||
|
||||
if (current['server'] == repository or
|
||||
current['repository'] == repository):
|
||||
if (
|
||||
current['server'] == repository
|
||||
or current['repository'] == repository
|
||||
):
|
||||
return current
|
||||
elif 'server-login' in sections:
|
||||
# old format
|
||||
@@ -101,17 +107,20 @@ class PyPIRCCommand(Command):
|
||||
repository = config.get(server, 'repository')
|
||||
else:
|
||||
repository = self.DEFAULT_REPOSITORY
|
||||
return {'username': config.get(server, 'username'),
|
||||
'password': config.get(server, 'password'),
|
||||
'repository': repository,
|
||||
'server': server,
|
||||
'realm': self.DEFAULT_REALM}
|
||||
return {
|
||||
'username': config.get(server, 'username'),
|
||||
'password': config.get(server, 'password'),
|
||||
'repository': repository,
|
||||
'server': server,
|
||||
'realm': self.DEFAULT_REALM,
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
def _read_pypi_response(self, response):
|
||||
"""Read and decode a PyPI HTTP response."""
|
||||
import cgi
|
||||
|
||||
content_type = response.getheader('content-type', 'text/plain')
|
||||
encoding = cgi.parse_header(content_type)[1].get('charset', 'ascii')
|
||||
return response.read().decode(encoding)
|
||||
|
||||
@@ -8,9 +8,15 @@ really defined in distutils.dist and distutils.cmd.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tokenize
|
||||
|
||||
from distutils.debug import DEBUG
|
||||
from distutils.errors import *
|
||||
from distutils.errors import (
|
||||
DistutilsSetupError,
|
||||
DistutilsError,
|
||||
CCompilerError,
|
||||
DistutilsArgError,
|
||||
)
|
||||
|
||||
# Mainly import these so setup scripts can "from distutils.core import" them.
|
||||
from distutils.dist import Distribution
|
||||
@@ -18,6 +24,9 @@ from distutils.cmd import Command
|
||||
from distutils.config import PyPIRCCommand
|
||||
from distutils.extension import Extension
|
||||
|
||||
|
||||
__all__ = ['Distribution', 'Command', 'PyPIRCCommand', 'Extension', 'setup']
|
||||
|
||||
# This is a barebones help message generated displayed when the user
|
||||
# runs the setup script with no arguments at all. More useful help
|
||||
# is generated with various --help options: global help, list commands,
|
||||
@@ -29,9 +38,10 @@ usage: %(script)s [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
|
||||
or: %(script)s cmd --help
|
||||
"""
|
||||
|
||||
def gen_usage (script_name):
|
||||
|
||||
def gen_usage(script_name):
|
||||
script = os.path.basename(script_name)
|
||||
return USAGE % vars()
|
||||
return USAGE % locals()
|
||||
|
||||
|
||||
# Some mild magic to control the behaviour of 'setup()' from 'run_setup()'.
|
||||
@@ -39,22 +49,51 @@ _setup_stop_after = None
|
||||
_setup_distribution = None
|
||||
|
||||
# Legal keyword arguments for the setup() function
|
||||
setup_keywords = ('distclass', 'script_name', 'script_args', 'options',
|
||||
'name', 'version', 'author', 'author_email',
|
||||
'maintainer', 'maintainer_email', 'url', 'license',
|
||||
'description', 'long_description', 'keywords',
|
||||
'platforms', 'classifiers', 'download_url',
|
||||
'requires', 'provides', 'obsoletes',
|
||||
)
|
||||
setup_keywords = (
|
||||
'distclass',
|
||||
'script_name',
|
||||
'script_args',
|
||||
'options',
|
||||
'name',
|
||||
'version',
|
||||
'author',
|
||||
'author_email',
|
||||
'maintainer',
|
||||
'maintainer_email',
|
||||
'url',
|
||||
'license',
|
||||
'description',
|
||||
'long_description',
|
||||
'keywords',
|
||||
'platforms',
|
||||
'classifiers',
|
||||
'download_url',
|
||||
'requires',
|
||||
'provides',
|
||||
'obsoletes',
|
||||
)
|
||||
|
||||
# Legal keyword arguments for the Extension constructor
|
||||
extension_keywords = ('name', 'sources', 'include_dirs',
|
||||
'define_macros', 'undef_macros',
|
||||
'library_dirs', 'libraries', 'runtime_library_dirs',
|
||||
'extra_objects', 'extra_compile_args', 'extra_link_args',
|
||||
'swig_opts', 'export_symbols', 'depends', 'language')
|
||||
extension_keywords = (
|
||||
'name',
|
||||
'sources',
|
||||
'include_dirs',
|
||||
'define_macros',
|
||||
'undef_macros',
|
||||
'library_dirs',
|
||||
'libraries',
|
||||
'runtime_library_dirs',
|
||||
'extra_objects',
|
||||
'extra_compile_args',
|
||||
'extra_link_args',
|
||||
'swig_opts',
|
||||
'export_symbols',
|
||||
'depends',
|
||||
'language',
|
||||
)
|
||||
|
||||
def setup (**attrs):
|
||||
|
||||
def setup(**attrs): # noqa: C901
|
||||
"""The gateway to the Distutils: do everything your setup script needs
|
||||
to do, in a highly flexible and user-driven way. Briefly: create a
|
||||
Distribution instance; find and parse config files; parse the command
|
||||
@@ -99,7 +138,7 @@ def setup (**attrs):
|
||||
|
||||
if 'script_name' not in attrs:
|
||||
attrs['script_name'] = os.path.basename(sys.argv[0])
|
||||
if 'script_args' not in attrs:
|
||||
if 'script_args' not in attrs:
|
||||
attrs['script_args'] = sys.argv[1:]
|
||||
|
||||
# Create the Distribution instance, using the remaining arguments
|
||||
@@ -110,8 +149,7 @@ def setup (**attrs):
|
||||
if 'name' not in attrs:
|
||||
raise SystemExit("error in setup command: %s" % msg)
|
||||
else:
|
||||
raise SystemExit("error in %s setup command: %s" % \
|
||||
(attrs['name'], msg))
|
||||
raise SystemExit("error in {} setup command: {}".format(attrs['name'], msg))
|
||||
|
||||
if _setup_stop_after == "init":
|
||||
return dist
|
||||
@@ -144,30 +182,42 @@ def setup (**attrs):
|
||||
|
||||
# And finally, run all the commands found on the command line.
|
||||
if ok:
|
||||
try:
|
||||
dist.run_commands()
|
||||
except KeyboardInterrupt:
|
||||
raise SystemExit("interrupted")
|
||||
except OSError as exc:
|
||||
if DEBUG:
|
||||
sys.stderr.write("error: %s\n" % (exc,))
|
||||
raise
|
||||
else:
|
||||
raise SystemExit("error: %s" % (exc,))
|
||||
|
||||
except (DistutilsError,
|
||||
CCompilerError) as msg:
|
||||
if DEBUG:
|
||||
raise
|
||||
else:
|
||||
raise SystemExit("error: " + str(msg))
|
||||
return run_commands(dist)
|
||||
|
||||
return dist
|
||||
|
||||
|
||||
# setup ()
|
||||
|
||||
|
||||
def run_setup (script_name, script_args=None, stop_after="run"):
|
||||
def run_commands(dist):
|
||||
"""Given a Distribution object run all the commands,
|
||||
raising ``SystemExit`` errors in the case of failure.
|
||||
|
||||
This function assumes that either ``sys.argv`` or ``dist.script_args``
|
||||
is already set accordingly.
|
||||
"""
|
||||
try:
|
||||
dist.run_commands()
|
||||
except KeyboardInterrupt:
|
||||
raise SystemExit("interrupted")
|
||||
except OSError as exc:
|
||||
if DEBUG:
|
||||
sys.stderr.write("error: {}\n".format(exc))
|
||||
raise
|
||||
else:
|
||||
raise SystemExit("error: {}".format(exc))
|
||||
|
||||
except (DistutilsError, CCompilerError) as msg:
|
||||
if DEBUG:
|
||||
raise
|
||||
else:
|
||||
raise SystemExit("error: " + str(msg))
|
||||
|
||||
return dist
|
||||
|
||||
|
||||
def run_setup(script_name, script_args=None, stop_after="run"):
|
||||
"""Run a setup script in a somewhat controlled environment, and
|
||||
return the Distribution instance that drives things. This is useful
|
||||
if you need to find out the distribution meta-data (passed as
|
||||
@@ -199,20 +249,22 @@ def run_setup (script_name, script_args=None, stop_after="run"):
|
||||
used to drive the Distutils.
|
||||
"""
|
||||
if stop_after not in ('init', 'config', 'commandline', 'run'):
|
||||
raise ValueError("invalid value for 'stop_after': %r" % (stop_after,))
|
||||
raise ValueError("invalid value for 'stop_after': {!r}".format(stop_after))
|
||||
|
||||
global _setup_stop_after, _setup_distribution
|
||||
_setup_stop_after = stop_after
|
||||
|
||||
save_argv = sys.argv.copy()
|
||||
g = {'__file__': script_name}
|
||||
g = {'__file__': script_name, '__name__': '__main__'}
|
||||
try:
|
||||
try:
|
||||
sys.argv[0] = script_name
|
||||
if script_args is not None:
|
||||
sys.argv[1:] = script_args
|
||||
with open(script_name, 'rb') as f:
|
||||
exec(f.read(), g)
|
||||
# tokenize.open supports automatic encoding detection
|
||||
with tokenize.open(script_name) as f:
|
||||
code = f.read().replace(r'\r\n', r'\n')
|
||||
exec(code, g)
|
||||
finally:
|
||||
sys.argv = save_argv
|
||||
_setup_stop_after = None
|
||||
@@ -222,13 +274,18 @@ def run_setup (script_name, script_args=None, stop_after="run"):
|
||||
pass
|
||||
|
||||
if _setup_distribution is None:
|
||||
raise RuntimeError(("'distutils.core.setup()' was never called -- "
|
||||
"perhaps '%s' is not a Distutils setup script?") % \
|
||||
script_name)
|
||||
raise RuntimeError(
|
||||
(
|
||||
"'distutils.core.setup()' was never called -- "
|
||||
"perhaps '%s' is not a Distutils setup script?"
|
||||
)
|
||||
% script_name
|
||||
)
|
||||
|
||||
# I wonder if the setup script's namespace -- g and l -- would be of
|
||||
# any interest to callers?
|
||||
#print "_setup_distribution:", _setup_distribution
|
||||
# print "_setup_distribution:", _setup_distribution
|
||||
return _setup_distribution
|
||||
|
||||
|
||||
# run_setup ()
|
||||
|
||||
@@ -6,59 +6,23 @@ the Mingw32CCompiler class which handles the mingw32 port of GCC (same as
|
||||
cygwin in no-cygwin mode).
|
||||
"""
|
||||
|
||||
# problems:
|
||||
#
|
||||
# * if you use a msvc compiled python version (1.5.2)
|
||||
# 1. you have to insert a __GNUC__ section in its config.h
|
||||
# 2. you have to generate an import library for its dll
|
||||
# - create a def-file for python??.dll
|
||||
# - create an import library using
|
||||
# dlltool --dllname python15.dll --def python15.def \
|
||||
# --output-lib libpython15.a
|
||||
#
|
||||
# see also http://starship.python.net/crew/kernr/mingw32/Notes.html
|
||||
#
|
||||
# * We put export_symbols in a def-file, and don't use
|
||||
# --export-all-symbols because it doesn't worked reliable in some
|
||||
# tested configurations. And because other windows compilers also
|
||||
# need their symbols specified this no serious problem.
|
||||
#
|
||||
# tested configurations:
|
||||
#
|
||||
# * cygwin gcc 2.91.57/ld 2.9.4/dllwrap 0.2.4 works
|
||||
# (after patching python's config.h and for C++ some other include files)
|
||||
# see also http://starship.python.net/crew/kernr/mingw32/Notes.html
|
||||
# * mingw32 gcc 2.95.2/ld 2.9.4/dllwrap 0.2.4 works
|
||||
# (ld doesn't support -shared, so we use dllwrap)
|
||||
# * cygwin gcc 2.95.2/ld 2.10.90/dllwrap 2.10.90 works now
|
||||
# - its dllwrap doesn't work, there is a bug in binutils 2.10.90
|
||||
# see also http://sources.redhat.com/ml/cygwin/2000-06/msg01274.html
|
||||
# - using gcc -mdll instead dllwrap doesn't work without -static because
|
||||
# it tries to link against dlls instead their import libraries. (If
|
||||
# it finds the dll first.)
|
||||
# By specifying -static we force ld to link against the import libraries,
|
||||
# this is windows standard and there are normally not the necessary symbols
|
||||
# in the dlls.
|
||||
# *** only the version of June 2000 shows these problems
|
||||
# * cygwin gcc 3.2/ld 2.13.90 works
|
||||
# (ld supports -shared)
|
||||
# * mingw gcc 3.2/ld 2.13 works
|
||||
# (ld supports -shared)
|
||||
# * llvm-mingw with Clang 11 works
|
||||
# (lld supports -shared)
|
||||
|
||||
import os
|
||||
import sys
|
||||
import copy
|
||||
from subprocess import Popen, PIPE, check_output
|
||||
import re
|
||||
import shlex
|
||||
import warnings
|
||||
from subprocess import check_output
|
||||
|
||||
from distutils.unixccompiler import UnixCCompiler
|
||||
from distutils.file_util import write_file
|
||||
from distutils.errors import (DistutilsExecError, CCompilerError,
|
||||
CompileError, UnknownFileError)
|
||||
from distutils.version import LooseVersion
|
||||
from distutils.spawn import find_executable
|
||||
from distutils.errors import (
|
||||
DistutilsExecError,
|
||||
DistutilsPlatformError,
|
||||
CCompilerError,
|
||||
CompileError,
|
||||
)
|
||||
from distutils.version import LooseVersion, suppress_known_deprecation
|
||||
|
||||
|
||||
def get_msvcr():
|
||||
"""Include the appropriate MSVC runtime library if Python was built
|
||||
@@ -66,7 +30,7 @@ def get_msvcr():
|
||||
"""
|
||||
msc_pos = sys.version.find('MSC v.')
|
||||
if msc_pos != -1:
|
||||
msc_ver = sys.version[msc_pos+6:msc_pos+10]
|
||||
msc_ver = sys.version[msc_pos + 6 : msc_pos + 10]
|
||||
if msc_ver == '1300':
|
||||
# MSVC 7.0
|
||||
return ['msvcr70']
|
||||
@@ -82,84 +46,85 @@ def get_msvcr():
|
||||
elif msc_ver == '1600':
|
||||
# VS2010 / MSVC 10.0
|
||||
return ['msvcr100']
|
||||
elif msc_ver == '1700':
|
||||
# VS2012 / MSVC 11.0
|
||||
return ['msvcr110']
|
||||
elif msc_ver == '1800':
|
||||
# VS2013 / MSVC 12.0
|
||||
return ['msvcr120']
|
||||
elif 1900 <= int(msc_ver) < 2000:
|
||||
# VS2015 / MSVC 14.0
|
||||
return ['ucrt', 'vcruntime140']
|
||||
else:
|
||||
raise ValueError("Unknown MS Compiler version %s " % msc_ver)
|
||||
|
||||
|
||||
_runtime_library_dirs_msg = (
|
||||
"Unable to set runtime library search path on Windows, "
|
||||
"usually indicated by `runtime_library_dirs` parameter to Extension"
|
||||
)
|
||||
|
||||
|
||||
class CygwinCCompiler(UnixCCompiler):
|
||||
""" Handles the Cygwin port of the GNU C compiler to Windows.
|
||||
"""
|
||||
"""Handles the Cygwin port of the GNU C compiler to Windows."""
|
||||
|
||||
compiler_type = 'cygwin'
|
||||
obj_extension = ".o"
|
||||
static_lib_extension = ".a"
|
||||
shared_lib_extension = ".dll"
|
||||
shared_lib_extension = ".dll.a"
|
||||
dylib_lib_extension = ".dll"
|
||||
static_lib_format = "lib%s%s"
|
||||
shared_lib_format = "%s%s"
|
||||
shared_lib_format = "lib%s%s"
|
||||
dylib_lib_format = "cyg%s%s"
|
||||
exe_extension = ".exe"
|
||||
|
||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
||||
|
||||
UnixCCompiler.__init__(self, verbose, dry_run, force)
|
||||
super().__init__(verbose, dry_run, force)
|
||||
|
||||
status, details = check_config_h()
|
||||
self.debug_print("Python's GCC status: %s (details: %s)" %
|
||||
(status, details))
|
||||
self.debug_print(
|
||||
"Python's GCC status: {} (details: {})".format(status, details)
|
||||
)
|
||||
if status is not CONFIG_H_OK:
|
||||
self.warn(
|
||||
"Python's pyconfig.h doesn't seem to support your compiler. "
|
||||
"Reason: %s. "
|
||||
"Compiling may fail because of undefined preprocessor macros."
|
||||
% details)
|
||||
"Compiling may fail because of undefined preprocessor macros." % details
|
||||
)
|
||||
|
||||
self.cc = os.environ.get('CC', 'gcc')
|
||||
self.cxx = os.environ.get('CXX', 'g++')
|
||||
|
||||
if ('gcc' in self.cc): # Start gcc workaround
|
||||
self.gcc_version, self.ld_version, self.dllwrap_version = \
|
||||
get_versions()
|
||||
self.debug_print(self.compiler_type + ": gcc %s, ld %s, dllwrap %s\n" %
|
||||
(self.gcc_version,
|
||||
self.ld_version,
|
||||
self.dllwrap_version) )
|
||||
self.linker_dll = self.cc
|
||||
shared_option = "-shared"
|
||||
|
||||
# ld_version >= "2.10.90" and < "2.13" should also be able to use
|
||||
# gcc -mdll instead of dllwrap
|
||||
# Older dllwraps had own version numbers, newer ones use the
|
||||
# same as the rest of binutils ( also ld )
|
||||
# dllwrap 2.10.90 is buggy
|
||||
if self.ld_version >= "2.10.90":
|
||||
self.linker_dll = self.cc
|
||||
else:
|
||||
self.linker_dll = "dllwrap"
|
||||
self.set_executables(
|
||||
compiler='%s -mcygwin -O -Wall' % self.cc,
|
||||
compiler_so='%s -mcygwin -mdll -O -Wall' % self.cc,
|
||||
compiler_cxx='%s -mcygwin -O -Wall' % self.cxx,
|
||||
linker_exe='%s -mcygwin' % self.cc,
|
||||
linker_so=('{} -mcygwin {}'.format(self.linker_dll, shared_option)),
|
||||
)
|
||||
|
||||
# ld_version >= "2.13" support -shared so use it instead of
|
||||
# -mdll -static
|
||||
if self.ld_version >= "2.13":
|
||||
shared_option = "-shared"
|
||||
else:
|
||||
shared_option = "-mdll -static"
|
||||
else: # Assume linker is up to date
|
||||
self.linker_dll = self.cc
|
||||
shared_option = "-shared"
|
||||
# Include the appropriate MSVC runtime library if Python was built
|
||||
# with MSVC 7.0 or later.
|
||||
self.dll_libraries = get_msvcr()
|
||||
|
||||
self.set_executables(compiler='%s -mcygwin -O -Wall' % self.cc,
|
||||
compiler_so='%s -mcygwin -mdll -O -Wall' % self.cc,
|
||||
compiler_cxx='%s -mcygwin -O -Wall' % self.cxx,
|
||||
linker_exe='%s -mcygwin' % self.cc,
|
||||
linker_so=('%s -mcygwin %s' %
|
||||
(self.linker_dll, shared_option)))
|
||||
|
||||
# cygwin and mingw32 need different sets of libraries
|
||||
if ('gcc' in self.cc and self.gcc_version == "2.91.57"):
|
||||
# cygwin shouldn't need msvcrt, but without the dlls will crash
|
||||
# (gcc version 2.91.57) -- perhaps something about initialization
|
||||
self.dll_libraries=["msvcrt"]
|
||||
self.warn(
|
||||
"Consider upgrading to a newer version of gcc")
|
||||
else:
|
||||
# Include the appropriate MSVC runtime library if Python was built
|
||||
# with MSVC 7.0 or later.
|
||||
self.dll_libraries = get_msvcr()
|
||||
@property
|
||||
def gcc_version(self):
|
||||
# Older numpy dependend on this existing to check for ancient
|
||||
# gcc versions. This doesn't make much sense with clang etc so
|
||||
# just hardcode to something recent.
|
||||
# https://github.com/numpy/numpy/pull/20333
|
||||
warnings.warn(
|
||||
"gcc_version attribute of CygwinCCompiler is deprecated. "
|
||||
"Instead of returning actual gcc version a fixed value 11.2.0 is returned.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
with suppress_known_deprecation():
|
||||
return LooseVersion("11.2.0")
|
||||
|
||||
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
|
||||
"""Compiles the source by spawning GCC and windres if needed."""
|
||||
@@ -169,30 +134,47 @@ class CygwinCCompiler(UnixCCompiler):
|
||||
self.spawn(["windres", "-i", src, "-o", obj])
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
else: # for other files use the C-compiler
|
||||
else: # for other files use the C-compiler
|
||||
try:
|
||||
self.spawn(self.compiler_so + cc_args + [src, '-o', obj] +
|
||||
extra_postargs)
|
||||
self.spawn(
|
||||
self.compiler_so + cc_args + [src, '-o', obj] + extra_postargs
|
||||
)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
def link(self, target_desc, objects, output_filename, output_dir=None,
|
||||
libraries=None, library_dirs=None, runtime_library_dirs=None,
|
||||
export_symbols=None, debug=0, extra_preargs=None,
|
||||
extra_postargs=None, build_temp=None, target_lang=None):
|
||||
def link(
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
"""Link the objects."""
|
||||
# use separate copies, so we can modify the lists
|
||||
extra_preargs = copy.copy(extra_preargs or [])
|
||||
libraries = copy.copy(libraries or [])
|
||||
objects = copy.copy(objects or [])
|
||||
|
||||
if runtime_library_dirs:
|
||||
self.warn(_runtime_library_dirs_msg)
|
||||
|
||||
# Additional libraries
|
||||
libraries.extend(self.dll_libraries)
|
||||
|
||||
# handle export symbols by creating a def-file
|
||||
# with executables this only works with gcc/ld as linker
|
||||
if ((export_symbols is not None) and
|
||||
(target_desc != self.EXECUTABLE or self.linker_dll == "gcc")):
|
||||
if (export_symbols is not None) and (
|
||||
target_desc != self.EXECUTABLE or self.linker_dll == "gcc"
|
||||
):
|
||||
# (The linker doesn't do anything if output is up-to-date.
|
||||
# So it would probably better to check if we really need this,
|
||||
# but for this we had to insert some unchanged parts of
|
||||
@@ -204,124 +186,115 @@ class CygwinCCompiler(UnixCCompiler):
|
||||
temp_dir = os.path.dirname(objects[0])
|
||||
# name of dll to give the helper files the same base name
|
||||
(dll_name, dll_extension) = os.path.splitext(
|
||||
os.path.basename(output_filename))
|
||||
os.path.basename(output_filename)
|
||||
)
|
||||
|
||||
# generate the filenames for these files
|
||||
def_file = os.path.join(temp_dir, dll_name + ".def")
|
||||
lib_file = os.path.join(temp_dir, 'lib' + dll_name + ".a")
|
||||
|
||||
# Generate .def file
|
||||
contents = [
|
||||
"LIBRARY %s" % os.path.basename(output_filename),
|
||||
"EXPORTS"]
|
||||
contents = ["LIBRARY %s" % os.path.basename(output_filename), "EXPORTS"]
|
||||
for sym in export_symbols:
|
||||
contents.append(sym)
|
||||
self.execute(write_file, (def_file, contents),
|
||||
"writing %s" % def_file)
|
||||
self.execute(write_file, (def_file, contents), "writing %s" % def_file)
|
||||
|
||||
# next add options for def-file and to creating import libraries
|
||||
# next add options for def-file
|
||||
|
||||
# dllwrap uses different options than gcc/ld
|
||||
if self.linker_dll == "dllwrap":
|
||||
extra_preargs.extend(["--output-lib", lib_file])
|
||||
# for dllwrap we have to use a special option
|
||||
extra_preargs.extend(["--def", def_file])
|
||||
# we use gcc/ld here and can be sure ld is >= 2.9.10
|
||||
else:
|
||||
# doesn't work: bfd_close build\...\libfoo.a: Invalid operation
|
||||
#extra_preargs.extend(["-Wl,--out-implib,%s" % lib_file])
|
||||
# for gcc/ld the def-file is specified as any object files
|
||||
objects.append(def_file)
|
||||
# for gcc/ld the def-file is specified as any object files
|
||||
objects.append(def_file)
|
||||
|
||||
#end: if ((export_symbols is not None) and
|
||||
# end: if ((export_symbols is not None) and
|
||||
# (target_desc != self.EXECUTABLE or self.linker_dll == "gcc")):
|
||||
|
||||
# who wants symbols and a many times larger output file
|
||||
# should explicitly switch the debug mode on
|
||||
# otherwise we let dllwrap/ld strip the output file
|
||||
# otherwise we let ld strip the output file
|
||||
# (On my machine: 10KiB < stripped_file < ??100KiB
|
||||
# unstripped_file = stripped_file + XXX KiB
|
||||
# ( XXX=254 for a typical python extension))
|
||||
if not debug:
|
||||
extra_preargs.append("-s")
|
||||
|
||||
UnixCCompiler.link(self, target_desc, objects, output_filename,
|
||||
output_dir, libraries, library_dirs,
|
||||
runtime_library_dirs,
|
||||
None, # export_symbols, we do this in our def-file
|
||||
debug, extra_preargs, extra_postargs, build_temp,
|
||||
target_lang)
|
||||
UnixCCompiler.link(
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir,
|
||||
libraries,
|
||||
library_dirs,
|
||||
runtime_library_dirs,
|
||||
None, # export_symbols, we do this in our def-file
|
||||
debug,
|
||||
extra_preargs,
|
||||
extra_postargs,
|
||||
build_temp,
|
||||
target_lang,
|
||||
)
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
# cygwin doesn't support rpath. While in theory we could error
|
||||
# out like MSVC does, code might expect it to work like on Unix, so
|
||||
# just warn and hope for the best.
|
||||
self.warn(_runtime_library_dirs_msg)
|
||||
return []
|
||||
|
||||
# -- Miscellaneous methods -----------------------------------------
|
||||
|
||||
def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
|
||||
"""Adds supports for rc and res files."""
|
||||
if output_dir is None:
|
||||
output_dir = ''
|
||||
obj_names = []
|
||||
for src_name in source_filenames:
|
||||
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
|
||||
base, ext = os.path.splitext(os.path.normcase(src_name))
|
||||
if ext not in (self.src_extensions + ['.rc','.res']):
|
||||
raise UnknownFileError("unknown file type '%s' (from '%s')" % \
|
||||
(ext, src_name))
|
||||
if strip_dir:
|
||||
base = os.path.basename (base)
|
||||
if ext in ('.res', '.rc'):
|
||||
# these need to be compiled to object files
|
||||
obj_names.append (os.path.join(output_dir,
|
||||
base + ext + self.obj_extension))
|
||||
else:
|
||||
obj_names.append (os.path.join(output_dir,
|
||||
base + self.obj_extension))
|
||||
return obj_names
|
||||
def _make_out_path(self, output_dir, strip_dir, src_name):
|
||||
# use normcase to make sure '.rc' is really '.rc' and not '.RC'
|
||||
norm_src_name = os.path.normcase(src_name)
|
||||
return super()._make_out_path(output_dir, strip_dir, norm_src_name)
|
||||
|
||||
@property
|
||||
def out_extensions(self):
|
||||
"""
|
||||
Add support for rc and res files.
|
||||
"""
|
||||
return {
|
||||
**super().out_extensions,
|
||||
**{ext: ext + self.obj_extension for ext in ('.res', '.rc')},
|
||||
}
|
||||
|
||||
|
||||
# the same as cygwin plus some additional parameters
|
||||
class Mingw32CCompiler(CygwinCCompiler):
|
||||
""" Handles the Mingw32 port of the GNU C compiler to Windows.
|
||||
"""
|
||||
"""Handles the Mingw32 port of the GNU C compiler to Windows."""
|
||||
|
||||
compiler_type = 'mingw32'
|
||||
|
||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
||||
|
||||
CygwinCCompiler.__init__ (self, verbose, dry_run, force)
|
||||
super().__init__(verbose, dry_run, force)
|
||||
|
||||
# ld_version >= "2.13" support -shared so use it instead of
|
||||
# -mdll -static
|
||||
if ('gcc' in self.cc and self.ld_version < "2.13"):
|
||||
shared_option = "-mdll -static"
|
||||
else:
|
||||
shared_option = "-shared"
|
||||
|
||||
# A real mingw32 doesn't need to specify a different entry point,
|
||||
# but cygwin 2.91.57 in no-cygwin-mode needs it.
|
||||
if ('gcc' in self.cc and self.gcc_version <= "2.91.57"):
|
||||
entry_point = '--entry _DllMain@12'
|
||||
else:
|
||||
entry_point = ''
|
||||
shared_option = "-shared"
|
||||
|
||||
if is_cygwincc(self.cc):
|
||||
raise CCompilerError(
|
||||
'Cygwin gcc cannot be used with --compiler=mingw32')
|
||||
raise CCompilerError('Cygwin gcc cannot be used with --compiler=mingw32')
|
||||
|
||||
self.set_executables(
|
||||
compiler='%s -O -Wall' % self.cc,
|
||||
compiler_so='%s -mdll -O -Wall' % self.cc,
|
||||
compiler_cxx='%s -O -Wall' % self.cxx,
|
||||
linker_exe='%s' % self.cc,
|
||||
linker_so='{} {}'.format(self.linker_dll, shared_option),
|
||||
)
|
||||
|
||||
self.set_executables(compiler='%s -O -Wall' % self.cc,
|
||||
compiler_so='%s -mdll -O -Wall' % self.cc,
|
||||
compiler_cxx='%s -O -Wall' % self.cxx,
|
||||
linker_exe='%s' % self.cc,
|
||||
linker_so='%s %s %s'
|
||||
% (self.linker_dll, shared_option,
|
||||
entry_point))
|
||||
# Maybe we should also append -mthreads, but then the finished
|
||||
# dlls need another dll (mingwm10.dll see Mingw32 docs)
|
||||
# (-mthreads: Support thread-safe exception handling on `Mingw32')
|
||||
|
||||
# no additional libraries needed
|
||||
self.dll_libraries=[]
|
||||
self.dll_libraries = []
|
||||
|
||||
# Include the appropriate MSVC runtime library if Python was built
|
||||
# with MSVC 7.0 or later.
|
||||
self.dll_libraries = get_msvcr()
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
raise DistutilsPlatformError(_runtime_library_dirs_msg)
|
||||
|
||||
|
||||
# Because these compilers aren't configured in Python's pyconfig.h file by
|
||||
# default, we should at least warn the user if he is using an unmodified
|
||||
# version.
|
||||
@@ -330,6 +303,7 @@ CONFIG_H_OK = "ok"
|
||||
CONFIG_H_NOTOK = "not ok"
|
||||
CONFIG_H_UNCERTAIN = "uncertain"
|
||||
|
||||
|
||||
def check_config_h():
|
||||
"""Check if the current Python installation appears amenable to building
|
||||
extensions with GCC.
|
||||
@@ -374,41 +348,17 @@ def check_config_h():
|
||||
finally:
|
||||
config_h.close()
|
||||
except OSError as exc:
|
||||
return (CONFIG_H_UNCERTAIN,
|
||||
"couldn't read '%s': %s" % (fn, exc.strerror))
|
||||
return (CONFIG_H_UNCERTAIN, "couldn't read '{}': {}".format(fn, exc.strerror))
|
||||
|
||||
RE_VERSION = re.compile(br'(\d+\.\d+(\.\d+)*)')
|
||||
|
||||
def _find_exe_version(cmd):
|
||||
"""Find the version of an executable by running `cmd` in the shell.
|
||||
|
||||
If the command is not found, or the output does not match
|
||||
`RE_VERSION`, returns None.
|
||||
"""
|
||||
executable = cmd.split()[0]
|
||||
if find_executable(executable) is None:
|
||||
return None
|
||||
out = Popen(cmd, shell=True, stdout=PIPE).stdout
|
||||
try:
|
||||
out_string = out.read()
|
||||
finally:
|
||||
out.close()
|
||||
result = RE_VERSION.search(out_string)
|
||||
if result is None:
|
||||
return None
|
||||
# LooseVersion works with strings
|
||||
# so we need to decode our bytes
|
||||
return LooseVersion(result.group(1).decode())
|
||||
|
||||
def get_versions():
|
||||
""" Try to find out the versions of gcc, ld and dllwrap.
|
||||
|
||||
If not possible it returns None for it.
|
||||
"""
|
||||
commands = ['gcc -dumpversion', 'ld -v', 'dllwrap --version']
|
||||
return tuple([_find_exe_version(cmd) for cmd in commands])
|
||||
|
||||
def is_cygwincc(cc):
|
||||
'''Try to determine if the compiler that would be used is from cygwin.'''
|
||||
out_string = check_output([cc, '-dumpmachine'])
|
||||
out_string = check_output(shlex.split(cc) + ['-dumpmachine'])
|
||||
return out_string.strip().endswith(b'cygwin')
|
||||
|
||||
|
||||
get_versions = None
|
||||
"""
|
||||
A stand-in for the previous get_versions() function to prevent failures
|
||||
when monkeypatched. See pypa/setuptools#2969.
|
||||
"""
|
||||
|
||||
@@ -8,28 +8,29 @@ import os
|
||||
from distutils.errors import DistutilsFileError
|
||||
|
||||
|
||||
def newer (source, target):
|
||||
def newer(source, target):
|
||||
"""Return true if 'source' exists and is more recently modified than
|
||||
'target', or if 'source' exists and 'target' doesn't. Return false if
|
||||
both exist and 'target' is the same age or younger than 'source'.
|
||||
Raise DistutilsFileError if 'source' does not exist.
|
||||
"""
|
||||
if not os.path.exists(source):
|
||||
raise DistutilsFileError("file '%s' does not exist" %
|
||||
os.path.abspath(source))
|
||||
raise DistutilsFileError("file '%s' does not exist" % os.path.abspath(source))
|
||||
if not os.path.exists(target):
|
||||
return 1
|
||||
|
||||
from stat import ST_MTIME
|
||||
|
||||
mtime1 = os.stat(source)[ST_MTIME]
|
||||
mtime2 = os.stat(target)[ST_MTIME]
|
||||
|
||||
return mtime1 > mtime2
|
||||
|
||||
|
||||
# newer ()
|
||||
|
||||
|
||||
def newer_pairwise (sources, targets):
|
||||
def newer_pairwise(sources, targets):
|
||||
"""Walk two filename lists in parallel, testing if each source is newer
|
||||
than its corresponding target. Return a pair of lists (sources,
|
||||
targets) where source is newer than target, according to the semantics
|
||||
@@ -48,10 +49,11 @@ def newer_pairwise (sources, targets):
|
||||
|
||||
return (n_sources, n_targets)
|
||||
|
||||
|
||||
# newer_pairwise ()
|
||||
|
||||
|
||||
def newer_group (sources, target, missing='error'):
|
||||
def newer_group(sources, target, missing='error'):
|
||||
"""Return true if 'target' is out-of-date with respect to any file
|
||||
listed in 'sources'. In other words, if 'target' exists and is newer
|
||||
than every file in 'sources', return false; otherwise return true.
|
||||
@@ -73,15 +75,16 @@ def newer_group (sources, target, missing='error'):
|
||||
# we can immediately return true. If we fall through to the end
|
||||
# of the loop, then 'target' is up-to-date and we return false.
|
||||
from stat import ST_MTIME
|
||||
|
||||
target_mtime = os.stat(target)[ST_MTIME]
|
||||
for source in sources:
|
||||
if not os.path.exists(source):
|
||||
if missing == 'error': # blow up when we stat() the file
|
||||
if missing == 'error': # blow up when we stat() the file
|
||||
pass
|
||||
elif missing == 'ignore': # missing source dropped from
|
||||
continue # target's dependency list
|
||||
elif missing == 'newer': # missing source means target is
|
||||
return 1 # out-of-date
|
||||
elif missing == 'ignore': # missing source dropped from
|
||||
continue # target's dependency list
|
||||
elif missing == 'newer': # missing source means target is
|
||||
return 1 # out-of-date
|
||||
|
||||
source_mtime = os.stat(source)[ST_MTIME]
|
||||
if source_mtime > target_mtime:
|
||||
@@ -89,4 +92,5 @@ def newer_group (sources, target, missing='error'):
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
# newer_group ()
|
||||
|
||||
@@ -4,17 +4,15 @@ Utility functions for manipulating directories and directory trees."""
|
||||
|
||||
import os
|
||||
import errno
|
||||
from distutils.errors import DistutilsFileError, DistutilsInternalError
|
||||
from distutils.errors import DistutilsInternalError, DistutilsFileError
|
||||
from distutils import log
|
||||
|
||||
# cache for by mkpath() -- in addition to cheapening redundant calls,
|
||||
# eliminates redundant "creating /foo/bar/baz" messages in dry-run mode
|
||||
_path_created = {}
|
||||
|
||||
# I don't use os.makedirs because a) it's new to Python 1.5.2, and
|
||||
# b) it blows up if the directory already exists (I want to silently
|
||||
# succeed in that case).
|
||||
def mkpath(name, mode=0o777, verbose=1, dry_run=0):
|
||||
|
||||
def mkpath(name, mode=0o777, verbose=1, dry_run=0): # noqa: C901
|
||||
"""Create a directory and any missing ancestor directories.
|
||||
|
||||
If the directory already exists (or if 'name' is the empty string, which
|
||||
@@ -23,6 +21,12 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0):
|
||||
(eg. some sub-path exists, but is a file rather than a directory).
|
||||
If 'verbose' is true, print a one-line summary of each mkdir to stdout.
|
||||
Return the list of directories actually created.
|
||||
|
||||
os.makedirs is not used because:
|
||||
|
||||
a) It's new to Python 1.5.2, and
|
||||
b) it blows up if the directory already exists (in which case it should
|
||||
silently succeed).
|
||||
"""
|
||||
|
||||
global _path_created
|
||||
@@ -30,7 +34,8 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0):
|
||||
# Detect a common bug -- name is None
|
||||
if not isinstance(name, str):
|
||||
raise DistutilsInternalError(
|
||||
"mkpath: 'name' must be a string (got %r)" % (name,))
|
||||
"mkpath: 'name' must be a string (got {!r})".format(name)
|
||||
)
|
||||
|
||||
# XXX what's the better way to handle verbosity? print as we create
|
||||
# each directory in the path (the current behaviour), or only announce
|
||||
@@ -45,17 +50,17 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0):
|
||||
return created_dirs
|
||||
|
||||
(head, tail) = os.path.split(name)
|
||||
tails = [tail] # stack of lone dirs to create
|
||||
tails = [tail] # stack of lone dirs to create
|
||||
|
||||
while head and tail and not os.path.isdir(head):
|
||||
(head, tail) = os.path.split(head)
|
||||
tails.insert(0, tail) # push next higher dir onto stack
|
||||
tails.insert(0, tail) # push next higher dir onto stack
|
||||
|
||||
# now 'head' contains the deepest directory that already exists
|
||||
# (that is, the child of 'head' in 'name' is the highest directory
|
||||
# that does *not* exist)
|
||||
for d in tails:
|
||||
#print "head = %s, d = %s: " % (head, d),
|
||||
# print "head = %s, d = %s: " % (head, d),
|
||||
head = os.path.join(head, d)
|
||||
abs_head = os.path.abspath(head)
|
||||
|
||||
@@ -71,12 +76,14 @@ def mkpath(name, mode=0o777, verbose=1, dry_run=0):
|
||||
except OSError as exc:
|
||||
if not (exc.errno == errno.EEXIST and os.path.isdir(head)):
|
||||
raise DistutilsFileError(
|
||||
"could not create '%s': %s" % (head, exc.args[-1]))
|
||||
"could not create '{}': {}".format(head, exc.args[-1])
|
||||
)
|
||||
created_dirs.append(head)
|
||||
|
||||
_path_created[abs_head] = 1
|
||||
return created_dirs
|
||||
|
||||
|
||||
def create_tree(base_dir, files, mode=0o777, verbose=1, dry_run=0):
|
||||
"""Create all the empty directories under 'base_dir' needed to put 'files'
|
||||
there.
|
||||
@@ -96,8 +103,17 @@ def create_tree(base_dir, files, mode=0o777, verbose=1, dry_run=0):
|
||||
for dir in sorted(need_dir):
|
||||
mkpath(dir, mode, verbose=verbose, dry_run=dry_run)
|
||||
|
||||
def copy_tree(src, dst, preserve_mode=1, preserve_times=1,
|
||||
preserve_symlinks=0, update=0, verbose=1, dry_run=0):
|
||||
|
||||
def copy_tree( # noqa: C901
|
||||
src,
|
||||
dst,
|
||||
preserve_mode=1,
|
||||
preserve_times=1,
|
||||
preserve_symlinks=0,
|
||||
update=0,
|
||||
verbose=1,
|
||||
dry_run=0,
|
||||
):
|
||||
"""Copy an entire directory tree 'src' to a new location 'dst'.
|
||||
|
||||
Both 'src' and 'dst' must be directory names. If 'src' is not a
|
||||
@@ -120,8 +136,7 @@ def copy_tree(src, dst, preserve_mode=1, preserve_times=1,
|
||||
from distutils.file_util import copy_file
|
||||
|
||||
if not dry_run and not os.path.isdir(src):
|
||||
raise DistutilsFileError(
|
||||
"cannot copy tree '%s': not a directory" % src)
|
||||
raise DistutilsFileError("cannot copy tree '%s': not a directory" % src)
|
||||
try:
|
||||
names = os.listdir(src)
|
||||
except OSError as e:
|
||||
@@ -129,7 +144,8 @@ def copy_tree(src, dst, preserve_mode=1, preserve_times=1,
|
||||
names = []
|
||||
else:
|
||||
raise DistutilsFileError(
|
||||
"error listing files in '%s': %s" % (src, e.strerror))
|
||||
"error listing files in '{}': {}".format(src, e.strerror)
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
mkpath(dst, verbose=verbose)
|
||||
@@ -154,27 +170,43 @@ def copy_tree(src, dst, preserve_mode=1, preserve_times=1,
|
||||
|
||||
elif os.path.isdir(src_name):
|
||||
outputs.extend(
|
||||
copy_tree(src_name, dst_name, preserve_mode,
|
||||
preserve_times, preserve_symlinks, update,
|
||||
verbose=verbose, dry_run=dry_run))
|
||||
copy_tree(
|
||||
src_name,
|
||||
dst_name,
|
||||
preserve_mode,
|
||||
preserve_times,
|
||||
preserve_symlinks,
|
||||
update,
|
||||
verbose=verbose,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
)
|
||||
else:
|
||||
copy_file(src_name, dst_name, preserve_mode,
|
||||
preserve_times, update, verbose=verbose,
|
||||
dry_run=dry_run)
|
||||
copy_file(
|
||||
src_name,
|
||||
dst_name,
|
||||
preserve_mode,
|
||||
preserve_times,
|
||||
update,
|
||||
verbose=verbose,
|
||||
dry_run=dry_run,
|
||||
)
|
||||
outputs.append(dst_name)
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def _build_cmdtuple(path, cmdtuples):
|
||||
"""Helper for remove_tree()."""
|
||||
for f in os.listdir(path):
|
||||
real_f = os.path.join(path,f)
|
||||
real_f = os.path.join(path, f)
|
||||
if os.path.isdir(real_f) and not os.path.islink(real_f):
|
||||
_build_cmdtuple(real_f, cmdtuples)
|
||||
else:
|
||||
cmdtuples.append((os.remove, real_f))
|
||||
cmdtuples.append((os.rmdir, path))
|
||||
|
||||
|
||||
def remove_tree(directory, verbose=1, dry_run=0):
|
||||
"""Recursively remove an entire directory tree.
|
||||
|
||||
@@ -199,6 +231,7 @@ def remove_tree(directory, verbose=1, dry_run=0):
|
||||
except OSError as exc:
|
||||
log.warn("error removing %s: %s", directory, exc)
|
||||
|
||||
|
||||
def ensure_relative(path):
|
||||
"""Take the full path 'path', and make it a relative path.
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ being built/installed/distributed.
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import pathlib
|
||||
import contextlib
|
||||
from email import message_from_file
|
||||
|
||||
try:
|
||||
@@ -14,7 +16,12 @@ try:
|
||||
except ImportError:
|
||||
warnings = None
|
||||
|
||||
from distutils.errors import *
|
||||
from distutils.errors import (
|
||||
DistutilsOptionError,
|
||||
DistutilsModuleError,
|
||||
DistutilsArgError,
|
||||
DistutilsClassError,
|
||||
)
|
||||
from distutils.fancy_getopt import FancyGetopt, translate_longopt
|
||||
from distutils.util import check_environ, strtobool, rfc822_escape
|
||||
from distutils import log
|
||||
@@ -69,8 +76,7 @@ class Distribution:
|
||||
('quiet', 'q', "run quietly (turns verbosity off)"),
|
||||
('dry-run', 'n', "don't actually do anything"),
|
||||
('help', 'h', "show detailed help message"),
|
||||
('no-user-cfg', None,
|
||||
'ignore pydistutils.cfg in your home directory'),
|
||||
('no-user-cfg', None, 'ignore pydistutils.cfg in your home directory'),
|
||||
]
|
||||
|
||||
# 'common_usage' is a short (2-3 line) string describing the common
|
||||
@@ -84,49 +90,32 @@ Common commands: (see '--help-commands' for more)
|
||||
|
||||
# options that are not propagated to the commands
|
||||
display_options = [
|
||||
('help-commands', None,
|
||||
"list all available commands"),
|
||||
('name', None,
|
||||
"print package name"),
|
||||
('version', 'V',
|
||||
"print package version"),
|
||||
('fullname', None,
|
||||
"print <package name>-<version>"),
|
||||
('author', None,
|
||||
"print the author's name"),
|
||||
('author-email', None,
|
||||
"print the author's email address"),
|
||||
('maintainer', None,
|
||||
"print the maintainer's name"),
|
||||
('maintainer-email', None,
|
||||
"print the maintainer's email address"),
|
||||
('contact', None,
|
||||
"print the maintainer's name if known, else the author's"),
|
||||
('contact-email', None,
|
||||
"print the maintainer's email address if known, else the author's"),
|
||||
('url', None,
|
||||
"print the URL for this package"),
|
||||
('license', None,
|
||||
"print the license of the package"),
|
||||
('licence', None,
|
||||
"alias for --license"),
|
||||
('description', None,
|
||||
"print the package description"),
|
||||
('long-description', None,
|
||||
"print the long package description"),
|
||||
('platforms', None,
|
||||
"print the list of platforms"),
|
||||
('classifiers', None,
|
||||
"print the list of classifiers"),
|
||||
('keywords', None,
|
||||
"print the list of keywords"),
|
||||
('provides', None,
|
||||
"print the list of packages/modules provided"),
|
||||
('requires', None,
|
||||
"print the list of packages/modules required"),
|
||||
('obsoletes', None,
|
||||
"print the list of packages/modules made obsolete")
|
||||
]
|
||||
('help-commands', None, "list all available commands"),
|
||||
('name', None, "print package name"),
|
||||
('version', 'V', "print package version"),
|
||||
('fullname', None, "print <package name>-<version>"),
|
||||
('author', None, "print the author's name"),
|
||||
('author-email', None, "print the author's email address"),
|
||||
('maintainer', None, "print the maintainer's name"),
|
||||
('maintainer-email', None, "print the maintainer's email address"),
|
||||
('contact', None, "print the maintainer's name if known, else the author's"),
|
||||
(
|
||||
'contact-email',
|
||||
None,
|
||||
"print the maintainer's email address if known, else the author's",
|
||||
),
|
||||
('url', None, "print the URL for this package"),
|
||||
('license', None, "print the license of the package"),
|
||||
('licence', None, "alias for --license"),
|
||||
('description', None, "print the package description"),
|
||||
('long-description', None, "print the long package description"),
|
||||
('platforms', None, "print the list of platforms"),
|
||||
('classifiers', None, "print the list of classifiers"),
|
||||
('keywords', None, "print the list of keywords"),
|
||||
('provides', None, "print the list of packages/modules provided"),
|
||||
('requires', None, "print the list of packages/modules required"),
|
||||
('obsoletes', None, "print the list of packages/modules made obsolete"),
|
||||
]
|
||||
display_option_names = [translate_longopt(x[0]) for x in display_options]
|
||||
|
||||
# negative options are options that exclude other options
|
||||
@@ -134,7 +123,7 @@ Common commands: (see '--help-commands' for more)
|
||||
|
||||
# -- Creation/initialization methods -------------------------------
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
def __init__(self, attrs=None): # noqa: C901
|
||||
"""Construct a new Distribution instance: initialize all the
|
||||
attributes of a Distribution, and then use 'attrs' (a dictionary
|
||||
mapping attribute names to values) to assign some of those
|
||||
@@ -306,7 +295,7 @@ Common commands: (see '--help-commands' for more)
|
||||
def dump_option_dicts(self, header=None, commands=None, indent=""):
|
||||
from pprint import pformat
|
||||
|
||||
if commands is None: # dump all command option dicts
|
||||
if commands is None: # dump all command option dicts
|
||||
commands = sorted(self.command_options.keys())
|
||||
|
||||
if header is not None:
|
||||
@@ -320,11 +309,9 @@ Common commands: (see '--help-commands' for more)
|
||||
for cmd_name in commands:
|
||||
opt_dict = self.command_options.get(cmd_name)
|
||||
if opt_dict is None:
|
||||
self.announce(indent +
|
||||
"no option dict for '%s' command" % cmd_name)
|
||||
self.announce(indent + "no option dict for '%s' command" % cmd_name)
|
||||
else:
|
||||
self.announce(indent +
|
||||
"option dict for '%s' command:" % cmd_name)
|
||||
self.announce(indent + "option dict for '%s' command:" % cmd_name)
|
||||
out = pformat(opt_dict)
|
||||
for line in out.split('\n'):
|
||||
self.announce(indent + " " + line)
|
||||
@@ -337,58 +324,61 @@ Common commands: (see '--help-commands' for more)
|
||||
should be parsed. The filenames returned are guaranteed to exist
|
||||
(modulo nasty race conditions).
|
||||
|
||||
There are three possible config files: distutils.cfg in the
|
||||
Distutils installation directory (ie. where the top-level
|
||||
Distutils __inst__.py file lives), a file in the user's home
|
||||
directory named .pydistutils.cfg on Unix and pydistutils.cfg
|
||||
on Windows/Mac; and setup.cfg in the current directory.
|
||||
|
||||
The file in the user's home directory can be disabled with the
|
||||
--no-user-cfg option.
|
||||
There are multiple possible config files:
|
||||
- distutils.cfg in the Distutils installation directory (i.e.
|
||||
where the top-level Distutils __inst__.py file lives)
|
||||
- a file in the user's home directory named .pydistutils.cfg
|
||||
on Unix and pydistutils.cfg on Windows/Mac; may be disabled
|
||||
with the ``--no-user-cfg`` option
|
||||
- setup.cfg in the current directory
|
||||
- a file named by an environment variable
|
||||
"""
|
||||
files = []
|
||||
check_environ()
|
||||
|
||||
# Where to look for the system-wide Distutils config file
|
||||
sys_dir = os.path.dirname(sys.modules['distutils'].__file__)
|
||||
|
||||
# Look for the system config file
|
||||
sys_file = os.path.join(sys_dir, "distutils.cfg")
|
||||
if os.path.isfile(sys_file):
|
||||
files.append(sys_file)
|
||||
|
||||
# What to call the per-user config file
|
||||
if os.name == 'posix':
|
||||
user_filename = ".pydistutils.cfg"
|
||||
else:
|
||||
user_filename = "pydistutils.cfg"
|
||||
|
||||
# And look for the user config file
|
||||
if self.want_user_cfg:
|
||||
user_file = os.path.join(os.path.expanduser('~'), user_filename)
|
||||
if os.path.isfile(user_file):
|
||||
files.append(user_file)
|
||||
|
||||
# All platforms support local setup.cfg
|
||||
local_file = "setup.cfg"
|
||||
if os.path.isfile(local_file):
|
||||
files.append(local_file)
|
||||
files = [str(path) for path in self._gen_paths() if os.path.isfile(path)]
|
||||
|
||||
if DEBUG:
|
||||
self.announce("using config files: %s" % ', '.join(files))
|
||||
|
||||
return files
|
||||
|
||||
def parse_config_files(self, filenames=None):
|
||||
def _gen_paths(self):
|
||||
# The system-wide Distutils config file
|
||||
sys_dir = pathlib.Path(sys.modules['distutils'].__file__).parent
|
||||
yield sys_dir / "distutils.cfg"
|
||||
|
||||
# The per-user config file
|
||||
prefix = '.' * (os.name == 'posix')
|
||||
filename = prefix + 'pydistutils.cfg'
|
||||
if self.want_user_cfg:
|
||||
yield pathlib.Path('~').expanduser() / filename
|
||||
|
||||
# All platforms support local setup.cfg
|
||||
yield pathlib.Path('setup.cfg')
|
||||
|
||||
# Additional config indicated in the environment
|
||||
with contextlib.suppress(TypeError):
|
||||
yield pathlib.Path(os.getenv("DIST_EXTRA_CONFIG"))
|
||||
|
||||
def parse_config_files(self, filenames=None): # noqa: C901
|
||||
from configparser import ConfigParser
|
||||
|
||||
# Ignore install directory options if we have a venv
|
||||
if sys.prefix != sys.base_prefix:
|
||||
ignore_options = [
|
||||
'install-base', 'install-platbase', 'install-lib',
|
||||
'install-platlib', 'install-purelib', 'install-headers',
|
||||
'install-scripts', 'install-data', 'prefix', 'exec-prefix',
|
||||
'home', 'user', 'root']
|
||||
'install-base',
|
||||
'install-platbase',
|
||||
'install-lib',
|
||||
'install-platlib',
|
||||
'install-purelib',
|
||||
'install-headers',
|
||||
'install-scripts',
|
||||
'install-data',
|
||||
'prefix',
|
||||
'exec-prefix',
|
||||
'home',
|
||||
'user',
|
||||
'root',
|
||||
]
|
||||
else:
|
||||
ignore_options = []
|
||||
|
||||
@@ -411,7 +401,7 @@ Common commands: (see '--help-commands' for more)
|
||||
|
||||
for opt in options:
|
||||
if opt != '__name__' and opt not in ignore_options:
|
||||
val = parser.get(section,opt)
|
||||
val = parser.get(section, opt)
|
||||
opt = opt.replace('-', '_')
|
||||
opt_dict[opt] = (filename, val)
|
||||
|
||||
@@ -428,7 +418,7 @@ Common commands: (see '--help-commands' for more)
|
||||
try:
|
||||
if alias:
|
||||
setattr(self, alias, not strtobool(val))
|
||||
elif opt in ('verbose', 'dry_run'): # ugh!
|
||||
elif opt in ('verbose', 'dry_run'): # ugh!
|
||||
setattr(self, opt, strtobool(val))
|
||||
else:
|
||||
setattr(self, opt, val)
|
||||
@@ -482,7 +472,7 @@ Common commands: (see '--help-commands' for more)
|
||||
return
|
||||
while args:
|
||||
args = self._parse_command_opts(parser, args)
|
||||
if args is None: # user asked for help (and got it)
|
||||
if args is None: # user asked for help (and got it)
|
||||
return
|
||||
|
||||
# Handle the cases of --help as a "global" option, ie.
|
||||
@@ -492,9 +482,9 @@ Common commands: (see '--help-commands' for more)
|
||||
# latter, we omit the display-only options and show help for
|
||||
# each command listed on the command line.
|
||||
if self.help:
|
||||
self._show_help(parser,
|
||||
display_options=len(self.commands) == 0,
|
||||
commands=self.commands)
|
||||
self._show_help(
|
||||
parser, display_options=len(self.commands) == 0, commands=self.commands
|
||||
)
|
||||
return
|
||||
|
||||
# Oops, no commands found -- an end-user error
|
||||
@@ -511,11 +501,14 @@ Common commands: (see '--help-commands' for more)
|
||||
level as well as options recognized for commands.
|
||||
"""
|
||||
return self.global_options + [
|
||||
("command-packages=", None,
|
||||
"list of packages that provide distutils commands"),
|
||||
]
|
||||
(
|
||||
"command-packages=",
|
||||
None,
|
||||
"list of packages that provide distutils commands",
|
||||
),
|
||||
]
|
||||
|
||||
def _parse_command_opts(self, parser, args):
|
||||
def _parse_command_opts(self, parser, args): # noqa: C901
|
||||
"""Parse the command-line options for a single command.
|
||||
'parser' must be a FancyGetopt instance; 'args' must be the list
|
||||
of arguments, starting with the current command (whose options
|
||||
@@ -545,14 +538,19 @@ Common commands: (see '--help-commands' for more)
|
||||
# to be sure that the basic "command" interface is implemented.
|
||||
if not issubclass(cmd_class, Command):
|
||||
raise DistutilsClassError(
|
||||
"command class %s must subclass Command" % cmd_class)
|
||||
"command class %s must subclass Command" % cmd_class
|
||||
)
|
||||
|
||||
# Also make sure that the command object provides a list of its
|
||||
# known options.
|
||||
if not (hasattr(cmd_class, 'user_options') and
|
||||
isinstance(cmd_class.user_options, list)):
|
||||
msg = ("command class %s must provide "
|
||||
"'user_options' attribute (a list of tuples)")
|
||||
if not (
|
||||
hasattr(cmd_class, 'user_options')
|
||||
and isinstance(cmd_class.user_options, list)
|
||||
):
|
||||
msg = (
|
||||
"command class %s must provide "
|
||||
"'user_options' attribute (a list of tuples)"
|
||||
)
|
||||
raise DistutilsClassError(msg % cmd_class)
|
||||
|
||||
# If the command class has a list of negative alias options,
|
||||
@@ -564,36 +562,39 @@ Common commands: (see '--help-commands' for more)
|
||||
|
||||
# Check for help_options in command class. They have a different
|
||||
# format (tuple of four) so we need to preprocess them here.
|
||||
if (hasattr(cmd_class, 'help_options') and
|
||||
isinstance(cmd_class.help_options, list)):
|
||||
if hasattr(cmd_class, 'help_options') and isinstance(
|
||||
cmd_class.help_options, list
|
||||
):
|
||||
help_options = fix_help_options(cmd_class.help_options)
|
||||
else:
|
||||
help_options = []
|
||||
|
||||
# All commands support the global options too, just by adding
|
||||
# in 'global_options'.
|
||||
parser.set_option_table(self.global_options +
|
||||
cmd_class.user_options +
|
||||
help_options)
|
||||
parser.set_option_table(
|
||||
self.global_options + cmd_class.user_options + help_options
|
||||
)
|
||||
parser.set_negative_aliases(negative_opt)
|
||||
(args, opts) = parser.getopt(args[1:])
|
||||
if hasattr(opts, 'help') and opts.help:
|
||||
self._show_help(parser, display_options=0, commands=[cmd_class])
|
||||
return
|
||||
|
||||
if (hasattr(cmd_class, 'help_options') and
|
||||
isinstance(cmd_class.help_options, list)):
|
||||
help_option_found=0
|
||||
if hasattr(cmd_class, 'help_options') and isinstance(
|
||||
cmd_class.help_options, list
|
||||
):
|
||||
help_option_found = 0
|
||||
for (help_option, short, desc, func) in cmd_class.help_options:
|
||||
if hasattr(opts, parser.get_attr_name(help_option)):
|
||||
help_option_found=1
|
||||
help_option_found = 1
|
||||
if callable(func):
|
||||
func()
|
||||
else:
|
||||
raise DistutilsClassError(
|
||||
"invalid help function %r for help option '%s': "
|
||||
"must be a callable object (function, etc.)"
|
||||
% (func, help_option))
|
||||
% (func, help_option)
|
||||
)
|
||||
|
||||
if help_option_found:
|
||||
return
|
||||
@@ -619,8 +620,7 @@ Common commands: (see '--help-commands' for more)
|
||||
value = [elm.strip() for elm in value.split(',')]
|
||||
setattr(self.metadata, attr, value)
|
||||
|
||||
def _show_help(self, parser, global_options=1, display_options=1,
|
||||
commands=[]):
|
||||
def _show_help(self, parser, global_options=1, display_options=1, commands=[]):
|
||||
"""Show help for the setup script command-line in the form of
|
||||
several lists of command-line options. 'parser' should be a
|
||||
FancyGetopt instance; do not expect it to be returned in the
|
||||
@@ -649,8 +649,9 @@ Common commands: (see '--help-commands' for more)
|
||||
if display_options:
|
||||
parser.set_option_table(self.display_options)
|
||||
parser.print_help(
|
||||
"Information display options (just display " +
|
||||
"information, ignore any commands)")
|
||||
"Information display options (just display "
|
||||
+ "information, ignore any commands)"
|
||||
)
|
||||
print('')
|
||||
|
||||
for command in self.commands:
|
||||
@@ -658,10 +659,10 @@ Common commands: (see '--help-commands' for more)
|
||||
klass = command
|
||||
else:
|
||||
klass = self.get_command_class(command)
|
||||
if (hasattr(klass, 'help_options') and
|
||||
isinstance(klass.help_options, list)):
|
||||
parser.set_option_table(klass.user_options +
|
||||
fix_help_options(klass.help_options))
|
||||
if hasattr(klass, 'help_options') and isinstance(klass.help_options, list):
|
||||
parser.set_option_table(
|
||||
klass.user_options + fix_help_options(klass.help_options)
|
||||
)
|
||||
else:
|
||||
parser.set_option_table(klass.user_options)
|
||||
parser.print_help("Options for '%s' command:" % klass.__name__)
|
||||
@@ -697,11 +698,10 @@ Common commands: (see '--help-commands' for more)
|
||||
for (opt, val) in option_order:
|
||||
if val and is_display_option.get(opt):
|
||||
opt = translate_longopt(opt)
|
||||
value = getattr(self.metadata, "get_"+opt)()
|
||||
value = getattr(self.metadata, "get_" + opt)()
|
||||
if opt in ['keywords', 'platforms']:
|
||||
print(','.join(value))
|
||||
elif opt in ('classifiers', 'provides', 'requires',
|
||||
'obsoletes'):
|
||||
elif opt in ('classifiers', 'provides', 'requires', 'obsoletes'):
|
||||
print('\n'.join(value))
|
||||
else:
|
||||
print(value)
|
||||
@@ -735,6 +735,7 @@ Common commands: (see '--help-commands' for more)
|
||||
'description'.
|
||||
"""
|
||||
import distutils.command
|
||||
|
||||
std_commands = distutils.command.__all__
|
||||
is_std = {}
|
||||
for cmd in std_commands:
|
||||
@@ -746,18 +747,14 @@ Common commands: (see '--help-commands' for more)
|
||||
extra_commands.append(cmd)
|
||||
|
||||
max_length = 0
|
||||
for cmd in (std_commands + extra_commands):
|
||||
for cmd in std_commands + extra_commands:
|
||||
if len(cmd) > max_length:
|
||||
max_length = len(cmd)
|
||||
|
||||
self.print_command_list(std_commands,
|
||||
"Standard commands",
|
||||
max_length)
|
||||
self.print_command_list(std_commands, "Standard commands", max_length)
|
||||
if extra_commands:
|
||||
print()
|
||||
self.print_command_list(extra_commands,
|
||||
"Extra commands",
|
||||
max_length)
|
||||
self.print_command_list(extra_commands, "Extra commands", max_length)
|
||||
|
||||
def get_command_list(self):
|
||||
"""Get a list of (command, description) tuples.
|
||||
@@ -769,6 +766,7 @@ Common commands: (see '--help-commands' for more)
|
||||
# Currently this is only used on Mac OS, for the Mac-only GUI
|
||||
# Distutils interface (by Jack Jansen)
|
||||
import distutils.command
|
||||
|
||||
std_commands = distutils.command.__all__
|
||||
is_std = {}
|
||||
for cmd in std_commands:
|
||||
@@ -780,7 +778,7 @@ Common commands: (see '--help-commands' for more)
|
||||
extra_commands.append(cmd)
|
||||
|
||||
rv = []
|
||||
for cmd in (std_commands + extra_commands):
|
||||
for cmd in std_commands + extra_commands:
|
||||
klass = self.cmdclass.get(cmd)
|
||||
if not klass:
|
||||
klass = self.get_command_class(cmd)
|
||||
@@ -822,7 +820,7 @@ Common commands: (see '--help-commands' for more)
|
||||
return klass
|
||||
|
||||
for pkgname in self.get_command_packages():
|
||||
module_name = "%s.%s" % (pkgname, command)
|
||||
module_name = "{}.{}".format(pkgname, command)
|
||||
klass_name = command
|
||||
|
||||
try:
|
||||
@@ -836,7 +834,8 @@ Common commands: (see '--help-commands' for more)
|
||||
except AttributeError:
|
||||
raise DistutilsModuleError(
|
||||
"invalid command '%s' (no class '%s' in module '%s')"
|
||||
% (command, klass_name, module_name))
|
||||
% (command, klass_name, module_name)
|
||||
)
|
||||
|
||||
self.cmdclass[command] = klass
|
||||
return klass
|
||||
@@ -852,8 +851,10 @@ Common commands: (see '--help-commands' for more)
|
||||
cmd_obj = self.command_obj.get(command)
|
||||
if not cmd_obj and create:
|
||||
if DEBUG:
|
||||
self.announce("Distribution.get_command_obj(): "
|
||||
"creating '%s' command object" % command)
|
||||
self.announce(
|
||||
"Distribution.get_command_obj(): "
|
||||
"creating '%s' command object" % command
|
||||
)
|
||||
|
||||
klass = self.get_command_class(command)
|
||||
cmd_obj = self.command_obj[command] = klass(self)
|
||||
@@ -870,7 +871,7 @@ Common commands: (see '--help-commands' for more)
|
||||
|
||||
return cmd_obj
|
||||
|
||||
def _set_command_options(self, command_obj, option_dict=None):
|
||||
def _set_command_options(self, command_obj, option_dict=None): # noqa: C901
|
||||
"""Set the options for 'command_obj' from 'option_dict'. Basically
|
||||
this means copying elements of a dictionary ('option_dict') to
|
||||
attributes of an instance ('command').
|
||||
@@ -887,11 +888,9 @@ Common commands: (see '--help-commands' for more)
|
||||
self.announce(" setting options for '%s' command:" % command_name)
|
||||
for (option, (source, value)) in option_dict.items():
|
||||
if DEBUG:
|
||||
self.announce(" %s = %s (from %s)" % (option, value,
|
||||
source))
|
||||
self.announce(" {} = {} (from {})".format(option, value, source))
|
||||
try:
|
||||
bool_opts = [translate_longopt(o)
|
||||
for o in command_obj.boolean_options]
|
||||
bool_opts = [translate_longopt(o) for o in command_obj.boolean_options]
|
||||
except AttributeError:
|
||||
bool_opts = []
|
||||
try:
|
||||
@@ -910,7 +909,8 @@ Common commands: (see '--help-commands' for more)
|
||||
else:
|
||||
raise DistutilsOptionError(
|
||||
"error in %s: command '%s' has no such option '%s'"
|
||||
% (source, command_name, option))
|
||||
% (source, command_name, option)
|
||||
)
|
||||
except ValueError as msg:
|
||||
raise DistutilsOptionError(msg)
|
||||
|
||||
@@ -934,6 +934,7 @@ Common commands: (see '--help-commands' for more)
|
||||
Returns the reinitialized command object.
|
||||
"""
|
||||
from distutils.cmd import Command
|
||||
|
||||
if not isinstance(command, Command):
|
||||
command_name = command
|
||||
command = self.get_command_obj(command_name)
|
||||
@@ -1010,9 +1011,11 @@ Common commands: (see '--help-commands' for more)
|
||||
return self.data_files and len(self.data_files) > 0
|
||||
|
||||
def is_pure(self):
|
||||
return (self.has_pure_modules() and
|
||||
not self.has_ext_modules() and
|
||||
not self.has_c_libraries())
|
||||
return (
|
||||
self.has_pure_modules()
|
||||
and not self.has_ext_modules()
|
||||
and not self.has_c_libraries()
|
||||
)
|
||||
|
||||
# -- Metadata query methods ----------------------------------------
|
||||
|
||||
@@ -1021,19 +1024,35 @@ Common commands: (see '--help-commands' for more)
|
||||
# to self.metadata.get_XXX. The actual code is in the
|
||||
# DistributionMetadata class, below.
|
||||
|
||||
|
||||
class DistributionMetadata:
|
||||
"""Dummy class to hold the distribution meta-data: name, version,
|
||||
author, and so forth.
|
||||
"""
|
||||
|
||||
_METHOD_BASENAMES = ("name", "version", "author", "author_email",
|
||||
"maintainer", "maintainer_email", "url",
|
||||
"license", "description", "long_description",
|
||||
"keywords", "platforms", "fullname", "contact",
|
||||
"contact_email", "classifiers", "download_url",
|
||||
# PEP 314
|
||||
"provides", "requires", "obsoletes",
|
||||
)
|
||||
_METHOD_BASENAMES = (
|
||||
"name",
|
||||
"version",
|
||||
"author",
|
||||
"author_email",
|
||||
"maintainer",
|
||||
"maintainer_email",
|
||||
"url",
|
||||
"license",
|
||||
"description",
|
||||
"long_description",
|
||||
"keywords",
|
||||
"platforms",
|
||||
"fullname",
|
||||
"contact",
|
||||
"contact_email",
|
||||
"classifiers",
|
||||
"download_url",
|
||||
# PEP 314
|
||||
"provides",
|
||||
"requires",
|
||||
"obsoletes",
|
||||
)
|
||||
|
||||
def __init__(self, path=None):
|
||||
if path is not None:
|
||||
@@ -1064,9 +1083,8 @@ class DistributionMetadata:
|
||||
|
||||
def _read_field(name):
|
||||
value = msg[name]
|
||||
if value == 'UNKNOWN':
|
||||
return None
|
||||
return value
|
||||
if value and value != "UNKNOWN":
|
||||
return value
|
||||
|
||||
def _read_list(name):
|
||||
values = msg.get_all(name, None)
|
||||
@@ -1111,37 +1129,42 @@ class DistributionMetadata:
|
||||
self.obsoletes = None
|
||||
|
||||
def write_pkg_info(self, base_dir):
|
||||
"""Write the PKG-INFO file into the release tree.
|
||||
"""
|
||||
with open(os.path.join(base_dir, 'PKG-INFO'), 'w',
|
||||
encoding='UTF-8') as pkg_info:
|
||||
"""Write the PKG-INFO file into the release tree."""
|
||||
with open(
|
||||
os.path.join(base_dir, 'PKG-INFO'), 'w', encoding='UTF-8'
|
||||
) as pkg_info:
|
||||
self.write_pkg_file(pkg_info)
|
||||
|
||||
def write_pkg_file(self, file):
|
||||
"""Write the PKG-INFO format data to a file object.
|
||||
"""
|
||||
"""Write the PKG-INFO format data to a file object."""
|
||||
version = '1.0'
|
||||
if (self.provides or self.requires or self.obsoletes or
|
||||
self.classifiers or self.download_url):
|
||||
if (
|
||||
self.provides
|
||||
or self.requires
|
||||
or self.obsoletes
|
||||
or self.classifiers
|
||||
or self.download_url
|
||||
):
|
||||
version = '1.1'
|
||||
|
||||
# required fields
|
||||
file.write('Metadata-Version: %s\n' % version)
|
||||
file.write('Name: %s\n' % self.get_name())
|
||||
file.write('Version: %s\n' % self.get_version())
|
||||
file.write('Summary: %s\n' % self.get_description())
|
||||
file.write('Home-page: %s\n' % self.get_url())
|
||||
file.write('Author: %s\n' % self.get_contact())
|
||||
file.write('Author-email: %s\n' % self.get_contact_email())
|
||||
file.write('License: %s\n' % self.get_license())
|
||||
if self.download_url:
|
||||
file.write('Download-URL: %s\n' % self.download_url)
|
||||
|
||||
long_desc = rfc822_escape(self.get_long_description())
|
||||
file.write('Description: %s\n' % long_desc)
|
||||
def maybe_write(header, val):
|
||||
if val:
|
||||
file.write(f"{header}: {val}\n")
|
||||
|
||||
keywords = ','.join(self.get_keywords())
|
||||
if keywords:
|
||||
file.write('Keywords: %s\n' % keywords)
|
||||
# optional fields
|
||||
maybe_write("Summary", self.get_description())
|
||||
maybe_write("Home-page", self.get_url())
|
||||
maybe_write("Author", self.get_contact())
|
||||
maybe_write("Author-email", self.get_contact_email())
|
||||
maybe_write("License", self.get_license())
|
||||
maybe_write("Download-URL", self.download_url)
|
||||
maybe_write("Description", rfc822_escape(self.get_long_description() or ""))
|
||||
maybe_write("Keywords", ",".join(self.get_keywords()))
|
||||
|
||||
self._write_list(file, 'Platform', self.get_platforms())
|
||||
self._write_list(file, 'Classifier', self.get_classifiers())
|
||||
@@ -1152,8 +1175,9 @@ class DistributionMetadata:
|
||||
self._write_list(file, 'Obsoletes', self.get_obsoletes())
|
||||
|
||||
def _write_list(self, file, name, values):
|
||||
values = values or []
|
||||
for value in values:
|
||||
file.write('%s: %s\n' % (name, value))
|
||||
file.write('{}: {}\n'.format(name, value))
|
||||
|
||||
# -- Metadata query methods ----------------------------------------
|
||||
|
||||
@@ -1164,38 +1188,39 @@ class DistributionMetadata:
|
||||
return self.version or "0.0.0"
|
||||
|
||||
def get_fullname(self):
|
||||
return "%s-%s" % (self.get_name(), self.get_version())
|
||||
return "{}-{}".format(self.get_name(), self.get_version())
|
||||
|
||||
def get_author(self):
|
||||
return self.author or "UNKNOWN"
|
||||
return self.author
|
||||
|
||||
def get_author_email(self):
|
||||
return self.author_email or "UNKNOWN"
|
||||
return self.author_email
|
||||
|
||||
def get_maintainer(self):
|
||||
return self.maintainer or "UNKNOWN"
|
||||
return self.maintainer
|
||||
|
||||
def get_maintainer_email(self):
|
||||
return self.maintainer_email or "UNKNOWN"
|
||||
return self.maintainer_email
|
||||
|
||||
def get_contact(self):
|
||||
return self.maintainer or self.author or "UNKNOWN"
|
||||
return self.maintainer or self.author
|
||||
|
||||
def get_contact_email(self):
|
||||
return self.maintainer_email or self.author_email or "UNKNOWN"
|
||||
return self.maintainer_email or self.author_email
|
||||
|
||||
def get_url(self):
|
||||
return self.url or "UNKNOWN"
|
||||
return self.url
|
||||
|
||||
def get_license(self):
|
||||
return self.license or "UNKNOWN"
|
||||
return self.license
|
||||
|
||||
get_licence = get_license
|
||||
|
||||
def get_description(self):
|
||||
return self.description or "UNKNOWN"
|
||||
return self.description
|
||||
|
||||
def get_long_description(self):
|
||||
return self.long_description or "UNKNOWN"
|
||||
return self.long_description
|
||||
|
||||
def get_keywords(self):
|
||||
return self.keywords or []
|
||||
@@ -1204,7 +1229,7 @@ class DistributionMetadata:
|
||||
self.keywords = _ensure_list(value, 'keywords')
|
||||
|
||||
def get_platforms(self):
|
||||
return self.platforms or ["UNKNOWN"]
|
||||
return self.platforms
|
||||
|
||||
def set_platforms(self, value):
|
||||
self.platforms = _ensure_list(value, 'platforms')
|
||||
@@ -1216,7 +1241,7 @@ class DistributionMetadata:
|
||||
self.classifiers = _ensure_list(value, 'classifiers')
|
||||
|
||||
def get_download_url(self):
|
||||
return self.download_url or "UNKNOWN"
|
||||
return self.download_url
|
||||
|
||||
# PEP 314
|
||||
def get_requires(self):
|
||||
@@ -1224,6 +1249,7 @@ class DistributionMetadata:
|
||||
|
||||
def set_requires(self, value):
|
||||
import distutils.versionpredicate
|
||||
|
||||
for v in value:
|
||||
distutils.versionpredicate.VersionPredicate(v)
|
||||
self.requires = list(value)
|
||||
@@ -1235,6 +1261,7 @@ class DistributionMetadata:
|
||||
value = [v.strip() for v in value]
|
||||
for v in value:
|
||||
import distutils.versionpredicate
|
||||
|
||||
distutils.versionpredicate.split_provision(v)
|
||||
self.provides = value
|
||||
|
||||
@@ -1243,10 +1270,12 @@ class DistributionMetadata:
|
||||
|
||||
def set_obsoletes(self, value):
|
||||
import distutils.versionpredicate
|
||||
|
||||
for v in value:
|
||||
distutils.versionpredicate.VersionPredicate(v)
|
||||
self.obsoletes = list(value)
|
||||
|
||||
|
||||
def fix_help_options(options):
|
||||
"""Convert a 4-tuple 'help_options' list as found in various command
|
||||
classes to the 3-tuple form required by FancyGetopt.
|
||||
|
||||
@@ -8,90 +8,120 @@ usually raised for errors that are obviously the end-user's fault
|
||||
This module is safe to use in "from ... import *" mode; it only exports
|
||||
symbols whose names start with "Distutils" and end with "Error"."""
|
||||
|
||||
class DistutilsError (Exception):
|
||||
|
||||
class DistutilsError(Exception):
|
||||
"""The root of all Distutils evil."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsModuleError (DistutilsError):
|
||||
|
||||
class DistutilsModuleError(DistutilsError):
|
||||
"""Unable to load an expected module, or to find an expected class
|
||||
within some module (in particular, command modules and classes)."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsClassError (DistutilsError):
|
||||
|
||||
class DistutilsClassError(DistutilsError):
|
||||
"""Some command class (or possibly distribution class, if anyone
|
||||
feels a need to subclass Distribution) is found not to be holding
|
||||
up its end of the bargain, ie. implementing some part of the
|
||||
"command "interface."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsGetoptError (DistutilsError):
|
||||
|
||||
class DistutilsGetoptError(DistutilsError):
|
||||
"""The option table provided to 'fancy_getopt()' is bogus."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsArgError (DistutilsError):
|
||||
|
||||
class DistutilsArgError(DistutilsError):
|
||||
"""Raised by fancy_getopt in response to getopt.error -- ie. an
|
||||
error in the command line usage."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsFileError (DistutilsError):
|
||||
|
||||
class DistutilsFileError(DistutilsError):
|
||||
"""Any problems in the filesystem: expected file not found, etc.
|
||||
Typically this is for problems that we detect before OSError
|
||||
could be raised."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsOptionError (DistutilsError):
|
||||
|
||||
class DistutilsOptionError(DistutilsError):
|
||||
"""Syntactic/semantic errors in command options, such as use of
|
||||
mutually conflicting options, or inconsistent options,
|
||||
badly-spelled values, etc. No distinction is made between option
|
||||
values originating in the setup script, the command line, config
|
||||
files, or what-have-you -- but if we *know* something originated in
|
||||
the setup script, we'll raise DistutilsSetupError instead."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsSetupError (DistutilsError):
|
||||
|
||||
class DistutilsSetupError(DistutilsError):
|
||||
"""For errors that can be definitely blamed on the setup script,
|
||||
such as invalid keyword arguments to 'setup()'."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsPlatformError (DistutilsError):
|
||||
|
||||
class DistutilsPlatformError(DistutilsError):
|
||||
"""We don't know how to do something on the current platform (but
|
||||
we do know how to do it on some platform) -- eg. trying to compile
|
||||
C files on a platform not supported by a CCompiler subclass."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsExecError (DistutilsError):
|
||||
|
||||
class DistutilsExecError(DistutilsError):
|
||||
"""Any problems executing an external program (such as the C
|
||||
compiler, when compiling C files)."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsInternalError (DistutilsError):
|
||||
|
||||
class DistutilsInternalError(DistutilsError):
|
||||
"""Internal inconsistencies or impossibilities (obviously, this
|
||||
should never be seen if the code is working!)."""
|
||||
|
||||
pass
|
||||
|
||||
class DistutilsTemplateError (DistutilsError):
|
||||
|
||||
class DistutilsTemplateError(DistutilsError):
|
||||
"""Syntax error in a file list template."""
|
||||
|
||||
|
||||
class DistutilsByteCompileError(DistutilsError):
|
||||
"""Byte compile error."""
|
||||
|
||||
|
||||
# Exception classes used by the CCompiler implementation classes
|
||||
class CCompilerError (Exception):
|
||||
class CCompilerError(Exception):
|
||||
"""Some compile/link operation failed."""
|
||||
|
||||
class PreprocessError (CCompilerError):
|
||||
|
||||
class PreprocessError(CCompilerError):
|
||||
"""Failure to preprocess one or more C/C++ files."""
|
||||
|
||||
class CompileError (CCompilerError):
|
||||
|
||||
class CompileError(CCompilerError):
|
||||
"""Failure to compile one or more C/C++ source files."""
|
||||
|
||||
class LibError (CCompilerError):
|
||||
|
||||
class LibError(CCompilerError):
|
||||
"""Failure to create a static library from one or more C/C++ object
|
||||
files."""
|
||||
|
||||
class LinkError (CCompilerError):
|
||||
|
||||
class LinkError(CCompilerError):
|
||||
"""Failure to link one or more C/C++ object files into an executable
|
||||
or shared library file."""
|
||||
|
||||
class UnknownFileError (CCompilerError):
|
||||
|
||||
class UnknownFileError(CCompilerError):
|
||||
"""Attempt to process an unknown file type."""
|
||||
|
||||
@@ -16,6 +16,7 @@ import warnings
|
||||
# import that large-ish module (indirectly, through distutils.core) in
|
||||
# order to do anything.
|
||||
|
||||
|
||||
class Extension:
|
||||
"""Just a collection of attributes that describes an extension
|
||||
module and everything needed to build it (hopefully in a portable
|
||||
@@ -83,27 +84,29 @@ class Extension:
|
||||
|
||||
# When adding arguments to this constructor, be sure to update
|
||||
# setup_keywords in core.py.
|
||||
def __init__(self, name, sources,
|
||||
include_dirs=None,
|
||||
define_macros=None,
|
||||
undef_macros=None,
|
||||
library_dirs=None,
|
||||
libraries=None,
|
||||
runtime_library_dirs=None,
|
||||
extra_objects=None,
|
||||
extra_compile_args=None,
|
||||
extra_link_args=None,
|
||||
export_symbols=None,
|
||||
swig_opts = None,
|
||||
depends=None,
|
||||
language=None,
|
||||
optional=None,
|
||||
**kw # To catch unknown keywords
|
||||
):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
sources,
|
||||
include_dirs=None,
|
||||
define_macros=None,
|
||||
undef_macros=None,
|
||||
library_dirs=None,
|
||||
libraries=None,
|
||||
runtime_library_dirs=None,
|
||||
extra_objects=None,
|
||||
extra_compile_args=None,
|
||||
extra_link_args=None,
|
||||
export_symbols=None,
|
||||
swig_opts=None,
|
||||
depends=None,
|
||||
language=None,
|
||||
optional=None,
|
||||
**kw # To catch unknown keywords
|
||||
):
|
||||
if not isinstance(name, str):
|
||||
raise AssertionError("'name' must be a string")
|
||||
if not (isinstance(sources, list) and
|
||||
all(isinstance(v, str) for v in sources)):
|
||||
if not (isinstance(sources, list) and all(isinstance(v, str) for v in sources)):
|
||||
raise AssertionError("'sources' must be a list of strings")
|
||||
|
||||
self.name = name
|
||||
@@ -131,17 +134,17 @@ class Extension:
|
||||
warnings.warn(msg)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s.%s(%r) at %#x>' % (
|
||||
return '<{}.{}({!r}) at {:#x}>'.format(
|
||||
self.__class__.__module__,
|
||||
self.__class__.__qualname__,
|
||||
self.name,
|
||||
id(self))
|
||||
id(self),
|
||||
)
|
||||
|
||||
|
||||
def read_setup_file(filename):
|
||||
def read_setup_file(filename): # noqa: C901
|
||||
"""Reads a Setup file and returns Extension instances."""
|
||||
from distutils.sysconfig import (parse_makefile, expand_makefile_vars,
|
||||
_variable_rx)
|
||||
from distutils.sysconfig import parse_makefile, expand_makefile_vars, _variable_rx
|
||||
|
||||
from distutils.text_file import TextFile
|
||||
from distutils.util import split_quoted
|
||||
@@ -151,17 +154,22 @@ def read_setup_file(filename):
|
||||
|
||||
# Second pass to gobble up the real content: lines of the form
|
||||
# <module> ... [<sourcefile> ...] [<cpparg> ...] [<library> ...]
|
||||
file = TextFile(filename,
|
||||
strip_comments=1, skip_blanks=1, join_lines=1,
|
||||
lstrip_ws=1, rstrip_ws=1)
|
||||
file = TextFile(
|
||||
filename,
|
||||
strip_comments=1,
|
||||
skip_blanks=1,
|
||||
join_lines=1,
|
||||
lstrip_ws=1,
|
||||
rstrip_ws=1,
|
||||
)
|
||||
try:
|
||||
extensions = []
|
||||
|
||||
while True:
|
||||
line = file.readline()
|
||||
if line is None: # eof
|
||||
if line is None: # eof
|
||||
break
|
||||
if _variable_rx.match(line): # VAR=VALUE, handled in first pass
|
||||
if _variable_rx.match(line): # VAR=VALUE, handled in first pass
|
||||
continue
|
||||
|
||||
if line[0] == line[-1] == "*":
|
||||
@@ -188,7 +196,8 @@ def read_setup_file(filename):
|
||||
continue
|
||||
|
||||
suffix = os.path.splitext(word)[1]
|
||||
switch = word[0:2] ; value = word[2:]
|
||||
switch = word[0:2]
|
||||
value = word[2:]
|
||||
|
||||
if suffix in (".c", ".cc", ".cpp", ".cxx", ".c++", ".m", ".mm"):
|
||||
# hmm, should we do something about C vs. C++ sources?
|
||||
@@ -199,14 +208,13 @@ def read_setup_file(filename):
|
||||
ext.include_dirs.append(value)
|
||||
elif switch == "-D":
|
||||
equals = value.find("=")
|
||||
if equals == -1: # bare "-DFOO" -- no value
|
||||
if equals == -1: # bare "-DFOO" -- no value
|
||||
ext.define_macros.append((value, None))
|
||||
else: # "-DFOO=blah"
|
||||
ext.define_macros.append((value[0:equals],
|
||||
value[equals+2:]))
|
||||
else: # "-DFOO=blah"
|
||||
ext.define_macros.append((value[0:equals], value[equals + 2 :]))
|
||||
elif switch == "-U":
|
||||
ext.undef_macros.append(value)
|
||||
elif switch == "-C": # only here 'cause makesetup has it!
|
||||
elif switch == "-C": # only here 'cause makesetup has it!
|
||||
ext.extra_compile_args.append(word)
|
||||
elif switch == "-l":
|
||||
ext.libraries.append(value)
|
||||
|
||||
@@ -8,9 +8,11 @@ additional features:
|
||||
* options set attributes of a passed-in object
|
||||
"""
|
||||
|
||||
import sys, string, re
|
||||
import sys
|
||||
import string
|
||||
import re
|
||||
import getopt
|
||||
from distutils.errors import *
|
||||
from distutils.errors import DistutilsGetoptError, DistutilsArgError
|
||||
|
||||
# Much like command_re in distutils.core, this is close to but not quite
|
||||
# the same as a Python NAME -- except, in the spirit of most GNU
|
||||
@@ -20,12 +22,13 @@ longopt_pat = r'[a-zA-Z](?:[a-zA-Z0-9-]*)'
|
||||
longopt_re = re.compile(r'^%s$' % longopt_pat)
|
||||
|
||||
# For recognizing "negative alias" options, eg. "quiet=!verbose"
|
||||
neg_alias_re = re.compile("^(%s)=!(%s)$" % (longopt_pat, longopt_pat))
|
||||
neg_alias_re = re.compile("^({})=!({})$".format(longopt_pat, longopt_pat))
|
||||
|
||||
# This is used to translate long options to legitimate Python identifiers
|
||||
# (for use as attributes of some object).
|
||||
longopt_xlate = str.maketrans('-', '_')
|
||||
|
||||
|
||||
class FancyGetopt:
|
||||
"""Wrapper around the standard 'getopt()' module that provides some
|
||||
handy extra functionality:
|
||||
@@ -90,7 +93,8 @@ class FancyGetopt:
|
||||
def add_option(self, long_option, short_option=None, help_string=None):
|
||||
if long_option in self.option_index:
|
||||
raise DistutilsGetoptError(
|
||||
"option conflict: already an option '%s'" % long_option)
|
||||
"option conflict: already an option '%s'" % long_option
|
||||
)
|
||||
else:
|
||||
option = (long_option, short_option, help_string)
|
||||
self.option_table.append(option)
|
||||
@@ -111,11 +115,15 @@ class FancyGetopt:
|
||||
assert isinstance(aliases, dict)
|
||||
for (alias, opt) in aliases.items():
|
||||
if alias not in self.option_index:
|
||||
raise DistutilsGetoptError(("invalid %s '%s': "
|
||||
"option '%s' not defined") % (what, alias, alias))
|
||||
raise DistutilsGetoptError(
|
||||
("invalid %s '%s': " "option '%s' not defined")
|
||||
% (what, alias, alias)
|
||||
)
|
||||
if opt not in self.option_index:
|
||||
raise DistutilsGetoptError(("invalid %s '%s': "
|
||||
"aliased option '%s' not defined") % (what, alias, opt))
|
||||
raise DistutilsGetoptError(
|
||||
("invalid %s '%s': " "aliased option '%s' not defined")
|
||||
% (what, alias, opt)
|
||||
)
|
||||
|
||||
def set_aliases(self, alias):
|
||||
"""Set the aliases for this option parser."""
|
||||
@@ -130,7 +138,7 @@ class FancyGetopt:
|
||||
self._check_alias_dict(negative_alias, "negative alias")
|
||||
self.negative_alias = negative_alias
|
||||
|
||||
def _grok_option_table(self):
|
||||
def _grok_option_table(self): # noqa: C901
|
||||
"""Populate the various data structures that keep tabs on the
|
||||
option table. Called by 'getopt()' before it can do anything
|
||||
worthwhile.
|
||||
@@ -149,23 +157,27 @@ class FancyGetopt:
|
||||
else:
|
||||
# the option table is part of the code, so simply
|
||||
# assert that it is correct
|
||||
raise ValueError("invalid option tuple: %r" % (option,))
|
||||
raise ValueError("invalid option tuple: {!r}".format(option))
|
||||
|
||||
# Type- and value-check the option names
|
||||
if not isinstance(long, str) or len(long) < 2:
|
||||
raise DistutilsGetoptError(("invalid long option '%s': "
|
||||
"must be a string of length >= 2") % long)
|
||||
raise DistutilsGetoptError(
|
||||
("invalid long option '%s': " "must be a string of length >= 2")
|
||||
% long
|
||||
)
|
||||
|
||||
if (not ((short is None) or
|
||||
(isinstance(short, str) and len(short) == 1))):
|
||||
raise DistutilsGetoptError("invalid short option '%s': "
|
||||
"must a single character or None" % short)
|
||||
if not ((short is None) or (isinstance(short, str) and len(short) == 1)):
|
||||
raise DistutilsGetoptError(
|
||||
"invalid short option '%s': "
|
||||
"must a single character or None" % short
|
||||
)
|
||||
|
||||
self.repeat[long] = repeat
|
||||
self.long_opts.append(long)
|
||||
|
||||
if long[-1] == '=': # option takes an argument?
|
||||
if short: short = short + ':'
|
||||
if long[-1] == '=': # option takes an argument?
|
||||
if short:
|
||||
short = short + ':'
|
||||
long = long[0:-1]
|
||||
self.takes_arg[long] = 1
|
||||
else:
|
||||
@@ -175,11 +187,11 @@ class FancyGetopt:
|
||||
if alias_to is not None:
|
||||
if self.takes_arg[alias_to]:
|
||||
raise DistutilsGetoptError(
|
||||
"invalid negative alias '%s': "
|
||||
"aliased option '%s' takes a value"
|
||||
% (long, alias_to))
|
||||
"invalid negative alias '%s': "
|
||||
"aliased option '%s' takes a value" % (long, alias_to)
|
||||
)
|
||||
|
||||
self.long_opts[-1] = long # XXX redundant?!
|
||||
self.long_opts[-1] = long # XXX redundant?!
|
||||
self.takes_arg[long] = 0
|
||||
|
||||
# If this is an alias option, make sure its "takes arg" flag is
|
||||
@@ -188,10 +200,10 @@ class FancyGetopt:
|
||||
if alias_to is not None:
|
||||
if self.takes_arg[long] != self.takes_arg[alias_to]:
|
||||
raise DistutilsGetoptError(
|
||||
"invalid alias '%s': inconsistent with "
|
||||
"aliased option '%s' (one of them takes a value, "
|
||||
"the other doesn't"
|
||||
% (long, alias_to))
|
||||
"invalid alias '%s': inconsistent with "
|
||||
"aliased option '%s' (one of them takes a value, "
|
||||
"the other doesn't" % (long, alias_to)
|
||||
)
|
||||
|
||||
# Now enforce some bondage on the long option name, so we can
|
||||
# later translate it to an attribute name on some object. Have
|
||||
@@ -199,15 +211,16 @@ class FancyGetopt:
|
||||
# '='.
|
||||
if not longopt_re.match(long):
|
||||
raise DistutilsGetoptError(
|
||||
"invalid long option name '%s' "
|
||||
"(must be letters, numbers, hyphens only" % long)
|
||||
"invalid long option name '%s' "
|
||||
"(must be letters, numbers, hyphens only" % long
|
||||
)
|
||||
|
||||
self.attr_name[long] = self.get_attr_name(long)
|
||||
if short:
|
||||
self.short_opts.append(short)
|
||||
self.short2long[short[0]] = long
|
||||
|
||||
def getopt(self, args=None, object=None):
|
||||
def getopt(self, args=None, object=None): # noqa: C901
|
||||
"""Parse command-line options in args. Store as attributes on object.
|
||||
|
||||
If 'args' is None or not supplied, uses 'sys.argv[1:]'. If
|
||||
@@ -235,7 +248,7 @@ class FancyGetopt:
|
||||
raise DistutilsArgError(msg)
|
||||
|
||||
for opt, val in opts:
|
||||
if len(opt) == 2 and opt[0] == '-': # it's a short option
|
||||
if len(opt) == 2 and opt[0] == '-': # it's a short option
|
||||
opt = self.short2long[opt[1]]
|
||||
else:
|
||||
assert len(opt) > 2 and opt[:2] == '--'
|
||||
@@ -245,7 +258,7 @@ class FancyGetopt:
|
||||
if alias:
|
||||
opt = alias
|
||||
|
||||
if not self.takes_arg[opt]: # boolean option?
|
||||
if not self.takes_arg[opt]: # boolean option?
|
||||
assert val == '', "boolean option can't have value"
|
||||
alias = self.negative_alias.get(opt)
|
||||
if alias:
|
||||
@@ -278,7 +291,7 @@ class FancyGetopt:
|
||||
else:
|
||||
return self.option_order
|
||||
|
||||
def generate_help(self, header=None):
|
||||
def generate_help(self, header=None): # noqa: C901
|
||||
"""Generate help text (a list of strings, one per suggested line of
|
||||
output) from the option table for this FancyGetopt object.
|
||||
"""
|
||||
@@ -290,15 +303,15 @@ class FancyGetopt:
|
||||
for option in self.option_table:
|
||||
long = option[0]
|
||||
short = option[1]
|
||||
l = len(long)
|
||||
ell = len(long)
|
||||
if long[-1] == '=':
|
||||
l = l - 1
|
||||
ell = ell - 1
|
||||
if short is not None:
|
||||
l = l + 5 # " (-x)" where short == 'x'
|
||||
if l > max_opt:
|
||||
max_opt = l
|
||||
ell = ell + 5 # " (-x)" where short == 'x'
|
||||
if ell > max_opt:
|
||||
max_opt = ell
|
||||
|
||||
opt_width = max_opt + 2 + 2 + 2 # room for indent + dashes + gutter
|
||||
opt_width = max_opt + 2 + 2 + 2 # room for indent + dashes + gutter
|
||||
|
||||
# Typical help block looks like this:
|
||||
# --foo controls foonabulation
|
||||
@@ -346,15 +359,14 @@ class FancyGetopt:
|
||||
# Case 2: we have a short option, so we have to include it
|
||||
# just after the long option
|
||||
else:
|
||||
opt_names = "%s (-%s)" % (long, short)
|
||||
opt_names = "{} (-{})".format(long, short)
|
||||
if text:
|
||||
lines.append(" --%-*s %s" %
|
||||
(max_opt, opt_names, text[0]))
|
||||
lines.append(" --%-*s %s" % (max_opt, opt_names, text[0]))
|
||||
else:
|
||||
lines.append(" --%-*s" % opt_names)
|
||||
|
||||
for l in text[1:]:
|
||||
lines.append(big_indent + l)
|
||||
for ell in text[1:]:
|
||||
lines.append(big_indent + ell)
|
||||
return lines
|
||||
|
||||
def print_help(self, header=None, file=None):
|
||||
@@ -370,7 +382,8 @@ def fancy_getopt(options, negative_opt, object, args):
|
||||
return parser.getopt(args, object)
|
||||
|
||||
|
||||
WS_TRANS = {ord(_wschar) : ' ' for _wschar in string.whitespace}
|
||||
WS_TRANS = {ord(_wschar): ' ' for _wschar in string.whitespace}
|
||||
|
||||
|
||||
def wrap_text(text, width):
|
||||
"""wrap_text(text : string, width : int) -> [string]
|
||||
@@ -386,26 +399,26 @@ def wrap_text(text, width):
|
||||
text = text.expandtabs()
|
||||
text = text.translate(WS_TRANS)
|
||||
chunks = re.split(r'( +|-+)', text)
|
||||
chunks = [ch for ch in chunks if ch] # ' - ' results in empty strings
|
||||
chunks = [ch for ch in chunks if ch] # ' - ' results in empty strings
|
||||
lines = []
|
||||
|
||||
while chunks:
|
||||
cur_line = [] # list of chunks (to-be-joined)
|
||||
cur_len = 0 # length of current line
|
||||
cur_line = [] # list of chunks (to-be-joined)
|
||||
cur_len = 0 # length of current line
|
||||
|
||||
while chunks:
|
||||
l = len(chunks[0])
|
||||
if cur_len + l <= width: # can squeeze (at least) this chunk in
|
||||
ell = len(chunks[0])
|
||||
if cur_len + ell <= width: # can squeeze (at least) this chunk in
|
||||
cur_line.append(chunks[0])
|
||||
del chunks[0]
|
||||
cur_len = cur_len + l
|
||||
else: # this line is full
|
||||
cur_len = cur_len + ell
|
||||
else: # this line is full
|
||||
# drop last chunk if all space
|
||||
if cur_line and cur_line[-1][0] == ' ':
|
||||
del cur_line[-1]
|
||||
break
|
||||
|
||||
if chunks: # any chunks left to process?
|
||||
if chunks: # any chunks left to process?
|
||||
# if the current line is still empty, then we had a single
|
||||
# chunk that's too big too fit on a line -- so we break
|
||||
# down and break it up at the line width
|
||||
|
||||
@@ -8,12 +8,10 @@ from distutils.errors import DistutilsFileError
|
||||
from distutils import log
|
||||
|
||||
# for generating verbose output in 'copy_file()'
|
||||
_copy_action = { None: 'copying',
|
||||
'hard': 'hard linking',
|
||||
'sym': 'symbolically linking' }
|
||||
_copy_action = {None: 'copying', 'hard': 'hard linking', 'sym': 'symbolically linking'}
|
||||
|
||||
|
||||
def _copy_file_contents(src, dst, buffer_size=16*1024):
|
||||
def _copy_file_contents(src, dst, buffer_size=16 * 1024): # noqa: C901
|
||||
"""Copy the file 'src' to 'dst'; both must be filenames. Any error
|
||||
opening either file, reading from 'src', or writing to 'dst', raises
|
||||
DistutilsFileError. Data is read/written in chunks of 'buffer_size'
|
||||
@@ -28,27 +26,30 @@ def _copy_file_contents(src, dst, buffer_size=16*1024):
|
||||
try:
|
||||
fsrc = open(src, 'rb')
|
||||
except OSError as e:
|
||||
raise DistutilsFileError("could not open '%s': %s" % (src, e.strerror))
|
||||
raise DistutilsFileError("could not open '{}': {}".format(src, e.strerror))
|
||||
|
||||
if os.path.exists(dst):
|
||||
try:
|
||||
os.unlink(dst)
|
||||
except OSError as e:
|
||||
raise DistutilsFileError(
|
||||
"could not delete '%s': %s" % (dst, e.strerror))
|
||||
"could not delete '{}': {}".format(dst, e.strerror)
|
||||
)
|
||||
|
||||
try:
|
||||
fdst = open(dst, 'wb')
|
||||
except OSError as e:
|
||||
raise DistutilsFileError(
|
||||
"could not create '%s': %s" % (dst, e.strerror))
|
||||
"could not create '{}': {}".format(dst, e.strerror)
|
||||
)
|
||||
|
||||
while True:
|
||||
try:
|
||||
buf = fsrc.read(buffer_size)
|
||||
except OSError as e:
|
||||
raise DistutilsFileError(
|
||||
"could not read from '%s': %s" % (src, e.strerror))
|
||||
"could not read from '{}': {}".format(src, e.strerror)
|
||||
)
|
||||
|
||||
if not buf:
|
||||
break
|
||||
@@ -57,15 +58,25 @@ def _copy_file_contents(src, dst, buffer_size=16*1024):
|
||||
fdst.write(buf)
|
||||
except OSError as e:
|
||||
raise DistutilsFileError(
|
||||
"could not write to '%s': %s" % (dst, e.strerror))
|
||||
"could not write to '{}': {}".format(dst, e.strerror)
|
||||
)
|
||||
finally:
|
||||
if fdst:
|
||||
fdst.close()
|
||||
if fsrc:
|
||||
fsrc.close()
|
||||
|
||||
def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
|
||||
link=None, verbose=1, dry_run=0):
|
||||
|
||||
def copy_file( # noqa: C901
|
||||
src,
|
||||
dst,
|
||||
preserve_mode=1,
|
||||
preserve_times=1,
|
||||
update=0,
|
||||
link=None,
|
||||
verbose=1,
|
||||
dry_run=0,
|
||||
):
|
||||
"""Copy a file 'src' to 'dst'. If 'dst' is a directory, then 'src' is
|
||||
copied there with the same name; otherwise, it must be a filename. (If
|
||||
the file exists, it will be ruthlessly clobbered.) If 'preserve_mode'
|
||||
@@ -102,7 +113,8 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
|
||||
|
||||
if not os.path.isfile(src):
|
||||
raise DistutilsFileError(
|
||||
"can't copy '%s': doesn't exist or not a regular file" % src)
|
||||
"can't copy '%s': doesn't exist or not a regular file" % src
|
||||
)
|
||||
|
||||
if os.path.isdir(dst):
|
||||
dir = dst
|
||||
@@ -163,9 +175,7 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
|
||||
|
||||
|
||||
# XXX I suspect this is Unix-specific -- need porting help!
|
||||
def move_file (src, dst,
|
||||
verbose=1,
|
||||
dry_run=0):
|
||||
def move_file(src, dst, verbose=1, dry_run=0): # noqa: C901
|
||||
|
||||
"""Move a file 'src' to 'dst'. If 'dst' is a directory, the file will
|
||||
be moved into it with the same name; otherwise, 'src' is just renamed
|
||||
@@ -190,13 +200,13 @@ def move_file (src, dst,
|
||||
dst = os.path.join(dst, basename(src))
|
||||
elif exists(dst):
|
||||
raise DistutilsFileError(
|
||||
"can't move '%s': destination '%s' already exists" %
|
||||
(src, dst))
|
||||
"can't move '{}': destination '{}' already exists".format(src, dst)
|
||||
)
|
||||
|
||||
if not isdir(dirname(dst)):
|
||||
raise DistutilsFileError(
|
||||
"can't move '%s': destination '%s' not a valid path" %
|
||||
(src, dst))
|
||||
"can't move '{}': destination '{}' not a valid path".format(src, dst)
|
||||
)
|
||||
|
||||
copy_it = False
|
||||
try:
|
||||
@@ -207,7 +217,8 @@ def move_file (src, dst,
|
||||
copy_it = True
|
||||
else:
|
||||
raise DistutilsFileError(
|
||||
"couldn't move '%s' to '%s': %s" % (src, dst, msg))
|
||||
"couldn't move '{}' to '{}': {}".format(src, dst, msg)
|
||||
)
|
||||
|
||||
if copy_it:
|
||||
copy_file(src, dst, verbose=verbose)
|
||||
@@ -220,13 +231,13 @@ def move_file (src, dst,
|
||||
except OSError:
|
||||
pass
|
||||
raise DistutilsFileError(
|
||||
"couldn't move '%s' to '%s' by copy/delete: "
|
||||
"delete '%s' failed: %s"
|
||||
% (src, dst, src, msg))
|
||||
"couldn't move '%s' to '%s' by copy/delete: "
|
||||
"delete '%s' failed: %s" % (src, dst, src, msg)
|
||||
)
|
||||
return dst
|
||||
|
||||
|
||||
def write_file (filename, contents):
|
||||
def write_file(filename, contents):
|
||||
"""Create a file with the specified name and write 'contents' (a
|
||||
sequence of strings without line terminators) to it.
|
||||
"""
|
||||
|
||||
@@ -46,6 +46,7 @@ class FileList:
|
||||
DISTUTILS_DEBUG environment variable) flag is true.
|
||||
"""
|
||||
from distutils.debug import DEBUG
|
||||
|
||||
if DEBUG:
|
||||
print(msg)
|
||||
|
||||
@@ -80,29 +81,31 @@ class FileList:
|
||||
|
||||
patterns = dir = dir_pattern = None
|
||||
|
||||
if action in ('include', 'exclude',
|
||||
'global-include', 'global-exclude'):
|
||||
if action in ('include', 'exclude', 'global-include', 'global-exclude'):
|
||||
if len(words) < 2:
|
||||
raise DistutilsTemplateError(
|
||||
"'%s' expects <pattern1> <pattern2> ..." % action)
|
||||
"'%s' expects <pattern1> <pattern2> ..." % action
|
||||
)
|
||||
patterns = [convert_path(w) for w in words[1:]]
|
||||
elif action in ('recursive-include', 'recursive-exclude'):
|
||||
if len(words) < 3:
|
||||
raise DistutilsTemplateError(
|
||||
"'%s' expects <dir> <pattern1> <pattern2> ..." % action)
|
||||
"'%s' expects <dir> <pattern1> <pattern2> ..." % action
|
||||
)
|
||||
dir = convert_path(words[1])
|
||||
patterns = [convert_path(w) for w in words[2:]]
|
||||
elif action in ('graft', 'prune'):
|
||||
if len(words) != 2:
|
||||
raise DistutilsTemplateError(
|
||||
"'%s' expects a single <dir_pattern>" % action)
|
||||
"'%s' expects a single <dir_pattern>" % action
|
||||
)
|
||||
dir_pattern = convert_path(words[1])
|
||||
else:
|
||||
raise DistutilsTemplateError("unknown action '%s'" % action)
|
||||
|
||||
return (action, patterns, dir, dir_pattern)
|
||||
|
||||
def process_template_line(self, line):
|
||||
def process_template_line(self, line): # noqa: C901
|
||||
# Parse the line: split it up, make sure the right number of words
|
||||
# is there, and return the relevant words. 'action' is always
|
||||
# defined: it's the first word of the line. Which of the other
|
||||
@@ -117,65 +120,82 @@ class FileList:
|
||||
self.debug_print("include " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.include_pattern(pattern, anchor=1):
|
||||
log.warn("warning: no files found matching '%s'",
|
||||
pattern)
|
||||
log.warn("warning: no files found matching '%s'", pattern)
|
||||
|
||||
elif action == 'exclude':
|
||||
self.debug_print("exclude " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.exclude_pattern(pattern, anchor=1):
|
||||
log.warn(("warning: no previously-included files "
|
||||
"found matching '%s'"), pattern)
|
||||
log.warn(
|
||||
(
|
||||
"warning: no previously-included files "
|
||||
"found matching '%s'"
|
||||
),
|
||||
pattern,
|
||||
)
|
||||
|
||||
elif action == 'global-include':
|
||||
self.debug_print("global-include " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.include_pattern(pattern, anchor=0):
|
||||
log.warn(("warning: no files found matching '%s' "
|
||||
"anywhere in distribution"), pattern)
|
||||
log.warn(
|
||||
(
|
||||
"warning: no files found matching '%s' "
|
||||
"anywhere in distribution"
|
||||
),
|
||||
pattern,
|
||||
)
|
||||
|
||||
elif action == 'global-exclude':
|
||||
self.debug_print("global-exclude " + ' '.join(patterns))
|
||||
for pattern in patterns:
|
||||
if not self.exclude_pattern(pattern, anchor=0):
|
||||
log.warn(("warning: no previously-included files matching "
|
||||
"'%s' found anywhere in distribution"),
|
||||
pattern)
|
||||
log.warn(
|
||||
(
|
||||
"warning: no previously-included files matching "
|
||||
"'%s' found anywhere in distribution"
|
||||
),
|
||||
pattern,
|
||||
)
|
||||
|
||||
elif action == 'recursive-include':
|
||||
self.debug_print("recursive-include %s %s" %
|
||||
(dir, ' '.join(patterns)))
|
||||
self.debug_print("recursive-include {} {}".format(dir, ' '.join(patterns)))
|
||||
for pattern in patterns:
|
||||
if not self.include_pattern(pattern, prefix=dir):
|
||||
msg = (
|
||||
"warning: no files found matching '%s' "
|
||||
"under directory '%s'"
|
||||
"warning: no files found matching '%s' " "under directory '%s'"
|
||||
)
|
||||
log.warn(msg, pattern, dir)
|
||||
|
||||
elif action == 'recursive-exclude':
|
||||
self.debug_print("recursive-exclude %s %s" %
|
||||
(dir, ' '.join(patterns)))
|
||||
self.debug_print("recursive-exclude {} {}".format(dir, ' '.join(patterns)))
|
||||
for pattern in patterns:
|
||||
if not self.exclude_pattern(pattern, prefix=dir):
|
||||
log.warn(("warning: no previously-included files matching "
|
||||
"'%s' found under directory '%s'"),
|
||||
pattern, dir)
|
||||
log.warn(
|
||||
(
|
||||
"warning: no previously-included files matching "
|
||||
"'%s' found under directory '%s'"
|
||||
),
|
||||
pattern,
|
||||
dir,
|
||||
)
|
||||
|
||||
elif action == 'graft':
|
||||
self.debug_print("graft " + dir_pattern)
|
||||
if not self.include_pattern(None, prefix=dir_pattern):
|
||||
log.warn("warning: no directories found matching '%s'",
|
||||
dir_pattern)
|
||||
log.warn("warning: no directories found matching '%s'", dir_pattern)
|
||||
|
||||
elif action == 'prune':
|
||||
self.debug_print("prune " + dir_pattern)
|
||||
if not self.exclude_pattern(None, prefix=dir_pattern):
|
||||
log.warn(("no previously-included directories found "
|
||||
"matching '%s'"), dir_pattern)
|
||||
log.warn(
|
||||
("no previously-included directories found " "matching '%s'"),
|
||||
dir_pattern,
|
||||
)
|
||||
else:
|
||||
raise DistutilsInternalError(
|
||||
"this cannot happen: invalid action '%s'" % action)
|
||||
"this cannot happen: invalid action '%s'" % action
|
||||
)
|
||||
|
||||
# Filtering/selection methods
|
||||
|
||||
@@ -207,8 +227,7 @@ class FileList:
|
||||
# XXX docstring lying about what the special chars are?
|
||||
files_found = False
|
||||
pattern_re = translate_pattern(pattern, anchor, prefix, is_regex)
|
||||
self.debug_print("include_pattern: applying regex r'%s'" %
|
||||
pattern_re.pattern)
|
||||
self.debug_print("include_pattern: applying regex r'%s'" % pattern_re.pattern)
|
||||
|
||||
# delayed loading of allfiles list
|
||||
if self.allfiles is None:
|
||||
@@ -221,8 +240,7 @@ class FileList:
|
||||
files_found = True
|
||||
return files_found
|
||||
|
||||
def exclude_pattern(
|
||||
self, pattern, anchor=1, prefix=None, is_regex=0):
|
||||
def exclude_pattern(self, pattern, anchor=1, prefix=None, is_regex=0):
|
||||
"""Remove strings (presumably filenames) from 'files' that match
|
||||
'pattern'. Other parameters are the same as for
|
||||
'include_pattern()', above.
|
||||
@@ -231,9 +249,8 @@ class FileList:
|
||||
"""
|
||||
files_found = False
|
||||
pattern_re = translate_pattern(pattern, anchor, prefix, is_regex)
|
||||
self.debug_print("exclude_pattern: applying regex r'%s'" %
|
||||
pattern_re.pattern)
|
||||
for i in range(len(self.files)-1, -1, -1):
|
||||
self.debug_print("exclude_pattern: applying regex r'%s'" % pattern_re.pattern)
|
||||
for i in range(len(self.files) - 1, -1, -1):
|
||||
if pattern_re.search(self.files[i]):
|
||||
self.debug_print(" removing " + self.files[i])
|
||||
del self.files[i]
|
||||
@@ -243,15 +260,14 @@ class FileList:
|
||||
|
||||
# Utility functions
|
||||
|
||||
|
||||
def _find_all_simple(path):
|
||||
"""
|
||||
Find all files under 'path'
|
||||
"""
|
||||
all_unique = _UniqueDirs.filter(os.walk(path, followlinks=True))
|
||||
results = (
|
||||
os.path.join(base, file)
|
||||
for base, dirs, files in all_unique
|
||||
for file in files
|
||||
os.path.join(base, file) for base, dirs, files in all_unique for file in files
|
||||
)
|
||||
return filter(os.path.isfile, results)
|
||||
|
||||
@@ -262,6 +278,7 @@ class _UniqueDirs(set):
|
||||
avoiding infinite recursion.
|
||||
Ref https://bugs.python.org/issue44497.
|
||||
"""
|
||||
|
||||
def __call__(self, walk_item):
|
||||
"""
|
||||
Given an item from an os.walk result, determine
|
||||
@@ -341,15 +358,14 @@ def translate_pattern(pattern, anchor=1, prefix=None, is_regex=0):
|
||||
if prefix is not None:
|
||||
prefix_re = glob_to_re(prefix)
|
||||
assert prefix_re.startswith(start) and prefix_re.endswith(end)
|
||||
prefix_re = prefix_re[len(start): len(prefix_re) - len(end)]
|
||||
prefix_re = prefix_re[len(start) : len(prefix_re) - len(end)]
|
||||
sep = os.sep
|
||||
if os.sep == '\\':
|
||||
sep = r'\\'
|
||||
pattern_re = pattern_re[len(start): len(pattern_re) - len(end)]
|
||||
pattern_re = r'%s\A%s%s.*%s%s' % (
|
||||
start, prefix_re, sep, pattern_re, end)
|
||||
else: # no prefix -- respect anchor flag
|
||||
pattern_re = pattern_re[len(start) : len(pattern_re) - len(end)]
|
||||
pattern_re = r'{}\A{}{}.*{}{}'.format(start, prefix_re, sep, pattern_re, end)
|
||||
else: # no prefix -- respect anchor flag
|
||||
if anchor:
|
||||
pattern_re = r'%s\A%s' % (start, pattern_re[len(start):])
|
||||
pattern_re = r'{}\A{}'.format(start, pattern_re[len(start) :])
|
||||
|
||||
return re.compile(pattern_re)
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
# The class here is styled after PEP 282 so that it could later be
|
||||
# replaced with a standard Python logging implementation.
|
||||
|
||||
import sys
|
||||
|
||||
DEBUG = 1
|
||||
INFO = 2
|
||||
WARN = 3
|
||||
ERROR = 4
|
||||
FATAL = 5
|
||||
|
||||
import sys
|
||||
|
||||
class Log:
|
||||
|
||||
def __init__(self, threshold=WARN):
|
||||
self.threshold = threshold
|
||||
|
||||
@@ -54,6 +54,7 @@ class Log:
|
||||
def fatal(self, msg, *args):
|
||||
self._log(FATAL, msg, args)
|
||||
|
||||
|
||||
_global_log = Log()
|
||||
log = _global_log.log
|
||||
debug = _global_log.debug
|
||||
@@ -62,12 +63,14 @@ warn = _global_log.warn
|
||||
error = _global_log.error
|
||||
fatal = _global_log.fatal
|
||||
|
||||
|
||||
def set_threshold(level):
|
||||
# return the old threshold for use from tests
|
||||
old = _global_log.threshold
|
||||
_global_log.threshold = level
|
||||
return old
|
||||
|
||||
|
||||
def set_verbosity(v):
|
||||
if v <= 0:
|
||||
set_threshold(WARN)
|
||||
|
||||
@@ -16,26 +16,41 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from distutils.errors import DistutilsExecError, DistutilsPlatformError, \
|
||||
CompileError, LibError, LinkError
|
||||
from distutils.errors import (
|
||||
DistutilsExecError,
|
||||
DistutilsPlatformError,
|
||||
CompileError,
|
||||
LibError,
|
||||
LinkError,
|
||||
)
|
||||
from distutils.ccompiler import CCompiler, gen_lib_options
|
||||
from distutils import log
|
||||
from distutils.util import get_platform
|
||||
|
||||
import winreg
|
||||
|
||||
warnings.warn(
|
||||
"msvc9compiler is deprecated and slated to be removed "
|
||||
"in the future. Please discontinue use or file an issue "
|
||||
"with pypa/distutils describing your use case.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
RegOpenKeyEx = winreg.OpenKeyEx
|
||||
RegEnumKey = winreg.EnumKey
|
||||
RegEnumValue = winreg.EnumValue
|
||||
RegError = winreg.error
|
||||
|
||||
HKEYS = (winreg.HKEY_USERS,
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
winreg.HKEY_CLASSES_ROOT)
|
||||
HKEYS = (
|
||||
winreg.HKEY_USERS,
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
winreg.HKEY_CLASSES_ROOT,
|
||||
)
|
||||
|
||||
NATIVE_WIN64 = (sys.platform == 'win32' and sys.maxsize > 2**32)
|
||||
NATIVE_WIN64 = sys.platform == 'win32' and sys.maxsize > 2**32
|
||||
if NATIVE_WIN64:
|
||||
# Visual C++ is a 32-bit application, so we need to look in
|
||||
# the corresponding registry branch, if we're running a
|
||||
@@ -52,13 +67,13 @@ else:
|
||||
# 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is
|
||||
# the param to cross-compile on x86 targeting amd64.)
|
||||
PLAT_TO_VCVARS = {
|
||||
'win32' : 'x86',
|
||||
'win-amd64' : 'amd64',
|
||||
'win32': 'x86',
|
||||
'win-amd64': 'amd64',
|
||||
}
|
||||
|
||||
|
||||
class Reg:
|
||||
"""Helper class to read values from the registry
|
||||
"""
|
||||
"""Helper class to read values from the registry"""
|
||||
|
||||
def get_value(cls, path, key):
|
||||
for base in HKEYS:
|
||||
@@ -66,6 +81,7 @@ class Reg:
|
||||
if d and key in d:
|
||||
return d[key]
|
||||
raise KeyError(key)
|
||||
|
||||
get_value = classmethod(get_value)
|
||||
|
||||
def read_keys(cls, base, key):
|
||||
@@ -84,6 +100,7 @@ class Reg:
|
||||
L.append(k)
|
||||
i += 1
|
||||
return L
|
||||
|
||||
read_keys = classmethod(read_keys)
|
||||
|
||||
def read_values(cls, base, key):
|
||||
@@ -106,6 +123,7 @@ class Reg:
|
||||
d[cls.convert_mbcs(name)] = cls.convert_mbcs(value)
|
||||
i += 1
|
||||
return d
|
||||
|
||||
read_values = classmethod(read_values)
|
||||
|
||||
def convert_mbcs(s):
|
||||
@@ -116,10 +134,11 @@ class Reg:
|
||||
except UnicodeError:
|
||||
pass
|
||||
return s
|
||||
|
||||
convert_mbcs = staticmethod(convert_mbcs)
|
||||
|
||||
class MacroExpander:
|
||||
|
||||
class MacroExpander:
|
||||
def __init__(self, version):
|
||||
self.macros = {}
|
||||
self.vsbase = VS_BASE % version
|
||||
@@ -134,16 +153,16 @@ class MacroExpander:
|
||||
self.set_macro("FrameworkDir", NET_BASE, "installroot")
|
||||
try:
|
||||
if version >= 8.0:
|
||||
self.set_macro("FrameworkSDKDir", NET_BASE,
|
||||
"sdkinstallrootv2.0")
|
||||
self.set_macro("FrameworkSDKDir", NET_BASE, "sdkinstallrootv2.0")
|
||||
else:
|
||||
raise KeyError("sdkinstallrootv2.0")
|
||||
except KeyError:
|
||||
raise DistutilsPlatformError(
|
||||
"""Python was built with Visual Studio 2008;
|
||||
"""Python was built with Visual Studio 2008;
|
||||
extensions must be built with a compiler than can generate compatible binaries.
|
||||
Visual Studio 2008 was not found on this system. If you have Cygwin installed,
|
||||
you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
|
||||
you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""
|
||||
)
|
||||
|
||||
if version >= 9.0:
|
||||
self.set_macro("FrameworkVersion", self.vsbase, "clr version")
|
||||
@@ -156,7 +175,7 @@ you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
|
||||
except RegError:
|
||||
continue
|
||||
key = RegEnumKey(h, 0)
|
||||
d = Reg.get_value(base, r"%s\%s" % (p, key))
|
||||
d = Reg.get_value(base, r"{}\{}".format(p, key))
|
||||
self.macros["$(FrameworkVersion)"] = d["version"]
|
||||
|
||||
def sub(self, s):
|
||||
@@ -164,6 +183,7 @@ you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
|
||||
s = s.replace(k, v)
|
||||
return s
|
||||
|
||||
|
||||
def get_build_version():
|
||||
"""Return the version of MSVC that was used to build Python.
|
||||
|
||||
@@ -189,6 +209,7 @@ def get_build_version():
|
||||
# else we don't know what version of the compiler this is
|
||||
return None
|
||||
|
||||
|
||||
def normalize_and_reduce_paths(paths):
|
||||
"""Return a list of normalized paths with duplicates removed.
|
||||
|
||||
@@ -203,9 +224,9 @@ def normalize_and_reduce_paths(paths):
|
||||
reduced_paths.append(np)
|
||||
return reduced_paths
|
||||
|
||||
|
||||
def removeDuplicates(variable):
|
||||
"""Remove duplicate values of an environment variable.
|
||||
"""
|
||||
"""Remove duplicate values of an environment variable."""
|
||||
oldList = variable.split(os.pathsep)
|
||||
newList = []
|
||||
for i in oldList:
|
||||
@@ -214,6 +235,7 @@ def removeDuplicates(variable):
|
||||
newVariable = os.pathsep.join(newList)
|
||||
return newVariable
|
||||
|
||||
|
||||
def find_vcvarsall(version):
|
||||
"""Find the vcvarsall.bat file
|
||||
|
||||
@@ -222,8 +244,7 @@ def find_vcvarsall(version):
|
||||
"""
|
||||
vsbase = VS_BASE % version
|
||||
try:
|
||||
productdir = Reg.get_value(r"%s\Setup\VC" % vsbase,
|
||||
"productdir")
|
||||
productdir = Reg.get_value(r"%s\Setup\VC" % vsbase, "productdir")
|
||||
except KeyError:
|
||||
log.debug("Unable to find productdir in registry")
|
||||
productdir = None
|
||||
@@ -249,9 +270,9 @@ def find_vcvarsall(version):
|
||||
log.debug("Unable to find vcvarsall.bat")
|
||||
return None
|
||||
|
||||
|
||||
def query_vcvarsall(version, arch="x86"):
|
||||
"""Launch vcvarsall.bat and read the settings from its environment
|
||||
"""
|
||||
"""Launch vcvarsall.bat and read the settings from its environment"""
|
||||
vcvarsall = find_vcvarsall(version)
|
||||
interesting = {"include", "lib", "libpath", "path"}
|
||||
result = {}
|
||||
@@ -259,9 +280,11 @@ def query_vcvarsall(version, arch="x86"):
|
||||
if vcvarsall is None:
|
||||
raise DistutilsPlatformError("Unable to find vcvarsall.bat")
|
||||
log.debug("Calling 'vcvarsall.bat %s' (version=%s)", arch, version)
|
||||
popen = subprocess.Popen('"%s" %s & set' % (vcvarsall, arch),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
popen = subprocess.Popen(
|
||||
'"{}" {} & set'.format(vcvarsall, arch),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
try:
|
||||
stdout, stderr = popen.communicate()
|
||||
if popen.wait() != 0:
|
||||
@@ -289,15 +312,15 @@ def query_vcvarsall(version, arch="x86"):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# More globals
|
||||
VERSION = get_build_version()
|
||||
if VERSION < 8.0:
|
||||
raise DistutilsPlatformError("VC %0.1f is not supported by this module" % VERSION)
|
||||
# MACROS = MacroExpander(VERSION)
|
||||
|
||||
class MSVCCompiler(CCompiler) :
|
||||
|
||||
class MSVCCompiler(CCompiler):
|
||||
"""Concrete class that implements an interface to Microsoft Visual C++,
|
||||
as defined by the CCompiler abstract class."""
|
||||
as defined by the CCompiler abstract class."""
|
||||
|
||||
compiler_type = 'msvc'
|
||||
|
||||
@@ -316,8 +339,7 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
# Needed for the filename generation methods provided by the
|
||||
# base class, CCompiler.
|
||||
src_extensions = (_c_extensions + _cpp_extensions +
|
||||
_rc_extensions + _mc_extensions)
|
||||
src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions
|
||||
res_extension = '.res'
|
||||
obj_extension = '.obj'
|
||||
static_lib_extension = '.lib'
|
||||
@@ -326,28 +348,37 @@ class MSVCCompiler(CCompiler) :
|
||||
exe_extension = '.exe'
|
||||
|
||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
||||
CCompiler.__init__ (self, verbose, dry_run, force)
|
||||
super().__init__(verbose, dry_run, force)
|
||||
self.__version = VERSION
|
||||
self.__root = r"Software\Microsoft\VisualStudio"
|
||||
# self.__macros = MACROS
|
||||
self.__paths = []
|
||||
# target platform (.plat_name is consistent with 'bdist')
|
||||
self.plat_name = None
|
||||
self.__arch = None # deprecated name
|
||||
self.__arch = None # deprecated name
|
||||
self.initialized = False
|
||||
|
||||
def initialize(self, plat_name=None):
|
||||
def initialize(self, plat_name=None): # noqa: C901
|
||||
# multi-init means we would need to check platform same each time...
|
||||
assert not self.initialized, "don't init multiple times"
|
||||
if self.__version < 8.0:
|
||||
raise DistutilsPlatformError(
|
||||
"VC %0.1f is not supported by this module" % self.__version
|
||||
)
|
||||
if plat_name is None:
|
||||
plat_name = get_platform()
|
||||
# sanity check for platforms to prevent obscure errors later.
|
||||
ok_plats = 'win32', 'win-amd64'
|
||||
if plat_name not in ok_plats:
|
||||
raise DistutilsPlatformError("--plat-name must be one of %s" %
|
||||
(ok_plats,))
|
||||
raise DistutilsPlatformError(
|
||||
"--plat-name must be one of {}".format(ok_plats)
|
||||
)
|
||||
|
||||
if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"):
|
||||
if (
|
||||
"DISTUTILS_USE_SDK" in os.environ
|
||||
and "MSSdk" in os.environ
|
||||
and self.find_exe("cl.exe")
|
||||
):
|
||||
# Assume that the SDK set up everything alright; don't try to be
|
||||
# smarter
|
||||
self.cc = "cl.exe"
|
||||
@@ -365,8 +396,9 @@ class MSVCCompiler(CCompiler) :
|
||||
plat_spec = PLAT_TO_VCVARS[plat_name]
|
||||
else:
|
||||
# cross compile from win32 -> some 64bit
|
||||
plat_spec = PLAT_TO_VCVARS[get_platform()] + '_' + \
|
||||
PLAT_TO_VCVARS[plat_name]
|
||||
plat_spec = (
|
||||
PLAT_TO_VCVARS[get_platform()] + '_' + PLAT_TO_VCVARS[plat_name]
|
||||
)
|
||||
|
||||
vc_env = query_vcvarsall(VERSION, plat_spec)
|
||||
|
||||
@@ -375,18 +407,19 @@ class MSVCCompiler(CCompiler) :
|
||||
os.environ['include'] = vc_env['include']
|
||||
|
||||
if len(self.__paths) == 0:
|
||||
raise DistutilsPlatformError("Python was built with %s, "
|
||||
"and extensions need to be built with the same "
|
||||
"version of the compiler, but it isn't installed."
|
||||
% self.__product)
|
||||
raise DistutilsPlatformError(
|
||||
"Python was built with %s, "
|
||||
"and extensions need to be built with the same "
|
||||
"version of the compiler, but it isn't installed." % self.__product
|
||||
)
|
||||
|
||||
self.cc = self.find_exe("cl.exe")
|
||||
self.linker = self.find_exe("link.exe")
|
||||
self.lib = self.find_exe("lib.exe")
|
||||
self.rc = self.find_exe("rc.exe") # resource compiler
|
||||
self.mc = self.find_exe("mc.exe") # message compiler
|
||||
#self.set_path_env_var('lib')
|
||||
#self.set_path_env_var('include')
|
||||
self.rc = self.find_exe("rc.exe") # resource compiler
|
||||
self.mc = self.find_exe("mc.exe") # message compiler
|
||||
# self.set_path_env_var('lib')
|
||||
# self.set_path_env_var('include')
|
||||
|
||||
# extend the MSVC path with the current path
|
||||
try:
|
||||
@@ -399,71 +432,83 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
self.preprocess_options = None
|
||||
if self.__arch == "x86":
|
||||
self.compile_options = [ '/nologo', '/O2', '/MD', '/W3',
|
||||
'/DNDEBUG']
|
||||
self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3',
|
||||
'/Z7', '/D_DEBUG']
|
||||
self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/DNDEBUG']
|
||||
self.compile_options_debug = [
|
||||
'/nologo',
|
||||
'/Od',
|
||||
'/MDd',
|
||||
'/W3',
|
||||
'/Z7',
|
||||
'/D_DEBUG',
|
||||
]
|
||||
else:
|
||||
# Win64
|
||||
self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GS-' ,
|
||||
'/DNDEBUG']
|
||||
self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-',
|
||||
'/Z7', '/D_DEBUG']
|
||||
self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG']
|
||||
self.compile_options_debug = [
|
||||
'/nologo',
|
||||
'/Od',
|
||||
'/MDd',
|
||||
'/W3',
|
||||
'/GS-',
|
||||
'/Z7',
|
||||
'/D_DEBUG',
|
||||
]
|
||||
|
||||
self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']
|
||||
if self.__version >= 7:
|
||||
self.ldflags_shared_debug = [
|
||||
'/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG'
|
||||
]
|
||||
self.ldflags_static = [ '/nologo']
|
||||
self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG']
|
||||
self.ldflags_static = ['/nologo']
|
||||
|
||||
self.initialized = True
|
||||
|
||||
# -- Worker methods ------------------------------------------------
|
||||
|
||||
def object_filenames(self,
|
||||
source_filenames,
|
||||
strip_dir=0,
|
||||
output_dir=''):
|
||||
def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
|
||||
# Copied from ccompiler.py, extended to return .res as 'object'-file
|
||||
# for .rc input file
|
||||
if output_dir is None: output_dir = ''
|
||||
if output_dir is None:
|
||||
output_dir = ''
|
||||
obj_names = []
|
||||
for src_name in source_filenames:
|
||||
(base, ext) = os.path.splitext (src_name)
|
||||
base = os.path.splitdrive(base)[1] # Chop off the drive
|
||||
base = base[os.path.isabs(base):] # If abs, chop off leading /
|
||||
(base, ext) = os.path.splitext(src_name)
|
||||
base = os.path.splitdrive(base)[1] # Chop off the drive
|
||||
base = base[os.path.isabs(base) :] # If abs, chop off leading /
|
||||
if ext not in self.src_extensions:
|
||||
# Better to raise an exception instead of silently continuing
|
||||
# and later complain about sources and targets having
|
||||
# different lengths
|
||||
raise CompileError ("Don't know how to compile %s" % src_name)
|
||||
raise CompileError("Don't know how to compile %s" % src_name)
|
||||
if strip_dir:
|
||||
base = os.path.basename (base)
|
||||
base = os.path.basename(base)
|
||||
if ext in self._rc_extensions:
|
||||
obj_names.append (os.path.join (output_dir,
|
||||
base + self.res_extension))
|
||||
obj_names.append(os.path.join(output_dir, base + self.res_extension))
|
||||
elif ext in self._mc_extensions:
|
||||
obj_names.append (os.path.join (output_dir,
|
||||
base + self.res_extension))
|
||||
obj_names.append(os.path.join(output_dir, base + self.res_extension))
|
||||
else:
|
||||
obj_names.append (os.path.join (output_dir,
|
||||
base + self.obj_extension))
|
||||
obj_names.append(os.path.join(output_dir, base + self.obj_extension))
|
||||
return obj_names
|
||||
|
||||
|
||||
def compile(self, sources,
|
||||
output_dir=None, macros=None, include_dirs=None, debug=0,
|
||||
extra_preargs=None, extra_postargs=None, depends=None):
|
||||
def compile( # noqa: C901
|
||||
self,
|
||||
sources,
|
||||
output_dir=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
depends=None,
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
compile_info = self._setup_compile(output_dir, macros, include_dirs,
|
||||
sources, depends, extra_postargs)
|
||||
compile_info = self._setup_compile(
|
||||
output_dir, macros, include_dirs, sources, depends, extra_postargs
|
||||
)
|
||||
macros, objects, extra_postargs, pp_opts, build = compile_info
|
||||
|
||||
compile_opts = extra_preargs or []
|
||||
compile_opts.append ('/c')
|
||||
compile_opts.append('/c')
|
||||
if debug:
|
||||
compile_opts.extend(self.compile_options_debug)
|
||||
else:
|
||||
@@ -489,8 +534,7 @@ class MSVCCompiler(CCompiler) :
|
||||
input_opt = src
|
||||
output_opt = "/fo" + obj
|
||||
try:
|
||||
self.spawn([self.rc] + pp_opts +
|
||||
[output_opt] + [input_opt])
|
||||
self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt])
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
continue
|
||||
@@ -510,50 +554,48 @@ class MSVCCompiler(CCompiler) :
|
||||
rc_dir = os.path.dirname(obj)
|
||||
try:
|
||||
# first compile .MC to .RC and .H file
|
||||
self.spawn([self.mc] +
|
||||
['-h', h_dir, '-r', rc_dir] + [src])
|
||||
base, _ = os.path.splitext (os.path.basename (src))
|
||||
rc_file = os.path.join (rc_dir, base + '.rc')
|
||||
self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src])
|
||||
base, _ = os.path.splitext(os.path.basename(src))
|
||||
rc_file = os.path.join(rc_dir, base + '.rc')
|
||||
# then compile .RC to .RES file
|
||||
self.spawn([self.rc] +
|
||||
["/fo" + obj] + [rc_file])
|
||||
self.spawn([self.rc] + ["/fo" + obj] + [rc_file])
|
||||
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
continue
|
||||
else:
|
||||
# how to handle this file?
|
||||
raise CompileError("Don't know how to compile %s to %s"
|
||||
% (src, obj))
|
||||
raise CompileError(
|
||||
"Don't know how to compile {} to {}".format(src, obj)
|
||||
)
|
||||
|
||||
output_opt = "/Fo" + obj
|
||||
try:
|
||||
self.spawn([self.cc] + compile_opts + pp_opts +
|
||||
[input_opt, output_opt] +
|
||||
extra_postargs)
|
||||
self.spawn(
|
||||
[self.cc]
|
||||
+ compile_opts
|
||||
+ pp_opts
|
||||
+ [input_opt, output_opt]
|
||||
+ extra_postargs
|
||||
)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def create_static_lib(self,
|
||||
objects,
|
||||
output_libname,
|
||||
output_dir=None,
|
||||
debug=0,
|
||||
target_lang=None):
|
||||
def create_static_lib(
|
||||
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
(objects, output_dir) = self._fix_object_args(objects, output_dir)
|
||||
output_filename = self.library_filename(output_libname,
|
||||
output_dir=output_dir)
|
||||
output_filename = self.library_filename(output_libname, output_dir=output_dir)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
lib_args = objects + ['/OUT:' + output_filename]
|
||||
if debug:
|
||||
pass # XXX what goes here?
|
||||
pass # XXX what goes here?
|
||||
try:
|
||||
self.spawn([self.lib] + lib_args)
|
||||
except DistutilsExecError as msg:
|
||||
@@ -561,36 +603,36 @@ class MSVCCompiler(CCompiler) :
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
|
||||
def link(self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None):
|
||||
def link( # noqa: C901
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
(objects, output_dir) = self._fix_object_args(objects, output_dir)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs,
|
||||
runtime_library_dirs)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
|
||||
(libraries, library_dirs, runtime_library_dirs) = fixed_args
|
||||
|
||||
if runtime_library_dirs:
|
||||
self.warn ("I don't know what to do with 'runtime_library_dirs': "
|
||||
+ str (runtime_library_dirs))
|
||||
self.warn(
|
||||
"I don't know what to do with 'runtime_library_dirs': "
|
||||
+ str(runtime_library_dirs)
|
||||
)
|
||||
|
||||
lib_opts = gen_lib_options(self,
|
||||
library_dirs, runtime_library_dirs,
|
||||
libraries)
|
||||
lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
|
||||
if output_dir is not None:
|
||||
output_filename = os.path.join(output_dir, output_filename)
|
||||
|
||||
@@ -607,11 +649,12 @@ class MSVCCompiler(CCompiler) :
|
||||
ldflags = self.ldflags_shared
|
||||
|
||||
export_opts = []
|
||||
for sym in (export_symbols or []):
|
||||
for sym in export_symbols or []:
|
||||
export_opts.append("/EXPORT:" + sym)
|
||||
|
||||
ld_args = (ldflags + lib_opts + export_opts +
|
||||
objects + ['/OUT:' + output_filename])
|
||||
ld_args = (
|
||||
ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]
|
||||
)
|
||||
|
||||
# The MSVC linker generates .lib and .exp files, which cannot be
|
||||
# suppressed by any linker switches. The .lib files may even be
|
||||
@@ -621,11 +664,10 @@ class MSVCCompiler(CCompiler) :
|
||||
build_temp = os.path.dirname(objects[0])
|
||||
if export_symbols is not None:
|
||||
(dll_name, dll_ext) = os.path.splitext(
|
||||
os.path.basename(output_filename))
|
||||
implib_file = os.path.join(
|
||||
build_temp,
|
||||
self.library_filename(dll_name))
|
||||
ld_args.append ('/IMPLIB:' + implib_file)
|
||||
os.path.basename(output_filename)
|
||||
)
|
||||
implib_file = os.path.join(build_temp, self.library_filename(dll_name))
|
||||
ld_args.append('/IMPLIB:' + implib_file)
|
||||
|
||||
self.manifest_setup_ldargs(output_filename, build_temp, ld_args)
|
||||
|
||||
@@ -648,10 +690,9 @@ class MSVCCompiler(CCompiler) :
|
||||
mfinfo = self.manifest_get_embed_info(target_desc, ld_args)
|
||||
if mfinfo is not None:
|
||||
mffilename, mfid = mfinfo
|
||||
out_arg = '-outputresource:%s;%s' % (output_filename, mfid)
|
||||
out_arg = '-outputresource:{};{}'.format(output_filename, mfid)
|
||||
try:
|
||||
self.spawn(['mt.exe', '-nologo', '-manifest',
|
||||
mffilename, out_arg])
|
||||
self.spawn(['mt.exe', '-nologo', '-manifest', mffilename, out_arg])
|
||||
except DistutilsExecError as msg:
|
||||
raise LinkError(msg)
|
||||
else:
|
||||
@@ -665,8 +706,8 @@ class MSVCCompiler(CCompiler) :
|
||||
# Ask the linker to generate the manifest in the temp dir, so
|
||||
# we can check it, and possibly embed it, later.
|
||||
temp_manifest = os.path.join(
|
||||
build_temp,
|
||||
os.path.basename(output_filename) + ".manifest")
|
||||
build_temp, os.path.basename(output_filename) + ".manifest"
|
||||
)
|
||||
ld_args.append('/MANIFESTFILE:' + temp_manifest)
|
||||
|
||||
def manifest_get_embed_info(self, target_desc, ld_args):
|
||||
@@ -709,9 +750,10 @@ class MSVCCompiler(CCompiler) :
|
||||
finally:
|
||||
manifest_f.close()
|
||||
pattern = re.compile(
|
||||
r"""<assemblyIdentity.*?name=("|')Microsoft\."""\
|
||||
r"""<assemblyIdentity.*?name=("|')Microsoft\."""
|
||||
r"""VC\d{2}\.CRT("|').*?(/>|</assemblyIdentity>)""",
|
||||
re.DOTALL)
|
||||
re.DOTALL,
|
||||
)
|
||||
manifest_buf = re.sub(pattern, "", manifest_buf)
|
||||
pattern = r"<dependentAssembly>\s*</dependentAssembly>"
|
||||
manifest_buf = re.sub(pattern, "", manifest_buf)
|
||||
@@ -719,7 +761,9 @@ class MSVCCompiler(CCompiler) :
|
||||
# don't want a manifest embedded.
|
||||
pattern = re.compile(
|
||||
r"""<assemblyIdentity.*?name=(?:"|')(.+?)(?:"|')"""
|
||||
r""".*?(?:/>|</assemblyIdentity>)""", re.DOTALL)
|
||||
r""".*?(?:/>|</assemblyIdentity>)""",
|
||||
re.DOTALL,
|
||||
)
|
||||
if re.search(pattern, manifest_buf) is None:
|
||||
return None
|
||||
|
||||
@@ -741,12 +785,12 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
raise DistutilsPlatformError(
|
||||
"don't know how to set runtime library search path for MSVC++")
|
||||
"don't know how to set runtime library search path for MSVC++"
|
||||
)
|
||||
|
||||
def library_option(self, lib):
|
||||
return self.library_filename(lib)
|
||||
|
||||
|
||||
def find_library_file(self, dirs, lib, debug=0):
|
||||
# Prefer a debugging library if found (and requested), but deal
|
||||
# with it if we don't have one.
|
||||
@@ -756,7 +800,7 @@ class MSVCCompiler(CCompiler) :
|
||||
try_names = [lib]
|
||||
for dir in dirs:
|
||||
for name in try_names:
|
||||
libfile = os.path.join(dir, self.library_filename (name))
|
||||
libfile = os.path.join(dir, self.library_filename(name))
|
||||
if os.path.exists(libfile):
|
||||
return libfile
|
||||
else:
|
||||
@@ -781,7 +825,7 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
# didn't find it; try existing path
|
||||
for p in os.environ['Path'].split(';'):
|
||||
fn = os.path.join(os.path.abspath(p),exe)
|
||||
fn = os.path.join(os.path.abspath(p), exe)
|
||||
if os.path.isfile(fn):
|
||||
return fn
|
||||
|
||||
|
||||
@@ -8,12 +8,17 @@ for the Microsoft Visual Studio.
|
||||
# hacked by Robin Becker and Thomas Heller to do a better job of
|
||||
# finding DevStudio (through the registry)
|
||||
|
||||
import sys, os
|
||||
from distutils.errors import \
|
||||
DistutilsExecError, DistutilsPlatformError, \
|
||||
CompileError, LibError, LinkError
|
||||
from distutils.ccompiler import \
|
||||
CCompiler, gen_lib_options
|
||||
import sys
|
||||
import os
|
||||
import warnings
|
||||
from distutils.errors import (
|
||||
DistutilsExecError,
|
||||
DistutilsPlatformError,
|
||||
CompileError,
|
||||
LibError,
|
||||
LinkError,
|
||||
)
|
||||
from distutils.ccompiler import CCompiler, gen_lib_options
|
||||
from distutils import log
|
||||
|
||||
_can_read_reg = False
|
||||
@@ -32,6 +37,7 @@ except ImportError:
|
||||
try:
|
||||
import win32api
|
||||
import win32con
|
||||
|
||||
_can_read_reg = True
|
||||
hkey_mod = win32con
|
||||
|
||||
@@ -40,17 +46,30 @@ except ImportError:
|
||||
RegEnumValue = win32api.RegEnumValue
|
||||
RegError = win32api.error
|
||||
except ImportError:
|
||||
log.info("Warning: Can't read registry to find the "
|
||||
"necessary compiler setting\n"
|
||||
"Make sure that Python modules winreg, "
|
||||
"win32api or win32con are installed.")
|
||||
log.info(
|
||||
"Warning: Can't read registry to find the "
|
||||
"necessary compiler setting\n"
|
||||
"Make sure that Python modules winreg, "
|
||||
"win32api or win32con are installed."
|
||||
)
|
||||
pass
|
||||
|
||||
if _can_read_reg:
|
||||
HKEYS = (hkey_mod.HKEY_USERS,
|
||||
hkey_mod.HKEY_CURRENT_USER,
|
||||
hkey_mod.HKEY_LOCAL_MACHINE,
|
||||
hkey_mod.HKEY_CLASSES_ROOT)
|
||||
HKEYS = (
|
||||
hkey_mod.HKEY_USERS,
|
||||
hkey_mod.HKEY_CURRENT_USER,
|
||||
hkey_mod.HKEY_LOCAL_MACHINE,
|
||||
hkey_mod.HKEY_CLASSES_ROOT,
|
||||
)
|
||||
|
||||
|
||||
warnings.warn(
|
||||
"msvccompiler is deprecated and slated to be removed "
|
||||
"in the future. Please discontinue use or file an issue "
|
||||
"with pypa/distutils describing your use case.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
def read_keys(base, key):
|
||||
"""Return list of registry keys."""
|
||||
@@ -69,6 +88,7 @@ def read_keys(base, key):
|
||||
i += 1
|
||||
return L
|
||||
|
||||
|
||||
def read_values(base, key):
|
||||
"""Return dict of registry keys and values.
|
||||
|
||||
@@ -90,6 +110,7 @@ def read_values(base, key):
|
||||
i += 1
|
||||
return d
|
||||
|
||||
|
||||
def convert_mbcs(s):
|
||||
dec = getattr(s, "decode", None)
|
||||
if dec is not None:
|
||||
@@ -99,6 +120,7 @@ def convert_mbcs(s):
|
||||
pass
|
||||
return s
|
||||
|
||||
|
||||
class MacroExpander:
|
||||
def __init__(self, version):
|
||||
self.macros = {}
|
||||
@@ -122,12 +144,13 @@ class MacroExpander:
|
||||
self.set_macro("FrameworkSDKDir", net, "sdkinstallrootv1.1")
|
||||
else:
|
||||
self.set_macro("FrameworkSDKDir", net, "sdkinstallroot")
|
||||
except KeyError as exc: #
|
||||
except KeyError:
|
||||
raise DistutilsPlatformError(
|
||||
"""Python was built with Visual Studio 2003;
|
||||
"""Python was built with Visual Studio 2003;
|
||||
extensions must be built with a compiler than can generate compatible binaries.
|
||||
Visual Studio 2003 was not found on this system. If you have Cygwin installed,
|
||||
you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
|
||||
you can try compiling with MingW32, by passing "-c mingw32" to setup.py."""
|
||||
)
|
||||
|
||||
p = r"Software\Microsoft\NET Framework Setup\Product"
|
||||
for base in HKEYS:
|
||||
@@ -136,7 +159,7 @@ you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
|
||||
except RegError:
|
||||
continue
|
||||
key = RegEnumKey(h, 0)
|
||||
d = read_values(base, r"%s\%s" % (p, key))
|
||||
d = read_values(base, r"{}\{}".format(p, key))
|
||||
self.macros["$(FrameworkVersion)"] = d["version"]
|
||||
|
||||
def sub(self, s):
|
||||
@@ -144,6 +167,7 @@ you can try compiling with MingW32, by passing "-c mingw32" to setup.py.""")
|
||||
s = s.replace(k, v)
|
||||
return s
|
||||
|
||||
|
||||
def get_build_version():
|
||||
"""Return the version of MSVC that was used to build Python.
|
||||
|
||||
@@ -169,6 +193,7 @@ def get_build_version():
|
||||
# else we don't know what version of the compiler this is
|
||||
return None
|
||||
|
||||
|
||||
def get_build_architecture():
|
||||
"""Return the processor architecture.
|
||||
|
||||
@@ -180,7 +205,8 @@ def get_build_architecture():
|
||||
if i == -1:
|
||||
return "Intel"
|
||||
j = sys.version.find(")", i)
|
||||
return sys.version[i+len(prefix):j]
|
||||
return sys.version[i + len(prefix) : j]
|
||||
|
||||
|
||||
def normalize_and_reduce_paths(paths):
|
||||
"""Return a list of normalized paths with duplicates removed.
|
||||
@@ -197,9 +223,9 @@ def normalize_and_reduce_paths(paths):
|
||||
return reduced_paths
|
||||
|
||||
|
||||
class MSVCCompiler(CCompiler) :
|
||||
class MSVCCompiler(CCompiler):
|
||||
"""Concrete class that implements an interface to Microsoft Visual C++,
|
||||
as defined by the CCompiler abstract class."""
|
||||
as defined by the CCompiler abstract class."""
|
||||
|
||||
compiler_type = 'msvc'
|
||||
|
||||
@@ -218,8 +244,7 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
# Needed for the filename generation methods provided by the
|
||||
# base class, CCompiler.
|
||||
src_extensions = (_c_extensions + _cpp_extensions +
|
||||
_rc_extensions + _mc_extensions)
|
||||
src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions
|
||||
res_extension = '.res'
|
||||
obj_extension = '.obj'
|
||||
static_lib_extension = '.lib'
|
||||
@@ -228,7 +253,7 @@ class MSVCCompiler(CCompiler) :
|
||||
exe_extension = '.exe'
|
||||
|
||||
def __init__(self, verbose=0, dry_run=0, force=0):
|
||||
CCompiler.__init__ (self, verbose, dry_run, force)
|
||||
super().__init__(verbose, dry_run, force)
|
||||
self.__version = get_build_version()
|
||||
self.__arch = get_build_architecture()
|
||||
if self.__arch == "Intel":
|
||||
@@ -247,7 +272,11 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
def initialize(self):
|
||||
self.__paths = []
|
||||
if "DISTUTILS_USE_SDK" in os.environ and "MSSdk" in os.environ and self.find_exe("cl.exe"):
|
||||
if (
|
||||
"DISTUTILS_USE_SDK" in os.environ
|
||||
and "MSSdk" in os.environ
|
||||
and self.find_exe("cl.exe")
|
||||
):
|
||||
# Assume that the SDK set up everything alright; don't try to be
|
||||
# smarter
|
||||
self.cc = "cl.exe"
|
||||
@@ -259,16 +288,17 @@ class MSVCCompiler(CCompiler) :
|
||||
self.__paths = self.get_msvc_paths("path")
|
||||
|
||||
if len(self.__paths) == 0:
|
||||
raise DistutilsPlatformError("Python was built with %s, "
|
||||
"and extensions need to be built with the same "
|
||||
"version of the compiler, but it isn't installed."
|
||||
% self.__product)
|
||||
raise DistutilsPlatformError(
|
||||
"Python was built with %s, "
|
||||
"and extensions need to be built with the same "
|
||||
"version of the compiler, but it isn't installed." % self.__product
|
||||
)
|
||||
|
||||
self.cc = self.find_exe("cl.exe")
|
||||
self.linker = self.find_exe("link.exe")
|
||||
self.lib = self.find_exe("lib.exe")
|
||||
self.rc = self.find_exe("rc.exe") # resource compiler
|
||||
self.mc = self.find_exe("mc.exe") # message compiler
|
||||
self.rc = self.find_exe("rc.exe") # resource compiler
|
||||
self.mc = self.find_exe("mc.exe") # message compiler
|
||||
self.set_path_env_var('lib')
|
||||
self.set_path_env_var('include')
|
||||
|
||||
@@ -283,75 +313,92 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
self.preprocess_options = None
|
||||
if self.__arch == "Intel":
|
||||
self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GX' ,
|
||||
'/DNDEBUG']
|
||||
self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GX',
|
||||
'/Z7', '/D_DEBUG']
|
||||
self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GX', '/DNDEBUG']
|
||||
self.compile_options_debug = [
|
||||
'/nologo',
|
||||
'/Od',
|
||||
'/MDd',
|
||||
'/W3',
|
||||
'/GX',
|
||||
'/Z7',
|
||||
'/D_DEBUG',
|
||||
]
|
||||
else:
|
||||
# Win64
|
||||
self.compile_options = [ '/nologo', '/O2', '/MD', '/W3', '/GS-' ,
|
||||
'/DNDEBUG']
|
||||
self.compile_options_debug = ['/nologo', '/Od', '/MDd', '/W3', '/GS-',
|
||||
'/Z7', '/D_DEBUG']
|
||||
self.compile_options = ['/nologo', '/O2', '/MD', '/W3', '/GS-', '/DNDEBUG']
|
||||
self.compile_options_debug = [
|
||||
'/nologo',
|
||||
'/Od',
|
||||
'/MDd',
|
||||
'/W3',
|
||||
'/GS-',
|
||||
'/Z7',
|
||||
'/D_DEBUG',
|
||||
]
|
||||
|
||||
self.ldflags_shared = ['/DLL', '/nologo', '/INCREMENTAL:NO']
|
||||
if self.__version >= 7:
|
||||
self.ldflags_shared_debug = [
|
||||
'/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG'
|
||||
]
|
||||
self.ldflags_shared_debug = ['/DLL', '/nologo', '/INCREMENTAL:no', '/DEBUG']
|
||||
else:
|
||||
self.ldflags_shared_debug = [
|
||||
'/DLL', '/nologo', '/INCREMENTAL:no', '/pdb:None', '/DEBUG'
|
||||
]
|
||||
self.ldflags_static = [ '/nologo']
|
||||
'/DLL',
|
||||
'/nologo',
|
||||
'/INCREMENTAL:no',
|
||||
'/pdb:None',
|
||||
'/DEBUG',
|
||||
]
|
||||
self.ldflags_static = ['/nologo']
|
||||
|
||||
self.initialized = True
|
||||
|
||||
# -- Worker methods ------------------------------------------------
|
||||
|
||||
def object_filenames(self,
|
||||
source_filenames,
|
||||
strip_dir=0,
|
||||
output_dir=''):
|
||||
def object_filenames(self, source_filenames, strip_dir=0, output_dir=''):
|
||||
# Copied from ccompiler.py, extended to return .res as 'object'-file
|
||||
# for .rc input file
|
||||
if output_dir is None: output_dir = ''
|
||||
if output_dir is None:
|
||||
output_dir = ''
|
||||
obj_names = []
|
||||
for src_name in source_filenames:
|
||||
(base, ext) = os.path.splitext (src_name)
|
||||
base = os.path.splitdrive(base)[1] # Chop off the drive
|
||||
base = base[os.path.isabs(base):] # If abs, chop off leading /
|
||||
(base, ext) = os.path.splitext(src_name)
|
||||
base = os.path.splitdrive(base)[1] # Chop off the drive
|
||||
base = base[os.path.isabs(base) :] # If abs, chop off leading /
|
||||
if ext not in self.src_extensions:
|
||||
# Better to raise an exception instead of silently continuing
|
||||
# and later complain about sources and targets having
|
||||
# different lengths
|
||||
raise CompileError ("Don't know how to compile %s" % src_name)
|
||||
raise CompileError("Don't know how to compile %s" % src_name)
|
||||
if strip_dir:
|
||||
base = os.path.basename (base)
|
||||
base = os.path.basename(base)
|
||||
if ext in self._rc_extensions:
|
||||
obj_names.append (os.path.join (output_dir,
|
||||
base + self.res_extension))
|
||||
obj_names.append(os.path.join(output_dir, base + self.res_extension))
|
||||
elif ext in self._mc_extensions:
|
||||
obj_names.append (os.path.join (output_dir,
|
||||
base + self.res_extension))
|
||||
obj_names.append(os.path.join(output_dir, base + self.res_extension))
|
||||
else:
|
||||
obj_names.append (os.path.join (output_dir,
|
||||
base + self.obj_extension))
|
||||
obj_names.append(os.path.join(output_dir, base + self.obj_extension))
|
||||
return obj_names
|
||||
|
||||
|
||||
def compile(self, sources,
|
||||
output_dir=None, macros=None, include_dirs=None, debug=0,
|
||||
extra_preargs=None, extra_postargs=None, depends=None):
|
||||
def compile( # noqa: C901
|
||||
self,
|
||||
sources,
|
||||
output_dir=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
depends=None,
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
compile_info = self._setup_compile(output_dir, macros, include_dirs,
|
||||
sources, depends, extra_postargs)
|
||||
compile_info = self._setup_compile(
|
||||
output_dir, macros, include_dirs, sources, depends, extra_postargs
|
||||
)
|
||||
macros, objects, extra_postargs, pp_opts, build = compile_info
|
||||
|
||||
compile_opts = extra_preargs or []
|
||||
compile_opts.append ('/c')
|
||||
compile_opts.append('/c')
|
||||
if debug:
|
||||
compile_opts.extend(self.compile_options_debug)
|
||||
else:
|
||||
@@ -377,8 +424,7 @@ class MSVCCompiler(CCompiler) :
|
||||
input_opt = src
|
||||
output_opt = "/fo" + obj
|
||||
try:
|
||||
self.spawn([self.rc] + pp_opts +
|
||||
[output_opt] + [input_opt])
|
||||
self.spawn([self.rc] + pp_opts + [output_opt] + [input_opt])
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
continue
|
||||
@@ -398,50 +444,48 @@ class MSVCCompiler(CCompiler) :
|
||||
rc_dir = os.path.dirname(obj)
|
||||
try:
|
||||
# first compile .MC to .RC and .H file
|
||||
self.spawn([self.mc] +
|
||||
['-h', h_dir, '-r', rc_dir] + [src])
|
||||
base, _ = os.path.splitext (os.path.basename (src))
|
||||
rc_file = os.path.join (rc_dir, base + '.rc')
|
||||
self.spawn([self.mc] + ['-h', h_dir, '-r', rc_dir] + [src])
|
||||
base, _ = os.path.splitext(os.path.basename(src))
|
||||
rc_file = os.path.join(rc_dir, base + '.rc')
|
||||
# then compile .RC to .RES file
|
||||
self.spawn([self.rc] +
|
||||
["/fo" + obj] + [rc_file])
|
||||
self.spawn([self.rc] + ["/fo" + obj] + [rc_file])
|
||||
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
continue
|
||||
else:
|
||||
# how to handle this file?
|
||||
raise CompileError("Don't know how to compile %s to %s"
|
||||
% (src, obj))
|
||||
raise CompileError(
|
||||
"Don't know how to compile {} to {}".format(src, obj)
|
||||
)
|
||||
|
||||
output_opt = "/Fo" + obj
|
||||
try:
|
||||
self.spawn([self.cc] + compile_opts + pp_opts +
|
||||
[input_opt, output_opt] +
|
||||
extra_postargs)
|
||||
self.spawn(
|
||||
[self.cc]
|
||||
+ compile_opts
|
||||
+ pp_opts
|
||||
+ [input_opt, output_opt]
|
||||
+ extra_postargs
|
||||
)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
def create_static_lib(self,
|
||||
objects,
|
||||
output_libname,
|
||||
output_dir=None,
|
||||
debug=0,
|
||||
target_lang=None):
|
||||
def create_static_lib(
|
||||
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
(objects, output_dir) = self._fix_object_args(objects, output_dir)
|
||||
output_filename = self.library_filename(output_libname,
|
||||
output_dir=output_dir)
|
||||
output_filename = self.library_filename(output_libname, output_dir=output_dir)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
lib_args = objects + ['/OUT:' + output_filename]
|
||||
if debug:
|
||||
pass # XXX what goes here?
|
||||
pass # XXX what goes here?
|
||||
try:
|
||||
self.spawn([self.lib] + lib_args)
|
||||
except DistutilsExecError as msg:
|
||||
@@ -449,36 +493,36 @@ class MSVCCompiler(CCompiler) :
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
|
||||
def link(self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None):
|
||||
def link( # noqa: C901
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
|
||||
if not self.initialized:
|
||||
self.initialize()
|
||||
(objects, output_dir) = self._fix_object_args(objects, output_dir)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs,
|
||||
runtime_library_dirs)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
|
||||
(libraries, library_dirs, runtime_library_dirs) = fixed_args
|
||||
|
||||
if runtime_library_dirs:
|
||||
self.warn ("I don't know what to do with 'runtime_library_dirs': "
|
||||
+ str (runtime_library_dirs))
|
||||
self.warn(
|
||||
"I don't know what to do with 'runtime_library_dirs': "
|
||||
+ str(runtime_library_dirs)
|
||||
)
|
||||
|
||||
lib_opts = gen_lib_options(self,
|
||||
library_dirs, runtime_library_dirs,
|
||||
libraries)
|
||||
lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
|
||||
if output_dir is not None:
|
||||
output_filename = os.path.join(output_dir, output_filename)
|
||||
|
||||
@@ -495,11 +539,12 @@ class MSVCCompiler(CCompiler) :
|
||||
ldflags = self.ldflags_shared
|
||||
|
||||
export_opts = []
|
||||
for sym in (export_symbols or []):
|
||||
for sym in export_symbols or []:
|
||||
export_opts.append("/EXPORT:" + sym)
|
||||
|
||||
ld_args = (ldflags + lib_opts + export_opts +
|
||||
objects + ['/OUT:' + output_filename])
|
||||
ld_args = (
|
||||
ldflags + lib_opts + export_opts + objects + ['/OUT:' + output_filename]
|
||||
)
|
||||
|
||||
# The MSVC linker generates .lib and .exp files, which cannot be
|
||||
# suppressed by any linker switches. The .lib files may even be
|
||||
@@ -508,11 +553,12 @@ class MSVCCompiler(CCompiler) :
|
||||
# builds, they can go into the same directory.
|
||||
if export_symbols is not None:
|
||||
(dll_name, dll_ext) = os.path.splitext(
|
||||
os.path.basename(output_filename))
|
||||
os.path.basename(output_filename)
|
||||
)
|
||||
implib_file = os.path.join(
|
||||
os.path.dirname(objects[0]),
|
||||
self.library_filename(dll_name))
|
||||
ld_args.append ('/IMPLIB:' + implib_file)
|
||||
os.path.dirname(objects[0]), self.library_filename(dll_name)
|
||||
)
|
||||
ld_args.append('/IMPLIB:' + implib_file)
|
||||
|
||||
if extra_preargs:
|
||||
ld_args[:0] = extra_preargs
|
||||
@@ -528,7 +574,6 @@ class MSVCCompiler(CCompiler) :
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
|
||||
# -- Miscellaneous methods -----------------------------------------
|
||||
# These are all used by the 'gen_lib_options() function, in
|
||||
# ccompiler.py.
|
||||
@@ -538,12 +583,12 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
raise DistutilsPlatformError(
|
||||
"don't know how to set runtime library search path for MSVC++")
|
||||
"don't know how to set runtime library search path for MSVC++"
|
||||
)
|
||||
|
||||
def library_option(self, lib):
|
||||
return self.library_filename(lib)
|
||||
|
||||
|
||||
def find_library_file(self, dirs, lib, debug=0):
|
||||
# Prefer a debugging library if found (and requested), but deal
|
||||
# with it if we don't have one.
|
||||
@@ -553,7 +598,7 @@ class MSVCCompiler(CCompiler) :
|
||||
try_names = [lib]
|
||||
for dir in dirs:
|
||||
for name in try_names:
|
||||
libfile = os.path.join(dir, self.library_filename (name))
|
||||
libfile = os.path.join(dir, self.library_filename(name))
|
||||
if os.path.exists(libfile):
|
||||
return libfile
|
||||
else:
|
||||
@@ -578,7 +623,7 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
# didn't find it; try existing path
|
||||
for p in os.environ['Path'].split(';'):
|
||||
fn = os.path.join(os.path.abspath(p),exe)
|
||||
fn = os.path.join(os.path.abspath(p), exe)
|
||||
if os.path.isfile(fn):
|
||||
return fn
|
||||
|
||||
@@ -595,11 +640,15 @@ class MSVCCompiler(CCompiler) :
|
||||
|
||||
path = path + " dirs"
|
||||
if self.__version >= 7:
|
||||
key = (r"%s\%0.1f\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories"
|
||||
% (self.__root, self.__version))
|
||||
key = r"{}\{:0.1f}\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories".format(
|
||||
self.__root,
|
||||
self.__version,
|
||||
)
|
||||
else:
|
||||
key = (r"%s\6.0\Build System\Components\Platforms"
|
||||
r"\Win32 (%s)\Directories" % (self.__root, platform))
|
||||
key = (
|
||||
r"%s\6.0\Build System\Components\Platforms"
|
||||
r"\Win32 (%s)\Directories" % (self.__root, platform)
|
||||
)
|
||||
|
||||
for base in HKEYS:
|
||||
d = read_values(base, key)
|
||||
@@ -613,10 +662,12 @@ class MSVCCompiler(CCompiler) :
|
||||
if self.__version == 6:
|
||||
for base in HKEYS:
|
||||
if read_values(base, r"%s\6.0" % self.__root) is not None:
|
||||
self.warn("It seems you have Visual Studio 6 installed, "
|
||||
self.warn(
|
||||
"It seems you have Visual Studio 6 installed, "
|
||||
"but the expected registry settings are not present.\n"
|
||||
"You must at least run the Visual Studio GUI once "
|
||||
"so that these entries are created.")
|
||||
"so that these entries are created."
|
||||
)
|
||||
break
|
||||
return []
|
||||
|
||||
@@ -639,5 +690,6 @@ if get_build_version() >= 8.0:
|
||||
log.debug("Importing new compiler from distutils.msvc9compiler")
|
||||
OldMSVCCompiler = MSVCCompiler
|
||||
from distutils.msvc9compiler import MSVCCompiler
|
||||
|
||||
# get_build_architecture not really relevant now we support cross-compile
|
||||
from distutils.msvc9compiler import MacroExpander
|
||||
from distutils.msvc9compiler import MacroExpander # noqa: F811
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
|
||||
def __optim_args_from_interpreter_flags():
|
||||
"""Return a list of command-line arguments reproducing the current
|
||||
optimization settings in sys.flags."""
|
||||
args = []
|
||||
value = sys.flags.optimize
|
||||
if value > 0:
|
||||
args.append("-" + "O" * value)
|
||||
return args
|
||||
|
||||
|
||||
_optim_args_from_interpreter_flags = getattr(
|
||||
subprocess,
|
||||
"_optim_args_from_interpreter_flags",
|
||||
__optim_args_from_interpreter_flags,
|
||||
)
|
||||
@@ -1,7 +1,8 @@
|
||||
def aix_platform(osname, version, release):
|
||||
try:
|
||||
import _aix_support
|
||||
|
||||
return _aix_support.aix_platform()
|
||||
except ImportError:
|
||||
pass
|
||||
return "%s-%s.%s" % (osname, version, release)
|
||||
return "{}-{}.{}".format(osname, version, release)
|
||||
|
||||
@@ -10,12 +10,12 @@ import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from distutils.errors import DistutilsPlatformError, DistutilsExecError
|
||||
from distutils.errors import DistutilsExecError
|
||||
from distutils.debug import DEBUG
|
||||
from distutils import log
|
||||
|
||||
|
||||
def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
|
||||
def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None): # noqa: C901
|
||||
"""Run another program, specified as a command list 'cmd', in a new process.
|
||||
|
||||
'cmd' is just the argument list for the new process, ie.
|
||||
@@ -48,6 +48,7 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
from distutils.util import MACOSX_VERSION_VAR, get_macosx_target_ver
|
||||
|
||||
macosx_target_ver = get_macosx_target_ver()
|
||||
if macosx_target_ver:
|
||||
env[MACOSX_VERSION_VAR] = macosx_target_ver
|
||||
@@ -60,13 +61,15 @@ def spawn(cmd, search_path=1, verbose=0, dry_run=0, env=None):
|
||||
if not DEBUG:
|
||||
cmd = cmd[0]
|
||||
raise DistutilsExecError(
|
||||
"command %r failed: %s" % (cmd, exc.args[-1])) from exc
|
||||
"command {!r} failed: {}".format(cmd, exc.args[-1])
|
||||
) from exc
|
||||
|
||||
if exitcode:
|
||||
if not DEBUG:
|
||||
cmd = cmd[0]
|
||||
raise DistutilsExecError(
|
||||
"command %r failed with exit code %s" % (cmd, exitcode))
|
||||
"command {!r} failed with exit code {}".format(cmd, exitcode)
|
||||
)
|
||||
|
||||
|
||||
def find_executable(executable, path=None):
|
||||
|
||||
@@ -9,12 +9,15 @@ Written by: Fred L. Drake, Jr.
|
||||
Email: <fdrake@acm.org>
|
||||
"""
|
||||
|
||||
import _imp
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import sysconfig
|
||||
import pathlib
|
||||
|
||||
from .errors import DistutilsPlatformError
|
||||
from . import py39compat
|
||||
from ._functools import pass_none
|
||||
|
||||
IS_PYPY = '__pypy__' in sys.builtin_module_names
|
||||
|
||||
@@ -38,31 +41,48 @@ else:
|
||||
project_base = os.getcwd()
|
||||
|
||||
|
||||
# python_build: (Boolean) if true, we're either building Python or
|
||||
# building an extension with an un-installed Python, so we use
|
||||
# different (hard-wired) directories.
|
||||
def _is_python_source_dir(d):
|
||||
for fn in ("Setup", "Setup.local"):
|
||||
if os.path.isfile(os.path.join(d, "Modules", fn)):
|
||||
return True
|
||||
return False
|
||||
"""
|
||||
Return True if the target directory appears to point to an
|
||||
un-installed Python.
|
||||
"""
|
||||
modules = pathlib.Path(d).joinpath('Modules')
|
||||
return any(modules.joinpath(fn).is_file() for fn in ('Setup', 'Setup.local'))
|
||||
|
||||
|
||||
_sys_home = getattr(sys, '_home', None)
|
||||
|
||||
|
||||
def _is_parent(dir_a, dir_b):
|
||||
"""
|
||||
Return True if a is a parent of b.
|
||||
"""
|
||||
return os.path.normcase(dir_a).startswith(os.path.normcase(dir_b))
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
|
||||
@pass_none
|
||||
def _fix_pcbuild(d):
|
||||
if d and os.path.normcase(d).startswith(
|
||||
os.path.normcase(os.path.join(PREFIX, "PCbuild"))):
|
||||
return PREFIX
|
||||
return d
|
||||
# In a venv, sys._home will be inside BASE_PREFIX rather than PREFIX.
|
||||
prefixes = PREFIX, BASE_PREFIX
|
||||
matched = (
|
||||
prefix
|
||||
for prefix in prefixes
|
||||
if _is_parent(d, os.path.join(prefix, "PCbuild"))
|
||||
)
|
||||
return next(matched, d)
|
||||
|
||||
project_base = _fix_pcbuild(project_base)
|
||||
_sys_home = _fix_pcbuild(_sys_home)
|
||||
|
||||
|
||||
def _python_build():
|
||||
if _sys_home:
|
||||
return _is_python_source_dir(_sys_home)
|
||||
return _is_python_source_dir(project_base)
|
||||
|
||||
|
||||
python_build = _python_build()
|
||||
|
||||
|
||||
@@ -78,6 +98,7 @@ except AttributeError:
|
||||
# this attribute, which is fine.
|
||||
pass
|
||||
|
||||
|
||||
def get_python_version():
|
||||
"""Return a string containing the major and minor Python version,
|
||||
leaving off the patchlevel. Sample return values could be '1.5'
|
||||
@@ -97,36 +118,91 @@ def get_python_inc(plat_specific=0, prefix=None):
|
||||
If 'prefix' is supplied, use it instead of sys.base_prefix or
|
||||
sys.base_exec_prefix -- i.e., ignore 'plat_specific'.
|
||||
"""
|
||||
if prefix is None:
|
||||
prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX
|
||||
if os.name == "posix":
|
||||
if IS_PYPY and sys.version_info < (3, 8):
|
||||
return os.path.join(prefix, 'include')
|
||||
if python_build:
|
||||
# Assume the executable is in the build directory. The
|
||||
# pyconfig.h file should be in the same directory. Since
|
||||
# the build directory may not be the source directory, we
|
||||
# must use "srcdir" from the makefile to find the "Include"
|
||||
# directory.
|
||||
if plat_specific:
|
||||
return _sys_home or project_base
|
||||
else:
|
||||
incdir = os.path.join(get_config_var('srcdir'), 'Include')
|
||||
return os.path.normpath(incdir)
|
||||
implementation = 'pypy' if IS_PYPY else 'python'
|
||||
python_dir = implementation + get_python_version() + build_flags
|
||||
return os.path.join(prefix, "include", python_dir)
|
||||
elif os.name == "nt":
|
||||
if python_build:
|
||||
# Include both the include and PC dir to ensure we can find
|
||||
# pyconfig.h
|
||||
return (os.path.join(prefix, "include") + os.path.pathsep +
|
||||
os.path.join(prefix, "PC"))
|
||||
return os.path.join(prefix, "include")
|
||||
else:
|
||||
default_prefix = BASE_EXEC_PREFIX if plat_specific else BASE_PREFIX
|
||||
resolved_prefix = prefix if prefix is not None else default_prefix
|
||||
try:
|
||||
getter = globals()[f'_get_python_inc_{os.name}']
|
||||
except KeyError:
|
||||
raise DistutilsPlatformError(
|
||||
"I don't know where Python installs its C header files "
|
||||
"on platform '%s'" % os.name)
|
||||
"on platform '%s'" % os.name
|
||||
)
|
||||
return getter(resolved_prefix, prefix, plat_specific)
|
||||
|
||||
|
||||
def _get_python_inc_posix(prefix, spec_prefix, plat_specific):
|
||||
if IS_PYPY and sys.version_info < (3, 8):
|
||||
return os.path.join(prefix, 'include')
|
||||
return (
|
||||
_get_python_inc_posix_python(plat_specific)
|
||||
or _get_python_inc_from_config(plat_specific, spec_prefix)
|
||||
or _get_python_inc_posix_prefix(prefix)
|
||||
)
|
||||
|
||||
|
||||
def _get_python_inc_posix_python(plat_specific):
|
||||
"""
|
||||
Assume the executable is in the build directory. The
|
||||
pyconfig.h file should be in the same directory. Since
|
||||
the build directory may not be the source directory,
|
||||
use "srcdir" from the makefile to find the "Include"
|
||||
directory.
|
||||
"""
|
||||
if not python_build:
|
||||
return
|
||||
if plat_specific:
|
||||
return _sys_home or project_base
|
||||
incdir = os.path.join(get_config_var('srcdir'), 'Include')
|
||||
return os.path.normpath(incdir)
|
||||
|
||||
|
||||
def _get_python_inc_from_config(plat_specific, spec_prefix):
|
||||
"""
|
||||
If no prefix was explicitly specified, provide the include
|
||||
directory from the config vars. Useful when
|
||||
cross-compiling, since the config vars may come from
|
||||
the host
|
||||
platform Python installation, while the current Python
|
||||
executable is from the build platform installation.
|
||||
|
||||
>>> monkeypatch = getfixture('monkeypatch')
|
||||
>>> gpifc = _get_python_inc_from_config
|
||||
>>> monkeypatch.setitem(gpifc.__globals__, 'get_config_var', str.lower)
|
||||
>>> gpifc(False, '/usr/bin/')
|
||||
>>> gpifc(False, '')
|
||||
>>> gpifc(False, None)
|
||||
'includepy'
|
||||
>>> gpifc(True, None)
|
||||
'confincludepy'
|
||||
"""
|
||||
if spec_prefix is None:
|
||||
return get_config_var('CONF' * plat_specific + 'INCLUDEPY')
|
||||
|
||||
|
||||
def _get_python_inc_posix_prefix(prefix):
|
||||
implementation = 'pypy' if IS_PYPY else 'python'
|
||||
python_dir = implementation + get_python_version() + build_flags
|
||||
return os.path.join(prefix, "include", python_dir)
|
||||
|
||||
|
||||
def _get_python_inc_nt(prefix, spec_prefix, plat_specific):
|
||||
if python_build:
|
||||
# Include both the include and PC dir to ensure we can find
|
||||
# pyconfig.h
|
||||
return (
|
||||
os.path.join(prefix, "include")
|
||||
+ os.path.pathsep
|
||||
+ os.path.join(prefix, "PC")
|
||||
)
|
||||
return os.path.join(prefix, "include")
|
||||
|
||||
|
||||
# allow this behavior to be monkey-patched. Ref pypa/distutils#2.
|
||||
def _posix_lib(standard_lib, libpython, early_prefix, prefix):
|
||||
if standard_lib:
|
||||
return libpython
|
||||
else:
|
||||
return os.path.join(libpython, "site-packages")
|
||||
|
||||
|
||||
def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
|
||||
@@ -152,6 +228,8 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
|
||||
return os.path.join(prefix, "lib-python", sys.version[0])
|
||||
return os.path.join(prefix, 'site-packages')
|
||||
|
||||
early_prefix = prefix
|
||||
|
||||
if prefix is None:
|
||||
if standard_lib:
|
||||
prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX
|
||||
@@ -167,12 +245,8 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
|
||||
# Pure Python
|
||||
libdir = "lib"
|
||||
implementation = 'pypy' if IS_PYPY else 'python'
|
||||
libpython = os.path.join(prefix, libdir,
|
||||
implementation + get_python_version())
|
||||
if standard_lib:
|
||||
return libpython
|
||||
else:
|
||||
return os.path.join(libpython, "site-packages")
|
||||
libpython = os.path.join(prefix, libdir, implementation + get_python_version())
|
||||
return _posix_lib(standard_lib, libpython, early_prefix, prefix)
|
||||
elif os.name == "nt":
|
||||
if standard_lib:
|
||||
return os.path.join(prefix, "Lib")
|
||||
@@ -181,11 +255,11 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
|
||||
else:
|
||||
raise DistutilsPlatformError(
|
||||
"I don't know where Python installs its library "
|
||||
"on platform '%s'" % os.name)
|
||||
"on platform '%s'" % os.name
|
||||
)
|
||||
|
||||
|
||||
|
||||
def customize_compiler(compiler):
|
||||
def customize_compiler(compiler): # noqa: C901
|
||||
"""Do any platform-specific customization of a CCompiler instance.
|
||||
|
||||
Mainly needed on Unix, so we can plug in the information that
|
||||
@@ -205,20 +279,36 @@ def customize_compiler(compiler):
|
||||
# Use get_config_var() to ensure _config_vars is initialized.
|
||||
if not get_config_var('CUSTOMIZED_OSX_COMPILER'):
|
||||
import _osx_support
|
||||
|
||||
_osx_support.customize_compiler(_config_vars)
|
||||
_config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True'
|
||||
|
||||
(cc, cxx, cflags, ccshared, ldshared, shlib_suffix, ar, ar_flags) = \
|
||||
get_config_vars('CC', 'CXX', 'CFLAGS',
|
||||
'CCSHARED', 'LDSHARED', 'SHLIB_SUFFIX', 'AR', 'ARFLAGS')
|
||||
(
|
||||
cc,
|
||||
cxx,
|
||||
cflags,
|
||||
ccshared,
|
||||
ldshared,
|
||||
shlib_suffix,
|
||||
ar,
|
||||
ar_flags,
|
||||
) = get_config_vars(
|
||||
'CC',
|
||||
'CXX',
|
||||
'CFLAGS',
|
||||
'CCSHARED',
|
||||
'LDSHARED',
|
||||
'SHLIB_SUFFIX',
|
||||
'AR',
|
||||
'ARFLAGS',
|
||||
)
|
||||
|
||||
if 'CC' in os.environ:
|
||||
newcc = os.environ['CC']
|
||||
if('LDSHARED' not in os.environ
|
||||
and ldshared.startswith(cc)):
|
||||
if 'LDSHARED' not in os.environ and ldshared.startswith(cc):
|
||||
# If CC is overridden, use that as the default
|
||||
# command for LDSHARED as well
|
||||
ldshared = newcc + ldshared[len(cc):]
|
||||
ldshared = newcc + ldshared[len(cc) :]
|
||||
cc = newcc
|
||||
if 'CXX' in os.environ:
|
||||
cxx = os.environ['CXX']
|
||||
@@ -227,7 +317,7 @@ def customize_compiler(compiler):
|
||||
if 'CPP' in os.environ:
|
||||
cpp = os.environ['CPP']
|
||||
else:
|
||||
cpp = cc + " -E" # not always
|
||||
cpp = cc + " -E" # not always
|
||||
if 'LDFLAGS' in os.environ:
|
||||
ldshared = ldshared + ' ' + os.environ['LDFLAGS']
|
||||
if 'CFLAGS' in os.environ:
|
||||
@@ -252,7 +342,8 @@ def customize_compiler(compiler):
|
||||
compiler_cxx=cxx,
|
||||
linker_so=ldshared,
|
||||
linker_exe=cc,
|
||||
archiver=archiver)
|
||||
archiver=archiver,
|
||||
)
|
||||
|
||||
if 'RANLIB' in os.environ and compiler.executables.get('ranlib', None):
|
||||
compiler.set_executables(ranlib=os.environ['RANLIB'])
|
||||
@@ -267,21 +358,14 @@ def get_config_h_filename():
|
||||
inc_dir = os.path.join(_sys_home or project_base, "PC")
|
||||
else:
|
||||
inc_dir = _sys_home or project_base
|
||||
return os.path.join(inc_dir, 'pyconfig.h')
|
||||
else:
|
||||
inc_dir = get_python_inc(plat_specific=1)
|
||||
|
||||
return os.path.join(inc_dir, 'pyconfig.h')
|
||||
return sysconfig.get_config_h_filename()
|
||||
|
||||
|
||||
def get_makefile_filename():
|
||||
"""Return full pathname of installed Makefile from the Python build."""
|
||||
if python_build:
|
||||
return os.path.join(_sys_home or project_base, "Makefile")
|
||||
lib_dir = get_python_lib(plat_specific=0, standard_lib=1)
|
||||
config_file = 'config-{}{}'.format(get_python_version(), build_flags)
|
||||
if hasattr(sys.implementation, '_multiarch'):
|
||||
config_file += '-%s' % sys.implementation._multiarch
|
||||
return os.path.join(lib_dir, config_file, 'Makefile')
|
||||
return sysconfig.get_makefile_filename()
|
||||
|
||||
|
||||
def parse_config_h(fp, g=None):
|
||||
@@ -291,26 +375,7 @@ def parse_config_h(fp, g=None):
|
||||
optional dictionary is passed in as the second argument, it is
|
||||
used instead of a new dictionary.
|
||||
"""
|
||||
if g is None:
|
||||
g = {}
|
||||
define_rx = re.compile("#define ([A-Z][A-Za-z0-9_]+) (.*)\n")
|
||||
undef_rx = re.compile("/[*] #undef ([A-Z][A-Za-z0-9_]+) [*]/\n")
|
||||
#
|
||||
while True:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
m = define_rx.match(line)
|
||||
if m:
|
||||
n, v = m.group(1, 2)
|
||||
try: v = int(v)
|
||||
except ValueError: pass
|
||||
g[n] = v
|
||||
else:
|
||||
m = undef_rx.match(line)
|
||||
if m:
|
||||
g[m.group(1)] = 0
|
||||
return g
|
||||
return sysconfig.parse_config_h(fp, vars=g)
|
||||
|
||||
|
||||
# Regexes needed for parsing Makefile (and similar syntaxes,
|
||||
@@ -319,7 +384,8 @@ _variable_rx = re.compile(r"([a-zA-Z][a-zA-Z0-9_]+)\s*=\s*(.*)")
|
||||
_findvar1_rx = re.compile(r"\$\(([A-Za-z][A-Za-z0-9_]*)\)")
|
||||
_findvar2_rx = re.compile(r"\${([A-Za-z][A-Za-z0-9_]*)}")
|
||||
|
||||
def parse_makefile(fn, g=None):
|
||||
|
||||
def parse_makefile(fn, g=None): # noqa: C901
|
||||
"""Parse a Makefile-style file.
|
||||
|
||||
A dictionary containing name/value pairs is returned. If an
|
||||
@@ -327,7 +393,10 @@ def parse_makefile(fn, g=None):
|
||||
used instead of a new dictionary.
|
||||
"""
|
||||
from distutils.text_file import TextFile
|
||||
fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape")
|
||||
|
||||
fp = TextFile(
|
||||
fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape"
|
||||
)
|
||||
|
||||
if g is None:
|
||||
g = {}
|
||||
@@ -336,7 +405,7 @@ def parse_makefile(fn, g=None):
|
||||
|
||||
while True:
|
||||
line = fp.readline()
|
||||
if line is None: # eof
|
||||
if line is None: # eof
|
||||
break
|
||||
m = _variable_rx.match(line)
|
||||
if m:
|
||||
@@ -391,20 +460,20 @@ def parse_makefile(fn, g=None):
|
||||
else:
|
||||
done[n] = item = ""
|
||||
if found:
|
||||
after = value[m.end():]
|
||||
value = value[:m.start()] + item + after
|
||||
after = value[m.end() :]
|
||||
value = value[: m.start()] + item + after
|
||||
if "$" in after:
|
||||
notdone[name] = value
|
||||
else:
|
||||
try: value = int(value)
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
done[name] = value.strip()
|
||||
else:
|
||||
done[name] = value
|
||||
del notdone[name]
|
||||
|
||||
if name.startswith('PY_') \
|
||||
and name[3:] in renamed_variables:
|
||||
if name.startswith('PY_') and name[3:] in renamed_variables:
|
||||
|
||||
name = name[3:]
|
||||
if name not in done:
|
||||
@@ -452,45 +521,6 @@ def expand_makefile_vars(s, vars):
|
||||
|
||||
_config_vars = None
|
||||
|
||||
def _init_posix():
|
||||
"""Initialize the module as appropriate for POSIX systems."""
|
||||
# _sysconfigdata is generated at build time, see the sysconfig module
|
||||
name = os.environ.get('_PYTHON_SYSCONFIGDATA_NAME',
|
||||
'_sysconfigdata_{abi}_{platform}_{multiarch}'.format(
|
||||
abi=sys.abiflags,
|
||||
platform=sys.platform,
|
||||
multiarch=getattr(sys.implementation, '_multiarch', ''),
|
||||
))
|
||||
try:
|
||||
_temp = __import__(name, globals(), locals(), ['build_time_vars'], 0)
|
||||
except ImportError:
|
||||
# Python 3.5 and pypy 7.3.1
|
||||
_temp = __import__(
|
||||
'_sysconfigdata', globals(), locals(), ['build_time_vars'], 0)
|
||||
build_time_vars = _temp.build_time_vars
|
||||
global _config_vars
|
||||
_config_vars = {}
|
||||
_config_vars.update(build_time_vars)
|
||||
|
||||
|
||||
def _init_nt():
|
||||
"""Initialize the module as appropriate for NT"""
|
||||
g = {}
|
||||
# set basic install directories
|
||||
g['LIBDEST'] = get_python_lib(plat_specific=0, standard_lib=1)
|
||||
g['BINLIBDEST'] = get_python_lib(plat_specific=1, standard_lib=1)
|
||||
|
||||
# XXX hmmm.. a normal install puts include files here
|
||||
g['INCLUDEPY'] = get_python_inc(plat_specific=0)
|
||||
|
||||
g['EXT_SUFFIX'] = _imp.extension_suffixes()[0]
|
||||
g['EXE'] = ".exe"
|
||||
g['VERSION'] = get_python_version().replace(".", "")
|
||||
g['BINDIR'] = os.path.dirname(os.path.abspath(sys.executable))
|
||||
|
||||
global _config_vars
|
||||
_config_vars = g
|
||||
|
||||
|
||||
def get_config_vars(*args):
|
||||
"""With no arguments, return a dictionary of all configuration
|
||||
@@ -504,60 +534,8 @@ def get_config_vars(*args):
|
||||
"""
|
||||
global _config_vars
|
||||
if _config_vars is None:
|
||||
func = globals().get("_init_" + os.name)
|
||||
if func:
|
||||
func()
|
||||
else:
|
||||
_config_vars = {}
|
||||
|
||||
# Normalized versions of prefix and exec_prefix are handy to have;
|
||||
# in fact, these are the standard versions used most places in the
|
||||
# Distutils.
|
||||
_config_vars['prefix'] = PREFIX
|
||||
_config_vars['exec_prefix'] = EXEC_PREFIX
|
||||
|
||||
if not IS_PYPY:
|
||||
# For backward compatibility, see issue19555
|
||||
SO = _config_vars.get('EXT_SUFFIX')
|
||||
if SO is not None:
|
||||
_config_vars['SO'] = SO
|
||||
|
||||
# Always convert srcdir to an absolute path
|
||||
srcdir = _config_vars.get('srcdir', project_base)
|
||||
if os.name == 'posix':
|
||||
if python_build:
|
||||
# If srcdir is a relative path (typically '.' or '..')
|
||||
# then it should be interpreted relative to the directory
|
||||
# containing Makefile.
|
||||
base = os.path.dirname(get_makefile_filename())
|
||||
srcdir = os.path.join(base, srcdir)
|
||||
else:
|
||||
# srcdir is not meaningful since the installation is
|
||||
# spread about the filesystem. We choose the
|
||||
# directory containing the Makefile since we know it
|
||||
# exists.
|
||||
srcdir = os.path.dirname(get_makefile_filename())
|
||||
_config_vars['srcdir'] = os.path.abspath(os.path.normpath(srcdir))
|
||||
|
||||
# Convert srcdir into an absolute path if it appears necessary.
|
||||
# Normally it is relative to the build directory. However, during
|
||||
# testing, for example, we might be running a non-installed python
|
||||
# from a different directory.
|
||||
if python_build and os.name == "posix":
|
||||
base = project_base
|
||||
if (not os.path.isabs(_config_vars['srcdir']) and
|
||||
base != os.getcwd()):
|
||||
# srcdir is relative and we are not in the same directory
|
||||
# as the executable. Assume executable is in the build
|
||||
# directory and make srcdir absolute.
|
||||
srcdir = os.path.join(base, _config_vars['srcdir'])
|
||||
_config_vars['srcdir'] = os.path.normpath(srcdir)
|
||||
|
||||
# OS X platforms require special customization to handle
|
||||
# multi-architecture, multi-os-version installers
|
||||
if sys.platform == 'darwin':
|
||||
import _osx_support
|
||||
_osx_support.customize_config_vars(_config_vars)
|
||||
_config_vars = sysconfig.get_config_vars().copy()
|
||||
py39compat.add_ext_suffix(_config_vars)
|
||||
|
||||
if args:
|
||||
vals = []
|
||||
@@ -567,6 +545,7 @@ def get_config_vars(*args):
|
||||
else:
|
||||
return _config_vars
|
||||
|
||||
|
||||
def get_config_var(name):
|
||||
"""Return the value of a single variable using the dictionary
|
||||
returned by 'get_config_vars()'. Equivalent to
|
||||
@@ -574,5 +553,6 @@ def get_config_var(name):
|
||||
"""
|
||||
if name == 'SO':
|
||||
import warnings
|
||||
|
||||
warnings.warn('SO is deprecated, use EXT_SUFFIX', DeprecationWarning, 2)
|
||||
return get_config_vars().get(name)
|
||||
|
||||
@@ -4,84 +4,87 @@ provides the TextFile class, which gives an interface to text files
|
||||
that (optionally) takes care of stripping comments, ignoring blank
|
||||
lines, and joining lines with backslashes."""
|
||||
|
||||
import sys, io
|
||||
import sys
|
||||
|
||||
|
||||
class TextFile:
|
||||
"""Provides a file-like object that takes care of all the things you
|
||||
commonly want to do when processing a text file that has some
|
||||
line-by-line syntax: strip comments (as long as "#" is your
|
||||
comment character), skip blank lines, join adjacent lines by
|
||||
escaping the newline (ie. backslash at end of line), strip
|
||||
leading and/or trailing whitespace. All of these are optional
|
||||
and independently controllable.
|
||||
commonly want to do when processing a text file that has some
|
||||
line-by-line syntax: strip comments (as long as "#" is your
|
||||
comment character), skip blank lines, join adjacent lines by
|
||||
escaping the newline (ie. backslash at end of line), strip
|
||||
leading and/or trailing whitespace. All of these are optional
|
||||
and independently controllable.
|
||||
|
||||
Provides a 'warn()' method so you can generate warning messages that
|
||||
report physical line number, even if the logical line in question
|
||||
spans multiple physical lines. Also provides 'unreadline()' for
|
||||
implementing line-at-a-time lookahead.
|
||||
Provides a 'warn()' method so you can generate warning messages that
|
||||
report physical line number, even if the logical line in question
|
||||
spans multiple physical lines. Also provides 'unreadline()' for
|
||||
implementing line-at-a-time lookahead.
|
||||
|
||||
Constructor is called as:
|
||||
Constructor is called as:
|
||||
|
||||
TextFile (filename=None, file=None, **options)
|
||||
TextFile (filename=None, file=None, **options)
|
||||
|
||||
It bombs (RuntimeError) if both 'filename' and 'file' are None;
|
||||
'filename' should be a string, and 'file' a file object (or
|
||||
something that provides 'readline()' and 'close()' methods). It is
|
||||
recommended that you supply at least 'filename', so that TextFile
|
||||
can include it in warning messages. If 'file' is not supplied,
|
||||
TextFile creates its own using 'io.open()'.
|
||||
It bombs (RuntimeError) if both 'filename' and 'file' are None;
|
||||
'filename' should be a string, and 'file' a file object (or
|
||||
something that provides 'readline()' and 'close()' methods). It is
|
||||
recommended that you supply at least 'filename', so that TextFile
|
||||
can include it in warning messages. If 'file' is not supplied,
|
||||
TextFile creates its own using 'io.open()'.
|
||||
|
||||
The options are all boolean, and affect the value returned by
|
||||
'readline()':
|
||||
strip_comments [default: true]
|
||||
strip from "#" to end-of-line, as well as any whitespace
|
||||
leading up to the "#" -- unless it is escaped by a backslash
|
||||
lstrip_ws [default: false]
|
||||
strip leading whitespace from each line before returning it
|
||||
rstrip_ws [default: true]
|
||||
strip trailing whitespace (including line terminator!) from
|
||||
each line before returning it
|
||||
skip_blanks [default: true}
|
||||
skip lines that are empty *after* stripping comments and
|
||||
whitespace. (If both lstrip_ws and rstrip_ws are false,
|
||||
then some lines may consist of solely whitespace: these will
|
||||
*not* be skipped, even if 'skip_blanks' is true.)
|
||||
join_lines [default: false]
|
||||
if a backslash is the last non-newline character on a line
|
||||
after stripping comments and whitespace, join the following line
|
||||
to it to form one "logical line"; if N consecutive lines end
|
||||
with a backslash, then N+1 physical lines will be joined to
|
||||
form one logical line.
|
||||
collapse_join [default: false]
|
||||
strip leading whitespace from lines that are joined to their
|
||||
predecessor; only matters if (join_lines and not lstrip_ws)
|
||||
errors [default: 'strict']
|
||||
error handler used to decode the file content
|
||||
The options are all boolean, and affect the value returned by
|
||||
'readline()':
|
||||
strip_comments [default: true]
|
||||
strip from "#" to end-of-line, as well as any whitespace
|
||||
leading up to the "#" -- unless it is escaped by a backslash
|
||||
lstrip_ws [default: false]
|
||||
strip leading whitespace from each line before returning it
|
||||
rstrip_ws [default: true]
|
||||
strip trailing whitespace (including line terminator!) from
|
||||
each line before returning it
|
||||
skip_blanks [default: true}
|
||||
skip lines that are empty *after* stripping comments and
|
||||
whitespace. (If both lstrip_ws and rstrip_ws are false,
|
||||
then some lines may consist of solely whitespace: these will
|
||||
*not* be skipped, even if 'skip_blanks' is true.)
|
||||
join_lines [default: false]
|
||||
if a backslash is the last non-newline character on a line
|
||||
after stripping comments and whitespace, join the following line
|
||||
to it to form one "logical line"; if N consecutive lines end
|
||||
with a backslash, then N+1 physical lines will be joined to
|
||||
form one logical line.
|
||||
collapse_join [default: false]
|
||||
strip leading whitespace from lines that are joined to their
|
||||
predecessor; only matters if (join_lines and not lstrip_ws)
|
||||
errors [default: 'strict']
|
||||
error handler used to decode the file content
|
||||
|
||||
Note that since 'rstrip_ws' can strip the trailing newline, the
|
||||
semantics of 'readline()' must differ from those of the builtin file
|
||||
object's 'readline()' method! In particular, 'readline()' returns
|
||||
None for end-of-file: an empty string might just be a blank line (or
|
||||
an all-whitespace line), if 'rstrip_ws' is true but 'skip_blanks' is
|
||||
not."""
|
||||
Note that since 'rstrip_ws' can strip the trailing newline, the
|
||||
semantics of 'readline()' must differ from those of the builtin file
|
||||
object's 'readline()' method! In particular, 'readline()' returns
|
||||
None for end-of-file: an empty string might just be a blank line (or
|
||||
an all-whitespace line), if 'rstrip_ws' is true but 'skip_blanks' is
|
||||
not."""
|
||||
|
||||
default_options = { 'strip_comments': 1,
|
||||
'skip_blanks': 1,
|
||||
'lstrip_ws': 0,
|
||||
'rstrip_ws': 1,
|
||||
'join_lines': 0,
|
||||
'collapse_join': 0,
|
||||
'errors': 'strict',
|
||||
}
|
||||
default_options = {
|
||||
'strip_comments': 1,
|
||||
'skip_blanks': 1,
|
||||
'lstrip_ws': 0,
|
||||
'rstrip_ws': 1,
|
||||
'join_lines': 0,
|
||||
'collapse_join': 0,
|
||||
'errors': 'strict',
|
||||
}
|
||||
|
||||
def __init__(self, filename=None, file=None, **options):
|
||||
"""Construct a new TextFile object. At least one of 'filename'
|
||||
(a string) and 'file' (a file-like object) must be supplied.
|
||||
They keyword argument options are described above and affect
|
||||
the values returned by 'readline()'."""
|
||||
(a string) and 'file' (a file-like object) must be supplied.
|
||||
They keyword argument options are described above and affect
|
||||
the values returned by 'readline()'."""
|
||||
if filename is None and file is None:
|
||||
raise RuntimeError("you must supply either or both of 'filename' and 'file'")
|
||||
raise RuntimeError(
|
||||
"you must supply either or both of 'filename' and 'file'"
|
||||
)
|
||||
|
||||
# set values for all options -- either from client option hash
|
||||
# or fallback to default_options
|
||||
@@ -101,7 +104,7 @@ class TextFile:
|
||||
else:
|
||||
self.filename = filename
|
||||
self.file = file
|
||||
self.current_line = 0 # assuming that file is at BOF!
|
||||
self.current_line = 0 # assuming that file is at BOF!
|
||||
|
||||
# 'linebuf' is a stack of lines that will be emptied before we
|
||||
# actually read from the file; it's only populated by an
|
||||
@@ -110,14 +113,14 @@ class TextFile:
|
||||
|
||||
def open(self, filename):
|
||||
"""Open a new file named 'filename'. This overrides both the
|
||||
'filename' and 'file' arguments to the constructor."""
|
||||
'filename' and 'file' arguments to the constructor."""
|
||||
self.filename = filename
|
||||
self.file = io.open(self.filename, 'r', errors=self.errors)
|
||||
self.file = open(self.filename, errors=self.errors)
|
||||
self.current_line = 0
|
||||
|
||||
def close(self):
|
||||
"""Close the current file and forget everything we know about it
|
||||
(filename, current line number)."""
|
||||
(filename, current line number)."""
|
||||
file = self.file
|
||||
self.file = None
|
||||
self.filename = None
|
||||
@@ -141,24 +144,24 @@ class TextFile:
|
||||
|
||||
def warn(self, msg, line=None):
|
||||
"""Print (to stderr) a warning message tied to the current logical
|
||||
line in the current file. If the current logical line in the
|
||||
file spans multiple physical lines, the warning refers to the
|
||||
whole range, eg. "lines 3-5". If 'line' supplied, it overrides
|
||||
the current line number; it may be a list or tuple to indicate a
|
||||
range of physical lines, or an integer for a single physical
|
||||
line."""
|
||||
line in the current file. If the current logical line in the
|
||||
file spans multiple physical lines, the warning refers to the
|
||||
whole range, eg. "lines 3-5". If 'line' supplied, it overrides
|
||||
the current line number; it may be a list or tuple to indicate a
|
||||
range of physical lines, or an integer for a single physical
|
||||
line."""
|
||||
sys.stderr.write("warning: " + self.gen_error(msg, line) + "\n")
|
||||
|
||||
def readline(self):
|
||||
def readline(self): # noqa: C901
|
||||
"""Read and return a single logical line from the current file (or
|
||||
from an internal buffer if lines have previously been "unread"
|
||||
with 'unreadline()'). If the 'join_lines' option is true, this
|
||||
may involve reading multiple physical lines concatenated into a
|
||||
single string. Updates the current line number, so calling
|
||||
'warn()' after 'readline()' emits a warning about the physical
|
||||
line(s) just read. Returns None on end-of-file, since the empty
|
||||
string can occur if 'rstrip_ws' is true but 'strip_blanks' is
|
||||
not."""
|
||||
from an internal buffer if lines have previously been "unread"
|
||||
with 'unreadline()'). If the 'join_lines' option is true, this
|
||||
may involve reading multiple physical lines concatenated into a
|
||||
single string. Updates the current line number, so calling
|
||||
'warn()' after 'readline()' emits a warning about the physical
|
||||
line(s) just read. Returns None on end-of-file, since the empty
|
||||
string can occur if 'rstrip_ws' is true but 'strip_blanks' is
|
||||
not."""
|
||||
# If any "unread" lines waiting in 'linebuf', return the top
|
||||
# one. (We don't actually buffer read-ahead data -- lines only
|
||||
# get put in 'linebuf' if the client explicitly does an
|
||||
@@ -187,12 +190,12 @@ class TextFile:
|
||||
# lurking in there) and otherwise leave the line alone.
|
||||
|
||||
pos = line.find("#")
|
||||
if pos == -1: # no "#" -- no comments
|
||||
if pos == -1: # no "#" -- no comments
|
||||
pass
|
||||
|
||||
# It's definitely a comment -- either "#" is the first
|
||||
# character, or it's elsewhere and unescaped.
|
||||
elif pos == 0 or line[pos-1] != "\\":
|
||||
elif pos == 0 or line[pos - 1] != "\\":
|
||||
# Have to preserve the trailing newline, because it's
|
||||
# the job of a later step (rstrip_ws) to remove it --
|
||||
# and if rstrip_ws is false, we'd better preserve it!
|
||||
@@ -211,15 +214,14 @@ class TextFile:
|
||||
# result in "hello there".
|
||||
if line.strip() == "":
|
||||
continue
|
||||
else: # it's an escaped "#"
|
||||
else: # it's an escaped "#"
|
||||
line = line.replace("\\#", "#")
|
||||
|
||||
# did previous line end with a backslash? then accumulate
|
||||
if self.join_lines and buildup_line:
|
||||
# oops: end of file
|
||||
if line is None:
|
||||
self.warn("continuation line immediately precedes "
|
||||
"end-of-file")
|
||||
self.warn("continuation line immediately precedes " "end-of-file")
|
||||
return buildup_line
|
||||
|
||||
if self.collapse_join:
|
||||
@@ -230,11 +232,10 @@ class TextFile:
|
||||
if isinstance(self.current_line, list):
|
||||
self.current_line[1] = self.current_line[1] + 1
|
||||
else:
|
||||
self.current_line = [self.current_line,
|
||||
self.current_line + 1]
|
||||
self.current_line = [self.current_line, self.current_line + 1]
|
||||
# just an ordinary line, read it as usual
|
||||
else:
|
||||
if line is None: # eof
|
||||
if line is None: # eof
|
||||
return None
|
||||
|
||||
# still have to be careful about incrementing the line number!
|
||||
@@ -271,7 +272,7 @@ class TextFile:
|
||||
|
||||
def readlines(self):
|
||||
"""Read and return the list of all logical lines remaining in the
|
||||
current file."""
|
||||
current file."""
|
||||
lines = []
|
||||
while True:
|
||||
line = self.readline()
|
||||
@@ -281,6 +282,6 @@ class TextFile:
|
||||
|
||||
def unreadline(self, line):
|
||||
"""Push 'line' (a string) onto an internal buffer that will be
|
||||
checked by future 'readline()' calls. Handy for implementing
|
||||
a parser with line-at-a-time lookahead."""
|
||||
checked by future 'readline()' calls. Handy for implementing
|
||||
a parser with line-at-a-time lookahead."""
|
||||
self.linebuf.append(line)
|
||||
|
||||
@@ -13,18 +13,18 @@ the "typical" Unix-style command-line C compiler:
|
||||
* link shared library handled by 'cc -shared'
|
||||
"""
|
||||
|
||||
import os, sys, re, shlex
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import shlex
|
||||
import itertools
|
||||
|
||||
from distutils import sysconfig
|
||||
from distutils.dep_util import newer
|
||||
from distutils.ccompiler import \
|
||||
CCompiler, gen_preprocess_options, gen_lib_options
|
||||
from distutils.errors import \
|
||||
DistutilsExecError, CompileError, LibError, LinkError
|
||||
from distutils.ccompiler import CCompiler, gen_preprocess_options, gen_lib_options
|
||||
from distutils.errors import DistutilsExecError, CompileError, LibError, LinkError
|
||||
from distutils import log
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
import _osx_support
|
||||
from ._macos_compat import compiler_fixup
|
||||
|
||||
# XXX Things not currently handled:
|
||||
# * optimization/debug/warning flags; we just use whatever's in Python's
|
||||
@@ -42,6 +42,66 @@ if sys.platform == 'darwin':
|
||||
# options and carry on.
|
||||
|
||||
|
||||
def _split_env(cmd):
|
||||
"""
|
||||
For macOS, split command into 'env' portion (if any)
|
||||
and the rest of the linker command.
|
||||
|
||||
>>> _split_env(['a', 'b', 'c'])
|
||||
([], ['a', 'b', 'c'])
|
||||
>>> _split_env(['/usr/bin/env', 'A=3', 'gcc'])
|
||||
(['/usr/bin/env', 'A=3'], ['gcc'])
|
||||
"""
|
||||
pivot = 0
|
||||
if os.path.basename(cmd[0]) == "env":
|
||||
pivot = 1
|
||||
while '=' in cmd[pivot]:
|
||||
pivot += 1
|
||||
return cmd[:pivot], cmd[pivot:]
|
||||
|
||||
|
||||
def _split_aix(cmd):
|
||||
"""
|
||||
AIX platforms prefix the compiler with the ld_so_aix
|
||||
script, so split that from the linker command.
|
||||
|
||||
>>> _split_aix(['a', 'b', 'c'])
|
||||
([], ['a', 'b', 'c'])
|
||||
>>> _split_aix(['/bin/foo/ld_so_aix', 'gcc'])
|
||||
(['/bin/foo/ld_so_aix'], ['gcc'])
|
||||
"""
|
||||
pivot = os.path.basename(cmd[0]) == 'ld_so_aix'
|
||||
return cmd[:pivot], cmd[pivot:]
|
||||
|
||||
|
||||
def _linker_params(linker_cmd, compiler_cmd):
|
||||
"""
|
||||
The linker command usually begins with the compiler
|
||||
command (possibly multiple elements), followed by zero or more
|
||||
params for shared library building.
|
||||
|
||||
If the LDSHARED env variable overrides the linker command,
|
||||
however, the commands may not match.
|
||||
|
||||
Return the best guess of the linker parameters by stripping
|
||||
the linker command. If the compiler command does not
|
||||
match the linker command, assume the linker command is
|
||||
just the first element.
|
||||
|
||||
>>> _linker_params('gcc foo bar'.split(), ['gcc'])
|
||||
['foo', 'bar']
|
||||
>>> _linker_params('gcc foo bar'.split(), ['other'])
|
||||
['foo', 'bar']
|
||||
>>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split())
|
||||
['foo', 'bar']
|
||||
>>> _linker_params(['gcc'], ['gcc'])
|
||||
[]
|
||||
"""
|
||||
c_len = len(compiler_cmd)
|
||||
pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1
|
||||
return linker_cmd[pivot:]
|
||||
|
||||
|
||||
class UnixCCompiler(CCompiler):
|
||||
|
||||
compiler_type = 'unix'
|
||||
@@ -52,15 +112,16 @@ class UnixCCompiler(CCompiler):
|
||||
# are pretty generic; they will probably have to be set by an outsider
|
||||
# (eg. using information discovered by the sysconfig about building
|
||||
# Python extensions).
|
||||
executables = {'preprocessor' : None,
|
||||
'compiler' : ["cc"],
|
||||
'compiler_so' : ["cc"],
|
||||
'compiler_cxx' : ["cc"],
|
||||
'linker_so' : ["cc", "-shared"],
|
||||
'linker_exe' : ["cc"],
|
||||
'archiver' : ["ar", "-cr"],
|
||||
'ranlib' : None,
|
||||
}
|
||||
executables = {
|
||||
'preprocessor': None,
|
||||
'compiler': ["cc"],
|
||||
'compiler_so': ["cc"],
|
||||
'compiler_cxx': ["cc"],
|
||||
'linker_so': ["cc", "-shared"],
|
||||
'linker_exe': ["cc"],
|
||||
'archiver': ["ar", "-cr"],
|
||||
'ranlib': None,
|
||||
}
|
||||
|
||||
if sys.platform[:6] == "darwin":
|
||||
executables['ranlib'] = ["ranlib"]
|
||||
@@ -71,7 +132,7 @@ class UnixCCompiler(CCompiler):
|
||||
# reasonable common default here, but it's not necessarily used on all
|
||||
# Unices!
|
||||
|
||||
src_extensions = [".c",".C",".cc",".cxx",".cpp",".m"]
|
||||
src_extensions = [".c", ".C", ".cc", ".cxx", ".cpp", ".m"]
|
||||
obj_extension = ".o"
|
||||
static_lib_extension = ".a"
|
||||
shared_lib_extension = ".so"
|
||||
@@ -82,8 +143,15 @@ class UnixCCompiler(CCompiler):
|
||||
if sys.platform == "cygwin":
|
||||
exe_extension = ".exe"
|
||||
|
||||
def preprocess(self, source, output_file=None, macros=None,
|
||||
include_dirs=None, extra_preargs=None, extra_postargs=None):
|
||||
def preprocess(
|
||||
self,
|
||||
source,
|
||||
output_file=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
):
|
||||
fixed_args = self._fix_compile_args(None, macros, include_dirs)
|
||||
ignore, macros, include_dirs = fixed_args
|
||||
pp_opts = gen_preprocess_options(macros, include_dirs)
|
||||
@@ -96,41 +164,39 @@ class UnixCCompiler(CCompiler):
|
||||
pp_args.extend(extra_postargs)
|
||||
pp_args.append(source)
|
||||
|
||||
# We need to preprocess: either we're being forced to, or we're
|
||||
# generating output to stdout, or there's a target output file and
|
||||
# the source file is newer than the target (or the target doesn't
|
||||
# exist).
|
||||
if self.force or output_file is None or newer(source, output_file):
|
||||
if output_file:
|
||||
self.mkpath(os.path.dirname(output_file))
|
||||
try:
|
||||
self.spawn(pp_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
# reasons to preprocess:
|
||||
# - force is indicated
|
||||
# - output is directed to stdout
|
||||
# - source file is newer than the target
|
||||
preprocess = self.force or output_file is None or newer(source, output_file)
|
||||
if not preprocess:
|
||||
return
|
||||
|
||||
if output_file:
|
||||
self.mkpath(os.path.dirname(output_file))
|
||||
|
||||
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
|
||||
compiler_so = self.compiler_so
|
||||
if sys.platform == 'darwin':
|
||||
compiler_so = _osx_support.compiler_fixup(compiler_so,
|
||||
cc_args + extra_postargs)
|
||||
try:
|
||||
self.spawn(compiler_so + cc_args + [src, '-o', obj] +
|
||||
extra_postargs)
|
||||
self.spawn(pp_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
def create_static_lib(self, objects, output_libname,
|
||||
output_dir=None, debug=0, target_lang=None):
|
||||
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
|
||||
compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs)
|
||||
try:
|
||||
self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
def create_static_lib(
|
||||
self, objects, output_libname, output_dir=None, debug=0, target_lang=None
|
||||
):
|
||||
objects, output_dir = self._fix_object_args(objects, output_dir)
|
||||
|
||||
output_filename = \
|
||||
self.library_filename(output_libname, output_dir=output_dir)
|
||||
output_filename = self.library_filename(output_libname, output_dir=output_dir)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
self.mkpath(os.path.dirname(output_filename))
|
||||
self.spawn(self.archiver +
|
||||
[output_filename] +
|
||||
objects + self.objects)
|
||||
self.spawn(self.archiver + [output_filename] + objects + self.objects)
|
||||
|
||||
# Not many Unices required ranlib anymore -- SunOS 4.x is, I
|
||||
# think the only major Unix that does. Maybe we need some
|
||||
@@ -145,26 +211,34 @@ class UnixCCompiler(CCompiler):
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
def link(self, target_desc, objects,
|
||||
output_filename, output_dir=None, libraries=None,
|
||||
library_dirs=None, runtime_library_dirs=None,
|
||||
export_symbols=None, debug=0, extra_preargs=None,
|
||||
extra_postargs=None, build_temp=None, target_lang=None):
|
||||
def link(
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=0,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
objects, output_dir = self._fix_object_args(objects, output_dir)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs,
|
||||
runtime_library_dirs)
|
||||
fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)
|
||||
libraries, library_dirs, runtime_library_dirs = fixed_args
|
||||
|
||||
lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs,
|
||||
libraries)
|
||||
lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)
|
||||
if not isinstance(output_dir, (str, type(None))):
|
||||
raise TypeError("'output_dir' must be a string or None")
|
||||
if output_dir is not None:
|
||||
output_filename = os.path.join(output_dir, output_filename)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
ld_args = (objects + self.objects +
|
||||
lib_opts + ['-o', output_filename])
|
||||
ld_args = objects + self.objects + lib_opts + ['-o', output_filename]
|
||||
if debug:
|
||||
ld_args[:0] = ['-g']
|
||||
if extra_preargs:
|
||||
@@ -173,33 +247,22 @@ class UnixCCompiler(CCompiler):
|
||||
ld_args.extend(extra_postargs)
|
||||
self.mkpath(os.path.dirname(output_filename))
|
||||
try:
|
||||
if target_desc == CCompiler.EXECUTABLE:
|
||||
linker = self.linker_exe[:]
|
||||
else:
|
||||
linker = self.linker_so[:]
|
||||
# Select a linker based on context: linker_exe when
|
||||
# building an executable or linker_so (with shared options)
|
||||
# when building a shared library.
|
||||
building_exe = target_desc == CCompiler.EXECUTABLE
|
||||
linker = (self.linker_exe if building_exe else self.linker_so)[:]
|
||||
|
||||
if target_lang == "c++" and self.compiler_cxx:
|
||||
# skip over environment variable settings if /usr/bin/env
|
||||
# is used to set up the linker's environment.
|
||||
# This is needed on OSX. Note: this assumes that the
|
||||
# normal and C++ compiler have the same environment
|
||||
# settings.
|
||||
i = 0
|
||||
if os.path.basename(linker[0]) == "env":
|
||||
i = 1
|
||||
while '=' in linker[i]:
|
||||
i += 1
|
||||
env, linker_ne = _split_env(linker)
|
||||
aix, linker_na = _split_aix(linker_ne)
|
||||
_, compiler_cxx_ne = _split_env(self.compiler_cxx)
|
||||
_, linker_exe_ne = _split_env(self.linker_exe)
|
||||
|
||||
if os.path.basename(linker[i]) == 'ld_so_aix':
|
||||
# AIX platforms prefix the compiler with the ld_so_aix
|
||||
# script, so we need to adjust our linker index
|
||||
offset = 1
|
||||
else:
|
||||
offset = 0
|
||||
params = _linker_params(linker_na, linker_exe_ne)
|
||||
linker = env + aix + compiler_cxx_ne + params
|
||||
|
||||
linker[i+offset] = self.compiler_cxx[i]
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
linker = _osx_support.compiler_fixup(linker, ld_args)
|
||||
linker = compiler_fixup(linker, ld_args)
|
||||
|
||||
self.spawn(linker + ld_args)
|
||||
except DistutilsExecError as msg:
|
||||
@@ -214,8 +277,10 @@ class UnixCCompiler(CCompiler):
|
||||
def library_dir_option(self, dir):
|
||||
return "-L" + dir
|
||||
|
||||
def _is_gcc(self, compiler_name):
|
||||
return "gcc" in compiler_name or "g++" in compiler_name
|
||||
def _is_gcc(self):
|
||||
cc_var = sysconfig.get_config_var("CC")
|
||||
compiler = os.path.basename(shlex.split(cc_var)[0])
|
||||
return "gcc" in compiler or "g++" in compiler
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
# XXX Hackish, at the very least. See Python bug #445902:
|
||||
@@ -231,102 +296,106 @@ class UnixCCompiler(CCompiler):
|
||||
# this time, there's no way to determine this information from
|
||||
# the configuration data stored in the Python installation, so
|
||||
# we use this hack.
|
||||
compiler = os.path.basename(shlex.split(sysconfig.get_config_var("CC"))[0])
|
||||
if sys.platform[:6] == "darwin":
|
||||
from distutils.util import get_macosx_target_ver, split_version
|
||||
|
||||
macosx_target_ver = get_macosx_target_ver()
|
||||
if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]:
|
||||
return "-Wl,-rpath," + dir
|
||||
else: # no support for -rpath on earlier macOS versions
|
||||
else: # no support for -rpath on earlier macOS versions
|
||||
return "-L" + dir
|
||||
elif sys.platform[:7] == "freebsd":
|
||||
return "-Wl,-rpath=" + dir
|
||||
elif sys.platform[:5] == "hp-ux":
|
||||
if self._is_gcc(compiler):
|
||||
return ["-Wl,+s", "-L" + dir]
|
||||
return ["+s", "-L" + dir]
|
||||
return [
|
||||
"-Wl,+s" if self._is_gcc() else "+s",
|
||||
"-L" + dir,
|
||||
]
|
||||
|
||||
# For all compilers, `-Wl` is the presumed way to
|
||||
# pass a compiler option to the linker and `-R` is
|
||||
# the way to pass an RPATH.
|
||||
if sysconfig.get_config_var("GNULD") == "yes":
|
||||
# GNU ld needs an extra option to get a RUNPATH
|
||||
# instead of just an RPATH.
|
||||
return "-Wl,--enable-new-dtags,-R" + dir
|
||||
else:
|
||||
if self._is_gcc(compiler):
|
||||
# gcc on non-GNU systems does not need -Wl, but can
|
||||
# use it anyway. Since distutils has always passed in
|
||||
# -Wl whenever gcc was used in the past it is probably
|
||||
# safest to keep doing so.
|
||||
if sysconfig.get_config_var("GNULD") == "yes":
|
||||
# GNU ld needs an extra option to get a RUNPATH
|
||||
# instead of just an RPATH.
|
||||
return "-Wl,--enable-new-dtags,-R" + dir
|
||||
else:
|
||||
return "-Wl,-R" + dir
|
||||
else:
|
||||
# No idea how --enable-new-dtags would be passed on to
|
||||
# ld if this system was using GNU ld. Don't know if a
|
||||
# system like this even exists.
|
||||
return "-R" + dir
|
||||
return "-Wl,-R" + dir
|
||||
|
||||
def library_option(self, lib):
|
||||
return "-l" + lib
|
||||
|
||||
@staticmethod
|
||||
def _library_root(dir):
|
||||
"""
|
||||
macOS users can specify an alternate SDK using'-isysroot'.
|
||||
Calculate the SDK root if it is specified.
|
||||
|
||||
Note that, as of Xcode 7, Apple SDKs may contain textual stub
|
||||
libraries with .tbd extensions rather than the normal .dylib
|
||||
shared libraries installed in /. The Apple compiler tool
|
||||
chain handles this transparently but it can cause problems
|
||||
for programs that are being built with an SDK and searching
|
||||
for specific libraries. Callers of find_library_file need to
|
||||
keep in mind that the base filename of the returned SDK library
|
||||
file might have a different extension from that of the library
|
||||
file installed on the running system, for example:
|
||||
/Applications/Xcode.app/Contents/Developer/Platforms/
|
||||
MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/
|
||||
usr/lib/libedit.tbd
|
||||
vs
|
||||
/usr/lib/libedit.dylib
|
||||
"""
|
||||
cflags = sysconfig.get_config_var('CFLAGS')
|
||||
match = re.search(r'-isysroot\s*(\S+)', cflags)
|
||||
|
||||
apply_root = (
|
||||
sys.platform == 'darwin'
|
||||
and match
|
||||
and (
|
||||
dir.startswith('/System/')
|
||||
or (dir.startswith('/usr/') and not dir.startswith('/usr/local/'))
|
||||
)
|
||||
)
|
||||
|
||||
return os.path.join(match.group(1), dir[1:]) if apply_root else dir
|
||||
|
||||
def find_library_file(self, dirs, lib, debug=0):
|
||||
shared_f = self.library_filename(lib, lib_type='shared')
|
||||
dylib_f = self.library_filename(lib, lib_type='dylib')
|
||||
xcode_stub_f = self.library_filename(lib, lib_type='xcode_stub')
|
||||
static_f = self.library_filename(lib, lib_type='static')
|
||||
r"""
|
||||
Second-guess the linker with not much hard
|
||||
data to go on: GCC seems to prefer the shared library, so
|
||||
assume that *all* Unix C compilers do,
|
||||
ignoring even GCC's "-static" option.
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
# On OSX users can specify an alternate SDK using
|
||||
# '-isysroot', calculate the SDK root if it is specified
|
||||
# (and use it further on)
|
||||
#
|
||||
# Note that, as of Xcode 7, Apple SDKs may contain textual stub
|
||||
# libraries with .tbd extensions rather than the normal .dylib
|
||||
# shared libraries installed in /. The Apple compiler tool
|
||||
# chain handles this transparently but it can cause problems
|
||||
# for programs that are being built with an SDK and searching
|
||||
# for specific libraries. Callers of find_library_file need to
|
||||
# keep in mind that the base filename of the returned SDK library
|
||||
# file might have a different extension from that of the library
|
||||
# file installed on the running system, for example:
|
||||
# /Applications/Xcode.app/Contents/Developer/Platforms/
|
||||
# MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/
|
||||
# usr/lib/libedit.tbd
|
||||
# vs
|
||||
# /usr/lib/libedit.dylib
|
||||
cflags = sysconfig.get_config_var('CFLAGS')
|
||||
m = re.search(r'-isysroot\s*(\S+)', cflags)
|
||||
if m is None:
|
||||
sysroot = '/'
|
||||
else:
|
||||
sysroot = m.group(1)
|
||||
>>> compiler = UnixCCompiler()
|
||||
>>> compiler._library_root = lambda dir: dir
|
||||
>>> monkeypatch = getfixture('monkeypatch')
|
||||
>>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d)
|
||||
>>> dirs = ('/foo/bar/missing', '/foo/bar/existing')
|
||||
>>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')
|
||||
'/foo/bar/existing/libabc.dylib'
|
||||
>>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')
|
||||
'/foo/bar/existing/libabc.dylib'
|
||||
>>> monkeypatch.setattr(os.path, 'exists',
|
||||
... lambda d: 'existing' in d and '.a' in d)
|
||||
>>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')
|
||||
'/foo/bar/existing/libabc.a'
|
||||
>>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')
|
||||
'/foo/bar/existing/libabc.a'
|
||||
"""
|
||||
lib_names = (
|
||||
self.library_filename(lib, lib_type=type)
|
||||
for type in 'dylib xcode_stub shared static'.split()
|
||||
)
|
||||
|
||||
roots = map(self._library_root, dirs)
|
||||
|
||||
searched = (
|
||||
os.path.join(root, lib_name)
|
||||
for root, lib_name in itertools.product(roots, lib_names)
|
||||
)
|
||||
|
||||
for dir in dirs:
|
||||
shared = os.path.join(dir, shared_f)
|
||||
dylib = os.path.join(dir, dylib_f)
|
||||
static = os.path.join(dir, static_f)
|
||||
xcode_stub = os.path.join(dir, xcode_stub_f)
|
||||
found = filter(os.path.exists, searched)
|
||||
|
||||
if sys.platform == 'darwin' and (
|
||||
dir.startswith('/System/') or (
|
||||
dir.startswith('/usr/') and not dir.startswith('/usr/local/'))):
|
||||
|
||||
shared = os.path.join(sysroot, dir[1:], shared_f)
|
||||
dylib = os.path.join(sysroot, dir[1:], dylib_f)
|
||||
static = os.path.join(sysroot, dir[1:], static_f)
|
||||
xcode_stub = os.path.join(sysroot, dir[1:], xcode_stub_f)
|
||||
|
||||
# We're second-guessing the linker here, with not much hard
|
||||
# data to go on: GCC seems to prefer the shared library, so I'm
|
||||
# assuming that *all* Unix C compilers do. And of course I'm
|
||||
# ignoring even GCC's "-static" option. So sue me.
|
||||
if os.path.exists(dylib):
|
||||
return dylib
|
||||
elif os.path.exists(xcode_stub):
|
||||
return xcode_stub
|
||||
elif os.path.exists(shared):
|
||||
return shared
|
||||
elif os.path.exists(static):
|
||||
return static
|
||||
|
||||
# Oops, didn't find it in *any* of 'dirs'
|
||||
return None
|
||||
# Return None if it could not be found in any dir.
|
||||
return next(found, None)
|
||||
|
||||
@@ -4,155 +4,114 @@ Miscellaneous utility functions -- anything that doesn't fit into
|
||||
one of the other *util.py modules.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
import os
|
||||
import re
|
||||
import importlib.util
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
from distutils.errors import DistutilsPlatformError
|
||||
import sysconfig
|
||||
import functools
|
||||
|
||||
from distutils.errors import DistutilsPlatformError, DistutilsByteCompileError
|
||||
from distutils.dep_util import newer
|
||||
from distutils.spawn import spawn
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsByteCompileError
|
||||
from .py35compat import _optim_args_from_interpreter_flags
|
||||
|
||||
|
||||
def get_host_platform():
|
||||
"""Return a string that identifies the current platform. This is used mainly to
|
||||
distinguish platform-specific build directories and platform-specific built
|
||||
distributions. Typically includes the OS name and version and the
|
||||
architecture (as supplied by 'os.uname()'), although the exact information
|
||||
included depends on the OS; eg. on Linux, the kernel version isn't
|
||||
particularly important.
|
||||
|
||||
Examples of returned values:
|
||||
linux-i586
|
||||
linux-alpha (?)
|
||||
solaris-2.6-sun4u
|
||||
|
||||
Windows will return one of:
|
||||
win-amd64 (64bit Windows on AMD64 (aka x86_64, Intel64, EM64T, etc)
|
||||
win32 (all others - specifically, sys.platform is returned)
|
||||
|
||||
For other non-POSIX platforms, currently just returns 'sys.platform'.
|
||||
|
||||
"""
|
||||
if os.name == 'nt':
|
||||
if 'amd64' in sys.version.lower():
|
||||
return 'win-amd64'
|
||||
if '(arm)' in sys.version.lower():
|
||||
return 'win-arm32'
|
||||
if '(arm64)' in sys.version.lower():
|
||||
return 'win-arm64'
|
||||
return sys.platform
|
||||
Return a string that identifies the current platform. Use this
|
||||
function to distinguish platform-specific build directories and
|
||||
platform-specific built distributions.
|
||||
"""
|
||||
|
||||
# Set for cross builds explicitly
|
||||
if "_PYTHON_HOST_PLATFORM" in os.environ:
|
||||
return os.environ["_PYTHON_HOST_PLATFORM"]
|
||||
# This function initially exposed platforms as defined in Python 3.9
|
||||
# even with older Python versions when distutils was split out.
|
||||
# Now it delegates to stdlib sysconfig, but maintains compatibility.
|
||||
|
||||
if os.name != "posix" or not hasattr(os, 'uname'):
|
||||
# XXX what about the architecture? NT is Intel or Alpha,
|
||||
# Mac OS is M68k or PPC, etc.
|
||||
return sys.platform
|
||||
if sys.version_info < (3, 8):
|
||||
if os.name == 'nt':
|
||||
if '(arm)' in sys.version.lower():
|
||||
return 'win-arm32'
|
||||
if '(arm64)' in sys.version.lower():
|
||||
return 'win-arm64'
|
||||
|
||||
# Try to distinguish various flavours of Unix
|
||||
if sys.version_info < (3, 9):
|
||||
if os.name == "posix" and hasattr(os, 'uname'):
|
||||
osname, host, release, version, machine = os.uname()
|
||||
if osname[:3] == "aix":
|
||||
from .py38compat import aix_platform
|
||||
|
||||
(osname, host, release, version, machine) = os.uname()
|
||||
return aix_platform(osname, version, release)
|
||||
|
||||
# Convert the OS name to lowercase, remove '/' characters, and translate
|
||||
# spaces (for "Power Macintosh")
|
||||
osname = osname.lower().replace('/', '')
|
||||
machine = machine.replace(' ', '_')
|
||||
machine = machine.replace('/', '-')
|
||||
return sysconfig.get_platform()
|
||||
|
||||
if osname[:5] == "linux":
|
||||
# At least on Linux/Intel, 'machine' is the processor --
|
||||
# i386, etc.
|
||||
# XXX what about Alpha, SPARC, etc?
|
||||
return "%s-%s" % (osname, machine)
|
||||
elif osname[:5] == "sunos":
|
||||
if release[0] >= "5": # SunOS 5 == Solaris 2
|
||||
osname = "solaris"
|
||||
release = "%d.%s" % (int(release[0]) - 3, release[2:])
|
||||
# We can't use "platform.architecture()[0]" because a
|
||||
# bootstrap problem. We use a dict to get an error
|
||||
# if some suspicious happens.
|
||||
bitness = {2147483647:"32bit", 9223372036854775807:"64bit"}
|
||||
machine += ".%s" % bitness[sys.maxsize]
|
||||
# fall through to standard osname-release-machine representation
|
||||
elif osname[:3] == "aix":
|
||||
from .py38compat import aix_platform
|
||||
return aix_platform(osname, version, release)
|
||||
elif osname[:6] == "cygwin":
|
||||
osname = "cygwin"
|
||||
rel_re = re.compile (r'[\d.]+', re.ASCII)
|
||||
m = rel_re.match(release)
|
||||
if m:
|
||||
release = m.group()
|
||||
elif osname[:6] == "darwin":
|
||||
import _osx_support, distutils.sysconfig
|
||||
osname, release, machine = _osx_support.get_platform_osx(
|
||||
distutils.sysconfig.get_config_vars(),
|
||||
osname, release, machine)
|
||||
|
||||
return "%s-%s-%s" % (osname, release, machine)
|
||||
|
||||
def get_platform():
|
||||
if os.name == 'nt':
|
||||
TARGET_TO_PLAT = {
|
||||
'x86' : 'win32',
|
||||
'x64' : 'win-amd64',
|
||||
'arm' : 'win-arm32',
|
||||
'x86': 'win32',
|
||||
'x64': 'win-amd64',
|
||||
'arm': 'win-arm32',
|
||||
'arm64': 'win-arm64',
|
||||
}
|
||||
return TARGET_TO_PLAT.get(os.environ.get('VSCMD_ARG_TGT_ARCH')) or get_host_platform()
|
||||
else:
|
||||
return get_host_platform()
|
||||
target = os.environ.get('VSCMD_ARG_TGT_ARCH')
|
||||
return TARGET_TO_PLAT.get(target) or get_host_platform()
|
||||
return get_host_platform()
|
||||
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
_syscfg_macosx_ver = None # cache the version pulled from sysconfig
|
||||
_syscfg_macosx_ver = None # cache the version pulled from sysconfig
|
||||
MACOSX_VERSION_VAR = 'MACOSX_DEPLOYMENT_TARGET'
|
||||
|
||||
|
||||
def _clear_cached_macosx_ver():
|
||||
"""For testing only. Do not call."""
|
||||
global _syscfg_macosx_ver
|
||||
_syscfg_macosx_ver = None
|
||||
|
||||
|
||||
def get_macosx_target_ver_from_syscfg():
|
||||
"""Get the version of macOS latched in the Python interpreter configuration.
|
||||
Returns the version as a string or None if can't obtain one. Cached."""
|
||||
global _syscfg_macosx_ver
|
||||
if _syscfg_macosx_ver is None:
|
||||
from distutils import sysconfig
|
||||
|
||||
ver = sysconfig.get_config_var(MACOSX_VERSION_VAR) or ''
|
||||
if ver:
|
||||
_syscfg_macosx_ver = ver
|
||||
return _syscfg_macosx_ver
|
||||
|
||||
|
||||
def get_macosx_target_ver():
|
||||
"""Return the version of macOS for which we are building.
|
||||
|
||||
The target version defaults to the version in sysconfig latched at time
|
||||
the Python interpreter was built, unless overriden by an environment
|
||||
the Python interpreter was built, unless overridden by an environment
|
||||
variable. If neither source has a value, then None is returned"""
|
||||
|
||||
syscfg_ver = get_macosx_target_ver_from_syscfg()
|
||||
env_ver = os.environ.get(MACOSX_VERSION_VAR)
|
||||
|
||||
if env_ver:
|
||||
# Validate overriden version against sysconfig version, if have both.
|
||||
# Validate overridden version against sysconfig version, if have both.
|
||||
# Ensure that the deployment target of the build process is not less
|
||||
# than 10.3 if the interpreter was built for 10.3 or later. This
|
||||
# ensures extension modules are built with correct compatibility
|
||||
# values, specifically LDSHARED which can use
|
||||
# '-undefined dynamic_lookup' which only works on >= 10.3.
|
||||
if syscfg_ver and split_version(syscfg_ver) >= [10, 3] and \
|
||||
split_version(env_ver) < [10, 3]:
|
||||
my_msg = ('$' + MACOSX_VERSION_VAR + ' mismatch: '
|
||||
'now "%s" but "%s" during configure; '
|
||||
'must use 10.3 or later'
|
||||
% (env_ver, syscfg_ver))
|
||||
if (
|
||||
syscfg_ver
|
||||
and split_version(syscfg_ver) >= [10, 3]
|
||||
and split_version(env_ver) < [10, 3]
|
||||
):
|
||||
my_msg = (
|
||||
'$' + MACOSX_VERSION_VAR + ' mismatch: '
|
||||
'now "%s" but "%s" during configure; '
|
||||
'must use 10.3 or later' % (env_ver, syscfg_ver)
|
||||
)
|
||||
raise DistutilsPlatformError(my_msg)
|
||||
return env_ver
|
||||
return syscfg_ver
|
||||
@@ -163,7 +122,7 @@ def split_version(s):
|
||||
return [int(n) for n in s.split('.')]
|
||||
|
||||
|
||||
def convert_path (pathname):
|
||||
def convert_path(pathname):
|
||||
"""Return 'pathname' as a name that will work on the native filesystem,
|
||||
i.e. split it on '/' and put it back together again using the current
|
||||
directory separator. Needed because filenames in the setup script are
|
||||
@@ -188,10 +147,11 @@ def convert_path (pathname):
|
||||
return os.curdir
|
||||
return os.path.join(*paths)
|
||||
|
||||
|
||||
# convert_path ()
|
||||
|
||||
|
||||
def change_root (new_root, pathname):
|
||||
def change_root(new_root, pathname):
|
||||
"""Return 'pathname' with 'new_root' prepended. If 'pathname' is
|
||||
relative, this is equivalent to "os.path.join(new_root,pathname)".
|
||||
Otherwise, it requires making 'pathname' relative and then joining the
|
||||
@@ -209,12 +169,11 @@ def change_root (new_root, pathname):
|
||||
path = path[1:]
|
||||
return os.path.join(new_root, path)
|
||||
|
||||
else:
|
||||
raise DistutilsPlatformError("nothing known about platform '%s'" % os.name)
|
||||
raise DistutilsPlatformError(f"nothing known about platform '{os.name}'")
|
||||
|
||||
|
||||
_environ_checked = 0
|
||||
def check_environ ():
|
||||
@functools.lru_cache()
|
||||
def check_environ():
|
||||
"""Ensure that 'os.environ' has all the environment variables we
|
||||
guarantee that users can use in config files, command-line options,
|
||||
etc. Currently this includes:
|
||||
@@ -222,13 +181,10 @@ def check_environ ():
|
||||
PLAT - description of the current platform, including hardware
|
||||
and OS (see 'get_platform()')
|
||||
"""
|
||||
global _environ_checked
|
||||
if _environ_checked:
|
||||
return
|
||||
|
||||
if os.name == 'posix' and 'HOME' not in os.environ:
|
||||
try:
|
||||
import pwd
|
||||
|
||||
os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
|
||||
except (ImportError, KeyError):
|
||||
# bpo-10496: if the current user identifier doesn't exist in the
|
||||
@@ -238,35 +194,47 @@ def check_environ ():
|
||||
if 'PLAT' not in os.environ:
|
||||
os.environ['PLAT'] = get_platform()
|
||||
|
||||
_environ_checked = 1
|
||||
|
||||
|
||||
def subst_vars (s, local_vars):
|
||||
"""Perform shell/Perl-style variable substitution on 'string'. Every
|
||||
occurrence of '$' followed by a name is considered a variable, and
|
||||
variable is substituted by the value found in the 'local_vars'
|
||||
dictionary, or in 'os.environ' if it's not in 'local_vars'.
|
||||
def subst_vars(s, local_vars):
|
||||
"""
|
||||
Perform variable substitution on 'string'.
|
||||
Variables are indicated by format-style braces ("{var}").
|
||||
Variable is substituted by the value found in the 'local_vars'
|
||||
dictionary or in 'os.environ' if it's not in 'local_vars'.
|
||||
'os.environ' is first checked/augmented to guarantee that it contains
|
||||
certain values: see 'check_environ()'. Raise ValueError for any
|
||||
variables not found in either 'local_vars' or 'os.environ'.
|
||||
"""
|
||||
check_environ()
|
||||
def _subst (match, local_vars=local_vars):
|
||||
var_name = match.group(1)
|
||||
if var_name in local_vars:
|
||||
return str(local_vars[var_name])
|
||||
else:
|
||||
return os.environ[var_name]
|
||||
|
||||
lookup = dict(os.environ)
|
||||
lookup.update((name, str(value)) for name, value in local_vars.items())
|
||||
try:
|
||||
return re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
|
||||
return _subst_compat(s).format_map(lookup)
|
||||
except KeyError as var:
|
||||
raise ValueError("invalid variable '$%s'" % var)
|
||||
|
||||
# subst_vars ()
|
||||
raise ValueError(f"invalid variable {var}")
|
||||
|
||||
|
||||
def grok_environment_error (exc, prefix="error: "):
|
||||
def _subst_compat(s):
|
||||
"""
|
||||
Replace shell/Perl-style variable substitution with
|
||||
format-style. For compatibility.
|
||||
"""
|
||||
|
||||
def _subst(match):
|
||||
return f'{{{match.group(1)}}}'
|
||||
|
||||
repl = re.sub(r'\$([a-zA-Z_][a-zA-Z_0-9]*)', _subst, s)
|
||||
if repl != s:
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"shell/Perl-style substitions are deprecated",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return repl
|
||||
|
||||
|
||||
def grok_environment_error(exc, prefix="error: "):
|
||||
# Function kept for backward compatibility.
|
||||
# Used to try clever things with EnvironmentErrors,
|
||||
# but nowadays str(exception) produces good messages.
|
||||
@@ -275,13 +243,16 @@ def grok_environment_error (exc, prefix="error: "):
|
||||
|
||||
# Needed by 'split_quoted()'
|
||||
_wordchars_re = _squote_re = _dquote_re = None
|
||||
|
||||
|
||||
def _init_regex():
|
||||
global _wordchars_re, _squote_re, _dquote_re
|
||||
_wordchars_re = re.compile(r'[^\\\'\"%s ]*' % string.whitespace)
|
||||
_squote_re = re.compile(r"'(?:[^'\\]|\\.)*'")
|
||||
_dquote_re = re.compile(r'"(?:[^"\\]|\\.)*"')
|
||||
|
||||
def split_quoted (s):
|
||||
|
||||
def split_quoted(s):
|
||||
"""Split a string up according to Unix shell-like rules for quotes and
|
||||
backslashes. In short: words are delimited by spaces, as long as those
|
||||
spaces are not escaped by a backslash, or inside a quoted string.
|
||||
@@ -295,7 +266,8 @@ def split_quoted (s):
|
||||
# This is a nice algorithm for splitting up a single string, since it
|
||||
# doesn't require character-by-character examination. It was a little
|
||||
# bit of a brain-bender to get it working right, though...
|
||||
if _wordchars_re is None: _init_regex()
|
||||
if _wordchars_re is None:
|
||||
_init_regex()
|
||||
|
||||
s = s.strip()
|
||||
words = []
|
||||
@@ -308,20 +280,23 @@ def split_quoted (s):
|
||||
words.append(s[:end])
|
||||
break
|
||||
|
||||
if s[end] in string.whitespace: # unescaped, unquoted whitespace: now
|
||||
words.append(s[:end]) # we definitely have a word delimiter
|
||||
if s[end] in string.whitespace:
|
||||
# unescaped, unquoted whitespace: now
|
||||
# we definitely have a word delimiter
|
||||
words.append(s[:end])
|
||||
s = s[end:].lstrip()
|
||||
pos = 0
|
||||
|
||||
elif s[end] == '\\': # preserve whatever is being escaped;
|
||||
# will become part of the current word
|
||||
s = s[:end] + s[end+1:]
|
||||
pos = end+1
|
||||
elif s[end] == '\\':
|
||||
# preserve whatever is being escaped;
|
||||
# will become part of the current word
|
||||
s = s[:end] + s[end + 1 :]
|
||||
pos = end + 1
|
||||
|
||||
else:
|
||||
if s[end] == "'": # slurp singly-quoted string
|
||||
if s[end] == "'": # slurp singly-quoted string
|
||||
m = _squote_re.match(s, end)
|
||||
elif s[end] == '"': # slurp doubly-quoted string
|
||||
elif s[end] == '"': # slurp doubly-quoted string
|
||||
m = _dquote_re.match(s, end)
|
||||
else:
|
||||
raise RuntimeError("this can't happen (bad char '%c')" % s[end])
|
||||
@@ -330,7 +305,7 @@ def split_quoted (s):
|
||||
raise ValueError("bad string (mismatched %s quotes?)" % s[end])
|
||||
|
||||
(beg, end) = m.span()
|
||||
s = s[:beg] + s[beg+1:end-1] + s[end:]
|
||||
s = s[:beg] + s[beg + 1 : end - 1] + s[end:]
|
||||
pos = m.end() - 2
|
||||
|
||||
if pos >= len(s):
|
||||
@@ -339,10 +314,11 @@ def split_quoted (s):
|
||||
|
||||
return words
|
||||
|
||||
|
||||
# split_quoted ()
|
||||
|
||||
|
||||
def execute (func, args, msg=None, verbose=0, dry_run=0):
|
||||
def execute(func, args, msg=None, verbose=0, dry_run=0):
|
||||
"""Perform some action that affects the outside world (eg. by
|
||||
writing to the filesystem). Such actions are special because they
|
||||
are disabled by the 'dry_run' flag. This method takes care of all
|
||||
@@ -352,8 +328,8 @@ def execute (func, args, msg=None, verbose=0, dry_run=0):
|
||||
print.
|
||||
"""
|
||||
if msg is None:
|
||||
msg = "%s%r" % (func.__name__, args)
|
||||
if msg[-2:] == ',)': # correct for singleton tuple
|
||||
msg = "{}{!r}".format(func.__name__, args)
|
||||
if msg[-2:] == ',)': # correct for singleton tuple
|
||||
msg = msg[0:-2] + ')'
|
||||
|
||||
log.info(msg)
|
||||
@@ -361,7 +337,7 @@ def execute (func, args, msg=None, verbose=0, dry_run=0):
|
||||
func(*args)
|
||||
|
||||
|
||||
def strtobool (val):
|
||||
def strtobool(val):
|
||||
"""Convert a string representation of truth to true (1) or false (0).
|
||||
|
||||
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
|
||||
@@ -374,14 +350,19 @@ def strtobool (val):
|
||||
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
|
||||
return 0
|
||||
else:
|
||||
raise ValueError("invalid truth value %r" % (val,))
|
||||
raise ValueError("invalid truth value {!r}".format(val))
|
||||
|
||||
|
||||
def byte_compile (py_files,
|
||||
optimize=0, force=0,
|
||||
prefix=None, base_dir=None,
|
||||
verbose=1, dry_run=0,
|
||||
direct=None):
|
||||
def byte_compile( # noqa: C901
|
||||
py_files,
|
||||
optimize=0,
|
||||
force=0,
|
||||
prefix=None,
|
||||
base_dir=None,
|
||||
verbose=1,
|
||||
dry_run=0,
|
||||
direct=None,
|
||||
):
|
||||
"""Byte-compile a collection of Python source files to .pyc
|
||||
files in a __pycache__ subdirectory. 'py_files' is a list
|
||||
of files to compile; any files that don't end in ".py" are silently
|
||||
@@ -411,10 +392,6 @@ def byte_compile (py_files,
|
||||
it set to None.
|
||||
"""
|
||||
|
||||
# Late import to fix a bootstrap issue: _posixsubprocess is built by
|
||||
# setup.py, but setup.py uses distutils.
|
||||
import subprocess
|
||||
|
||||
# nothing is done if sys.dont_write_bytecode is True
|
||||
if sys.dont_write_bytecode:
|
||||
raise DistutilsByteCompileError('byte-compiling is disabled.')
|
||||
@@ -430,16 +407,18 @@ def byte_compile (py_files,
|
||||
# optimize mode, or if either optimization level was requested by
|
||||
# the caller.
|
||||
if direct is None:
|
||||
direct = (__debug__ and optimize == 0)
|
||||
direct = __debug__ and optimize == 0
|
||||
|
||||
# "Indirect" byte-compilation: write a temporary script and then
|
||||
# run it with the appropriate flags.
|
||||
if not direct:
|
||||
try:
|
||||
from tempfile import mkstemp
|
||||
|
||||
(script_fd, script_name) = mkstemp(".py")
|
||||
except ImportError:
|
||||
from tempfile import mktemp
|
||||
|
||||
(script_fd, script_name) = None, mktemp(".py")
|
||||
log.info("writing byte-compilation script '%s'", script_name)
|
||||
if not dry_run:
|
||||
@@ -449,10 +428,12 @@ def byte_compile (py_files,
|
||||
script = open(script_name, "w")
|
||||
|
||||
with script:
|
||||
script.write("""\
|
||||
script.write(
|
||||
"""\
|
||||
from distutils.util import byte_compile
|
||||
files = [
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
# XXX would be nice to write absolute filenames, just for
|
||||
# safety's sake (script should be more robust in the face of
|
||||
@@ -464,24 +445,22 @@ files = [
|
||||
# problem is that it's really a directory, but I'm treating it
|
||||
# as a dumb string, so trailing slashes and so forth matter.
|
||||
|
||||
#py_files = map(os.path.abspath, py_files)
|
||||
#if prefix:
|
||||
# prefix = os.path.abspath(prefix)
|
||||
|
||||
script.write(",\n".join(map(repr, py_files)) + "]\n")
|
||||
script.write("""
|
||||
script.write(
|
||||
"""
|
||||
byte_compile(files, optimize=%r, force=%r,
|
||||
prefix=%r, base_dir=%r,
|
||||
verbose=%r, dry_run=0,
|
||||
direct=1)
|
||||
""" % (optimize, force, prefix, base_dir, verbose))
|
||||
"""
|
||||
% (optimize, force, prefix, base_dir, verbose)
|
||||
)
|
||||
|
||||
cmd = [sys.executable]
|
||||
cmd.extend(_optim_args_from_interpreter_flags())
|
||||
cmd.extend(subprocess._optim_args_from_interpreter_flags())
|
||||
cmd.append(script_name)
|
||||
spawn(cmd, dry_run=dry_run)
|
||||
execute(os.remove, (script_name,), "removing %s" % script_name,
|
||||
dry_run=dry_run)
|
||||
execute(os.remove, (script_name,), "removing %s" % script_name, dry_run=dry_run)
|
||||
|
||||
# "Direct" byte-compilation: use the py_compile module to compile
|
||||
# right here, right now. Note that the script generated in indirect
|
||||
@@ -501,16 +480,17 @@ byte_compile(files, optimize=%r, force=%r,
|
||||
# dfile - purported source filename (same as 'file' by default)
|
||||
if optimize >= 0:
|
||||
opt = '' if optimize == 0 else optimize
|
||||
cfile = importlib.util.cache_from_source(
|
||||
file, optimization=opt)
|
||||
cfile = importlib.util.cache_from_source(file, optimization=opt)
|
||||
else:
|
||||
cfile = importlib.util.cache_from_source(file)
|
||||
dfile = file
|
||||
if prefix:
|
||||
if file[:len(prefix)] != prefix:
|
||||
raise ValueError("invalid prefix: filename %r doesn't start with %r"
|
||||
% (file, prefix))
|
||||
dfile = dfile[len(prefix):]
|
||||
if file[: len(prefix)] != prefix:
|
||||
raise ValueError(
|
||||
"invalid prefix: filename %r doesn't start with %r"
|
||||
% (file, prefix)
|
||||
)
|
||||
dfile = dfile[len(prefix) :]
|
||||
if base_dir:
|
||||
dfile = os.path.join(base_dir, dfile)
|
||||
|
||||
@@ -521,12 +501,10 @@ byte_compile(files, optimize=%r, force=%r,
|
||||
if not dry_run:
|
||||
compile(file, cfile, dfile)
|
||||
else:
|
||||
log.debug("skipping byte-compilation of %s to %s",
|
||||
file, cfile_base)
|
||||
log.debug("skipping byte-compilation of %s to %s", file, cfile_base)
|
||||
|
||||
# byte_compile ()
|
||||
|
||||
def rfc822_escape (header):
|
||||
def rfc822_escape(header):
|
||||
"""Return a version of the string escaped for inclusion in an
|
||||
RFC-822 header, by ensuring there are 8 spaces space after each newline.
|
||||
"""
|
||||
|
||||
@@ -27,6 +27,20 @@ Every version number class implements the following interface:
|
||||
"""
|
||||
|
||||
import re
|
||||
import warnings
|
||||
import contextlib
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def suppress_known_deprecation():
|
||||
with warnings.catch_warnings(record=True) as ctx:
|
||||
warnings.filterwarnings(
|
||||
action='default',
|
||||
category=DeprecationWarning,
|
||||
message="distutils Version classes are deprecated.",
|
||||
)
|
||||
yield ctx
|
||||
|
||||
|
||||
class Version:
|
||||
"""Abstract base class for version numbering classes. Just provides
|
||||
@@ -35,12 +49,18 @@ class Version:
|
||||
rich comparisons to _cmp.
|
||||
"""
|
||||
|
||||
def __init__ (self, vstring=None):
|
||||
def __init__(self, vstring=None):
|
||||
if vstring:
|
||||
self.parse(vstring)
|
||||
warnings.warn(
|
||||
"distutils Version classes are deprecated. "
|
||||
"Use packaging.version instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def __repr__ (self):
|
||||
return "%s ('%s')" % (self.__class__.__name__, str(self))
|
||||
def __repr__(self):
|
||||
return "{} ('{}')".format(self.__class__.__name__, str(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
c = self._cmp(other)
|
||||
@@ -90,7 +110,7 @@ class Version:
|
||||
# instance of your version class)
|
||||
|
||||
|
||||
class StrictVersion (Version):
|
||||
class StrictVersion(Version):
|
||||
|
||||
"""Version numbering for anal retentives and software idealists.
|
||||
Implements the standard interface for version number classes as
|
||||
@@ -127,17 +147,16 @@ class StrictVersion (Version):
|
||||
in the distutils documentation.
|
||||
"""
|
||||
|
||||
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
|
||||
re.VERBOSE | re.ASCII)
|
||||
version_re = re.compile(
|
||||
r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', re.VERBOSE | re.ASCII
|
||||
)
|
||||
|
||||
|
||||
def parse (self, vstring):
|
||||
def parse(self, vstring):
|
||||
match = self.version_re.match(vstring)
|
||||
if not match:
|
||||
raise ValueError("invalid version number '%s'" % vstring)
|
||||
|
||||
(major, minor, patch, prerelease, prerelease_num) = \
|
||||
match.group(1, 2, 4, 5, 6)
|
||||
(major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)
|
||||
|
||||
if patch:
|
||||
self.version = tuple(map(int, [major, minor, patch]))
|
||||
@@ -149,8 +168,7 @@ class StrictVersion (Version):
|
||||
else:
|
||||
self.prerelease = None
|
||||
|
||||
|
||||
def __str__ (self):
|
||||
def __str__(self):
|
||||
|
||||
if self.version[2] == 0:
|
||||
vstring = '.'.join(map(str, self.version[0:2]))
|
||||
@@ -162,10 +180,10 @@ class StrictVersion (Version):
|
||||
|
||||
return vstring
|
||||
|
||||
|
||||
def _cmp (self, other):
|
||||
def _cmp(self, other): # noqa: C901
|
||||
if isinstance(other, str):
|
||||
other = StrictVersion(other)
|
||||
with suppress_known_deprecation():
|
||||
other = StrictVersion(other)
|
||||
elif not isinstance(other, StrictVersion):
|
||||
return NotImplemented
|
||||
|
||||
@@ -183,13 +201,13 @@ class StrictVersion (Version):
|
||||
# case 3: self doesn't have prerelease, other does: self is greater
|
||||
# case 4: both have prerelease: must compare them!
|
||||
|
||||
if (not self.prerelease and not other.prerelease):
|
||||
if not self.prerelease and not other.prerelease:
|
||||
return 0
|
||||
elif (self.prerelease and not other.prerelease):
|
||||
elif self.prerelease and not other.prerelease:
|
||||
return -1
|
||||
elif (not self.prerelease and other.prerelease):
|
||||
elif not self.prerelease and other.prerelease:
|
||||
return 1
|
||||
elif (self.prerelease and other.prerelease):
|
||||
elif self.prerelease and other.prerelease:
|
||||
if self.prerelease == other.prerelease:
|
||||
return 0
|
||||
elif self.prerelease < other.prerelease:
|
||||
@@ -199,6 +217,7 @@ class StrictVersion (Version):
|
||||
else:
|
||||
assert False, "never get here"
|
||||
|
||||
|
||||
# end class StrictVersion
|
||||
|
||||
|
||||
@@ -266,7 +285,8 @@ class StrictVersion (Version):
|
||||
# the Right Thing" (ie. the code matches the conception). But I'd rather
|
||||
# have a conception that matches common notions about version numbers.
|
||||
|
||||
class LooseVersion (Version):
|
||||
|
||||
class LooseVersion(Version):
|
||||
|
||||
"""Version numbering for anarchists and software realists.
|
||||
Implements the standard interface for version number classes as
|
||||
@@ -301,18 +321,12 @@ class LooseVersion (Version):
|
||||
|
||||
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
|
||||
|
||||
def __init__ (self, vstring=None):
|
||||
if vstring:
|
||||
self.parse(vstring)
|
||||
|
||||
|
||||
def parse (self, vstring):
|
||||
def parse(self, vstring):
|
||||
# I've given up on thinking I can reconstruct the version string
|
||||
# from the parsed tuple -- so I just store the string here for
|
||||
# use by __str__
|
||||
self.vstring = vstring
|
||||
components = [x for x in self.component_re.split(vstring)
|
||||
if x and x != '.']
|
||||
components = [x for x in self.component_re.split(vstring) if x and x != '.']
|
||||
for i, obj in enumerate(components):
|
||||
try:
|
||||
components[i] = int(obj)
|
||||
@@ -321,16 +335,13 @@ class LooseVersion (Version):
|
||||
|
||||
self.version = components
|
||||
|
||||
|
||||
def __str__ (self):
|
||||
def __str__(self):
|
||||
return self.vstring
|
||||
|
||||
|
||||
def __repr__ (self):
|
||||
def __repr__(self):
|
||||
return "LooseVersion ('%s')" % str(self)
|
||||
|
||||
|
||||
def _cmp (self, other):
|
||||
def _cmp(self, other):
|
||||
if isinstance(other, str):
|
||||
other = LooseVersion(other)
|
||||
elif not isinstance(other, LooseVersion):
|
||||
|
||||
@@ -5,11 +5,10 @@ import distutils.version
|
||||
import operator
|
||||
|
||||
|
||||
re_validPackage = re.compile(r"(?i)^\s*([a-z_]\w*(?:\.[a-z_]\w*)*)(.*)",
|
||||
re.ASCII)
|
||||
re_validPackage = re.compile(r"(?i)^\s*([a-z_]\w*(?:\.[a-z_]\w*)*)(.*)", re.ASCII)
|
||||
# (package) (rest)
|
||||
|
||||
re_paren = re.compile(r"^\s*\((.*)\)\s*$") # (list) inside of parentheses
|
||||
re_paren = re.compile(r"^\s*\((.*)\)\s*$") # (list) inside of parentheses
|
||||
re_splitComparison = re.compile(r"^\s*(<=|>=|<|>|!=|==)\s*([^\s,]+)\s*$")
|
||||
# (comp) (version)
|
||||
|
||||
@@ -23,10 +22,20 @@ def splitUp(pred):
|
||||
if not res:
|
||||
raise ValueError("bad package restriction syntax: %r" % pred)
|
||||
comp, verStr = res.groups()
|
||||
return (comp, distutils.version.StrictVersion(verStr))
|
||||
with distutils.version.suppress_known_deprecation():
|
||||
other = distutils.version.StrictVersion(verStr)
|
||||
return (comp, other)
|
||||
|
||||
|
||||
compmap = {
|
||||
"<": operator.lt,
|
||||
"<=": operator.le,
|
||||
"==": operator.eq,
|
||||
">": operator.gt,
|
||||
">=": operator.ge,
|
||||
"!=": operator.ne,
|
||||
}
|
||||
|
||||
compmap = {"<": operator.lt, "<=": operator.le, "==": operator.eq,
|
||||
">": operator.gt, ">=": operator.ge, "!=": operator.ne}
|
||||
|
||||
class VersionPredicate:
|
||||
"""Parse and test package version predicates.
|
||||
@@ -94,8 +103,7 @@ class VersionPredicate:
|
||||
"""
|
||||
|
||||
def __init__(self, versionPredicateStr):
|
||||
"""Parse a version predicate string.
|
||||
"""
|
||||
"""Parse a version predicate string."""
|
||||
# Fields:
|
||||
# name: package name
|
||||
# pred: list of (comparison string, StrictVersion)
|
||||
@@ -115,8 +123,7 @@ class VersionPredicate:
|
||||
str = match.groups()[0]
|
||||
self.pred = [splitUp(aPred) for aPred in str.split(",")]
|
||||
if not self.pred:
|
||||
raise ValueError("empty parenthesized list in %r"
|
||||
% versionPredicateStr)
|
||||
raise ValueError("empty parenthesized list in %r" % versionPredicateStr)
|
||||
else:
|
||||
self.pred = []
|
||||
|
||||
@@ -140,6 +147,7 @@ class VersionPredicate:
|
||||
|
||||
_provision_rx = None
|
||||
|
||||
|
||||
def split_provision(value):
|
||||
"""Return the name and optional version number of a provision.
|
||||
|
||||
@@ -154,13 +162,14 @@ def split_provision(value):
|
||||
global _provision_rx
|
||||
if _provision_rx is None:
|
||||
_provision_rx = re.compile(
|
||||
r"([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)(?:\s*\(\s*([^)\s]+)\s*\))?$",
|
||||
re.ASCII)
|
||||
r"([a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*)(?:\s*\(\s*([^)\s]+)\s*\))?$", re.ASCII
|
||||
)
|
||||
value = value.strip()
|
||||
m = _provision_rx.match(value)
|
||||
if not m:
|
||||
raise ValueError("illegal provides specification: %r" % value)
|
||||
ver = m.group(2) or None
|
||||
if ver:
|
||||
ver = distutils.version.StrictVersion(ver)
|
||||
with distutils.version.suppress_known_deprecation():
|
||||
ver = distutils.version.StrictVersion(ver)
|
||||
return m.group(1), ver
|
||||
|
||||
@@ -2,7 +2,6 @@ import warnings
|
||||
|
||||
from collections import Counter, defaultdict, deque, abc
|
||||
from collections.abc import Sequence
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import partial, reduce, wraps
|
||||
from heapq import merge, heapify, heapreplace, heappop
|
||||
from itertools import (
|
||||
@@ -3454,7 +3453,7 @@ class callback_iter:
|
||||
self._aborted = False
|
||||
self._future = None
|
||||
self._wait_seconds = wait_seconds
|
||||
self._executor = ThreadPoolExecutor(max_workers=1)
|
||||
self._executor = __import__("concurrent.futures").futures.ThreadPoolExecutor(max_workers=1)
|
||||
self._iterator = self._reader()
|
||||
|
||||
def __enter__(self):
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__all__ = [
|
||||
"__title__",
|
||||
@@ -18,10 +17,10 @@ __title__ = "packaging"
|
||||
__summary__ = "Core utilities for Python packages"
|
||||
__uri__ = "https://github.com/pypa/packaging"
|
||||
|
||||
__version__ = "20.4"
|
||||
__version__ = "21.3"
|
||||
|
||||
__author__ = "Donald Stufft and individual contributors"
|
||||
__email__ = "donald@stufft.io"
|
||||
|
||||
__license__ = "BSD-2-Clause or Apache-2.0"
|
||||
__copyright__ = "Copyright 2014-2019 %s" % __author__
|
||||
__copyright__ = "2014-2019 %s" % __author__
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from .__about__ import (
|
||||
__author__,
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import sys
|
||||
|
||||
from ._typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from typing import Any, Dict, Tuple, Type
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
if PY3:
|
||||
string_types = (str,)
|
||||
else:
|
||||
string_types = (basestring,)
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
# type: (Type[Any], Tuple[Type[Any], ...]) -> Any
|
||||
"""
|
||||
Create a base class with a metaclass.
|
||||
"""
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(meta): # type: ignore
|
||||
def __new__(cls, name, this_bases, d):
|
||||
# type: (Type[Any], str, Tuple[Any], Dict[Any, Any]) -> Any
|
||||
return meta(name, bases, d)
|
||||
|
||||
return type.__new__(metaclass, "temporary_class", (), {})
|
||||
@@ -1,85 +1,60 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
class InfinityType(object):
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
class InfinityType:
|
||||
def __repr__(self) -> str:
|
||||
return "Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
def __hash__(self) -> int:
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __lt__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
def __le__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __le__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __gt__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
def __ge__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __ge__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
def __neg__(self):
|
||||
# type: (object) -> NegativeInfinityType
|
||||
def __neg__(self: object) -> "NegativeInfinityType":
|
||||
return NegativeInfinity
|
||||
|
||||
|
||||
Infinity = InfinityType()
|
||||
|
||||
|
||||
class NegativeInfinityType(object):
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
class NegativeInfinityType:
|
||||
def __repr__(self) -> str:
|
||||
return "-Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
def __hash__(self) -> int:
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __lt__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
def __le__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __le__(self, other: object) -> bool:
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __eq__(self, other: object) -> bool:
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __gt__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
def __ge__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __ge__(self, other: object) -> bool:
|
||||
return False
|
||||
|
||||
def __neg__(self):
|
||||
# type: (object) -> InfinityType
|
||||
def __neg__(self: object) -> InfinityType:
|
||||
return Infinity
|
||||
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
"""For neatly implementing static typing in packaging.
|
||||
|
||||
`mypy` - the static type analysis tool we use - uses the `typing` module, which
|
||||
provides core functionality fundamental to mypy's functioning.
|
||||
|
||||
Generally, `typing` would be imported at runtime and used in that fashion -
|
||||
it acts as a no-op at runtime and does not have any run-time overhead by
|
||||
design.
|
||||
|
||||
As it turns out, `typing` is not vendorable - it uses separate sources for
|
||||
Python 2/Python 3. Thus, this codebase can not expect it to be present.
|
||||
To work around this, mypy allows the typing import to be behind a False-y
|
||||
optional to prevent it from running at runtime and type-comments can be used
|
||||
to remove the need for the types to be accessible directly during runtime.
|
||||
|
||||
This module provides the False-y guard in a nicely named fashion so that a
|
||||
curious maintainer can reach here to read this.
|
||||
|
||||
In packaging, all static-typing related imports should be guarded as follows:
|
||||
|
||||
from packaging._typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import ...
|
||||
|
||||
Ref: https://github.com/python/mypy/issues/3216
|
||||
"""
|
||||
|
||||
__all__ = ["TYPE_CHECKING", "cast"]
|
||||
|
||||
# The TYPE_CHECKING constant defined by the typing module is False at runtime
|
||||
# but True while type checking.
|
||||
if False: # pragma: no cover
|
||||
from typing import TYPE_CHECKING
|
||||
else:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
# typing's cast syntax requires calling typing.cast at runtime, but we don't
|
||||
# want to import typing at runtime. Here, we inform the type checkers that
|
||||
# we're importing `typing.cast` as `cast` and re-implement typing.cast's
|
||||
# runtime behavior in a block that is ignored by type checkers.
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
# not executed at runtime
|
||||
from typing import cast
|
||||
else:
|
||||
# executed at runtime
|
||||
def cast(type_, value): # noqa
|
||||
return value
|
||||
@@ -1,26 +1,26 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import operator
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from setuptools.extern.pyparsing import ParseException, ParseResults, stringStart, stringEnd
|
||||
from setuptools.extern.pyparsing import ZeroOrMore, Group, Forward, QuotedString
|
||||
from setuptools.extern.pyparsing import Literal as L # noqa
|
||||
|
||||
from ._compat import string_types
|
||||
from ._typing import TYPE_CHECKING
|
||||
from .specifiers import Specifier, InvalidSpecifier
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
Operator = Callable[[str, str], bool]
|
||||
from setuptools.extern.pyparsing import ( # noqa: N817
|
||||
Forward,
|
||||
Group,
|
||||
Literal as L,
|
||||
ParseException,
|
||||
ParseResults,
|
||||
QuotedString,
|
||||
ZeroOrMore,
|
||||
stringEnd,
|
||||
stringStart,
|
||||
)
|
||||
|
||||
from .specifiers import InvalidSpecifier, Specifier
|
||||
|
||||
__all__ = [
|
||||
"InvalidMarker",
|
||||
@@ -30,6 +30,8 @@ __all__ = [
|
||||
"default_environment",
|
||||
]
|
||||
|
||||
Operator = Callable[[str, str], bool]
|
||||
|
||||
|
||||
class InvalidMarker(ValueError):
|
||||
"""
|
||||
@@ -50,39 +52,32 @@ class UndefinedEnvironmentName(ValueError):
|
||||
"""
|
||||
|
||||
|
||||
class Node(object):
|
||||
def __init__(self, value):
|
||||
# type: (Any) -> None
|
||||
class Node:
|
||||
def __init__(self, value: Any) -> None:
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
def __str__(self) -> str:
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "<{0}({1!r})>".format(self.__class__.__name__, str(self))
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__}('{self}')>"
|
||||
|
||||
def serialize(self):
|
||||
# type: () -> str
|
||||
def serialize(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Variable(Node):
|
||||
def serialize(self):
|
||||
# type: () -> str
|
||||
def serialize(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
class Value(Node):
|
||||
def serialize(self):
|
||||
# type: () -> str
|
||||
return '"{0}"'.format(self)
|
||||
def serialize(self) -> str:
|
||||
return f'"{self}"'
|
||||
|
||||
|
||||
class Op(Node):
|
||||
def serialize(self):
|
||||
# type: () -> str
|
||||
def serialize(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
@@ -143,18 +138,18 @@ MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
|
||||
MARKER = stringStart + MARKER_EXPR + stringEnd
|
||||
|
||||
|
||||
def _coerce_parse_result(results):
|
||||
# type: (Union[ParseResults, List[Any]]) -> List[Any]
|
||||
def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]:
|
||||
if isinstance(results, ParseResults):
|
||||
return [_coerce_parse_result(i) for i in results]
|
||||
else:
|
||||
return results
|
||||
|
||||
|
||||
def _format_marker(marker, first=True):
|
||||
# type: (Union[List[str], Tuple[Node, ...], str], Optional[bool]) -> str
|
||||
def _format_marker(
|
||||
marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True
|
||||
) -> str:
|
||||
|
||||
assert isinstance(marker, (list, tuple, string_types))
|
||||
assert isinstance(marker, (list, tuple, str))
|
||||
|
||||
# Sometimes we have a structure like [[...]] which is a single item list
|
||||
# where the single item is itself it's own list. In that case we want skip
|
||||
@@ -179,7 +174,7 @@ def _format_marker(marker, first=True):
|
||||
return marker
|
||||
|
||||
|
||||
_operators = {
|
||||
_operators: Dict[str, Operator] = {
|
||||
"in": lambda lhs, rhs: lhs in rhs,
|
||||
"not in": lambda lhs, rhs: lhs not in rhs,
|
||||
"<": operator.lt,
|
||||
@@ -188,11 +183,10 @@ _operators = {
|
||||
"!=": operator.ne,
|
||||
">=": operator.ge,
|
||||
">": operator.gt,
|
||||
} # type: Dict[str, Operator]
|
||||
}
|
||||
|
||||
|
||||
def _eval_op(lhs, op, rhs):
|
||||
# type: (str, Op, str) -> bool
|
||||
def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
|
||||
try:
|
||||
spec = Specifier("".join([op.serialize(), rhs]))
|
||||
except InvalidSpecifier:
|
||||
@@ -200,40 +194,36 @@ def _eval_op(lhs, op, rhs):
|
||||
else:
|
||||
return spec.contains(lhs)
|
||||
|
||||
oper = _operators.get(op.serialize()) # type: Optional[Operator]
|
||||
oper: Optional[Operator] = _operators.get(op.serialize())
|
||||
if oper is None:
|
||||
raise UndefinedComparison(
|
||||
"Undefined {0!r} on {1!r} and {2!r}.".format(op, lhs, rhs)
|
||||
)
|
||||
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
|
||||
|
||||
return oper(lhs, rhs)
|
||||
|
||||
|
||||
class Undefined(object):
|
||||
class Undefined:
|
||||
pass
|
||||
|
||||
|
||||
_undefined = Undefined()
|
||||
|
||||
|
||||
def _get_env(environment, name):
|
||||
# type: (Dict[str, str], str) -> str
|
||||
value = environment.get(name, _undefined) # type: Union[str, Undefined]
|
||||
def _get_env(environment: Dict[str, str], name: str) -> str:
|
||||
value: Union[str, Undefined] = environment.get(name, _undefined)
|
||||
|
||||
if isinstance(value, Undefined):
|
||||
raise UndefinedEnvironmentName(
|
||||
"{0!r} does not exist in evaluation environment.".format(name)
|
||||
f"{name!r} does not exist in evaluation environment."
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _evaluate_markers(markers, environment):
|
||||
# type: (List[Any], Dict[str, str]) -> bool
|
||||
groups = [[]] # type: List[List[bool]]
|
||||
def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool:
|
||||
groups: List[List[bool]] = [[]]
|
||||
|
||||
for marker in markers:
|
||||
assert isinstance(marker, (list, tuple, string_types))
|
||||
assert isinstance(marker, (list, tuple, str))
|
||||
|
||||
if isinstance(marker, list):
|
||||
groups[-1].append(_evaluate_markers(marker, environment))
|
||||
@@ -256,8 +246,7 @@ def _evaluate_markers(markers, environment):
|
||||
return any(all(item) for item in groups)
|
||||
|
||||
|
||||
def format_full_version(info):
|
||||
# type: (sys._version_info) -> str
|
||||
def format_full_version(info: "sys._version_info") -> str:
|
||||
version = "{0.major}.{0.minor}.{0.micro}".format(info)
|
||||
kind = info.releaselevel
|
||||
if kind != "final":
|
||||
@@ -265,18 +254,9 @@ def format_full_version(info):
|
||||
return version
|
||||
|
||||
|
||||
def default_environment():
|
||||
# type: () -> Dict[str, str]
|
||||
if hasattr(sys, "implementation"):
|
||||
# Ignoring the `sys.implementation` reference for type checking due to
|
||||
# mypy not liking that the attribute doesn't exist in Python 2.7 when
|
||||
# run with the `--py27` flag.
|
||||
iver = format_full_version(sys.implementation.version) # type: ignore
|
||||
implementation_name = sys.implementation.name # type: ignore
|
||||
else:
|
||||
iver = "0"
|
||||
implementation_name = ""
|
||||
|
||||
def default_environment() -> Dict[str, str]:
|
||||
iver = format_full_version(sys.implementation.version)
|
||||
implementation_name = sys.implementation.name
|
||||
return {
|
||||
"implementation_name": implementation_name,
|
||||
"implementation_version": iver,
|
||||
@@ -292,27 +272,23 @@ def default_environment():
|
||||
}
|
||||
|
||||
|
||||
class Marker(object):
|
||||
def __init__(self, marker):
|
||||
# type: (str) -> None
|
||||
class Marker:
|
||||
def __init__(self, marker: str) -> None:
|
||||
try:
|
||||
self._markers = _coerce_parse_result(MARKER.parseString(marker))
|
||||
except ParseException as e:
|
||||
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
|
||||
marker, marker[e.loc : e.loc + 8]
|
||||
raise InvalidMarker(
|
||||
f"Invalid marker: {marker!r}, parse error at "
|
||||
f"{marker[e.loc : e.loc + 8]!r}"
|
||||
)
|
||||
raise InvalidMarker(err_str)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
def __str__(self) -> str:
|
||||
return _format_marker(self._markers)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "<Marker({0!r})>".format(str(self))
|
||||
def __repr__(self) -> str:
|
||||
return f"<Marker('{self}')>"
|
||||
|
||||
def evaluate(self, environment=None):
|
||||
# type: (Optional[Dict[str, str]]) -> bool
|
||||
def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool:
|
||||
"""Evaluate a marker.
|
||||
|
||||
Return the boolean from evaluating the given marker against the
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import string
|
||||
import re
|
||||
import string
|
||||
import urllib.parse
|
||||
from typing import List, Optional as TOptional, Set
|
||||
|
||||
from setuptools.extern.pyparsing import stringStart, stringEnd, originalTextFor, ParseException
|
||||
from setuptools.extern.pyparsing import ZeroOrMore, Word, Optional, Regex, Combine
|
||||
from setuptools.extern.pyparsing import Literal as L # noqa
|
||||
from urllib import parse as urlparse
|
||||
from setuptools.extern.pyparsing import ( # noqa
|
||||
Combine,
|
||||
Literal as L,
|
||||
Optional,
|
||||
ParseException,
|
||||
Regex,
|
||||
Word,
|
||||
ZeroOrMore,
|
||||
originalTextFor,
|
||||
stringEnd,
|
||||
stringStart,
|
||||
)
|
||||
|
||||
from ._typing import TYPE_CHECKING
|
||||
from .markers import MARKER_EXPR, Marker
|
||||
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from typing import List
|
||||
|
||||
|
||||
class InvalidRequirement(ValueError):
|
||||
"""
|
||||
@@ -55,7 +60,7 @@ VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
|
||||
VERSION_MANY = Combine(
|
||||
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
|
||||
)("_raw_spec")
|
||||
_VERSION_SPEC = Optional(((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY))
|
||||
_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)
|
||||
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
|
||||
|
||||
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
|
||||
@@ -79,7 +84,7 @@ REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
|
||||
REQUIREMENT.parseString("x[]")
|
||||
|
||||
|
||||
class Requirement(object):
|
||||
class Requirement:
|
||||
"""Parse a requirement.
|
||||
|
||||
Parse a given requirement string into its parts, such as name, specifier,
|
||||
@@ -92,54 +97,50 @@ class Requirement(object):
|
||||
# the thing as well as the version? What about the markers?
|
||||
# TODO: Can we normalize the name and extra name?
|
||||
|
||||
def __init__(self, requirement_string):
|
||||
# type: (str) -> None
|
||||
def __init__(self, requirement_string: str) -> None:
|
||||
try:
|
||||
req = REQUIREMENT.parseString(requirement_string)
|
||||
except ParseException as e:
|
||||
raise InvalidRequirement(
|
||||
'Parse error at "{0!r}": {1}'.format(
|
||||
requirement_string[e.loc : e.loc + 8], e.msg
|
||||
)
|
||||
f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}'
|
||||
)
|
||||
|
||||
self.name = req.name
|
||||
self.name: str = req.name
|
||||
if req.url:
|
||||
parsed_url = urlparse.urlparse(req.url)
|
||||
parsed_url = urllib.parse.urlparse(req.url)
|
||||
if parsed_url.scheme == "file":
|
||||
if urlparse.urlunparse(parsed_url) != req.url:
|
||||
if urllib.parse.urlunparse(parsed_url) != req.url:
|
||||
raise InvalidRequirement("Invalid URL given")
|
||||
elif not (parsed_url.scheme and parsed_url.netloc) or (
|
||||
not parsed_url.scheme and not parsed_url.netloc
|
||||
):
|
||||
raise InvalidRequirement("Invalid URL: {0}".format(req.url))
|
||||
self.url = req.url
|
||||
raise InvalidRequirement(f"Invalid URL: {req.url}")
|
||||
self.url: TOptional[str] = req.url
|
||||
else:
|
||||
self.url = None
|
||||
self.extras = set(req.extras.asList() if req.extras else [])
|
||||
self.specifier = SpecifierSet(req.specifier)
|
||||
self.marker = req.marker if req.marker else None
|
||||
self.extras: Set[str] = set(req.extras.asList() if req.extras else [])
|
||||
self.specifier: SpecifierSet = SpecifierSet(req.specifier)
|
||||
self.marker: TOptional[Marker] = req.marker if req.marker else None
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
parts = [self.name] # type: List[str]
|
||||
def __str__(self) -> str:
|
||||
parts: List[str] = [self.name]
|
||||
|
||||
if self.extras:
|
||||
parts.append("[{0}]".format(",".join(sorted(self.extras))))
|
||||
formatted_extras = ",".join(sorted(self.extras))
|
||||
parts.append(f"[{formatted_extras}]")
|
||||
|
||||
if self.specifier:
|
||||
parts.append(str(self.specifier))
|
||||
|
||||
if self.url:
|
||||
parts.append("@ {0}".format(self.url))
|
||||
parts.append(f"@ {self.url}")
|
||||
if self.marker:
|
||||
parts.append(" ")
|
||||
|
||||
if self.marker:
|
||||
parts.append("; {0}".format(self.marker))
|
||||
parts.append(f"; {self.marker}")
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "<Requirement({0!r})>".format(str(self))
|
||||
def __repr__(self) -> str:
|
||||
return f"<Requirement('{self}')>"
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import abc
|
||||
import functools
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
from typing import (
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Pattern,
|
||||
Set,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from ._compat import string_types, with_metaclass
|
||||
from ._typing import TYPE_CHECKING
|
||||
from .utils import canonicalize_version
|
||||
from .version import Version, LegacyVersion, parse
|
||||
from .version import LegacyVersion, Version, parse
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from typing import (
|
||||
List,
|
||||
Dict,
|
||||
Union,
|
||||
Iterable,
|
||||
Iterator,
|
||||
Optional,
|
||||
Callable,
|
||||
Tuple,
|
||||
FrozenSet,
|
||||
)
|
||||
|
||||
ParsedVersion = Union[Version, LegacyVersion]
|
||||
UnparsedVersion = Union[Version, LegacyVersion, str]
|
||||
CallableOperator = Callable[[ParsedVersion, str], bool]
|
||||
ParsedVersion = Union[Version, LegacyVersion]
|
||||
UnparsedVersion = Union[Version, LegacyVersion, str]
|
||||
VersionTypeVar = TypeVar("VersionTypeVar", bound=UnparsedVersion)
|
||||
CallableOperator = Callable[[ParsedVersion, str], bool]
|
||||
|
||||
|
||||
class InvalidSpecifier(ValueError):
|
||||
@@ -37,64 +36,51 @@ class InvalidSpecifier(ValueError):
|
||||
"""
|
||||
|
||||
|
||||
class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
|
||||
class BaseSpecifier(metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Returns the str representation of this Specifier like object. This
|
||||
should be representative of the Specifier itself.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
def __hash__(self) -> int:
|
||||
"""
|
||||
Returns a hash value for this Specifier like object.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""
|
||||
Returns a boolean representing whether or not the two Specifier like
|
||||
objects are equal.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
"""
|
||||
Returns a boolean representing whether or not the two Specifier like
|
||||
objects are not equal.
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
def prereleases(self):
|
||||
# type: () -> Optional[bool]
|
||||
def prereleases(self) -> Optional[bool]:
|
||||
"""
|
||||
Returns whether or not pre-releases as a whole are allowed by this
|
||||
specifier.
|
||||
"""
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
# type: (bool) -> None
|
||||
def prereleases(self, value: bool) -> None:
|
||||
"""
|
||||
Sets whether or not pre-releases as a whole are allowed by this
|
||||
specifier.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def contains(self, item, prereleases=None):
|
||||
# type: (str, Optional[bool]) -> bool
|
||||
def contains(self, item: str, prereleases: Optional[bool] = None) -> bool:
|
||||
"""
|
||||
Determines if the given item is contained within this specifier.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def filter(self, iterable, prereleases=None):
|
||||
# type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion]
|
||||
def filter(
|
||||
self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
|
||||
) -> Iterable[VersionTypeVar]:
|
||||
"""
|
||||
Takes an iterable of items and filters them so that only items which
|
||||
are contained within this specifier are allowed in it.
|
||||
@@ -103,48 +89,43 @@ class BaseSpecifier(with_metaclass(abc.ABCMeta, object)): # type: ignore
|
||||
|
||||
class _IndividualSpecifier(BaseSpecifier):
|
||||
|
||||
_operators = {} # type: Dict[str, str]
|
||||
_operators: Dict[str, str] = {}
|
||||
_regex: Pattern[str]
|
||||
|
||||
def __init__(self, spec="", prereleases=None):
|
||||
# type: (str, Optional[bool]) -> None
|
||||
def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
|
||||
match = self._regex.search(spec)
|
||||
if not match:
|
||||
raise InvalidSpecifier("Invalid specifier: '{0}'".format(spec))
|
||||
raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
|
||||
|
||||
self._spec = (
|
||||
self._spec: Tuple[str, str] = (
|
||||
match.group("operator").strip(),
|
||||
match.group("version").strip(),
|
||||
) # type: Tuple[str, str]
|
||||
)
|
||||
|
||||
# Store whether or not this Specifier should accept prereleases
|
||||
self._prereleases = prereleases
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
def __repr__(self) -> str:
|
||||
pre = (
|
||||
", prereleases={0!r}".format(self.prereleases)
|
||||
f", prereleases={self.prereleases!r}"
|
||||
if self._prereleases is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
return "<{0}({1!r}{2})>".format(self.__class__.__name__, str(self), pre)
|
||||
return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "{0}{1}".format(*self._spec)
|
||||
def __str__(self) -> str:
|
||||
return "{}{}".format(*self._spec)
|
||||
|
||||
@property
|
||||
def _canonical_spec(self):
|
||||
# type: () -> Tuple[str, Union[Version, str]]
|
||||
def _canonical_spec(self) -> Tuple[str, str]:
|
||||
return self._spec[0], canonicalize_version(self._spec[1])
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._canonical_spec)
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
if isinstance(other, string_types):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, str):
|
||||
try:
|
||||
other = self.__class__(str(other))
|
||||
except InvalidSpecifier:
|
||||
@@ -154,57 +135,39 @@ class _IndividualSpecifier(BaseSpecifier):
|
||||
|
||||
return self._canonical_spec == other._canonical_spec
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
if isinstance(other, string_types):
|
||||
try:
|
||||
other = self.__class__(str(other))
|
||||
except InvalidSpecifier:
|
||||
return NotImplemented
|
||||
elif not isinstance(other, self.__class__):
|
||||
return NotImplemented
|
||||
|
||||
return self._spec != other._spec
|
||||
|
||||
def _get_operator(self, op):
|
||||
# type: (str) -> CallableOperator
|
||||
operator_callable = getattr(
|
||||
self, "_compare_{0}".format(self._operators[op])
|
||||
) # type: CallableOperator
|
||||
def _get_operator(self, op: str) -> CallableOperator:
|
||||
operator_callable: CallableOperator = getattr(
|
||||
self, f"_compare_{self._operators[op]}"
|
||||
)
|
||||
return operator_callable
|
||||
|
||||
def _coerce_version(self, version):
|
||||
# type: (UnparsedVersion) -> ParsedVersion
|
||||
def _coerce_version(self, version: UnparsedVersion) -> ParsedVersion:
|
||||
if not isinstance(version, (LegacyVersion, Version)):
|
||||
version = parse(version)
|
||||
return version
|
||||
|
||||
@property
|
||||
def operator(self):
|
||||
# type: () -> str
|
||||
def operator(self) -> str:
|
||||
return self._spec[0]
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
# type: () -> str
|
||||
def version(self) -> str:
|
||||
return self._spec[1]
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
# type: () -> Optional[bool]
|
||||
def prereleases(self) -> Optional[bool]:
|
||||
return self._prereleases
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
# type: (bool) -> None
|
||||
def prereleases(self, value: bool) -> None:
|
||||
self._prereleases = value
|
||||
|
||||
def __contains__(self, item):
|
||||
# type: (str) -> bool
|
||||
def __contains__(self, item: str) -> bool:
|
||||
return self.contains(item)
|
||||
|
||||
def contains(self, item, prereleases=None):
|
||||
# type: (UnparsedVersion, Optional[bool]) -> bool
|
||||
def contains(
|
||||
self, item: UnparsedVersion, prereleases: Optional[bool] = None
|
||||
) -> bool:
|
||||
|
||||
# Determine if prereleases are to be allowed or not.
|
||||
if prereleases is None:
|
||||
@@ -222,11 +185,12 @@ class _IndividualSpecifier(BaseSpecifier):
|
||||
|
||||
# Actually do the comparison to determine if this item is contained
|
||||
# within this Specifier or not.
|
||||
operator_callable = self._get_operator(self.operator) # type: CallableOperator
|
||||
operator_callable: CallableOperator = self._get_operator(self.operator)
|
||||
return operator_callable(normalized_item, self.version)
|
||||
|
||||
def filter(self, iterable, prereleases=None):
|
||||
# type: (Iterable[UnparsedVersion], Optional[bool]) -> Iterable[UnparsedVersion]
|
||||
def filter(
|
||||
self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
|
||||
) -> Iterable[VersionTypeVar]:
|
||||
|
||||
yielded = False
|
||||
found_prereleases = []
|
||||
@@ -240,7 +204,7 @@ class _IndividualSpecifier(BaseSpecifier):
|
||||
|
||||
if self.contains(parsed_version, **kw):
|
||||
# If our version is a prerelease, and we were not set to allow
|
||||
# prereleases, then we'll store it for later incase nothing
|
||||
# prereleases, then we'll store it for later in case nothing
|
||||
# else matches this specifier.
|
||||
if parsed_version.is_prerelease and not (
|
||||
prereleases or self.prereleases
|
||||
@@ -285,44 +249,46 @@ class LegacySpecifier(_IndividualSpecifier):
|
||||
">": "greater_than",
|
||||
}
|
||||
|
||||
def _coerce_version(self, version):
|
||||
# type: (Union[ParsedVersion, str]) -> LegacyVersion
|
||||
def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None:
|
||||
super().__init__(spec, prereleases)
|
||||
|
||||
warnings.warn(
|
||||
"Creating a LegacyVersion has been deprecated and will be "
|
||||
"removed in the next major release",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
def _coerce_version(self, version: UnparsedVersion) -> LegacyVersion:
|
||||
if not isinstance(version, LegacyVersion):
|
||||
version = LegacyVersion(str(version))
|
||||
return version
|
||||
|
||||
def _compare_equal(self, prospective, spec):
|
||||
# type: (LegacyVersion, str) -> bool
|
||||
def _compare_equal(self, prospective: LegacyVersion, spec: str) -> bool:
|
||||
return prospective == self._coerce_version(spec)
|
||||
|
||||
def _compare_not_equal(self, prospective, spec):
|
||||
# type: (LegacyVersion, str) -> bool
|
||||
def _compare_not_equal(self, prospective: LegacyVersion, spec: str) -> bool:
|
||||
return prospective != self._coerce_version(spec)
|
||||
|
||||
def _compare_less_than_equal(self, prospective, spec):
|
||||
# type: (LegacyVersion, str) -> bool
|
||||
def _compare_less_than_equal(self, prospective: LegacyVersion, spec: str) -> bool:
|
||||
return prospective <= self._coerce_version(spec)
|
||||
|
||||
def _compare_greater_than_equal(self, prospective, spec):
|
||||
# type: (LegacyVersion, str) -> bool
|
||||
def _compare_greater_than_equal(
|
||||
self, prospective: LegacyVersion, spec: str
|
||||
) -> bool:
|
||||
return prospective >= self._coerce_version(spec)
|
||||
|
||||
def _compare_less_than(self, prospective, spec):
|
||||
# type: (LegacyVersion, str) -> bool
|
||||
def _compare_less_than(self, prospective: LegacyVersion, spec: str) -> bool:
|
||||
return prospective < self._coerce_version(spec)
|
||||
|
||||
def _compare_greater_than(self, prospective, spec):
|
||||
# type: (LegacyVersion, str) -> bool
|
||||
def _compare_greater_than(self, prospective: LegacyVersion, spec: str) -> bool:
|
||||
return prospective > self._coerce_version(spec)
|
||||
|
||||
|
||||
def _require_version_compare(
|
||||
fn # type: (Callable[[Specifier, ParsedVersion, str], bool])
|
||||
):
|
||||
# type: (...) -> Callable[[Specifier, ParsedVersion, str], bool]
|
||||
fn: Callable[["Specifier", ParsedVersion, str], bool]
|
||||
) -> Callable[["Specifier", ParsedVersion, str], bool]:
|
||||
@functools.wraps(fn)
|
||||
def wrapped(self, prospective, spec):
|
||||
# type: (Specifier, ParsedVersion, str) -> bool
|
||||
def wrapped(self: "Specifier", prospective: ParsedVersion, spec: str) -> bool:
|
||||
if not isinstance(prospective, Version):
|
||||
return False
|
||||
return fn(self, prospective, spec)
|
||||
@@ -439,8 +405,7 @@ class Specifier(_IndividualSpecifier):
|
||||
}
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_compatible(self, prospective, spec):
|
||||
# type: (ParsedVersion, str) -> bool
|
||||
def _compare_compatible(self, prospective: ParsedVersion, spec: str) -> bool:
|
||||
|
||||
# Compatible releases have an equivalent combination of >= and ==. That
|
||||
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
|
||||
@@ -449,15 +414,9 @@ class Specifier(_IndividualSpecifier):
|
||||
# the other specifiers.
|
||||
|
||||
# We want everything but the last item in the version, but we want to
|
||||
# ignore post and dev releases and we want to treat the pre-release as
|
||||
# it's own separate segment.
|
||||
# ignore suffix segments.
|
||||
prefix = ".".join(
|
||||
list(
|
||||
itertools.takewhile(
|
||||
lambda x: (not x.startswith("post") and not x.startswith("dev")),
|
||||
_version_split(spec),
|
||||
)
|
||||
)[:-1]
|
||||
list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
|
||||
)
|
||||
|
||||
# Add the prefix notation to the end of our string
|
||||
@@ -468,8 +427,7 @@ class Specifier(_IndividualSpecifier):
|
||||
)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_equal(self, prospective, spec):
|
||||
# type: (ParsedVersion, str) -> bool
|
||||
def _compare_equal(self, prospective: ParsedVersion, spec: str) -> bool:
|
||||
|
||||
# We need special logic to handle prefix matching
|
||||
if spec.endswith(".*"):
|
||||
@@ -509,13 +467,11 @@ class Specifier(_IndividualSpecifier):
|
||||
return prospective == spec_version
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_not_equal(self, prospective, spec):
|
||||
# type: (ParsedVersion, str) -> bool
|
||||
def _compare_not_equal(self, prospective: ParsedVersion, spec: str) -> bool:
|
||||
return not self._compare_equal(prospective, spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_less_than_equal(self, prospective, spec):
|
||||
# type: (ParsedVersion, str) -> bool
|
||||
def _compare_less_than_equal(self, prospective: ParsedVersion, spec: str) -> bool:
|
||||
|
||||
# NB: Local version identifiers are NOT permitted in the version
|
||||
# specifier, so local version labels can be universally removed from
|
||||
@@ -523,8 +479,9 @@ class Specifier(_IndividualSpecifier):
|
||||
return Version(prospective.public) <= Version(spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_greater_than_equal(self, prospective, spec):
|
||||
# type: (ParsedVersion, str) -> bool
|
||||
def _compare_greater_than_equal(
|
||||
self, prospective: ParsedVersion, spec: str
|
||||
) -> bool:
|
||||
|
||||
# NB: Local version identifiers are NOT permitted in the version
|
||||
# specifier, so local version labels can be universally removed from
|
||||
@@ -532,8 +489,7 @@ class Specifier(_IndividualSpecifier):
|
||||
return Version(prospective.public) >= Version(spec)
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_less_than(self, prospective, spec_str):
|
||||
# type: (ParsedVersion, str) -> bool
|
||||
def _compare_less_than(self, prospective: ParsedVersion, spec_str: str) -> bool:
|
||||
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
@@ -559,8 +515,7 @@ class Specifier(_IndividualSpecifier):
|
||||
return True
|
||||
|
||||
@_require_version_compare
|
||||
def _compare_greater_than(self, prospective, spec_str):
|
||||
# type: (ParsedVersion, str) -> bool
|
||||
def _compare_greater_than(self, prospective: ParsedVersion, spec_str: str) -> bool:
|
||||
|
||||
# Convert our spec to a Version instance, since we'll want to work with
|
||||
# it as a version.
|
||||
@@ -591,13 +546,11 @@ class Specifier(_IndividualSpecifier):
|
||||
# same version in the spec.
|
||||
return True
|
||||
|
||||
def _compare_arbitrary(self, prospective, spec):
|
||||
# type: (Version, str) -> bool
|
||||
def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
|
||||
return str(prospective).lower() == str(spec).lower()
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
# type: () -> bool
|
||||
def prereleases(self) -> bool:
|
||||
|
||||
# If there is an explicit prereleases set for this, then we'll just
|
||||
# blindly use that.
|
||||
@@ -622,17 +575,15 @@ class Specifier(_IndividualSpecifier):
|
||||
return False
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
# type: (bool) -> None
|
||||
def prereleases(self, value: bool) -> None:
|
||||
self._prereleases = value
|
||||
|
||||
|
||||
_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$")
|
||||
|
||||
|
||||
def _version_split(version):
|
||||
# type: (str) -> List[str]
|
||||
result = [] # type: List[str]
|
||||
def _version_split(version: str) -> List[str]:
|
||||
result: List[str] = []
|
||||
for item in version.split("."):
|
||||
match = _prefix_regex.search(item)
|
||||
if match:
|
||||
@@ -642,8 +593,13 @@ def _version_split(version):
|
||||
return result
|
||||
|
||||
|
||||
def _pad_version(left, right):
|
||||
# type: (List[str], List[str]) -> Tuple[List[str], List[str]]
|
||||
def _is_not_suffix(segment: str) -> bool:
|
||||
return not any(
|
||||
segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
|
||||
)
|
||||
|
||||
|
||||
def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]:
|
||||
left_split, right_split = [], []
|
||||
|
||||
# Get the release segment of our versions
|
||||
@@ -662,8 +618,9 @@ def _pad_version(left, right):
|
||||
|
||||
|
||||
class SpecifierSet(BaseSpecifier):
|
||||
def __init__(self, specifiers="", prereleases=None):
|
||||
# type: (str, Optional[bool]) -> None
|
||||
def __init__(
|
||||
self, specifiers: str = "", prereleases: Optional[bool] = None
|
||||
) -> None:
|
||||
|
||||
# Split on , to break each individual specifier into it's own item, and
|
||||
# strip each item to remove leading/trailing whitespace.
|
||||
@@ -671,7 +628,7 @@ class SpecifierSet(BaseSpecifier):
|
||||
|
||||
# Parsed each individual specifier, attempting first to make it a
|
||||
# Specifier and falling back to a LegacySpecifier.
|
||||
parsed = set()
|
||||
parsed: Set[_IndividualSpecifier] = set()
|
||||
for specifier in split_specifiers:
|
||||
try:
|
||||
parsed.add(Specifier(specifier))
|
||||
@@ -685,27 +642,23 @@ class SpecifierSet(BaseSpecifier):
|
||||
# we accept prereleases or not.
|
||||
self._prereleases = prereleases
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
def __repr__(self) -> str:
|
||||
pre = (
|
||||
", prereleases={0!r}".format(self.prereleases)
|
||||
f", prereleases={self.prereleases!r}"
|
||||
if self._prereleases is not None
|
||||
else ""
|
||||
)
|
||||
|
||||
return "<SpecifierSet({0!r}{1})>".format(str(self), pre)
|
||||
return f"<SpecifierSet({str(self)!r}{pre})>"
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
def __str__(self) -> str:
|
||||
return ",".join(sorted(str(s) for s in self._specs))
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._specs)
|
||||
|
||||
def __and__(self, other):
|
||||
# type: (Union[SpecifierSet, str]) -> SpecifierSet
|
||||
if isinstance(other, string_types):
|
||||
def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet":
|
||||
if isinstance(other, str):
|
||||
other = SpecifierSet(other)
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
@@ -727,35 +680,22 @@ class SpecifierSet(BaseSpecifier):
|
||||
|
||||
return specifier
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
if isinstance(other, (string_types, _IndividualSpecifier)):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, (str, _IndividualSpecifier)):
|
||||
other = SpecifierSet(str(other))
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
return self._specs == other._specs
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
if isinstance(other, (string_types, _IndividualSpecifier)):
|
||||
other = SpecifierSet(str(other))
|
||||
elif not isinstance(other, SpecifierSet):
|
||||
return NotImplemented
|
||||
|
||||
return self._specs != other._specs
|
||||
|
||||
def __len__(self):
|
||||
# type: () -> int
|
||||
def __len__(self) -> int:
|
||||
return len(self._specs)
|
||||
|
||||
def __iter__(self):
|
||||
# type: () -> Iterator[FrozenSet[_IndividualSpecifier]]
|
||||
def __iter__(self) -> Iterator[_IndividualSpecifier]:
|
||||
return iter(self._specs)
|
||||
|
||||
@property
|
||||
def prereleases(self):
|
||||
# type: () -> Optional[bool]
|
||||
def prereleases(self) -> Optional[bool]:
|
||||
|
||||
# If we have been given an explicit prerelease modifier, then we'll
|
||||
# pass that through here.
|
||||
@@ -773,16 +713,15 @@ class SpecifierSet(BaseSpecifier):
|
||||
return any(s.prereleases for s in self._specs)
|
||||
|
||||
@prereleases.setter
|
||||
def prereleases(self, value):
|
||||
# type: (bool) -> None
|
||||
def prereleases(self, value: bool) -> None:
|
||||
self._prereleases = value
|
||||
|
||||
def __contains__(self, item):
|
||||
# type: (Union[ParsedVersion, str]) -> bool
|
||||
def __contains__(self, item: UnparsedVersion) -> bool:
|
||||
return self.contains(item)
|
||||
|
||||
def contains(self, item, prereleases=None):
|
||||
# type: (Union[ParsedVersion, str], Optional[bool]) -> bool
|
||||
def contains(
|
||||
self, item: UnparsedVersion, prereleases: Optional[bool] = None
|
||||
) -> bool:
|
||||
|
||||
# Ensure that our item is a Version or LegacyVersion instance.
|
||||
if not isinstance(item, (LegacyVersion, Version)):
|
||||
@@ -810,11 +749,8 @@ class SpecifierSet(BaseSpecifier):
|
||||
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
|
||||
|
||||
def filter(
|
||||
self,
|
||||
iterable, # type: Iterable[Union[ParsedVersion, str]]
|
||||
prereleases=None, # type: Optional[bool]
|
||||
):
|
||||
# type: (...) -> Iterable[Union[ParsedVersion, str]]
|
||||
self, iterable: Iterable[VersionTypeVar], prereleases: Optional[bool] = None
|
||||
) -> Iterable[VersionTypeVar]:
|
||||
|
||||
# Determine if we're forcing a prerelease or not, if we're not forcing
|
||||
# one for this particular filter call, then we'll use whatever the
|
||||
@@ -833,8 +769,11 @@ class SpecifierSet(BaseSpecifier):
|
||||
# which will filter out any pre-releases, unless there are no final
|
||||
# releases, and which will filter out LegacyVersion in general.
|
||||
else:
|
||||
filtered = [] # type: List[Union[ParsedVersion, str]]
|
||||
found_prereleases = [] # type: List[Union[ParsedVersion, str]]
|
||||
filtered: List[VersionTypeVar] = []
|
||||
found_prereleases: List[VersionTypeVar] = []
|
||||
|
||||
item: UnparsedVersion
|
||||
parsed_version: Union[Version, LegacyVersion]
|
||||
|
||||
for item in iterable:
|
||||
# Ensure that we some kind of Version class for this item.
|
||||
|
||||
@@ -2,62 +2,44 @@
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import distutils.util
|
||||
|
||||
try:
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
except ImportError: # pragma: no cover
|
||||
import imp
|
||||
|
||||
EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
|
||||
del imp
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import sysconfig
|
||||
import warnings
|
||||
|
||||
from ._typing import TYPE_CHECKING, cast
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from typing import (
|
||||
Dict,
|
||||
FrozenSet,
|
||||
IO,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
PythonVersion = Sequence[int]
|
||||
MacVersion = Tuple[int, int]
|
||||
GlibcVersion = Tuple[int, int]
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from typing import (
|
||||
Dict,
|
||||
FrozenSet,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from . import _manylinux, _musllinux
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
INTERPRETER_SHORT_NAMES = {
|
||||
PythonVersion = Sequence[int]
|
||||
MacVersion = Tuple[int, int]
|
||||
|
||||
INTERPRETER_SHORT_NAMES: Dict[str, str] = {
|
||||
"python": "py", # Generic.
|
||||
"cpython": "cp",
|
||||
"pypy": "pp",
|
||||
"ironpython": "ip",
|
||||
"jython": "jy",
|
||||
} # type: Dict[str, str]
|
||||
}
|
||||
|
||||
|
||||
_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
|
||||
|
||||
|
||||
class Tag(object):
|
||||
class Tag:
|
||||
"""
|
||||
A representation of the tag triple for a wheel.
|
||||
|
||||
@@ -65,55 +47,53 @@ class Tag(object):
|
||||
is also supported.
|
||||
"""
|
||||
|
||||
__slots__ = ["_interpreter", "_abi", "_platform"]
|
||||
__slots__ = ["_interpreter", "_abi", "_platform", "_hash"]
|
||||
|
||||
def __init__(self, interpreter, abi, platform):
|
||||
# type: (str, str, str) -> None
|
||||
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
|
||||
self._interpreter = interpreter.lower()
|
||||
self._abi = abi.lower()
|
||||
self._platform = platform.lower()
|
||||
# The __hash__ of every single element in a Set[Tag] will be evaluated each time
|
||||
# that a set calls its `.disjoint()` method, which may be called hundreds of
|
||||
# times when scanning a page of links for packages with tags matching that
|
||||
# Set[Tag]. Pre-computing the value here produces significant speedups for
|
||||
# downstream consumers.
|
||||
self._hash = hash((self._interpreter, self._abi, self._platform))
|
||||
|
||||
@property
|
||||
def interpreter(self):
|
||||
# type: () -> str
|
||||
def interpreter(self) -> str:
|
||||
return self._interpreter
|
||||
|
||||
@property
|
||||
def abi(self):
|
||||
# type: () -> str
|
||||
def abi(self) -> str:
|
||||
return self._abi
|
||||
|
||||
@property
|
||||
def platform(self):
|
||||
# type: () -> str
|
||||
def platform(self) -> str:
|
||||
return self._platform
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Tag):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
(self.platform == other.platform)
|
||||
and (self.abi == other.abi)
|
||||
and (self.interpreter == other.interpreter)
|
||||
(self._hash == other._hash) # Short-circuit ASAP for perf reasons.
|
||||
and (self._platform == other._platform)
|
||||
and (self._abi == other._abi)
|
||||
and (self._interpreter == other._interpreter)
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
return hash((self._interpreter, self._abi, self._platform))
|
||||
def __hash__(self) -> int:
|
||||
return self._hash
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "{}-{}-{}".format(self._interpreter, self._abi, self._platform)
|
||||
def __str__(self) -> str:
|
||||
return f"{self._interpreter}-{self._abi}-{self._platform}"
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "<{self} @ {self_id}>".format(self=self, self_id=id(self))
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self} @ {id(self)}>"
|
||||
|
||||
|
||||
def parse_tag(tag):
|
||||
# type: (str) -> FrozenSet[Tag]
|
||||
def parse_tag(tag: str) -> FrozenSet[Tag]:
|
||||
"""
|
||||
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
|
||||
|
||||
@@ -129,24 +109,7 @@ def parse_tag(tag):
|
||||
return frozenset(tags)
|
||||
|
||||
|
||||
def _warn_keyword_parameter(func_name, kwargs):
|
||||
# type: (str, Dict[str, bool]) -> bool
|
||||
"""
|
||||
Backwards-compatibility with Python 2.7 to allow treating 'warn' as keyword-only.
|
||||
"""
|
||||
if not kwargs:
|
||||
return False
|
||||
elif len(kwargs) > 1 or "warn" not in kwargs:
|
||||
kwargs.pop("warn", None)
|
||||
arg = next(iter(kwargs.keys()))
|
||||
raise TypeError(
|
||||
"{}() got an unexpected keyword argument {!r}".format(func_name, arg)
|
||||
)
|
||||
return kwargs["warn"]
|
||||
|
||||
|
||||
def _get_config_var(name, warn=False):
|
||||
# type: (str, bool) -> Union[int, str, None]
|
||||
def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
|
||||
value = sysconfig.get_config_var(name)
|
||||
if value is None and warn:
|
||||
logger.debug(
|
||||
@@ -155,13 +118,11 @@ def _get_config_var(name, warn=False):
|
||||
return value
|
||||
|
||||
|
||||
def _normalize_string(string):
|
||||
# type: (str) -> str
|
||||
def _normalize_string(string: str) -> str:
|
||||
return string.replace(".", "_").replace("-", "_")
|
||||
|
||||
|
||||
def _abi3_applies(python_version):
|
||||
# type: (PythonVersion) -> bool
|
||||
def _abi3_applies(python_version: PythonVersion) -> bool:
|
||||
"""
|
||||
Determine if the Python version supports abi3.
|
||||
|
||||
@@ -170,8 +131,7 @@ def _abi3_applies(python_version):
|
||||
return len(python_version) > 1 and tuple(python_version) >= (3, 2)
|
||||
|
||||
|
||||
def _cpython_abis(py_version, warn=False):
|
||||
# type: (PythonVersion, bool) -> List[str]
|
||||
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]:
|
||||
py_version = tuple(py_version) # To allow for version comparison.
|
||||
abis = []
|
||||
version = _version_nodot(py_version[:2])
|
||||
@@ -197,7 +157,7 @@ def _cpython_abis(py_version, warn=False):
|
||||
elif debug:
|
||||
# Debug builds can also load "normal" extension modules.
|
||||
# We can also assume no UCS-4 or pymalloc requirement.
|
||||
abis.append("cp{version}".format(version=version))
|
||||
abis.append(f"cp{version}")
|
||||
abis.insert(
|
||||
0,
|
||||
"cp{version}{debug}{pymalloc}{ucs4}".format(
|
||||
@@ -208,12 +168,12 @@ def _cpython_abis(py_version, warn=False):
|
||||
|
||||
|
||||
def cpython_tags(
|
||||
python_version=None, # type: Optional[PythonVersion]
|
||||
abis=None, # type: Optional[Iterable[str]]
|
||||
platforms=None, # type: Optional[Iterable[str]]
|
||||
**kwargs # type: bool
|
||||
):
|
||||
# type: (...) -> Iterator[Tag]
|
||||
python_version: Optional[PythonVersion] = None,
|
||||
abis: Optional[Iterable[str]] = None,
|
||||
platforms: Optional[Iterable[str]] = None,
|
||||
*,
|
||||
warn: bool = False,
|
||||
) -> Iterator[Tag]:
|
||||
"""
|
||||
Yields the tags for a CPython interpreter.
|
||||
|
||||
@@ -229,11 +189,10 @@ def cpython_tags(
|
||||
If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
|
||||
their normal position and not at the beginning.
|
||||
"""
|
||||
warn = _warn_keyword_parameter("cpython_tags", kwargs)
|
||||
if not python_version:
|
||||
python_version = sys.version_info[:2]
|
||||
|
||||
interpreter = "cp{}".format(_version_nodot(python_version[:2]))
|
||||
interpreter = f"cp{_version_nodot(python_version[:2])}"
|
||||
|
||||
if abis is None:
|
||||
if len(python_version) > 1:
|
||||
@@ -248,15 +207,13 @@ def cpython_tags(
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
platforms = list(platforms or _platform_tags())
|
||||
platforms = list(platforms or platform_tags())
|
||||
for abi in abis:
|
||||
for platform_ in platforms:
|
||||
yield Tag(interpreter, abi, platform_)
|
||||
if _abi3_applies(python_version):
|
||||
for tag in (Tag(interpreter, "abi3", platform_) for platform_ in platforms):
|
||||
yield tag
|
||||
for tag in (Tag(interpreter, "none", platform_) for platform_ in platforms):
|
||||
yield tag
|
||||
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
|
||||
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
|
||||
|
||||
if _abi3_applies(python_version):
|
||||
for minor_version in range(python_version[1] - 1, 1, -1):
|
||||
@@ -267,20 +224,19 @@ def cpython_tags(
|
||||
yield Tag(interpreter, "abi3", platform_)
|
||||
|
||||
|
||||
def _generic_abi():
|
||||
# type: () -> Iterator[str]
|
||||
def _generic_abi() -> Iterator[str]:
|
||||
abi = sysconfig.get_config_var("SOABI")
|
||||
if abi:
|
||||
yield _normalize_string(abi)
|
||||
|
||||
|
||||
def generic_tags(
|
||||
interpreter=None, # type: Optional[str]
|
||||
abis=None, # type: Optional[Iterable[str]]
|
||||
platforms=None, # type: Optional[Iterable[str]]
|
||||
**kwargs # type: bool
|
||||
):
|
||||
# type: (...) -> Iterator[Tag]
|
||||
interpreter: Optional[str] = None,
|
||||
abis: Optional[Iterable[str]] = None,
|
||||
platforms: Optional[Iterable[str]] = None,
|
||||
*,
|
||||
warn: bool = False,
|
||||
) -> Iterator[Tag]:
|
||||
"""
|
||||
Yields the tags for a generic interpreter.
|
||||
|
||||
@@ -289,14 +245,13 @@ def generic_tags(
|
||||
|
||||
The "none" ABI will be added if it was not explicitly provided.
|
||||
"""
|
||||
warn = _warn_keyword_parameter("generic_tags", kwargs)
|
||||
if not interpreter:
|
||||
interp_name = interpreter_name()
|
||||
interp_version = interpreter_version(warn=warn)
|
||||
interpreter = "".join([interp_name, interp_version])
|
||||
if abis is None:
|
||||
abis = _generic_abi()
|
||||
platforms = list(platforms or _platform_tags())
|
||||
platforms = list(platforms or platform_tags())
|
||||
abis = list(abis)
|
||||
if "none" not in abis:
|
||||
abis.append("none")
|
||||
@@ -305,8 +260,7 @@ def generic_tags(
|
||||
yield Tag(interpreter, abi, platform_)
|
||||
|
||||
|
||||
def _py_interpreter_range(py_version):
|
||||
# type: (PythonVersion) -> Iterator[str]
|
||||
def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
|
||||
"""
|
||||
Yields Python versions in descending order.
|
||||
|
||||
@@ -314,19 +268,18 @@ def _py_interpreter_range(py_version):
|
||||
all previous versions of that major version.
|
||||
"""
|
||||
if len(py_version) > 1:
|
||||
yield "py{version}".format(version=_version_nodot(py_version[:2]))
|
||||
yield "py{major}".format(major=py_version[0])
|
||||
yield f"py{_version_nodot(py_version[:2])}"
|
||||
yield f"py{py_version[0]}"
|
||||
if len(py_version) > 1:
|
||||
for minor in range(py_version[1] - 1, -1, -1):
|
||||
yield "py{version}".format(version=_version_nodot((py_version[0], minor)))
|
||||
yield f"py{_version_nodot((py_version[0], minor))}"
|
||||
|
||||
|
||||
def compatible_tags(
|
||||
python_version=None, # type: Optional[PythonVersion]
|
||||
interpreter=None, # type: Optional[str]
|
||||
platforms=None, # type: Optional[Iterable[str]]
|
||||
):
|
||||
# type: (...) -> Iterator[Tag]
|
||||
python_version: Optional[PythonVersion] = None,
|
||||
interpreter: Optional[str] = None,
|
||||
platforms: Optional[Iterable[str]] = None,
|
||||
) -> Iterator[Tag]:
|
||||
"""
|
||||
Yields the sequence of tags that are compatible with a specific version of Python.
|
||||
|
||||
@@ -337,7 +290,7 @@ def compatible_tags(
|
||||
"""
|
||||
if not python_version:
|
||||
python_version = sys.version_info[:2]
|
||||
platforms = list(platforms or _platform_tags())
|
||||
platforms = list(platforms or platform_tags())
|
||||
for version in _py_interpreter_range(python_version):
|
||||
for platform_ in platforms:
|
||||
yield Tag(version, "none", platform_)
|
||||
@@ -347,8 +300,7 @@ def compatible_tags(
|
||||
yield Tag(version, "none", "any")
|
||||
|
||||
|
||||
def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER):
|
||||
# type: (str, bool) -> str
|
||||
def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
|
||||
if not is_32bit:
|
||||
return arch
|
||||
|
||||
@@ -358,8 +310,7 @@ def _mac_arch(arch, is_32bit=_32_BIT_INTERPRETER):
|
||||
return "i386"
|
||||
|
||||
|
||||
def _mac_binary_formats(version, cpu_arch):
|
||||
# type: (MacVersion, str) -> List[str]
|
||||
def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]:
|
||||
formats = [cpu_arch]
|
||||
if cpu_arch == "x86_64":
|
||||
if version < (10, 4):
|
||||
@@ -382,12 +333,18 @@ def _mac_binary_formats(version, cpu_arch):
|
||||
return []
|
||||
formats.extend(["fat32", "fat"])
|
||||
|
||||
formats.append("universal")
|
||||
if cpu_arch in {"arm64", "x86_64"}:
|
||||
formats.append("universal2")
|
||||
|
||||
if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
|
||||
formats.append("universal")
|
||||
|
||||
return formats
|
||||
|
||||
|
||||
def mac_platforms(version=None, arch=None):
|
||||
# type: (Optional[MacVersion], Optional[str]) -> Iterator[str]
|
||||
def mac_platforms(
|
||||
version: Optional[MacVersion] = None, arch: Optional[str] = None
|
||||
) -> Iterator[str]:
|
||||
"""
|
||||
Yields the platform tags for a macOS system.
|
||||
|
||||
@@ -396,7 +353,7 @@ def mac_platforms(version=None, arch=None):
|
||||
generate platform tags for. Both parameters default to the appropriate value
|
||||
for the current system.
|
||||
"""
|
||||
version_str, _, cpu_arch = platform.mac_ver() # type: ignore
|
||||
version_str, _, cpu_arch = platform.mac_ver()
|
||||
if version is None:
|
||||
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
|
||||
else:
|
||||
@@ -405,283 +362,76 @@ def mac_platforms(version=None, arch=None):
|
||||
arch = _mac_arch(cpu_arch)
|
||||
else:
|
||||
arch = arch
|
||||
for minor_version in range(version[1], -1, -1):
|
||||
compat_version = version[0], minor_version
|
||||
binary_formats = _mac_binary_formats(compat_version, arch)
|
||||
for binary_format in binary_formats:
|
||||
yield "macosx_{major}_{minor}_{binary_format}".format(
|
||||
major=compat_version[0],
|
||||
minor=compat_version[1],
|
||||
binary_format=binary_format,
|
||||
)
|
||||
|
||||
if (10, 0) <= version and version < (11, 0):
|
||||
# Prior to Mac OS 11, each yearly release of Mac OS bumped the
|
||||
# "minor" version number. The major version was always 10.
|
||||
for minor_version in range(version[1], -1, -1):
|
||||
compat_version = 10, minor_version
|
||||
binary_formats = _mac_binary_formats(compat_version, arch)
|
||||
for binary_format in binary_formats:
|
||||
yield "macosx_{major}_{minor}_{binary_format}".format(
|
||||
major=10, minor=minor_version, binary_format=binary_format
|
||||
)
|
||||
|
||||
if version >= (11, 0):
|
||||
# Starting with Mac OS 11, each yearly release bumps the major version
|
||||
# number. The minor versions are now the midyear updates.
|
||||
for major_version in range(version[0], 10, -1):
|
||||
compat_version = major_version, 0
|
||||
binary_formats = _mac_binary_formats(compat_version, arch)
|
||||
for binary_format in binary_formats:
|
||||
yield "macosx_{major}_{minor}_{binary_format}".format(
|
||||
major=major_version, minor=0, binary_format=binary_format
|
||||
)
|
||||
|
||||
if version >= (11, 0):
|
||||
# Mac OS 11 on x86_64 is compatible with binaries from previous releases.
|
||||
# Arm64 support was introduced in 11.0, so no Arm binaries from previous
|
||||
# releases exist.
|
||||
#
|
||||
# However, the "universal2" binary format can have a
|
||||
# macOS version earlier than 11.0 when the x86_64 part of the binary supports
|
||||
# that version of macOS.
|
||||
if arch == "x86_64":
|
||||
for minor_version in range(16, 3, -1):
|
||||
compat_version = 10, minor_version
|
||||
binary_formats = _mac_binary_formats(compat_version, arch)
|
||||
for binary_format in binary_formats:
|
||||
yield "macosx_{major}_{minor}_{binary_format}".format(
|
||||
major=compat_version[0],
|
||||
minor=compat_version[1],
|
||||
binary_format=binary_format,
|
||||
)
|
||||
else:
|
||||
for minor_version in range(16, 3, -1):
|
||||
compat_version = 10, minor_version
|
||||
binary_format = "universal2"
|
||||
yield "macosx_{major}_{minor}_{binary_format}".format(
|
||||
major=compat_version[0],
|
||||
minor=compat_version[1],
|
||||
binary_format=binary_format,
|
||||
)
|
||||
|
||||
|
||||
# From PEP 513.
|
||||
def _is_manylinux_compatible(name, glibc_version):
|
||||
# type: (str, GlibcVersion) -> bool
|
||||
# Check for presence of _manylinux module.
|
||||
try:
|
||||
import _manylinux # noqa
|
||||
|
||||
return bool(getattr(_manylinux, name + "_compatible"))
|
||||
except (ImportError, AttributeError):
|
||||
# Fall through to heuristic check below.
|
||||
pass
|
||||
|
||||
return _have_compatible_glibc(*glibc_version)
|
||||
|
||||
|
||||
def _glibc_version_string():
|
||||
# type: () -> Optional[str]
|
||||
# Returns glibc version string, or None if not using glibc.
|
||||
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
|
||||
|
||||
|
||||
def _glibc_version_string_confstr():
|
||||
# type: () -> Optional[str]
|
||||
"""
|
||||
Primary implementation of glibc_version_string using os.confstr.
|
||||
"""
|
||||
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
||||
# to be broken or missing. This strategy is used in the standard library
|
||||
# platform module.
|
||||
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
|
||||
try:
|
||||
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17".
|
||||
version_string = os.confstr( # type: ignore[attr-defined] # noqa: F821
|
||||
"CS_GNU_LIBC_VERSION"
|
||||
)
|
||||
assert version_string is not None
|
||||
_, version = version_string.split() # type: Tuple[str, str]
|
||||
except (AssertionError, AttributeError, OSError, ValueError):
|
||||
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
||||
return None
|
||||
return version
|
||||
|
||||
|
||||
def _glibc_version_string_ctypes():
|
||||
# type: () -> Optional[str]
|
||||
"""
|
||||
Fallback implementation of glibc_version_string using ctypes.
|
||||
"""
|
||||
try:
|
||||
import ctypes
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
|
||||
# manpage says, "If filename is NULL, then the returned handle is for the
|
||||
# main program". This way we can let the linker do the work to figure out
|
||||
# which libc our process is actually using.
|
||||
#
|
||||
# Note: typeshed is wrong here so we are ignoring this line.
|
||||
process_namespace = ctypes.CDLL(None) # type: ignore
|
||||
try:
|
||||
gnu_get_libc_version = process_namespace.gnu_get_libc_version
|
||||
except AttributeError:
|
||||
# Symbol doesn't exist -> therefore, we are not linked to
|
||||
# glibc.
|
||||
return None
|
||||
|
||||
# Call gnu_get_libc_version, which returns a string like "2.5"
|
||||
gnu_get_libc_version.restype = ctypes.c_char_p
|
||||
version_str = gnu_get_libc_version() # type: str
|
||||
# py2 / py3 compatibility:
|
||||
if not isinstance(version_str, str):
|
||||
version_str = version_str.decode("ascii")
|
||||
|
||||
return version_str
|
||||
|
||||
|
||||
# Separated out from have_compatible_glibc for easier unit testing.
|
||||
def _check_glibc_version(version_str, required_major, minimum_minor):
|
||||
# type: (str, int, int) -> bool
|
||||
# Parse string and check against requested version.
|
||||
#
|
||||
# We use a regexp instead of str.split because we want to discard any
|
||||
# random junk that might come after the minor version -- this might happen
|
||||
# in patched/forked versions of glibc (e.g. Linaro's version of glibc
|
||||
# uses version strings like "2.20-2014.11"). See gh-3588.
|
||||
m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
|
||||
if not m:
|
||||
warnings.warn(
|
||||
"Expected glibc version with 2 components major.minor,"
|
||||
" got: %s" % version_str,
|
||||
RuntimeWarning,
|
||||
)
|
||||
return False
|
||||
return (
|
||||
int(m.group("major")) == required_major
|
||||
and int(m.group("minor")) >= minimum_minor
|
||||
)
|
||||
|
||||
|
||||
def _have_compatible_glibc(required_major, minimum_minor):
|
||||
# type: (int, int) -> bool
|
||||
version_str = _glibc_version_string()
|
||||
if version_str is None:
|
||||
return False
|
||||
return _check_glibc_version(version_str, required_major, minimum_minor)
|
||||
|
||||
|
||||
# Python does not provide platform information at sufficient granularity to
|
||||
# identify the architecture of the running executable in some cases, so we
|
||||
# determine it dynamically by reading the information from the running
|
||||
# process. This only applies on Linux, which uses the ELF format.
|
||||
class _ELFFileHeader(object):
|
||||
# https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
|
||||
class _InvalidELFFileHeader(ValueError):
|
||||
"""
|
||||
An invalid ELF file header was found.
|
||||
"""
|
||||
|
||||
ELF_MAGIC_NUMBER = 0x7F454C46
|
||||
ELFCLASS32 = 1
|
||||
ELFCLASS64 = 2
|
||||
ELFDATA2LSB = 1
|
||||
ELFDATA2MSB = 2
|
||||
EM_386 = 3
|
||||
EM_S390 = 22
|
||||
EM_ARM = 40
|
||||
EM_X86_64 = 62
|
||||
EF_ARM_ABIMASK = 0xFF000000
|
||||
EF_ARM_ABI_VER5 = 0x05000000
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
||||
|
||||
def __init__(self, file):
|
||||
# type: (IO[bytes]) -> None
|
||||
def unpack(fmt):
|
||||
# type: (str) -> int
|
||||
try:
|
||||
(result,) = struct.unpack(
|
||||
fmt, file.read(struct.calcsize(fmt))
|
||||
) # type: (int, )
|
||||
except struct.error:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
return result
|
||||
|
||||
self.e_ident_magic = unpack(">I")
|
||||
if self.e_ident_magic != self.ELF_MAGIC_NUMBER:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
self.e_ident_class = unpack("B")
|
||||
if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
self.e_ident_data = unpack("B")
|
||||
if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
self.e_ident_version = unpack("B")
|
||||
self.e_ident_osabi = unpack("B")
|
||||
self.e_ident_abiversion = unpack("B")
|
||||
self.e_ident_pad = file.read(7)
|
||||
format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H"
|
||||
format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
|
||||
format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q"
|
||||
format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q
|
||||
self.e_type = unpack(format_h)
|
||||
self.e_machine = unpack(format_h)
|
||||
self.e_version = unpack(format_i)
|
||||
self.e_entry = unpack(format_p)
|
||||
self.e_phoff = unpack(format_p)
|
||||
self.e_shoff = unpack(format_p)
|
||||
self.e_flags = unpack(format_i)
|
||||
self.e_ehsize = unpack(format_h)
|
||||
self.e_phentsize = unpack(format_h)
|
||||
self.e_phnum = unpack(format_h)
|
||||
self.e_shentsize = unpack(format_h)
|
||||
self.e_shnum = unpack(format_h)
|
||||
self.e_shstrndx = unpack(format_h)
|
||||
|
||||
|
||||
def _get_elf_header():
|
||||
# type: () -> Optional[_ELFFileHeader]
|
||||
try:
|
||||
with open(sys.executable, "rb") as f:
|
||||
elf_header = _ELFFileHeader(f)
|
||||
except (IOError, OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
|
||||
return None
|
||||
return elf_header
|
||||
|
||||
|
||||
def _is_linux_armhf():
|
||||
# type: () -> bool
|
||||
# hard-float ABI can be detected from the ELF header of the running
|
||||
# process
|
||||
# https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
|
||||
elf_header = _get_elf_header()
|
||||
if elf_header is None:
|
||||
return False
|
||||
result = elf_header.e_ident_class == elf_header.ELFCLASS32
|
||||
result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
|
||||
result &= elf_header.e_machine == elf_header.EM_ARM
|
||||
result &= (
|
||||
elf_header.e_flags & elf_header.EF_ARM_ABIMASK
|
||||
) == elf_header.EF_ARM_ABI_VER5
|
||||
result &= (
|
||||
elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD
|
||||
) == elf_header.EF_ARM_ABI_FLOAT_HARD
|
||||
return result
|
||||
|
||||
|
||||
def _is_linux_i686():
|
||||
# type: () -> bool
|
||||
elf_header = _get_elf_header()
|
||||
if elf_header is None:
|
||||
return False
|
||||
result = elf_header.e_ident_class == elf_header.ELFCLASS32
|
||||
result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
|
||||
result &= elf_header.e_machine == elf_header.EM_386
|
||||
return result
|
||||
|
||||
|
||||
def _have_compatible_manylinux_abi(arch):
|
||||
# type: (str) -> bool
|
||||
if arch == "armv7l":
|
||||
return _is_linux_armhf()
|
||||
if arch == "i686":
|
||||
return _is_linux_i686()
|
||||
return True
|
||||
|
||||
|
||||
def _linux_platforms(is_32bit=_32_BIT_INTERPRETER):
|
||||
# type: (bool) -> Iterator[str]
|
||||
linux = _normalize_string(distutils.util.get_platform())
|
||||
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
|
||||
linux = _normalize_string(sysconfig.get_platform())
|
||||
if is_32bit:
|
||||
if linux == "linux_x86_64":
|
||||
linux = "linux_i686"
|
||||
elif linux == "linux_aarch64":
|
||||
linux = "linux_armv7l"
|
||||
manylinux_support = []
|
||||
_, arch = linux.split("_", 1)
|
||||
if _have_compatible_manylinux_abi(arch):
|
||||
if arch in {"x86_64", "i686", "aarch64", "armv7l", "ppc64", "ppc64le", "s390x"}:
|
||||
manylinux_support.append(
|
||||
("manylinux2014", (2, 17))
|
||||
) # CentOS 7 w/ glibc 2.17 (PEP 599)
|
||||
if arch in {"x86_64", "i686"}:
|
||||
manylinux_support.append(
|
||||
("manylinux2010", (2, 12))
|
||||
) # CentOS 6 w/ glibc 2.12 (PEP 571)
|
||||
manylinux_support.append(
|
||||
("manylinux1", (2, 5))
|
||||
) # CentOS 5 w/ glibc 2.5 (PEP 513)
|
||||
manylinux_support_iter = iter(manylinux_support)
|
||||
for name, glibc_version in manylinux_support_iter:
|
||||
if _is_manylinux_compatible(name, glibc_version):
|
||||
yield linux.replace("linux", name)
|
||||
break
|
||||
# Support for a later manylinux implies support for an earlier version.
|
||||
for name, _ in manylinux_support_iter:
|
||||
yield linux.replace("linux", name)
|
||||
yield from _manylinux.platform_tags(linux, arch)
|
||||
yield from _musllinux.platform_tags(arch)
|
||||
yield linux
|
||||
|
||||
|
||||
def _generic_platforms():
|
||||
# type: () -> Iterator[str]
|
||||
yield _normalize_string(distutils.util.get_platform())
|
||||
def _generic_platforms() -> Iterator[str]:
|
||||
yield _normalize_string(sysconfig.get_platform())
|
||||
|
||||
|
||||
def _platform_tags():
|
||||
# type: () -> Iterator[str]
|
||||
def platform_tags() -> Iterator[str]:
|
||||
"""
|
||||
Provides the platform tags for this installation.
|
||||
"""
|
||||
@@ -693,25 +443,18 @@ def _platform_tags():
|
||||
return _generic_platforms()
|
||||
|
||||
|
||||
def interpreter_name():
|
||||
# type: () -> str
|
||||
def interpreter_name() -> str:
|
||||
"""
|
||||
Returns the name of the running interpreter.
|
||||
"""
|
||||
try:
|
||||
name = sys.implementation.name # type: ignore
|
||||
except AttributeError: # pragma: no cover
|
||||
# Python 2.7 compatibility.
|
||||
name = platform.python_implementation().lower()
|
||||
name = sys.implementation.name
|
||||
return INTERPRETER_SHORT_NAMES.get(name) or name
|
||||
|
||||
|
||||
def interpreter_version(**kwargs):
|
||||
# type: (bool) -> str
|
||||
def interpreter_version(*, warn: bool = False) -> str:
|
||||
"""
|
||||
Returns the version of the running interpreter.
|
||||
"""
|
||||
warn = _warn_keyword_parameter("interpreter_version", kwargs)
|
||||
version = _get_config_var("py_version_nodot", warn=warn)
|
||||
if version:
|
||||
version = str(version)
|
||||
@@ -720,32 +463,25 @@ def interpreter_version(**kwargs):
|
||||
return version
|
||||
|
||||
|
||||
def _version_nodot(version):
|
||||
# type: (PythonVersion) -> str
|
||||
if any(v >= 10 for v in version):
|
||||
sep = "_"
|
||||
else:
|
||||
sep = ""
|
||||
return sep.join(map(str, version))
|
||||
def _version_nodot(version: PythonVersion) -> str:
|
||||
return "".join(map(str, version))
|
||||
|
||||
|
||||
def sys_tags(**kwargs):
|
||||
# type: (bool) -> Iterator[Tag]
|
||||
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
||||
"""
|
||||
Returns the sequence of tag triples for the running interpreter.
|
||||
|
||||
The order of the sequence corresponds to priority order for the
|
||||
interpreter, from most to least important.
|
||||
"""
|
||||
warn = _warn_keyword_parameter("sys_tags", kwargs)
|
||||
|
||||
interp_name = interpreter_name()
|
||||
if interp_name == "cp":
|
||||
for tag in cpython_tags(warn=warn):
|
||||
yield tag
|
||||
yield from cpython_tags(warn=warn)
|
||||
else:
|
||||
for tag in generic_tags():
|
||||
yield tag
|
||||
yield from generic_tags()
|
||||
|
||||
for tag in compatible_tags():
|
||||
yield tag
|
||||
if interp_name == "pp":
|
||||
yield from compatible_tags(interpreter="pp3")
|
||||
else:
|
||||
yield from compatible_tags()
|
||||
|
||||
@@ -1,65 +1,136 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import re
|
||||
from typing import FrozenSet, NewType, Tuple, Union, cast
|
||||
|
||||
from ._typing import TYPE_CHECKING, cast
|
||||
from .tags import Tag, parse_tag
|
||||
from .version import InvalidVersion, Version
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from typing import NewType, Union
|
||||
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
||||
NormalizedName = NewType("NormalizedName", str)
|
||||
|
||||
|
||||
class InvalidWheelFilename(ValueError):
|
||||
"""
|
||||
An invalid wheel filename was found, users should refer to PEP 427.
|
||||
"""
|
||||
|
||||
|
||||
class InvalidSdistFilename(ValueError):
|
||||
"""
|
||||
An invalid sdist filename was found, users should refer to the packaging user guide.
|
||||
"""
|
||||
|
||||
NormalizedName = NewType("NormalizedName", str)
|
||||
|
||||
_canonicalize_regex = re.compile(r"[-_.]+")
|
||||
# PEP 427: The build number must start with a digit.
|
||||
_build_tag_regex = re.compile(r"(\d+)(.*)")
|
||||
|
||||
|
||||
def canonicalize_name(name):
|
||||
# type: (str) -> NormalizedName
|
||||
def canonicalize_name(name: str) -> NormalizedName:
|
||||
# This is taken from PEP 503.
|
||||
value = _canonicalize_regex.sub("-", name).lower()
|
||||
return cast("NormalizedName", value)
|
||||
return cast(NormalizedName, value)
|
||||
|
||||
|
||||
def canonicalize_version(_version):
|
||||
# type: (str) -> Union[Version, str]
|
||||
def canonicalize_version(version: Union[Version, str]) -> str:
|
||||
"""
|
||||
This is very similar to Version.__str__, but has one subtle difference
|
||||
with the way it handles the release segment.
|
||||
"""
|
||||
|
||||
try:
|
||||
version = Version(_version)
|
||||
except InvalidVersion:
|
||||
# Legacy versions cannot be normalized
|
||||
return _version
|
||||
if isinstance(version, str):
|
||||
try:
|
||||
parsed = Version(version)
|
||||
except InvalidVersion:
|
||||
# Legacy versions cannot be normalized
|
||||
return version
|
||||
else:
|
||||
parsed = version
|
||||
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if version.epoch != 0:
|
||||
parts.append("{0}!".format(version.epoch))
|
||||
if parsed.epoch != 0:
|
||||
parts.append(f"{parsed.epoch}!")
|
||||
|
||||
# Release segment
|
||||
# NB: This strips trailing '.0's to normalize
|
||||
parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in version.release)))
|
||||
parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release)))
|
||||
|
||||
# Pre-release
|
||||
if version.pre is not None:
|
||||
parts.append("".join(str(x) for x in version.pre))
|
||||
if parsed.pre is not None:
|
||||
parts.append("".join(str(x) for x in parsed.pre))
|
||||
|
||||
# Post-release
|
||||
if version.post is not None:
|
||||
parts.append(".post{0}".format(version.post))
|
||||
if parsed.post is not None:
|
||||
parts.append(f".post{parsed.post}")
|
||||
|
||||
# Development release
|
||||
if version.dev is not None:
|
||||
parts.append(".dev{0}".format(version.dev))
|
||||
if parsed.dev is not None:
|
||||
parts.append(f".dev{parsed.dev}")
|
||||
|
||||
# Local version segment
|
||||
if version.local is not None:
|
||||
parts.append("+{0}".format(version.local))
|
||||
if parsed.local is not None:
|
||||
parts.append(f"+{parsed.local}")
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def parse_wheel_filename(
|
||||
filename: str,
|
||||
) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]:
|
||||
if not filename.endswith(".whl"):
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid wheel filename (extension must be '.whl'): {filename}"
|
||||
)
|
||||
|
||||
filename = filename[:-4]
|
||||
dashes = filename.count("-")
|
||||
if dashes not in (4, 5):
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid wheel filename (wrong number of parts): {filename}"
|
||||
)
|
||||
|
||||
parts = filename.split("-", dashes - 2)
|
||||
name_part = parts[0]
|
||||
# See PEP 427 for the rules on escaping the project name
|
||||
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
|
||||
raise InvalidWheelFilename(f"Invalid project name: {filename}")
|
||||
name = canonicalize_name(name_part)
|
||||
version = Version(parts[1])
|
||||
if dashes == 5:
|
||||
build_part = parts[2]
|
||||
build_match = _build_tag_regex.match(build_part)
|
||||
if build_match is None:
|
||||
raise InvalidWheelFilename(
|
||||
f"Invalid build number: {build_part} in '{filename}'"
|
||||
)
|
||||
build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
|
||||
else:
|
||||
build = ()
|
||||
tags = parse_tag(parts[-1])
|
||||
return (name, version, build, tags)
|
||||
|
||||
|
||||
def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]:
|
||||
if filename.endswith(".tar.gz"):
|
||||
file_stem = filename[: -len(".tar.gz")]
|
||||
elif filename.endswith(".zip"):
|
||||
file_stem = filename[: -len(".zip")]
|
||||
else:
|
||||
raise InvalidSdistFilename(
|
||||
f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
|
||||
f" {filename}"
|
||||
)
|
||||
|
||||
# We are requiring a PEP 440 version, which cannot contain dashes,
|
||||
# so we split on the last dash.
|
||||
name_part, sep, version_part = file_stem.rpartition("-")
|
||||
if not sep:
|
||||
raise InvalidSdistFilename(f"Invalid sdist filename: {filename}")
|
||||
|
||||
name = canonicalize_name(name_part)
|
||||
version = Version(version_part)
|
||||
return (name, version)
|
||||
|
||||
@@ -1,52 +1,45 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
|
||||
|
||||
from ._structures import Infinity, NegativeInfinity
|
||||
from ._typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
|
||||
|
||||
from ._structures import InfinityType, NegativeInfinityType
|
||||
|
||||
InfiniteTypes = Union[InfinityType, NegativeInfinityType]
|
||||
PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
|
||||
SubLocalType = Union[InfiniteTypes, int, str]
|
||||
LocalType = Union[
|
||||
NegativeInfinityType,
|
||||
Tuple[
|
||||
Union[
|
||||
SubLocalType,
|
||||
Tuple[SubLocalType, str],
|
||||
Tuple[NegativeInfinityType, SubLocalType],
|
||||
],
|
||||
...,
|
||||
],
|
||||
]
|
||||
CmpKey = Tuple[
|
||||
int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
|
||||
]
|
||||
LegacyCmpKey = Tuple[int, Tuple[str, ...]]
|
||||
VersionComparisonMethod = Callable[
|
||||
[Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
|
||||
]
|
||||
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
|
||||
|
||||
__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
|
||||
|
||||
InfiniteTypes = Union[InfinityType, NegativeInfinityType]
|
||||
PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
|
||||
SubLocalType = Union[InfiniteTypes, int, str]
|
||||
LocalType = Union[
|
||||
NegativeInfinityType,
|
||||
Tuple[
|
||||
Union[
|
||||
SubLocalType,
|
||||
Tuple[SubLocalType, str],
|
||||
Tuple[NegativeInfinityType, SubLocalType],
|
||||
],
|
||||
...,
|
||||
],
|
||||
]
|
||||
CmpKey = Tuple[
|
||||
int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
|
||||
]
|
||||
LegacyCmpKey = Tuple[int, Tuple[str, ...]]
|
||||
VersionComparisonMethod = Callable[
|
||||
[Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
|
||||
]
|
||||
|
||||
_Version = collections.namedtuple(
|
||||
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
|
||||
)
|
||||
|
||||
|
||||
def parse(version):
|
||||
# type: (str) -> Union[LegacyVersion, Version]
|
||||
def parse(version: str) -> Union["LegacyVersion", "Version"]:
|
||||
"""
|
||||
Parse the given version string and return either a :class:`Version` object
|
||||
or a :class:`LegacyVersion` object depending on if the given version is
|
||||
@@ -64,112 +57,111 @@ class InvalidVersion(ValueError):
|
||||
"""
|
||||
|
||||
|
||||
class _BaseVersion(object):
|
||||
_key = None # type: Union[CmpKey, LegacyCmpKey]
|
||||
class _BaseVersion:
|
||||
_key: Union[CmpKey, LegacyCmpKey]
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._key)
|
||||
|
||||
def __lt__(self, other):
|
||||
# type: (_BaseVersion) -> bool
|
||||
return self._compare(other, lambda s, o: s < o)
|
||||
|
||||
def __le__(self, other):
|
||||
# type: (_BaseVersion) -> bool
|
||||
return self._compare(other, lambda s, o: s <= o)
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
return self._compare(other, lambda s, o: s == o)
|
||||
|
||||
def __ge__(self, other):
|
||||
# type: (_BaseVersion) -> bool
|
||||
return self._compare(other, lambda s, o: s >= o)
|
||||
|
||||
def __gt__(self, other):
|
||||
# type: (_BaseVersion) -> bool
|
||||
return self._compare(other, lambda s, o: s > o)
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
return self._compare(other, lambda s, o: s != o)
|
||||
|
||||
def _compare(self, other, method):
|
||||
# type: (object, VersionComparisonMethod) -> Union[bool, NotImplemented]
|
||||
# Please keep the duplicated `isinstance` check
|
||||
# in the six comparisons hereunder
|
||||
# unless you find a way to avoid adding overhead function calls.
|
||||
def __lt__(self, other: "_BaseVersion") -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return method(self._key, other._key)
|
||||
return self._key < other._key
|
||||
|
||||
def __le__(self, other: "_BaseVersion") -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key <= other._key
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key == other._key
|
||||
|
||||
def __ge__(self, other: "_BaseVersion") -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key >= other._key
|
||||
|
||||
def __gt__(self, other: "_BaseVersion") -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key > other._key
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return self._key != other._key
|
||||
|
||||
|
||||
class LegacyVersion(_BaseVersion):
|
||||
def __init__(self, version):
|
||||
# type: (str) -> None
|
||||
def __init__(self, version: str) -> None:
|
||||
self._version = str(version)
|
||||
self._key = _legacy_cmpkey(self._version)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
warnings.warn(
|
||||
"Creating a LegacyVersion has been deprecated and will be "
|
||||
"removed in the next major release",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._version
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "<LegacyVersion({0})>".format(repr(str(self)))
|
||||
def __repr__(self) -> str:
|
||||
return f"<LegacyVersion('{self}')>"
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
# type: () -> str
|
||||
def public(self) -> str:
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
# type: () -> str
|
||||
def base_version(self) -> str:
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def epoch(self):
|
||||
# type: () -> int
|
||||
def epoch(self) -> int:
|
||||
return -1
|
||||
|
||||
@property
|
||||
def release(self):
|
||||
# type: () -> None
|
||||
def release(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def pre(self):
|
||||
# type: () -> None
|
||||
def pre(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def post(self):
|
||||
# type: () -> None
|
||||
def post(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def dev(self):
|
||||
# type: () -> None
|
||||
def dev(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
# type: () -> None
|
||||
def local(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
# type: () -> bool
|
||||
def is_prerelease(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
# type: () -> bool
|
||||
def is_postrelease(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_devrelease(self):
|
||||
# type: () -> bool
|
||||
def is_devrelease(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
@@ -184,8 +176,7 @@ _legacy_version_replacement_map = {
|
||||
}
|
||||
|
||||
|
||||
def _parse_version_parts(s):
|
||||
# type: (str) -> Iterator[str]
|
||||
def _parse_version_parts(s: str) -> Iterator[str]:
|
||||
for part in _legacy_version_component_re.split(s):
|
||||
part = _legacy_version_replacement_map.get(part, part)
|
||||
|
||||
@@ -202,8 +193,7 @@ def _parse_version_parts(s):
|
||||
yield "*final"
|
||||
|
||||
|
||||
def _legacy_cmpkey(version):
|
||||
# type: (str) -> LegacyCmpKey
|
||||
def _legacy_cmpkey(version: str) -> LegacyCmpKey:
|
||||
|
||||
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
|
||||
# greater than or equal to 0. This will effectively put the LegacyVersion,
|
||||
@@ -213,7 +203,7 @@ def _legacy_cmpkey(version):
|
||||
|
||||
# This scheme is taken from pkg_resources.parse_version setuptools prior to
|
||||
# it's adoption of the packaging library.
|
||||
parts = [] # type: List[str]
|
||||
parts: List[str] = []
|
||||
for part in _parse_version_parts(version.lower()):
|
||||
if part.startswith("*"):
|
||||
# remove "-" before a prerelease tag
|
||||
@@ -268,13 +258,12 @@ class Version(_BaseVersion):
|
||||
|
||||
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
def __init__(self, version):
|
||||
# type: (str) -> None
|
||||
def __init__(self, version: str) -> None:
|
||||
|
||||
# Validate the version and parse it into pieces
|
||||
match = self._regex.search(version)
|
||||
if not match:
|
||||
raise InvalidVersion("Invalid version: '{0}'".format(version))
|
||||
raise InvalidVersion(f"Invalid version: '{version}'")
|
||||
|
||||
# Store the parsed out pieces of the version
|
||||
self._version = _Version(
|
||||
@@ -298,17 +287,15 @@ class Version(_BaseVersion):
|
||||
self._version.local,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "<Version({0})>".format(repr(str(self)))
|
||||
def __repr__(self) -> str:
|
||||
return f"<Version('{self}')>"
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
def __str__(self) -> str:
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self.epoch != 0:
|
||||
parts.append("{0}!".format(self.epoch))
|
||||
parts.append(f"{self.epoch}!")
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self.release))
|
||||
@@ -319,67 +306,59 @@ class Version(_BaseVersion):
|
||||
|
||||
# Post-release
|
||||
if self.post is not None:
|
||||
parts.append(".post{0}".format(self.post))
|
||||
parts.append(f".post{self.post}")
|
||||
|
||||
# Development release
|
||||
if self.dev is not None:
|
||||
parts.append(".dev{0}".format(self.dev))
|
||||
parts.append(f".dev{self.dev}")
|
||||
|
||||
# Local version segment
|
||||
if self.local is not None:
|
||||
parts.append("+{0}".format(self.local))
|
||||
parts.append(f"+{self.local}")
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
@property
|
||||
def epoch(self):
|
||||
# type: () -> int
|
||||
_epoch = self._version.epoch # type: int
|
||||
def epoch(self) -> int:
|
||||
_epoch: int = self._version.epoch
|
||||
return _epoch
|
||||
|
||||
@property
|
||||
def release(self):
|
||||
# type: () -> Tuple[int, ...]
|
||||
_release = self._version.release # type: Tuple[int, ...]
|
||||
def release(self) -> Tuple[int, ...]:
|
||||
_release: Tuple[int, ...] = self._version.release
|
||||
return _release
|
||||
|
||||
@property
|
||||
def pre(self):
|
||||
# type: () -> Optional[Tuple[str, int]]
|
||||
_pre = self._version.pre # type: Optional[Tuple[str, int]]
|
||||
def pre(self) -> Optional[Tuple[str, int]]:
|
||||
_pre: Optional[Tuple[str, int]] = self._version.pre
|
||||
return _pre
|
||||
|
||||
@property
|
||||
def post(self):
|
||||
# type: () -> Optional[Tuple[str, int]]
|
||||
def post(self) -> Optional[int]:
|
||||
return self._version.post[1] if self._version.post else None
|
||||
|
||||
@property
|
||||
def dev(self):
|
||||
# type: () -> Optional[Tuple[str, int]]
|
||||
def dev(self) -> Optional[int]:
|
||||
return self._version.dev[1] if self._version.dev else None
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
# type: () -> Optional[str]
|
||||
def local(self) -> Optional[str]:
|
||||
if self._version.local:
|
||||
return ".".join(str(x) for x in self._version.local)
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
# type: () -> str
|
||||
def public(self) -> str:
|
||||
return str(self).split("+", 1)[0]
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
# type: () -> str
|
||||
def base_version(self) -> str:
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self.epoch != 0:
|
||||
parts.append("{0}!".format(self.epoch))
|
||||
parts.append(f"{self.epoch}!")
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self.release))
|
||||
@@ -387,41 +366,33 @@ class Version(_BaseVersion):
|
||||
return "".join(parts)
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
# type: () -> bool
|
||||
def is_prerelease(self) -> bool:
|
||||
return self.dev is not None or self.pre is not None
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
# type: () -> bool
|
||||
def is_postrelease(self) -> bool:
|
||||
return self.post is not None
|
||||
|
||||
@property
|
||||
def is_devrelease(self):
|
||||
# type: () -> bool
|
||||
def is_devrelease(self) -> bool:
|
||||
return self.dev is not None
|
||||
|
||||
@property
|
||||
def major(self):
|
||||
# type: () -> int
|
||||
def major(self) -> int:
|
||||
return self.release[0] if len(self.release) >= 1 else 0
|
||||
|
||||
@property
|
||||
def minor(self):
|
||||
# type: () -> int
|
||||
def minor(self) -> int:
|
||||
return self.release[1] if len(self.release) >= 2 else 0
|
||||
|
||||
@property
|
||||
def micro(self):
|
||||
# type: () -> int
|
||||
def micro(self) -> int:
|
||||
return self.release[2] if len(self.release) >= 3 else 0
|
||||
|
||||
|
||||
def _parse_letter_version(
|
||||
letter, # type: str
|
||||
number, # type: Union[str, bytes, SupportsInt]
|
||||
):
|
||||
# type: (...) -> Optional[Tuple[str, int]]
|
||||
letter: str, number: Union[str, bytes, SupportsInt]
|
||||
) -> Optional[Tuple[str, int]]:
|
||||
|
||||
if letter:
|
||||
# We consider there to be an implicit 0 in a pre-release if there is
|
||||
@@ -458,8 +429,7 @@ def _parse_letter_version(
|
||||
_local_version_separators = re.compile(r"[\._-]")
|
||||
|
||||
|
||||
def _parse_local_version(local):
|
||||
# type: (str) -> Optional[LocalType]
|
||||
def _parse_local_version(local: str) -> Optional[LocalType]:
|
||||
"""
|
||||
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
||||
"""
|
||||
@@ -472,14 +442,13 @@ def _parse_local_version(local):
|
||||
|
||||
|
||||
def _cmpkey(
|
||||
epoch, # type: int
|
||||
release, # type: Tuple[int, ...]
|
||||
pre, # type: Optional[Tuple[str, int]]
|
||||
post, # type: Optional[Tuple[str, int]]
|
||||
dev, # type: Optional[Tuple[str, int]]
|
||||
local, # type: Optional[Tuple[SubLocalType]]
|
||||
):
|
||||
# type: (...) -> CmpKey
|
||||
epoch: int,
|
||||
release: Tuple[int, ...],
|
||||
pre: Optional[Tuple[str, int]],
|
||||
post: Optional[Tuple[str, int]],
|
||||
dev: Optional[Tuple[str, int]],
|
||||
local: Optional[Tuple[SubLocalType]],
|
||||
) -> CmpKey:
|
||||
|
||||
# When we compare a release version, we want to compare it with all of the
|
||||
# trailing zeros removed. So we'll use a reverse the list, drop all the now
|
||||
@@ -495,7 +464,7 @@ def _cmpkey(
|
||||
# if there is not a pre or a post segment. If we have one of those then
|
||||
# the normal sorting rules will handle this case correctly.
|
||||
if pre is None and post is None and dev is not None:
|
||||
_pre = NegativeInfinity # type: PrePostDevType
|
||||
_pre: PrePostDevType = NegativeInfinity
|
||||
# Versions without a pre-release (except as noted above) should sort after
|
||||
# those with one.
|
||||
elif pre is None:
|
||||
@@ -505,21 +474,21 @@ def _cmpkey(
|
||||
|
||||
# Versions without a post segment should sort before those with one.
|
||||
if post is None:
|
||||
_post = NegativeInfinity # type: PrePostDevType
|
||||
_post: PrePostDevType = NegativeInfinity
|
||||
|
||||
else:
|
||||
_post = post
|
||||
|
||||
# Versions without a development segment should sort after those with one.
|
||||
if dev is None:
|
||||
_dev = Infinity # type: PrePostDevType
|
||||
_dev: PrePostDevType = Infinity
|
||||
|
||||
else:
|
||||
_dev = dev
|
||||
|
||||
if local is None:
|
||||
# Versions without a local segment should sort before those with one.
|
||||
_local = NegativeInfinity # type: LocalType
|
||||
_local: LocalType = NegativeInfinity
|
||||
else:
|
||||
# Versions with a local segment need that segment parsed to implement
|
||||
# the sorting rules in PEP440.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ import posixpath
|
||||
import contextlib
|
||||
from distutils.errors import DistutilsError
|
||||
|
||||
from pkg_resources import ensure_directory
|
||||
from ._path import ensure_directory
|
||||
|
||||
__all__ = [
|
||||
"unpack_archive", "unpack_zipfile", "unpack_tarfile", "default_filter",
|
||||
@@ -100,29 +100,37 @@ def unpack_zipfile(filename, extract_dir, progress_filter=default_filter):
|
||||
raise UnrecognizedFormat("%s is not a zip file" % (filename,))
|
||||
|
||||
with zipfile.ZipFile(filename) as z:
|
||||
for info in z.infolist():
|
||||
name = info.filename
|
||||
_unpack_zipfile_obj(z, extract_dir, progress_filter)
|
||||
|
||||
# don't extract absolute paths or ones with .. in them
|
||||
if name.startswith('/') or '..' in name.split('/'):
|
||||
continue
|
||||
|
||||
target = os.path.join(extract_dir, *name.split('/'))
|
||||
target = progress_filter(name, target)
|
||||
if not target:
|
||||
continue
|
||||
if name.endswith('/'):
|
||||
# directory
|
||||
ensure_directory(target)
|
||||
else:
|
||||
# file
|
||||
ensure_directory(target)
|
||||
data = z.read(info.filename)
|
||||
with open(target, 'wb') as f:
|
||||
f.write(data)
|
||||
unix_attributes = info.external_attr >> 16
|
||||
if unix_attributes:
|
||||
os.chmod(target, unix_attributes)
|
||||
def _unpack_zipfile_obj(zipfile_obj, extract_dir, progress_filter=default_filter):
|
||||
"""Internal/private API used by other parts of setuptools.
|
||||
Similar to ``unpack_zipfile``, but receives an already opened :obj:`zipfile.ZipFile`
|
||||
object instead of a filename.
|
||||
"""
|
||||
for info in zipfile_obj.infolist():
|
||||
name = info.filename
|
||||
|
||||
# don't extract absolute paths or ones with .. in them
|
||||
if name.startswith('/') or '..' in name.split('/'):
|
||||
continue
|
||||
|
||||
target = os.path.join(extract_dir, *name.split('/'))
|
||||
target = progress_filter(name, target)
|
||||
if not target:
|
||||
continue
|
||||
if name.endswith('/'):
|
||||
# directory
|
||||
ensure_directory(target)
|
||||
else:
|
||||
# file
|
||||
ensure_directory(target)
|
||||
data = zipfile_obj.read(info.filename)
|
||||
with open(target, 'wb') as f:
|
||||
f.write(data)
|
||||
unix_attributes = info.external_attr >> 16
|
||||
if unix_attributes:
|
||||
os.chmod(target, unix_attributes)
|
||||
|
||||
|
||||
def _resolve_tar_file_or_dir(tar_obj, tar_member_obj):
|
||||
|
||||
@@ -28,25 +28,39 @@ Again, this is not a formal definition! Just a "taste" of the module.
|
||||
|
||||
import io
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
import tokenize
|
||||
import shutil
|
||||
import contextlib
|
||||
import tempfile
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterator, List, Optional, Union
|
||||
|
||||
import setuptools
|
||||
import distutils
|
||||
from . import errors
|
||||
from ._path import same_path
|
||||
from ._reqs import parse_strings
|
||||
from ._deprecation_warning import SetuptoolsDeprecationWarning
|
||||
from distutils.util import strtobool
|
||||
|
||||
from pkg_resources import parse_requirements
|
||||
|
||||
__all__ = ['get_requires_for_build_sdist',
|
||||
'get_requires_for_build_wheel',
|
||||
'prepare_metadata_for_build_wheel',
|
||||
'build_wheel',
|
||||
'build_sdist',
|
||||
'get_requires_for_build_editable',
|
||||
'prepare_metadata_for_build_editable',
|
||||
'build_editable',
|
||||
'__legacy__',
|
||||
'SetupRequirementsError']
|
||||
|
||||
SETUPTOOLS_ENABLE_FEATURES = os.getenv("SETUPTOOLS_ENABLE_FEATURES", "").lower()
|
||||
LEGACY_EDITABLE = "legacy-editable" in SETUPTOOLS_ENABLE_FEATURES.replace("_", "-")
|
||||
|
||||
|
||||
class SetupRequirementsError(BaseException):
|
||||
def __init__(self, specifiers):
|
||||
@@ -55,7 +69,7 @@ class SetupRequirementsError(BaseException):
|
||||
|
||||
class Distribution(setuptools.dist.Distribution):
|
||||
def fetch_build_eggs(self, specifiers):
|
||||
specifier_list = list(map(str, parse_requirements(specifiers)))
|
||||
specifier_list = list(parse_strings(specifiers))
|
||||
|
||||
raise SetupRequirementsError(specifier_list)
|
||||
|
||||
@@ -118,18 +132,189 @@ def _open_setup_script(setup_script):
|
||||
return getattr(tokenize, 'open', open)(setup_script)
|
||||
|
||||
|
||||
class _BuildMetaBackend(object):
|
||||
@contextlib.contextmanager
|
||||
def suppress_known_deprecation():
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings('ignore', 'setup.py install is deprecated')
|
||||
yield
|
||||
|
||||
def _fix_config(self, config_settings):
|
||||
config_settings = config_settings or {}
|
||||
config_settings.setdefault('--global-option', [])
|
||||
return config_settings
|
||||
|
||||
_ConfigSettings = Optional[Dict[str, Union[str, List[str], None]]]
|
||||
"""
|
||||
Currently the user can run::
|
||||
|
||||
pip install -e . --config-settings key=value
|
||||
python -m build -C--key=value -C key=value
|
||||
|
||||
- pip will pass both key and value as strings and overwriting repeated keys
|
||||
(pypa/pip#11059).
|
||||
- build will accumulate values associated with repeated keys in a list.
|
||||
It will also accept keys with no associated value.
|
||||
This means that an option passed by build can be ``str | list[str] | None``.
|
||||
- PEP 517 specifies that ``config_settings`` is an optional dict.
|
||||
"""
|
||||
|
||||
|
||||
class _ConfigSettingsTranslator:
|
||||
"""Translate ``config_settings`` into distutils-style command arguments.
|
||||
Only a limited number of options is currently supported.
|
||||
"""
|
||||
# See pypa/setuptools#1928 pypa/setuptools#2491
|
||||
|
||||
def _get_config(self, key: str, config_settings: _ConfigSettings) -> List[str]:
|
||||
"""
|
||||
Get the value of a specific key in ``config_settings`` as a list of strings.
|
||||
|
||||
>>> fn = _ConfigSettingsTranslator()._get_config
|
||||
>>> fn("--global-option", None)
|
||||
[]
|
||||
>>> fn("--global-option", {})
|
||||
[]
|
||||
>>> fn("--global-option", {'--global-option': 'foo'})
|
||||
['foo']
|
||||
>>> fn("--global-option", {'--global-option': ['foo']})
|
||||
['foo']
|
||||
>>> fn("--global-option", {'--global-option': 'foo'})
|
||||
['foo']
|
||||
>>> fn("--global-option", {'--global-option': 'foo bar'})
|
||||
['foo', 'bar']
|
||||
"""
|
||||
cfg = config_settings or {}
|
||||
opts = cfg.get(key) or []
|
||||
return shlex.split(opts) if isinstance(opts, str) else opts
|
||||
|
||||
def _valid_global_options(self):
|
||||
"""Global options accepted by setuptools (e.g. quiet or verbose)."""
|
||||
options = (opt[:2] for opt in setuptools.dist.Distribution.global_options)
|
||||
return {flag for long_and_short in options for flag in long_and_short if flag}
|
||||
|
||||
def _global_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
|
||||
"""
|
||||
Let the user specify ``verbose`` or ``quiet`` + escape hatch via
|
||||
``--global-option``.
|
||||
Note: ``-v``, ``-vv``, ``-vvv`` have similar effects in setuptools,
|
||||
so we just have to cover the basic scenario ``-v``.
|
||||
|
||||
>>> fn = _ConfigSettingsTranslator()._global_args
|
||||
>>> list(fn(None))
|
||||
[]
|
||||
>>> list(fn({"verbose": "False"}))
|
||||
['-q']
|
||||
>>> list(fn({"verbose": "1"}))
|
||||
['-v']
|
||||
>>> list(fn({"--verbose": None}))
|
||||
['-v']
|
||||
>>> list(fn({"verbose": "true", "--global-option": "-q --no-user-cfg"}))
|
||||
['-v', '-q', '--no-user-cfg']
|
||||
>>> list(fn({"--quiet": None}))
|
||||
['-q']
|
||||
"""
|
||||
cfg = config_settings or {}
|
||||
falsey = {"false", "no", "0", "off"}
|
||||
if "verbose" in cfg or "--verbose" in cfg:
|
||||
level = str(cfg.get("verbose") or cfg.get("--verbose") or "1")
|
||||
yield ("-q" if level.lower() in falsey else "-v")
|
||||
if "quiet" in cfg or "--quiet" in cfg:
|
||||
level = str(cfg.get("quiet") or cfg.get("--quiet") or "1")
|
||||
yield ("-v" if level.lower() in falsey else "-q")
|
||||
|
||||
valid = self._valid_global_options()
|
||||
args = self._get_config("--global-option", config_settings)
|
||||
yield from (arg for arg in args if arg.strip("-") in valid)
|
||||
|
||||
def __dist_info_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
|
||||
"""
|
||||
The ``dist_info`` command accepts ``tag-date`` and ``tag-build``.
|
||||
|
||||
.. warning::
|
||||
We cannot use this yet as it requires the ``sdist`` and ``bdist_wheel``
|
||||
commands run in ``build_sdist`` and ``build_wheel`` to re-use the egg-info
|
||||
directory created in ``prepare_metadata_for_build_wheel``.
|
||||
|
||||
>>> fn = _ConfigSettingsTranslator()._ConfigSettingsTranslator__dist_info_args
|
||||
>>> list(fn(None))
|
||||
[]
|
||||
>>> list(fn({"tag-date": "False"}))
|
||||
['--no-date']
|
||||
>>> list(fn({"tag-date": None}))
|
||||
['--no-date']
|
||||
>>> list(fn({"tag-date": "true", "tag-build": ".a"}))
|
||||
['--tag-date', '--tag-build', '.a']
|
||||
"""
|
||||
cfg = config_settings or {}
|
||||
if "tag-date" in cfg:
|
||||
val = strtobool(str(cfg["tag-date"] or "false"))
|
||||
yield ("--tag-date" if val else "--no-date")
|
||||
if "tag-build" in cfg:
|
||||
yield from ["--tag-build", str(cfg["tag-build"])]
|
||||
|
||||
def _editable_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
|
||||
"""
|
||||
The ``editable_wheel`` command accepts ``editable-mode=strict``.
|
||||
|
||||
>>> fn = _ConfigSettingsTranslator()._editable_args
|
||||
>>> list(fn(None))
|
||||
[]
|
||||
>>> list(fn({"editable-mode": "strict"}))
|
||||
['--mode', 'strict']
|
||||
"""
|
||||
cfg = config_settings or {}
|
||||
mode = cfg.get("editable-mode") or cfg.get("editable_mode")
|
||||
if not mode:
|
||||
return
|
||||
yield from ["--mode", str(mode)]
|
||||
|
||||
def _arbitrary_args(self, config_settings: _ConfigSettings) -> Iterator[str]:
|
||||
"""
|
||||
Users may expect to pass arbitrary lists of arguments to a command
|
||||
via "--global-option" (example provided in PEP 517 of a "escape hatch").
|
||||
|
||||
>>> fn = _ConfigSettingsTranslator()._arbitrary_args
|
||||
>>> list(fn(None))
|
||||
[]
|
||||
>>> list(fn({}))
|
||||
[]
|
||||
>>> list(fn({'--build-option': 'foo'}))
|
||||
['foo']
|
||||
>>> list(fn({'--build-option': ['foo']}))
|
||||
['foo']
|
||||
>>> list(fn({'--build-option': 'foo'}))
|
||||
['foo']
|
||||
>>> list(fn({'--build-option': 'foo bar'}))
|
||||
['foo', 'bar']
|
||||
>>> warnings.simplefilter('error', SetuptoolsDeprecationWarning)
|
||||
>>> list(fn({'--global-option': 'foo'})) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
Traceback (most recent call last):
|
||||
SetuptoolsDeprecationWarning: ...arguments given via `--global-option`...
|
||||
"""
|
||||
args = self._get_config("--global-option", config_settings)
|
||||
global_opts = self._valid_global_options()
|
||||
bad_args = []
|
||||
|
||||
for arg in args:
|
||||
if arg.strip("-") not in global_opts:
|
||||
bad_args.append(arg)
|
||||
yield arg
|
||||
|
||||
yield from self._get_config("--build-option", config_settings)
|
||||
|
||||
if bad_args:
|
||||
msg = f"""
|
||||
The arguments {bad_args!r} were given via `--global-option`.
|
||||
Please use `--build-option` instead,
|
||||
`--global-option` is reserved to flags like `--verbose` or `--quiet`.
|
||||
"""
|
||||
warnings.warn(msg, SetuptoolsDeprecationWarning)
|
||||
|
||||
|
||||
class _BuildMetaBackend(_ConfigSettingsTranslator):
|
||||
def _get_build_requires(self, config_settings, requirements):
|
||||
config_settings = self._fix_config(config_settings)
|
||||
|
||||
sys.argv = sys.argv[:1] + ['egg_info'] + \
|
||||
config_settings["--global-option"]
|
||||
sys.argv = [
|
||||
*sys.argv[:1],
|
||||
*self._global_args(config_settings),
|
||||
"egg_info",
|
||||
*self._arbitrary_args(config_settings),
|
||||
]
|
||||
try:
|
||||
with Distribution.patch():
|
||||
self.run_setup()
|
||||
@@ -147,62 +332,67 @@ class _BuildMetaBackend(object):
|
||||
with _open_setup_script(__file__) as f:
|
||||
code = f.read().replace(r'\r\n', r'\n')
|
||||
|
||||
exec(compile(code, __file__, 'exec'), locals())
|
||||
exec(code, locals())
|
||||
|
||||
def get_requires_for_build_wheel(self, config_settings=None):
|
||||
config_settings = self._fix_config(config_settings)
|
||||
return self._get_build_requires(
|
||||
config_settings, requirements=['wheel'])
|
||||
return self._get_build_requires(config_settings, requirements=['wheel'])
|
||||
|
||||
def get_requires_for_build_sdist(self, config_settings=None):
|
||||
config_settings = self._fix_config(config_settings)
|
||||
return self._get_build_requires(config_settings, requirements=[])
|
||||
|
||||
def _bubble_up_info_directory(self, metadata_directory: str, suffix: str) -> str:
|
||||
"""
|
||||
PEP 517 requires that the .dist-info directory be placed in the
|
||||
metadata_directory. To comply, we MUST copy the directory to the root.
|
||||
|
||||
Returns the basename of the info directory, e.g. `proj-0.0.0.dist-info`.
|
||||
"""
|
||||
info_dir = self._find_info_directory(metadata_directory, suffix)
|
||||
if not same_path(info_dir.parent, metadata_directory):
|
||||
shutil.move(str(info_dir), metadata_directory)
|
||||
# PEP 517 allow other files and dirs to exist in metadata_directory
|
||||
return info_dir.name
|
||||
|
||||
def _find_info_directory(self, metadata_directory: str, suffix: str) -> Path:
|
||||
for parent, dirs, _ in os.walk(metadata_directory):
|
||||
candidates = [f for f in dirs if f.endswith(suffix)]
|
||||
|
||||
if len(candidates) != 0 or len(dirs) != 1:
|
||||
assert len(candidates) == 1, f"Multiple {suffix} directories found"
|
||||
return Path(parent, candidates[0])
|
||||
|
||||
msg = f"No {suffix} directory found in {metadata_directory}"
|
||||
raise errors.InternalError(msg)
|
||||
|
||||
def prepare_metadata_for_build_wheel(self, metadata_directory,
|
||||
config_settings=None):
|
||||
sys.argv = sys.argv[:1] + [
|
||||
'dist_info', '--egg-base', metadata_directory]
|
||||
sys.argv = [
|
||||
*sys.argv[:1],
|
||||
*self._global_args(config_settings),
|
||||
"dist_info",
|
||||
"--output-dir", metadata_directory,
|
||||
"--keep-egg-info",
|
||||
]
|
||||
with no_install_setup_requires():
|
||||
self.run_setup()
|
||||
|
||||
dist_info_directory = metadata_directory
|
||||
while True:
|
||||
dist_infos = [f for f in os.listdir(dist_info_directory)
|
||||
if f.endswith('.dist-info')]
|
||||
|
||||
if (
|
||||
len(dist_infos) == 0 and
|
||||
len(_get_immediate_subdirectories(dist_info_directory)) == 1
|
||||
):
|
||||
|
||||
dist_info_directory = os.path.join(
|
||||
dist_info_directory, os.listdir(dist_info_directory)[0])
|
||||
continue
|
||||
|
||||
assert len(dist_infos) == 1
|
||||
break
|
||||
|
||||
# PEP 517 requires that the .dist-info directory be placed in the
|
||||
# metadata_directory. To comply, we MUST copy the directory to the root
|
||||
if dist_info_directory != metadata_directory:
|
||||
shutil.move(
|
||||
os.path.join(dist_info_directory, dist_infos[0]),
|
||||
metadata_directory)
|
||||
shutil.rmtree(dist_info_directory, ignore_errors=True)
|
||||
|
||||
return dist_infos[0]
|
||||
self._bubble_up_info_directory(metadata_directory, ".egg-info")
|
||||
return self._bubble_up_info_directory(metadata_directory, ".dist-info")
|
||||
|
||||
def _build_with_temp_dir(self, setup_command, result_extension,
|
||||
result_directory, config_settings):
|
||||
config_settings = self._fix_config(config_settings)
|
||||
result_directory = os.path.abspath(result_directory)
|
||||
|
||||
# Build in a temporary directory, then copy to the target.
|
||||
os.makedirs(result_directory, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
|
||||
sys.argv = (sys.argv[:1] + setup_command +
|
||||
['--dist-dir', tmp_dist_dir] +
|
||||
config_settings["--global-option"])
|
||||
sys.argv = [
|
||||
*sys.argv[:1],
|
||||
*self._global_args(config_settings),
|
||||
*setup_command,
|
||||
"--dist-dir", tmp_dist_dir,
|
||||
*self._arbitrary_args(config_settings),
|
||||
]
|
||||
with no_install_setup_requires():
|
||||
self.run_setup()
|
||||
|
||||
@@ -218,14 +408,49 @@ class _BuildMetaBackend(object):
|
||||
|
||||
def build_wheel(self, wheel_directory, config_settings=None,
|
||||
metadata_directory=None):
|
||||
return self._build_with_temp_dir(['bdist_wheel'], '.whl',
|
||||
wheel_directory, config_settings)
|
||||
with suppress_known_deprecation():
|
||||
return self._build_with_temp_dir(['bdist_wheel'], '.whl',
|
||||
wheel_directory, config_settings)
|
||||
|
||||
def build_sdist(self, sdist_directory, config_settings=None):
|
||||
return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],
|
||||
'.tar.gz', sdist_directory,
|
||||
config_settings)
|
||||
|
||||
def _get_dist_info_dir(self, metadata_directory: Optional[str]) -> Optional[str]:
|
||||
if not metadata_directory:
|
||||
return None
|
||||
dist_info_candidates = list(Path(metadata_directory).glob("*.dist-info"))
|
||||
assert len(dist_info_candidates) <= 1
|
||||
return str(dist_info_candidates[0]) if dist_info_candidates else None
|
||||
|
||||
if not LEGACY_EDITABLE:
|
||||
|
||||
# PEP660 hooks:
|
||||
# build_editable
|
||||
# get_requires_for_build_editable
|
||||
# prepare_metadata_for_build_editable
|
||||
def build_editable(
|
||||
self, wheel_directory, config_settings=None, metadata_directory=None
|
||||
):
|
||||
# XXX can or should we hide our editable_wheel command normally?
|
||||
info_dir = self._get_dist_info_dir(metadata_directory)
|
||||
opts = ["--dist-info-dir", info_dir] if info_dir else []
|
||||
cmd = ["editable_wheel", *opts, *self._editable_args(config_settings)]
|
||||
with suppress_known_deprecation():
|
||||
return self._build_with_temp_dir(
|
||||
cmd, ".whl", wheel_directory, config_settings
|
||||
)
|
||||
|
||||
def get_requires_for_build_editable(self, config_settings=None):
|
||||
return self.get_requires_for_build_wheel(config_settings)
|
||||
|
||||
def prepare_metadata_for_build_editable(self, metadata_directory,
|
||||
config_settings=None):
|
||||
return self.prepare_metadata_for_build_wheel(
|
||||
metadata_directory, config_settings
|
||||
)
|
||||
|
||||
|
||||
class _BuildMetaLegacyBackend(_BuildMetaBackend):
|
||||
"""Compatibility backend for setuptools
|
||||
@@ -276,6 +501,11 @@ prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
|
||||
build_wheel = _BACKEND.build_wheel
|
||||
build_sdist = _BACKEND.build_sdist
|
||||
|
||||
if not LEGACY_EDITABLE:
|
||||
get_requires_for_build_editable = _BACKEND.get_requires_for_build_editable
|
||||
prepare_metadata_for_build_editable = _BACKEND.prepare_metadata_for_build_editable
|
||||
build_editable = _BACKEND.build_editable
|
||||
|
||||
|
||||
# The legacy backend
|
||||
__legacy__ = _BuildMetaLegacyBackend()
|
||||
|
||||
@@ -2,7 +2,11 @@ from distutils.command.bdist import bdist
|
||||
import sys
|
||||
|
||||
if 'egg' not in bdist.format_commands:
|
||||
bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
|
||||
bdist.format_commands.append('egg')
|
||||
try:
|
||||
bdist.format_commands['egg'] = ('bdist_egg', "Python .egg file")
|
||||
except TypeError:
|
||||
# For backward compatibility with older distutils (stdlib)
|
||||
bdist.format_command['egg'] = ('bdist_egg', "Python .egg file")
|
||||
bdist.format_commands.append('egg')
|
||||
|
||||
del bdist, sys
|
||||
|
||||
@@ -11,9 +11,10 @@ import re
|
||||
import textwrap
|
||||
import marshal
|
||||
|
||||
from pkg_resources import get_build_platform, Distribution, ensure_directory
|
||||
from pkg_resources import get_build_platform, Distribution
|
||||
from setuptools.extension import Library
|
||||
from setuptools import Command
|
||||
from .._path import ensure_directory
|
||||
|
||||
from sysconfig import get_path, get_python_version
|
||||
|
||||
|
||||
@@ -2,14 +2,16 @@ import os
|
||||
import sys
|
||||
import itertools
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from importlib.util import cache_from_source as _compiled_file_name
|
||||
from typing import Dict, Iterator, List, Tuple
|
||||
|
||||
from distutils.command.build_ext import build_ext as _du_build_ext
|
||||
from distutils.file_util import copy_file
|
||||
from distutils.ccompiler import new_compiler
|
||||
from distutils.sysconfig import customize_compiler, get_config_var
|
||||
from distutils.errors import DistutilsError
|
||||
from distutils import log
|
||||
|
||||
from setuptools.extension import Library
|
||||
from setuptools.errors import BaseError
|
||||
from setuptools.extension import Extension, Library
|
||||
|
||||
try:
|
||||
# Attempt to use Cython for building extensions, if available
|
||||
@@ -73,6 +75,9 @@ def get_abi3_suffix():
|
||||
|
||||
|
||||
class build_ext(_build_ext):
|
||||
editable_mode: bool = False
|
||||
inplace: bool = False
|
||||
|
||||
def run(self):
|
||||
"""Build extensions in build directory, then copy if --inplace"""
|
||||
old_inplace, self.inplace = self.inplace, 0
|
||||
@@ -81,27 +86,62 @@ class build_ext(_build_ext):
|
||||
if old_inplace:
|
||||
self.copy_extensions_to_source()
|
||||
|
||||
def _get_inplace_equivalent(self, build_py, ext: Extension) -> Tuple[str, str]:
|
||||
fullname = self.get_ext_fullname(ext.name)
|
||||
filename = self.get_ext_filename(fullname)
|
||||
modpath = fullname.split('.')
|
||||
package = '.'.join(modpath[:-1])
|
||||
package_dir = build_py.get_package_dir(package)
|
||||
inplace_file = os.path.join(package_dir, os.path.basename(filename))
|
||||
regular_file = os.path.join(self.build_lib, filename)
|
||||
return (inplace_file, regular_file)
|
||||
|
||||
def copy_extensions_to_source(self):
|
||||
build_py = self.get_finalized_command('build_py')
|
||||
for ext in self.extensions:
|
||||
fullname = self.get_ext_fullname(ext.name)
|
||||
filename = self.get_ext_filename(fullname)
|
||||
modpath = fullname.split('.')
|
||||
package = '.'.join(modpath[:-1])
|
||||
package_dir = build_py.get_package_dir(package)
|
||||
dest_filename = os.path.join(package_dir,
|
||||
os.path.basename(filename))
|
||||
src_filename = os.path.join(self.build_lib, filename)
|
||||
inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext)
|
||||
|
||||
# Always copy, even if source is older than destination, to ensure
|
||||
# that the right extensions for the current Python/platform are
|
||||
# used.
|
||||
copy_file(
|
||||
src_filename, dest_filename, verbose=self.verbose,
|
||||
dry_run=self.dry_run
|
||||
)
|
||||
if os.path.exists(regular_file) or not ext.optional:
|
||||
self.copy_file(regular_file, inplace_file, level=self.verbose)
|
||||
|
||||
if ext._needs_stub:
|
||||
self.write_stub(package_dir or os.curdir, ext, True)
|
||||
inplace_stub = self._get_equivalent_stub(ext, inplace_file)
|
||||
self._write_stub_file(inplace_stub, ext, compile=True)
|
||||
# Always compile stub and remove the original (leave the cache behind)
|
||||
# (this behaviour was observed in previous iterations of the code)
|
||||
|
||||
def _get_equivalent_stub(self, ext: Extension, output_file: str) -> str:
|
||||
dir_ = os.path.dirname(output_file)
|
||||
_, _, name = ext.name.rpartition(".")
|
||||
return f"{os.path.join(dir_, name)}.py"
|
||||
|
||||
def _get_output_mapping(self) -> Iterator[Tuple[str, str]]:
|
||||
if not self.inplace:
|
||||
return
|
||||
|
||||
build_py = self.get_finalized_command('build_py')
|
||||
opt = self.get_finalized_command('install_lib').optimize or ""
|
||||
|
||||
for ext in self.extensions:
|
||||
inplace_file, regular_file = self._get_inplace_equivalent(build_py, ext)
|
||||
yield (regular_file, inplace_file)
|
||||
|
||||
if ext._needs_stub:
|
||||
# This version of `build_ext` always builds artifacts in another dir,
|
||||
# when "inplace=True" is given it just copies them back.
|
||||
# This is done in the `copy_extensions_to_source` function, which
|
||||
# always compile stub files via `_compile_and_remove_stub`.
|
||||
# At the end of the process, a `.pyc` stub file is created without the
|
||||
# corresponding `.py`.
|
||||
|
||||
inplace_stub = self._get_equivalent_stub(ext, inplace_file)
|
||||
regular_stub = self._get_equivalent_stub(ext, regular_file)
|
||||
inplace_cache = _compiled_file_name(inplace_stub, optimization=opt)
|
||||
output_cache = _compiled_file_name(regular_stub, optimization=opt)
|
||||
yield (output_cache, inplace_cache)
|
||||
|
||||
def get_ext_filename(self, fullname):
|
||||
so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX')
|
||||
@@ -131,6 +171,7 @@ class build_ext(_build_ext):
|
||||
self.shlib_compiler = None
|
||||
self.shlibs = []
|
||||
self.ext_map = {}
|
||||
self.editable_mode = False
|
||||
|
||||
def finalize_options(self):
|
||||
_build_ext.finalize_options(self)
|
||||
@@ -161,6 +202,9 @@ class build_ext(_build_ext):
|
||||
if ltd and use_stubs and os.curdir not in ext.runtime_library_dirs:
|
||||
ext.runtime_library_dirs.append(os.curdir)
|
||||
|
||||
if self.editable_mode:
|
||||
self.inplace = True
|
||||
|
||||
def setup_shlib_compiler(self):
|
||||
compiler = self.shlib_compiler = new_compiler(
|
||||
compiler=self.compiler, dry_run=self.dry_run, force=self.force
|
||||
@@ -201,8 +245,8 @@ class build_ext(_build_ext):
|
||||
self.compiler = self.shlib_compiler
|
||||
_build_ext.build_extension(self, ext)
|
||||
if ext._needs_stub:
|
||||
cmd = self.get_finalized_command('build_py').build_lib
|
||||
self.write_stub(cmd, ext)
|
||||
build_lib = self.get_finalized_command('build_py').build_lib
|
||||
self.write_stub(build_lib, ext)
|
||||
finally:
|
||||
self.compiler = _compiler
|
||||
|
||||
@@ -215,8 +259,15 @@ class build_ext(_build_ext):
|
||||
pkg = '.'.join(ext._full_name.split('.')[:-1] + [''])
|
||||
return any(pkg + libname in libnames for libname in ext.libraries)
|
||||
|
||||
def get_outputs(self):
|
||||
return _build_ext.get_outputs(self) + self.__get_stubs_outputs()
|
||||
def get_outputs(self) -> List[str]:
|
||||
if self.inplace:
|
||||
return list(self.get_output_mapping().keys())
|
||||
return sorted(_build_ext.get_outputs(self) + self.__get_stubs_outputs())
|
||||
|
||||
def get_output_mapping(self) -> Dict[str, str]:
|
||||
"""See :class:`setuptools.commands.build.SubCommand`"""
|
||||
mapping = self._get_output_mapping()
|
||||
return dict(sorted(mapping, key=lambda x: x[0]))
|
||||
|
||||
def __get_stubs_outputs(self):
|
||||
# assemble the base name for each extension that needs a stub
|
||||
@@ -236,12 +287,13 @@ class build_ext(_build_ext):
|
||||
yield '.pyo'
|
||||
|
||||
def write_stub(self, output_dir, ext, compile=False):
|
||||
log.info("writing stub loader for %s to %s", ext._full_name,
|
||||
output_dir)
|
||||
stub_file = (os.path.join(output_dir, *ext._full_name.split('.')) +
|
||||
'.py')
|
||||
stub_file = os.path.join(output_dir, *ext._full_name.split('.')) + '.py'
|
||||
self._write_stub_file(stub_file, ext, compile)
|
||||
|
||||
def _write_stub_file(self, stub_file: str, ext: Extension, compile=False):
|
||||
log.info("writing stub loader for %s to %s", ext._full_name, stub_file)
|
||||
if compile and os.path.exists(stub_file):
|
||||
raise DistutilsError(stub_file + " already exists! Please delete.")
|
||||
raise BaseError(stub_file + " already exists! Please delete.")
|
||||
if not self.dry_run:
|
||||
f = open(stub_file, 'w')
|
||||
f.write(
|
||||
@@ -274,16 +326,19 @@ class build_ext(_build_ext):
|
||||
)
|
||||
f.close()
|
||||
if compile:
|
||||
from distutils.util import byte_compile
|
||||
self._compile_and_remove_stub(stub_file)
|
||||
|
||||
byte_compile([stub_file], optimize=0,
|
||||
def _compile_and_remove_stub(self, stub_file: str):
|
||||
from distutils.util import byte_compile
|
||||
|
||||
byte_compile([stub_file], optimize=0,
|
||||
force=True, dry_run=self.dry_run)
|
||||
optimize = self.get_finalized_command('install_lib').optimize
|
||||
if optimize > 0:
|
||||
byte_compile([stub_file], optimize=optimize,
|
||||
force=True, dry_run=self.dry_run)
|
||||
optimize = self.get_finalized_command('install_lib').optimize
|
||||
if optimize > 0:
|
||||
byte_compile([stub_file], optimize=optimize,
|
||||
force=True, dry_run=self.dry_run)
|
||||
if os.path.exists(stub_file) and not self.dry_run:
|
||||
os.unlink(stub_file)
|
||||
if os.path.exists(stub_file) and not self.dry_run:
|
||||
os.unlink(stub_file)
|
||||
|
||||
|
||||
if use_stubs or os.name == 'nt':
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from functools import partial
|
||||
from glob import glob
|
||||
from distutils.util import convert_path
|
||||
import distutils.command.build_py as orig
|
||||
@@ -8,6 +9,11 @@ import io
|
||||
import distutils.errors
|
||||
import itertools
|
||||
import stat
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, Iterator, List, Optional, Tuple
|
||||
|
||||
from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
|
||||
from setuptools.extern.more_itertools import unique_everseen
|
||||
|
||||
|
||||
@@ -24,6 +30,8 @@ class build_py(orig.build_py):
|
||||
Also, this version of the 'build_py' command allows you to specify both
|
||||
'py_modules' and 'packages' in the same setup operation.
|
||||
"""
|
||||
editable_mode: bool = False
|
||||
existing_egg_info_dir: Optional[str] = None #: Private API, internal use only.
|
||||
|
||||
def finalize_options(self):
|
||||
orig.build_py.finalize_options(self)
|
||||
@@ -33,9 +41,18 @@ class build_py(orig.build_py):
|
||||
del self.__dict__['data_files']
|
||||
self.__updated_files = []
|
||||
|
||||
def copy_file(self, infile, outfile, preserve_mode=1, preserve_times=1,
|
||||
link=None, level=1):
|
||||
# Overwrite base class to allow using links
|
||||
if link:
|
||||
infile = str(Path(infile).resolve())
|
||||
outfile = str(Path(outfile).resolve())
|
||||
return super().copy_file(infile, outfile, preserve_mode, preserve_times,
|
||||
link, level)
|
||||
|
||||
def run(self):
|
||||
"""Build modules, packages, and copy data files to build directory"""
|
||||
if not self.py_modules and not self.packages:
|
||||
if not (self.py_modules or self.packages) or self.editable_mode:
|
||||
return
|
||||
|
||||
if self.py_modules:
|
||||
@@ -67,6 +84,16 @@ class build_py(orig.build_py):
|
||||
self.analyze_manifest()
|
||||
return list(map(self._get_pkg_data_files, self.packages or ()))
|
||||
|
||||
def get_data_files_without_manifest(self):
|
||||
"""
|
||||
Generate list of ``(package,src_dir,build_dir,filenames)`` tuples,
|
||||
but without triggering any attempt to analyze or build the manifest.
|
||||
"""
|
||||
# Prevent eventual errors from unset `manifest_files`
|
||||
# (that would otherwise be set by `analyze_manifest`)
|
||||
self.__dict__.setdefault('manifest_files', {})
|
||||
return list(map(self._get_pkg_data_files, self.packages or ()))
|
||||
|
||||
def _get_pkg_data_files(self, package):
|
||||
# Locate package source directory
|
||||
src_dir = self.get_package_dir(package)
|
||||
@@ -88,7 +115,7 @@ class build_py(orig.build_py):
|
||||
package,
|
||||
src_dir,
|
||||
)
|
||||
globs_expanded = map(glob, patterns)
|
||||
globs_expanded = map(partial(glob, recursive=True), patterns)
|
||||
# flatten the expanded globs into an iterable of matches
|
||||
globs_matches = itertools.chain.from_iterable(globs_expanded)
|
||||
glob_files = filter(os.path.isfile, globs_matches)
|
||||
@@ -98,16 +125,41 @@ class build_py(orig.build_py):
|
||||
)
|
||||
return self.exclude_data_files(package, src_dir, files)
|
||||
|
||||
def build_package_data(self):
|
||||
"""Copy data files into build directory"""
|
||||
def get_outputs(self, include_bytecode=1) -> List[str]:
|
||||
"""See :class:`setuptools.commands.build.SubCommand`"""
|
||||
if self.editable_mode:
|
||||
return list(self.get_output_mapping().keys())
|
||||
return super().get_outputs(include_bytecode)
|
||||
|
||||
def get_output_mapping(self) -> Dict[str, str]:
|
||||
"""See :class:`setuptools.commands.build.SubCommand`"""
|
||||
mapping = itertools.chain(
|
||||
self._get_package_data_output_mapping(),
|
||||
self._get_module_mapping(),
|
||||
)
|
||||
return dict(sorted(mapping, key=lambda x: x[0]))
|
||||
|
||||
def _get_module_mapping(self) -> Iterator[Tuple[str, str]]:
|
||||
"""Iterate over all modules producing (dest, src) pairs."""
|
||||
for (package, module, module_file) in self.find_all_modules():
|
||||
package = package.split('.')
|
||||
filename = self.get_module_outfile(self.build_lib, package, module)
|
||||
yield (filename, module_file)
|
||||
|
||||
def _get_package_data_output_mapping(self) -> Iterator[Tuple[str, str]]:
|
||||
"""Iterate over package data producing (dest, src) pairs."""
|
||||
for package, src_dir, build_dir, filenames in self.data_files:
|
||||
for filename in filenames:
|
||||
target = os.path.join(build_dir, filename)
|
||||
self.mkpath(os.path.dirname(target))
|
||||
srcfile = os.path.join(src_dir, filename)
|
||||
outf, copied = self.copy_file(srcfile, target)
|
||||
make_writable(target)
|
||||
srcfile = os.path.abspath(srcfile)
|
||||
yield (target, srcfile)
|
||||
|
||||
def build_package_data(self):
|
||||
"""Copy data files into build directory"""
|
||||
for target, srcfile in self._get_package_data_output_mapping():
|
||||
self.mkpath(os.path.dirname(target))
|
||||
_outf, _copied = self.copy_file(srcfile, target)
|
||||
make_writable(target)
|
||||
|
||||
def analyze_manifest(self):
|
||||
self.manifest_files = mf = {}
|
||||
@@ -118,9 +170,21 @@ class build_py(orig.build_py):
|
||||
# Locate package source directory
|
||||
src_dirs[assert_relative(self.get_package_dir(package))] = package
|
||||
|
||||
self.run_command('egg_info')
|
||||
ei_cmd = self.get_finalized_command('egg_info')
|
||||
for path in ei_cmd.filelist.files:
|
||||
if (
|
||||
getattr(self, 'existing_egg_info_dir', None)
|
||||
and Path(self.existing_egg_info_dir, "SOURCES.txt").exists()
|
||||
):
|
||||
egg_info_dir = self.existing_egg_info_dir
|
||||
manifest = Path(egg_info_dir, "SOURCES.txt")
|
||||
files = manifest.read_text(encoding="utf-8").splitlines()
|
||||
else:
|
||||
self.run_command('egg_info')
|
||||
ei_cmd = self.get_finalized_command('egg_info')
|
||||
egg_info_dir = ei_cmd.egg_info
|
||||
files = ei_cmd.filelist.files
|
||||
|
||||
check = _IncludePackageDataAbuse()
|
||||
for path in self._filter_build_files(files, egg_info_dir):
|
||||
d, f = os.path.split(assert_relative(path))
|
||||
prev = None
|
||||
oldf = f
|
||||
@@ -129,10 +193,34 @@ class build_py(orig.build_py):
|
||||
d, df = os.path.split(d)
|
||||
f = os.path.join(df, f)
|
||||
if d in src_dirs:
|
||||
if path.endswith('.py') and f == oldf:
|
||||
continue # it's a module, not data
|
||||
if f == oldf:
|
||||
if check.is_module(f):
|
||||
continue # it's a module, not data
|
||||
else:
|
||||
importable = check.importable_subpackage(src_dirs[d], f)
|
||||
if importable:
|
||||
check.warn(importable)
|
||||
mf.setdefault(src_dirs[d], []).append(path)
|
||||
|
||||
def _filter_build_files(self, files: Iterable[str], egg_info: str) -> Iterator[str]:
|
||||
"""
|
||||
``build_meta`` may try to create egg_info outside of the project directory,
|
||||
and this can be problematic for certain plugins (reported in issue #3500).
|
||||
|
||||
Extensions might also include between their sources files created on the
|
||||
``build_lib`` and ``build_temp`` directories.
|
||||
|
||||
This function should filter this case of invalid files out.
|
||||
"""
|
||||
build = self.get_finalized_command("build")
|
||||
build_dirs = (egg_info, self.build_lib, build.build_temp, build.build_base)
|
||||
norm_dirs = [os.path.normpath(p) for p in build_dirs if p]
|
||||
|
||||
for file in files:
|
||||
norm_path = os.path.normpath(file)
|
||||
if not os.path.isabs(file) or all(d not in norm_path for d in norm_dirs):
|
||||
yield file
|
||||
|
||||
def get_data_files(self):
|
||||
pass # Lazily compute data files in _get_data_files() function.
|
||||
|
||||
@@ -169,6 +257,8 @@ class build_py(orig.build_py):
|
||||
def initialize_options(self):
|
||||
self.packages_checked = {}
|
||||
orig.build_py.initialize_options(self)
|
||||
self.editable_mode = False
|
||||
self.existing_egg_info_dir = None
|
||||
|
||||
def get_package_dir(self, package):
|
||||
res = orig.build_py.get_package_dir(self, package)
|
||||
@@ -230,3 +320,49 @@ def assert_relative(path):
|
||||
% path
|
||||
)
|
||||
raise DistutilsSetupError(msg)
|
||||
|
||||
|
||||
class _IncludePackageDataAbuse:
|
||||
"""Inform users that package or module is included as 'data file'"""
|
||||
|
||||
MESSAGE = """\
|
||||
Installing {importable!r} as data is deprecated, please list it in `packages`.
|
||||
!!\n\n
|
||||
############################
|
||||
# Package would be ignored #
|
||||
############################
|
||||
Python recognizes {importable!r} as an importable package,
|
||||
but it is not listed in the `packages` configuration of setuptools.
|
||||
|
||||
{importable!r} has been automatically added to the distribution only
|
||||
because it may contain data files, but this behavior is likely to change
|
||||
in future versions of setuptools (and therefore is considered deprecated).
|
||||
|
||||
Please make sure that {importable!r} is included as a package by using
|
||||
the `packages` configuration field or the proper discovery methods
|
||||
(for example by using `find_namespace_packages(...)`/`find_namespace:`
|
||||
instead of `find_packages(...)`/`find:`).
|
||||
|
||||
You can read more about "package discovery" and "data files" on setuptools
|
||||
documentation page.
|
||||
\n\n!!
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._already_warned = set()
|
||||
|
||||
def is_module(self, file):
|
||||
return file.endswith(".py") and file[:-len(".py")].isidentifier()
|
||||
|
||||
def importable_subpackage(self, parent, file):
|
||||
pkg = Path(file).parent
|
||||
parts = list(itertools.takewhile(str.isidentifier, pkg.parts))
|
||||
if parts:
|
||||
return ".".join([parent, *parts])
|
||||
return None
|
||||
|
||||
def warn(self, importable):
|
||||
if importable not in self._already_warned:
|
||||
msg = textwrap.dedent(self.MESSAGE).format(importable=importable)
|
||||
warnings.warn(msg, SetuptoolsDeprecationWarning, stacklevel=2)
|
||||
self._already_warned.add(importable)
|
||||
|
||||
@@ -4,9 +4,18 @@ As defined in the wheel specification
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from inspect import cleandoc
|
||||
from pathlib import Path
|
||||
|
||||
from distutils.core import Command
|
||||
from distutils import log
|
||||
from setuptools.extern import packaging
|
||||
from setuptools._deprecation_warning import SetuptoolsDeprecationWarning
|
||||
|
||||
|
||||
class dist_info(Command):
|
||||
@@ -15,22 +24,119 @@ class dist_info(Command):
|
||||
|
||||
user_options = [
|
||||
('egg-base=', 'e', "directory containing .egg-info directories"
|
||||
" (default: top of the source tree)"),
|
||||
" (default: top of the source tree)"
|
||||
" DEPRECATED: use --output-dir."),
|
||||
('output-dir=', 'o', "directory inside of which the .dist-info will be"
|
||||
"created (default: top of the source tree)"),
|
||||
('tag-date', 'd', "Add date stamp (e.g. 20050528) to version number"),
|
||||
('tag-build=', 'b', "Specify explicit tag to add to version number"),
|
||||
('no-date', 'D', "Don't include date stamp [default]"),
|
||||
('keep-egg-info', None, "*TRANSITIONAL* will be removed in the future"),
|
||||
]
|
||||
|
||||
boolean_options = ['tag-date', 'keep-egg-info']
|
||||
negative_opt = {'no-date': 'tag-date'}
|
||||
|
||||
def initialize_options(self):
|
||||
self.egg_base = None
|
||||
self.output_dir = None
|
||||
self.name = None
|
||||
self.dist_info_dir = None
|
||||
self.tag_date = None
|
||||
self.tag_build = None
|
||||
self.keep_egg_info = False
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
if self.egg_base:
|
||||
msg = "--egg-base is deprecated for dist_info command. Use --output-dir."
|
||||
warnings.warn(msg, SetuptoolsDeprecationWarning)
|
||||
self.output_dir = self.egg_base or self.output_dir
|
||||
|
||||
dist = self.distribution
|
||||
project_dir = dist.src_root or os.curdir
|
||||
self.output_dir = Path(self.output_dir or project_dir)
|
||||
|
||||
egg_info = self.reinitialize_command("egg_info")
|
||||
egg_info.egg_base = str(self.output_dir)
|
||||
|
||||
if self.tag_date:
|
||||
egg_info.tag_date = self.tag_date
|
||||
else:
|
||||
self.tag_date = egg_info.tag_date
|
||||
|
||||
if self.tag_build:
|
||||
egg_info.tag_build = self.tag_build
|
||||
else:
|
||||
self.tag_build = egg_info.tag_build
|
||||
|
||||
egg_info.finalize_options()
|
||||
self.egg_info = egg_info
|
||||
|
||||
name = _safe(dist.get_name())
|
||||
version = _version(dist.get_version())
|
||||
self.name = f"{name}-{version}"
|
||||
self.dist_info_dir = os.path.join(self.output_dir, f"{self.name}.dist-info")
|
||||
|
||||
@contextmanager
|
||||
def _maybe_bkp_dir(self, dir_path: str, requires_bkp: bool):
|
||||
if requires_bkp:
|
||||
bkp_name = f"{dir_path}.__bkp__"
|
||||
_rm(bkp_name, ignore_errors=True)
|
||||
_copy(dir_path, bkp_name, dirs_exist_ok=True, symlinks=True)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_rm(dir_path, ignore_errors=True)
|
||||
shutil.move(bkp_name, dir_path)
|
||||
else:
|
||||
yield
|
||||
|
||||
def run(self):
|
||||
egg_info = self.get_finalized_command('egg_info')
|
||||
egg_info.egg_base = self.egg_base
|
||||
egg_info.finalize_options()
|
||||
egg_info.run()
|
||||
dist_info_dir = egg_info.egg_info[:-len('.egg-info')] + '.dist-info'
|
||||
log.info("creating '{}'".format(os.path.abspath(dist_info_dir)))
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.egg_info.run()
|
||||
egg_info_dir = self.egg_info.egg_info
|
||||
assert os.path.isdir(egg_info_dir), ".egg-info dir should have been created"
|
||||
|
||||
log.info("creating '{}'".format(os.path.abspath(self.dist_info_dir)))
|
||||
bdist_wheel = self.get_finalized_command('bdist_wheel')
|
||||
bdist_wheel.egg2dist(egg_info.egg_info, dist_info_dir)
|
||||
|
||||
# TODO: if bdist_wheel if merged into setuptools, just add "keep_egg_info" there
|
||||
with self._maybe_bkp_dir(egg_info_dir, self.keep_egg_info):
|
||||
bdist_wheel.egg2dist(egg_info_dir, self.dist_info_dir)
|
||||
|
||||
|
||||
def _safe(component: str) -> str:
|
||||
"""Escape a component used to form a wheel name according to PEP 491"""
|
||||
return re.sub(r"[^\w\d.]+", "_", component)
|
||||
|
||||
|
||||
def _version(version: str) -> str:
|
||||
"""Convert an arbitrary string to a version string."""
|
||||
v = version.replace(' ', '.')
|
||||
try:
|
||||
return str(packaging.version.Version(v)).replace("-", "_")
|
||||
except packaging.version.InvalidVersion:
|
||||
msg = f"""Invalid version: {version!r}.
|
||||
!!\n\n
|
||||
###################
|
||||
# Invalid version #
|
||||
###################
|
||||
{version!r} is not valid according to PEP 440.\n
|
||||
Please make sure specify a valid version for your package.
|
||||
Also note that future releases of setuptools may halt the build process
|
||||
if an invalid version is given.
|
||||
\n\n!!
|
||||
"""
|
||||
warnings.warn(cleandoc(msg))
|
||||
return _safe(v).strip("_")
|
||||
|
||||
|
||||
def _rm(dir_name, **opts):
|
||||
if os.path.isdir(dir_name):
|
||||
shutil.rmtree(dir_name, **opts)
|
||||
|
||||
|
||||
def _copy(src, dst, **opts):
|
||||
if sys.version_info < (3, 8):
|
||||
opts.pop("dirs_exist_ok", None)
|
||||
shutil.copytree(src, dst, **opts)
|
||||
|
||||
@@ -6,7 +6,7 @@ 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`__.
|
||||
|
||||
__ https://setuptools.readthedocs.io/en/latest/deprecated/easy_install.html
|
||||
__ https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
|
||||
|
||||
"""
|
||||
|
||||
@@ -17,10 +17,10 @@ from distutils.errors import (
|
||||
DistutilsArgError, DistutilsOptionError,
|
||||
DistutilsError, DistutilsPlatformError,
|
||||
)
|
||||
from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS
|
||||
from distutils import log, dir_util
|
||||
from distutils.command.build_scripts import first_line_re
|
||||
from distutils.spawn import find_executable
|
||||
from distutils.command import install
|
||||
import sys
|
||||
import os
|
||||
import zipimport
|
||||
@@ -39,9 +39,10 @@ import subprocess
|
||||
import shlex
|
||||
import io
|
||||
import configparser
|
||||
import sysconfig
|
||||
|
||||
|
||||
from sysconfig import get_config_vars, get_path
|
||||
from sysconfig import get_path
|
||||
|
||||
from setuptools import SetuptoolsDeprecationWarning
|
||||
|
||||
@@ -55,18 +56,21 @@ from setuptools.package_index import (
|
||||
from setuptools.command import bdist_egg, egg_info
|
||||
from setuptools.wheel import Wheel
|
||||
from pkg_resources import (
|
||||
yield_lines, normalize_path, resource_string, ensure_directory,
|
||||
normalize_path, resource_string,
|
||||
get_distribution, find_distributions, Environment, Requirement,
|
||||
Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound,
|
||||
VersionConflict, DEVELOP_DIST,
|
||||
)
|
||||
import pkg_resources
|
||||
from .._path import ensure_directory
|
||||
from ..extern.jaraco.text import yield_lines
|
||||
|
||||
|
||||
# Turn on PEP440Warnings
|
||||
warnings.filterwarnings("default", category=pkg_resources.PEP440Warning)
|
||||
|
||||
__all__ = [
|
||||
'samefile', 'easy_install', 'PthDistributions', 'extract_wininst_cfg',
|
||||
'easy_install', 'PthDistributions', 'extract_wininst_cfg',
|
||||
'get_exe_prefixes',
|
||||
]
|
||||
|
||||
@@ -75,22 +79,6 @@ def is_64bit():
|
||||
return struct.calcsize("P") == 8
|
||||
|
||||
|
||||
def samefile(p1, p2):
|
||||
"""
|
||||
Determine if two paths reference the same file.
|
||||
|
||||
Augments os.path.samefile to work on Windows and
|
||||
suppresses errors if the path doesn't exist.
|
||||
"""
|
||||
both_exist = os.path.exists(p1) and os.path.exists(p2)
|
||||
use_samefile = hasattr(os.path, 'samefile') and both_exist
|
||||
if use_samefile:
|
||||
return os.path.samefile(p1, p2)
|
||||
norm_p1 = os.path.normpath(os.path.normcase(p1))
|
||||
norm_p2 = os.path.normpath(os.path.normcase(p2))
|
||||
return norm_p1 == norm_p2
|
||||
|
||||
|
||||
def _to_bytes(s):
|
||||
return s.encode('utf8')
|
||||
|
||||
@@ -153,6 +141,12 @@ class easy_install(Command):
|
||||
create_index = PackageIndex
|
||||
|
||||
def initialize_options(self):
|
||||
warnings.warn(
|
||||
"easy_install command is deprecated. "
|
||||
"Use build and pip and other standards-based tools.",
|
||||
EasyInstallDeprecationWarning,
|
||||
)
|
||||
|
||||
# the --user option seems to be an opt-in one,
|
||||
# so the default should be False.
|
||||
self.user = 0
|
||||
@@ -175,12 +169,8 @@ class easy_install(Command):
|
||||
self.install_data = None
|
||||
self.install_base = None
|
||||
self.install_platbase = None
|
||||
if site.ENABLE_USER_SITE:
|
||||
self.install_userbase = site.USER_BASE
|
||||
self.install_usersite = site.USER_SITE
|
||||
else:
|
||||
self.install_userbase = None
|
||||
self.install_usersite = None
|
||||
self.install_userbase = site.USER_BASE
|
||||
self.install_usersite = site.USER_SITE
|
||||
self.no_find_links = None
|
||||
|
||||
# Options not specifiable via command line
|
||||
@@ -230,28 +220,38 @@ class easy_install(Command):
|
||||
self.version and self._render_version()
|
||||
|
||||
py_version = sys.version.split()[0]
|
||||
prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix')
|
||||
|
||||
self.config_vars = {
|
||||
self.config_vars = dict(sysconfig.get_config_vars())
|
||||
|
||||
self.config_vars.update({
|
||||
'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,
|
||||
'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}',
|
||||
'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}',
|
||||
'sys_prefix': self.config_vars['prefix'],
|
||||
'sys_exec_prefix': self.config_vars['exec_prefix'],
|
||||
# Only python 3.2+ has abiflags
|
||||
'abiflags': getattr(sys, 'abiflags', ''),
|
||||
}
|
||||
'platlibdir': getattr(sys, 'platlibdir', 'lib'),
|
||||
})
|
||||
with contextlib.suppress(AttributeError):
|
||||
# only for distutils outside stdlib
|
||||
self.config_vars.update({
|
||||
'implementation_lower': install._get_implementation().lower(),
|
||||
'implementation': install._get_implementation(),
|
||||
})
|
||||
|
||||
if site.ENABLE_USER_SITE:
|
||||
self.config_vars['userbase'] = self.install_userbase
|
||||
self.config_vars['usersite'] = self.install_usersite
|
||||
# pypa/distutils#113 Python 3.9 compat
|
||||
self.config_vars.setdefault(
|
||||
'py_version_nodot_plat',
|
||||
getattr(sys, 'windir', '').replace('.', ''),
|
||||
)
|
||||
|
||||
elif self.user:
|
||||
self.config_vars['userbase'] = self.install_userbase
|
||||
self.config_vars['usersite'] = self.install_usersite
|
||||
if self.user and not site.ENABLE_USER_SITE:
|
||||
log.warn("WARNING: The user site-packages directory is disabled.")
|
||||
|
||||
self._fix_install_dir_for_user_site()
|
||||
@@ -287,27 +287,14 @@ class easy_install(Command):
|
||||
self.script_dir = self.install_scripts
|
||||
# default --record from the install command
|
||||
self.set_undefined_options('install', ('record', 'record'))
|
||||
# Should this be moved to the if statement below? It's not used
|
||||
# elsewhere
|
||||
normpath = map(normalize_path, sys.path)
|
||||
self.all_site_dirs = get_site_dirs()
|
||||
if self.site_dirs is not None:
|
||||
site_dirs = [
|
||||
os.path.expanduser(s.strip()) for s in
|
||||
self.site_dirs.split(',')
|
||||
]
|
||||
for d in site_dirs:
|
||||
if not os.path.isdir(d):
|
||||
log.warn("%s (in --site-dirs) does not exist", d)
|
||||
elif normalize_path(d) not in normpath:
|
||||
raise DistutilsOptionError(
|
||||
d + " (in --site-dirs) is not on sys.path"
|
||||
)
|
||||
else:
|
||||
self.all_site_dirs.append(normalize_path(d))
|
||||
self.all_site_dirs.extend(self._process_site_dirs(self.site_dirs))
|
||||
|
||||
if not self.editable:
|
||||
self.check_site_dir()
|
||||
self.index_url = self.index_url or "https://pypi.org/simple/"
|
||||
default_index = os.getenv("__EASYINSTALL_INDEX", "https://pypi.org/simple/")
|
||||
# ^ Private API for testing purposes only
|
||||
self.index_url = self.index_url or default_index
|
||||
self.shadow_path = self.all_site_dirs[:]
|
||||
for path_item in self.install_dir, normalize_path(self.script_dir):
|
||||
if path_item not in self.shadow_path:
|
||||
@@ -333,15 +320,7 @@ class easy_install(Command):
|
||||
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:
|
||||
self.optimize = int(self.optimize)
|
||||
if not (0 <= self.optimize <= 2):
|
||||
raise ValueError
|
||||
except ValueError as e:
|
||||
raise DistutilsOptionError(
|
||||
"--optimize must be 0, 1, or 2"
|
||||
) from e
|
||||
self.optimize = self._validate_optimize(self.optimize)
|
||||
|
||||
if self.editable and not self.build_directory:
|
||||
raise DistutilsArgError(
|
||||
@@ -353,11 +332,44 @@ class easy_install(Command):
|
||||
|
||||
self.outputs = []
|
||||
|
||||
@staticmethod
|
||||
def _process_site_dirs(site_dirs):
|
||||
if site_dirs is None:
|
||||
return
|
||||
|
||||
normpath = map(normalize_path, sys.path)
|
||||
site_dirs = [
|
||||
os.path.expanduser(s.strip()) for s in
|
||||
site_dirs.split(',')
|
||||
]
|
||||
for d in site_dirs:
|
||||
if not os.path.isdir(d):
|
||||
log.warn("%s (in --site-dirs) does not exist", d)
|
||||
elif normalize_path(d) not in normpath:
|
||||
raise DistutilsOptionError(
|
||||
d + " (in --site-dirs) is not on sys.path"
|
||||
)
|
||||
else:
|
||||
yield normalize_path(d)
|
||||
|
||||
@staticmethod
|
||||
def _validate_optimize(value):
|
||||
try:
|
||||
value = int(value)
|
||||
if value not in range(3):
|
||||
raise ValueError
|
||||
except ValueError as e:
|
||||
raise DistutilsOptionError(
|
||||
"--optimize must be 0, 1, or 2"
|
||||
) from e
|
||||
|
||||
return value
|
||||
|
||||
def _fix_install_dir_for_user_site(self):
|
||||
"""
|
||||
Fix the install_dir if "--user" was used.
|
||||
"""
|
||||
if not self.user or not site.ENABLE_USER_SITE:
|
||||
if not self.user:
|
||||
return
|
||||
|
||||
self.create_home_path()
|
||||
@@ -365,7 +377,7 @@ class easy_install(Command):
|
||||
msg = "User base directory is not specified"
|
||||
raise DistutilsPlatformError(msg)
|
||||
self.install_base = self.install_platbase = self.install_userbase
|
||||
scheme_name = os.name.replace('posix', 'unix') + '_user'
|
||||
scheme_name = f'{os.name}_user'
|
||||
self.select_scheme(scheme_name)
|
||||
|
||||
def _expand_attrs(self, attrs):
|
||||
@@ -513,7 +525,7 @@ class easy_install(Command):
|
||||
For information on other options, you may wish to consult the
|
||||
documentation at:
|
||||
|
||||
https://setuptools.readthedocs.io/en/latest/deprecated/easy_install.html
|
||||
https://setuptools.pypa.io/en/latest/deprecated/easy_install.html
|
||||
|
||||
Please make the appropriate changes for your system and try again.
|
||||
""").lstrip() # noqa
|
||||
@@ -705,13 +717,11 @@ class easy_install(Command):
|
||||
return dist
|
||||
|
||||
def select_scheme(self, name):
|
||||
"""Sets the install directories by applying the install schemes."""
|
||||
# it's the caller's problem if they supply a bad name!
|
||||
scheme = INSTALL_SCHEMES[name]
|
||||
for key in SCHEME_KEYS:
|
||||
attrname = 'install_' + key
|
||||
if getattr(self, attrname) is None:
|
||||
setattr(self, attrname, scheme[key])
|
||||
try:
|
||||
install._select_scheme(self, name)
|
||||
except AttributeError:
|
||||
# stdlib distutils
|
||||
install.install.select_scheme(self, name.replace('posix', 'unix'))
|
||||
|
||||
# FIXME: 'easy_install.process_distribution' is too complex (12)
|
||||
def process_distribution( # noqa: C901
|
||||
@@ -908,7 +918,9 @@ class easy_install(Command):
|
||||
ensure_directory(destination)
|
||||
|
||||
dist = self.egg_distribution(egg_path)
|
||||
if not samefile(egg_path, destination):
|
||||
if not (
|
||||
os.path.exists(destination) and os.path.samefile(egg_path, destination)
|
||||
):
|
||||
if os.path.isdir(destination) and not os.path.islink(destination):
|
||||
dir_util.remove_tree(destination, dry_run=self.dry_run)
|
||||
elif os.path.exists(destination):
|
||||
@@ -1306,7 +1318,7 @@ class easy_install(Command):
|
||||
* You can set up the installation directory to support ".pth" files by
|
||||
using one of the approaches described here:
|
||||
|
||||
https://setuptools.readthedocs.io/en/latest/deprecated/easy_install.html#custom-installation-locations
|
||||
https://setuptools.pypa.io/en/latest/deprecated/easy_install.html#custom-installation-locations
|
||||
|
||||
|
||||
Please make the appropriate changes for your system and try again.
|
||||
@@ -1317,7 +1329,7 @@ class easy_install(Command):
|
||||
if not self.user:
|
||||
return
|
||||
home = convert_path(os.path.expanduser("~"))
|
||||
for name, path in self.config_vars.items():
|
||||
for path in only_strs(self.config_vars.values()):
|
||||
if path.startswith(home) and not os.path.isdir(path):
|
||||
self.debug_print("os.makedirs('%s', 0o700)" % path)
|
||||
os.makedirs(path, 0o700)
|
||||
@@ -1339,7 +1351,7 @@ class easy_install(Command):
|
||||
|
||||
if self.prefix:
|
||||
# Set default install_dir/scripts from --prefix
|
||||
config_vars = config_vars.copy()
|
||||
config_vars = dict(config_vars)
|
||||
config_vars['base'] = self.prefix
|
||||
scheme = self.INSTALL_SCHEMES.get(os.name, self.DEFAULT_SCHEME)
|
||||
for attr, val in scheme.items():
|
||||
@@ -1566,7 +1578,7 @@ class PthDistributions(Environment):
|
||||
self.sitedirs = list(map(normalize_path, sitedirs))
|
||||
self.basedir = normalize_path(os.path.dirname(self.filename))
|
||||
self._load()
|
||||
Environment.__init__(self, [], None, None)
|
||||
super().__init__([], None, None)
|
||||
for path in yield_lines(self.paths):
|
||||
list(map(self.add, find_distributions(path, True)))
|
||||
|
||||
@@ -1639,14 +1651,14 @@ class PthDistributions(Environment):
|
||||
if new_path:
|
||||
self.paths.append(dist.location)
|
||||
self.dirty = True
|
||||
Environment.add(self, dist)
|
||||
super().add(dist)
|
||||
|
||||
def remove(self, dist):
|
||||
"""Remove `dist` from the distribution map"""
|
||||
while dist.location in self.paths:
|
||||
self.paths.remove(dist.location)
|
||||
self.dirty = True
|
||||
Environment.remove(self, dist)
|
||||
super().remove(dist)
|
||||
|
||||
def make_relative(self, path):
|
||||
npath, last = os.path.split(normalize_path(path))
|
||||
@@ -2263,7 +2275,10 @@ def get_win_launcher(type):
|
||||
"""
|
||||
launcher_fn = '%s.exe' % type
|
||||
if is_64bit():
|
||||
launcher_fn = launcher_fn.replace(".", "-64.")
|
||||
if get_platform() == "win-arm64":
|
||||
launcher_fn = launcher_fn.replace(".", "-arm64.")
|
||||
else:
|
||||
launcher_fn = launcher_fn.replace(".", "-64.")
|
||||
else:
|
||||
launcher_fn = launcher_fn.replace(".", "-32.")
|
||||
return resource_string('setuptools', launcher_fn)
|
||||
@@ -2284,6 +2299,13 @@ def current_umask():
|
||||
return tmp
|
||||
|
||||
|
||||
def only_strs(values):
|
||||
"""
|
||||
Exclude non-str values. Ref #3063.
|
||||
"""
|
||||
return filter(lambda val: isinstance(val, str), values)
|
||||
|
||||
|
||||
class EasyInstallDeprecationWarning(SetuptoolsDeprecationWarning):
|
||||
"""
|
||||
Warning for EasyInstall deprecations, bypassing suppression.
|
||||
|
||||
@@ -17,18 +17,22 @@ import warnings
|
||||
import time
|
||||
import collections
|
||||
|
||||
from .._importlib import metadata
|
||||
from .. import _entry_points
|
||||
|
||||
from setuptools import Command
|
||||
from setuptools.command.sdist import sdist
|
||||
from setuptools.command.sdist import walk_revctrl
|
||||
from setuptools.command.setopt import edit_config
|
||||
from setuptools.command import bdist_egg
|
||||
from pkg_resources import (
|
||||
parse_requirements, safe_name, parse_version,
|
||||
safe_version, yield_lines, EntryPoint, iter_entry_points, to_filename)
|
||||
Requirement, safe_name, parse_version,
|
||||
safe_version, to_filename)
|
||||
import setuptools.unicode_utils as unicode_utils
|
||||
from setuptools.glob import glob
|
||||
|
||||
from setuptools.extern import packaging
|
||||
from setuptools.extern.jaraco.text import yield_lines
|
||||
from setuptools import SetuptoolsDeprecationWarning
|
||||
|
||||
|
||||
@@ -132,11 +136,21 @@ class InfoCommon:
|
||||
in which case the version string already contains all tags.
|
||||
"""
|
||||
return (
|
||||
version if self.vtags and version.endswith(self.vtags)
|
||||
version if self.vtags and self._already_tagged(version)
|
||||
else version + self.vtags
|
||||
)
|
||||
|
||||
def tags(self):
|
||||
def _already_tagged(self, version: str) -> bool:
|
||||
# Depending on their format, tags may change with version normalization.
|
||||
# So in addition the regular tags, we have to search for the normalized ones.
|
||||
return version.endswith(self.vtags) or version.endswith(self._safe_tags())
|
||||
|
||||
def _safe_tags(self) -> str:
|
||||
# To implement this we can rely on `safe_version` pretending to be version 0
|
||||
# followed by tags. Then we simply discard the starting 0 (fake version number)
|
||||
return safe_version(f"0{self.vtags}")[1:]
|
||||
|
||||
def tags(self) -> str:
|
||||
version = ''
|
||||
if self.tag_build:
|
||||
version += self.tag_build
|
||||
@@ -168,6 +182,7 @@ class egg_info(InfoCommon, Command):
|
||||
self.egg_info = None
|
||||
self.egg_version = None
|
||||
self.broken_egg_info = False
|
||||
self.ignore_egg_info_in_manifest = False
|
||||
|
||||
####################################
|
||||
# allow the 'tag_svn_revision' to be detected and
|
||||
@@ -205,12 +220,8 @@ class egg_info(InfoCommon, Command):
|
||||
|
||||
try:
|
||||
is_version = isinstance(parsed_version, packaging.version.Version)
|
||||
spec = (
|
||||
"%s==%s" if is_version else "%s===%s"
|
||||
)
|
||||
list(
|
||||
parse_requirements(spec % (self.egg_name, self.egg_version))
|
||||
)
|
||||
spec = "%s==%s" if is_version else "%s===%s"
|
||||
Requirement(spec % (self.egg_name, self.egg_version))
|
||||
except ValueError as e:
|
||||
raise distutils.errors.DistutilsOptionError(
|
||||
"Invalid distribution name or version syntax: %s-%s" %
|
||||
@@ -285,10 +296,8 @@ class egg_info(InfoCommon, Command):
|
||||
def run(self):
|
||||
self.mkpath(self.egg_info)
|
||||
os.utime(self.egg_info, None)
|
||||
installer = self.distribution.fetch_build_egg
|
||||
for ep in iter_entry_points('egg_info.writers'):
|
||||
ep.require(installer=installer)
|
||||
writer = ep.resolve()
|
||||
for ep in metadata.entry_points(group='egg_info.writers'):
|
||||
writer = ep.load()
|
||||
writer(self, ep.name, os.path.join(self.egg_info, ep.name))
|
||||
|
||||
# Get rid of native_libs.txt if it was put there by older bdist_egg
|
||||
@@ -302,6 +311,7 @@ class egg_info(InfoCommon, Command):
|
||||
"""Generate SOURCES.txt manifest file"""
|
||||
manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
|
||||
mm = manifest_maker(self.distribution)
|
||||
mm.ignore_egg_info_dir = self.ignore_egg_info_in_manifest
|
||||
mm.manifest = manifest_filename
|
||||
mm.run()
|
||||
self.filelist = mm.filelist
|
||||
@@ -325,6 +335,10 @@ class egg_info(InfoCommon, Command):
|
||||
class FileList(_FileList):
|
||||
# Implementations of the various MANIFEST.in commands
|
||||
|
||||
def __init__(self, warn=None, debug_print=None, ignore_egg_info_dir=False):
|
||||
super().__init__(warn, debug_print)
|
||||
self.ignore_egg_info_dir = ignore_egg_info_dir
|
||||
|
||||
def process_template_line(self, line):
|
||||
# Parse the line: split it up, make sure the right number of words
|
||||
# is there, and return the relevant words. 'action' is always
|
||||
@@ -514,6 +528,10 @@ class FileList(_FileList):
|
||||
return False
|
||||
|
||||
try:
|
||||
# ignore egg-info paths
|
||||
is_egg_info = ".egg-info" in u_path or b".egg-info" in utf8_path
|
||||
if self.ignore_egg_info_dir and is_egg_info:
|
||||
return False
|
||||
# accept is either way checks out
|
||||
if os.path.exists(u_path) or os.path.exists(utf8_path):
|
||||
return True
|
||||
@@ -530,12 +548,13 @@ class manifest_maker(sdist):
|
||||
self.prune = 1
|
||||
self.manifest_only = 1
|
||||
self.force_manifest = 1
|
||||
self.ignore_egg_info_dir = False
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
self.filelist = FileList()
|
||||
self.filelist = FileList(ignore_egg_info_dir=self.ignore_egg_info_dir)
|
||||
if not os.path.exists(self.manifest):
|
||||
self.write_manifest() # it must exist so it'll get in the list
|
||||
self.add_defaults()
|
||||
@@ -608,6 +627,27 @@ class manifest_maker(sdist):
|
||||
self.filelist.exclude_pattern(r'(^|' + sep + r')(RCS|CVS|\.svn)' + sep,
|
||||
is_regex=1)
|
||||
|
||||
def _safe_data_files(self, build_py):
|
||||
"""
|
||||
The parent class implementation of this method
|
||||
(``sdist``) will try to include data files, which
|
||||
might cause recursion problems when
|
||||
``include_package_data=True``.
|
||||
|
||||
Therefore, avoid triggering any attempt of
|
||||
analyzing/building the manifest again.
|
||||
"""
|
||||
if hasattr(build_py, 'get_data_files_without_manifest'):
|
||||
return build_py.get_data_files_without_manifest()
|
||||
|
||||
warnings.warn(
|
||||
"Custom 'build_py' does not implement "
|
||||
"'get_data_files_without_manifest'.\nPlease extend command classes"
|
||||
" from setuptools instead of distutils.",
|
||||
SetuptoolsDeprecationWarning
|
||||
)
|
||||
return build_py.get_data_files()
|
||||
|
||||
|
||||
def write_file(filename, contents):
|
||||
"""Create a file with the specified name and write 'contents' (a
|
||||
@@ -698,20 +738,9 @@ def write_arg(cmd, basename, filename, force=False):
|
||||
|
||||
|
||||
def write_entries(cmd, basename, filename):
|
||||
ep = cmd.distribution.entry_points
|
||||
|
||||
if isinstance(ep, str) or ep is None:
|
||||
data = ep
|
||||
elif ep is not None:
|
||||
data = []
|
||||
for section, contents in sorted(ep.items()):
|
||||
if not isinstance(contents, str):
|
||||
contents = EntryPoint.parse_group(section, contents)
|
||||
contents = '\n'.join(sorted(map(str, contents.values())))
|
||||
data.append('[%s]\n%s\n\n' % (section, contents))
|
||||
data = ''.join(data)
|
||||
|
||||
cmd.write_or_delete_file('entry points', filename, data, True)
|
||||
eps = _entry_points.load(cmd.distribution.entry_points)
|
||||
defn = _entry_points.render(eps)
|
||||
cmd.write_or_delete_file('entry points', filename, defn, True)
|
||||
|
||||
|
||||
def get_pkg_info_revision():
|
||||
|
||||
@@ -30,6 +30,13 @@ class install(orig.install):
|
||||
_nc = dict(new_commands)
|
||||
|
||||
def initialize_options(self):
|
||||
|
||||
warnings.warn(
|
||||
"setup.py install is deprecated. "
|
||||
"Use build and pip and other standards-based tools.",
|
||||
setuptools.SetuptoolsDeprecationWarning,
|
||||
)
|
||||
|
||||
orig.install.initialize_options(self)
|
||||
self.old_and_unmanageable = None
|
||||
self.single_version_externally_managed = None
|
||||
@@ -84,14 +91,21 @@ class install(orig.install):
|
||||
msg = "For best results, pass -X:Frames to enable call stack."
|
||||
warnings.warn(msg)
|
||||
return True
|
||||
res = inspect.getouterframes(run_frame)[2]
|
||||
caller, = res[:1]
|
||||
info = inspect.getframeinfo(caller)
|
||||
caller_module = caller.f_globals.get('__name__', '')
|
||||
return (
|
||||
caller_module == 'distutils.dist'
|
||||
and info.function == 'run_commands'
|
||||
)
|
||||
|
||||
frames = inspect.getouterframes(run_frame)
|
||||
for frame in frames[2:4]:
|
||||
caller, = frame[:1]
|
||||
info = inspect.getframeinfo(caller)
|
||||
caller_module = caller.f_globals.get('__name__', '')
|
||||
|
||||
if caller_module == "setuptools.dist" and info.function == "run_command":
|
||||
# Starting from v61.0.0 setuptools overwrites dist.run_command
|
||||
continue
|
||||
|
||||
return (
|
||||
caller_module == 'distutils.dist'
|
||||
and info.function == 'run_commands'
|
||||
)
|
||||
|
||||
def do_egg_install(self):
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
from setuptools import Command
|
||||
from setuptools import namespaces
|
||||
from setuptools.archive_util import unpack_archive
|
||||
from .._path import ensure_directory
|
||||
import pkg_resources
|
||||
|
||||
|
||||
@@ -37,7 +38,7 @@ class install_egg_info(namespaces.Installer, Command):
|
||||
elif os.path.exists(self.target):
|
||||
self.execute(os.unlink, (self.target,), "Removing " + self.target)
|
||||
if not self.dry_run:
|
||||
pkg_resources.ensure_directory(self.target)
|
||||
ensure_directory(self.target)
|
||||
self.execute(
|
||||
self.copytree, (), "Copying %s to %s" % (self.source, self.target)
|
||||
)
|
||||
|
||||
@@ -4,7 +4,8 @@ from distutils.errors import DistutilsModuleError
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pkg_resources import Distribution, PathMetadata, ensure_directory
|
||||
from pkg_resources import Distribution, PathMetadata
|
||||
from .._path import ensure_directory
|
||||
|
||||
|
||||
class install_scripts(orig.install_scripts):
|
||||
|
||||
@@ -4,17 +4,19 @@ import os
|
||||
import sys
|
||||
import io
|
||||
import contextlib
|
||||
from itertools import chain
|
||||
|
||||
from .py36compat import sdist_add_defaults
|
||||
|
||||
import pkg_resources
|
||||
from .._importlib import metadata
|
||||
from .build import _ORIGINAL_SUBCOMMANDS
|
||||
|
||||
_default_revctrl = list
|
||||
|
||||
|
||||
def walk_revctrl(dirname=''):
|
||||
"""Find all files under revision control"""
|
||||
for ep in pkg_resources.iter_entry_points('setuptools.file_finders'):
|
||||
for ep in metadata.entry_points(group='setuptools.file_finders'):
|
||||
for item in ep.load()(dirname):
|
||||
yield item
|
||||
|
||||
@@ -31,6 +33,10 @@ class sdist(sdist_add_defaults, orig.sdist):
|
||||
('dist-dir=', 'd',
|
||||
"directory to put the source distribution archive(s) in "
|
||||
"[default: dist]"),
|
||||
('owner=', 'u',
|
||||
"Owner name used when creating a tar file [default: current user]"),
|
||||
('group=', 'g',
|
||||
"Group name used when creating a tar file [default: current group]"),
|
||||
]
|
||||
|
||||
negative_opt = {}
|
||||
@@ -96,6 +102,10 @@ class sdist(sdist_add_defaults, orig.sdist):
|
||||
if orig_val is not NoValue:
|
||||
setattr(os, 'link', orig_val)
|
||||
|
||||
def add_defaults(self):
|
||||
super().add_defaults()
|
||||
self._add_defaults_build_sub_commands()
|
||||
|
||||
def _add_defaults_optional(self):
|
||||
super()._add_defaults_optional()
|
||||
if os.path.isfile('pyproject.toml'):
|
||||
@@ -108,14 +118,25 @@ class sdist(sdist_add_defaults, orig.sdist):
|
||||
self.filelist.extend(build_py.get_source_files())
|
||||
self._add_data_files(self._safe_data_files(build_py))
|
||||
|
||||
def _add_defaults_build_sub_commands(self):
|
||||
build = self.get_finalized_command("build")
|
||||
missing_cmds = set(build.get_sub_commands()) - _ORIGINAL_SUBCOMMANDS
|
||||
# ^-- the original built-in sub-commands are already handled by default.
|
||||
cmds = (self.get_finalized_command(c) for c in missing_cmds)
|
||||
files = (c.get_source_files() for c in cmds if hasattr(c, "get_source_files"))
|
||||
self.filelist.extend(chain.from_iterable(files))
|
||||
|
||||
def _safe_data_files(self, build_py):
|
||||
"""
|
||||
Extracting data_files from build_py is known to cause
|
||||
infinite recursion errors when `include_package_data`
|
||||
is enabled, so suppress it in that case.
|
||||
Since the ``sdist`` class is also used to compute the MANIFEST
|
||||
(via :obj:`setuptools.command.egg_info.manifest_maker`),
|
||||
there might be recursion problems when trying to obtain the list of
|
||||
data_files and ``include_package_data=True`` (which in turn depends on
|
||||
the files included in the MANIFEST).
|
||||
|
||||
To avoid that, ``manifest_maker`` should be able to overwrite this
|
||||
method and avoid recursive attempts to build/analyze the MANIFEST.
|
||||
"""
|
||||
if self.distribution.include_package_data:
|
||||
return ()
|
||||
return build_py.data_files
|
||||
|
||||
def _add_data_files(self, data_files):
|
||||
|
||||
@@ -16,10 +16,11 @@ from pkg_resources import (
|
||||
evaluate_marker,
|
||||
add_activation_listener,
|
||||
require,
|
||||
EntryPoint,
|
||||
)
|
||||
from .._importlib import metadata
|
||||
from setuptools import Command
|
||||
from setuptools.extern.more_itertools import unique_everseen
|
||||
from setuptools.extern.jaraco.functools import pass_none
|
||||
|
||||
|
||||
class ScanningLoader(TestLoader):
|
||||
@@ -117,7 +118,7 @@ class test(Command):
|
||||
return list(self._test_args())
|
||||
|
||||
def _test_args(self):
|
||||
if not self.test_suite and sys.version_info >= (2, 7):
|
||||
if not self.test_suite:
|
||||
yield 'discover'
|
||||
if self.verbose:
|
||||
yield '--verbose'
|
||||
@@ -241,12 +242,10 @@ class test(Command):
|
||||
return ['unittest'] + self.test_args
|
||||
|
||||
@staticmethod
|
||||
@pass_none
|
||||
def _resolve_as_ep(val):
|
||||
"""
|
||||
Load the indicated attribute value, called, as a as if it were
|
||||
specified as an entry point.
|
||||
"""
|
||||
if val is None:
|
||||
return
|
||||
parsed = EntryPoint.parse("x=" + val)
|
||||
return parsed.resolve()()
|
||||
return metadata.EntryPoint(value=val, name=None, group=None).load()()
|
||||
|
||||
@@ -17,8 +17,11 @@ import itertools
|
||||
import functools
|
||||
import http.client
|
||||
import urllib.parse
|
||||
import warnings
|
||||
|
||||
from .._importlib import metadata
|
||||
from .. import SetuptoolsDeprecationWarning
|
||||
|
||||
from pkg_resources import iter_entry_points
|
||||
from .upload import upload
|
||||
|
||||
|
||||
@@ -43,9 +46,10 @@ 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
|
||||
return bool(
|
||||
self.upload_dir is None
|
||||
and metadata.entry_points(group='distutils.commands', name='build_sphinx')
|
||||
)
|
||||
|
||||
sub_commands = [('build_sphinx', has_sphinx)]
|
||||
|
||||
@@ -55,6 +59,9 @@ class upload_docs(upload):
|
||||
self.target_dir = None
|
||||
|
||||
def finalize_options(self):
|
||||
log.warn(
|
||||
"Upload_docs command is deprecated. Use Read the Docs "
|
||||
"(https://readthedocs.org) instead.")
|
||||
upload.finalize_options(self)
|
||||
if self.upload_dir is None:
|
||||
if self.has_sphinx():
|
||||
@@ -66,8 +73,6 @@ class upload_docs(upload):
|
||||
else:
|
||||
self.ensure_dirname('upload_dir')
|
||||
self.target_dir = self.upload_dir
|
||||
if 'pypi.python.org' in self.repository:
|
||||
log.warn("Upload_docs command is deprecated for PyPi. Use RTD instead.")
|
||||
self.announce('Using upload directory %s' % self.target_dir)
|
||||
|
||||
def create_zipfile(self, filename):
|
||||
@@ -87,6 +92,12 @@ class upload_docs(upload):
|
||||
zip_file.close()
|
||||
|
||||
def run(self):
|
||||
warnings.warn(
|
||||
"upload_docs is deprecated and will be removed in a future "
|
||||
"version. Use tools like httpie or curl instead.",
|
||||
SetuptoolsDeprecationWarning,
|
||||
)
|
||||
|
||||
# Run sub commands
|
||||
for cmd_name in self.get_sub_commands():
|
||||
self.run_command(cmd_name)
|
||||
|
||||
@@ -1,749 +0,0 @@
|
||||
import ast
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
import warnings
|
||||
import functools
|
||||
import importlib
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from functools import wraps
|
||||
from glob import iglob
|
||||
import contextlib
|
||||
|
||||
from distutils.errors import DistutilsOptionError, DistutilsFileError
|
||||
from setuptools.extern.packaging.version import LegacyVersion, parse
|
||||
from setuptools.extern.packaging.specifiers import SpecifierSet
|
||||
|
||||
|
||||
class StaticModule:
|
||||
"""
|
||||
Attempt to load the module by the name
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
spec = importlib.util.find_spec(name)
|
||||
with open(spec.origin) as strm:
|
||||
src = strm.read()
|
||||
module = ast.parse(src)
|
||||
vars(self).update(locals())
|
||||
del self.self
|
||||
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
return next(
|
||||
ast.literal_eval(statement.value)
|
||||
for statement in self.module.body
|
||||
if isinstance(statement, ast.Assign)
|
||||
for target in statement.targets
|
||||
if isinstance(target, ast.Name) and target.id == attr
|
||||
)
|
||||
except Exception as e:
|
||||
raise AttributeError(
|
||||
"{self.name} has no attribute {attr}".format(**locals())
|
||||
) from e
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def patch_path(path):
|
||||
"""
|
||||
Add path to front of sys.path for the duration of the context.
|
||||
"""
|
||||
try:
|
||||
sys.path.insert(0, path)
|
||||
yield
|
||||
finally:
|
||||
sys.path.remove(path)
|
||||
|
||||
|
||||
def read_configuration(filepath, find_others=False, ignore_option_errors=False):
|
||||
"""Read given configuration file and returns options from it as a dict.
|
||||
|
||||
:param str|unicode filepath: Path to configuration file
|
||||
to get options from.
|
||||
|
||||
:param bool find_others: Whether to search for other configuration files
|
||||
which could be on in various places.
|
||||
|
||||
:param bool ignore_option_errors: Whether to silently ignore
|
||||
options, values of which could not be resolved (e.g. due to exceptions
|
||||
in directives such as file:, attr:, etc.).
|
||||
If False exceptions are propagated as expected.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
from setuptools.dist import Distribution, _Distribution
|
||||
|
||||
filepath = os.path.abspath(filepath)
|
||||
|
||||
if not os.path.isfile(filepath):
|
||||
raise DistutilsFileError('Configuration file %s does not exist.' % filepath)
|
||||
|
||||
current_directory = os.getcwd()
|
||||
os.chdir(os.path.dirname(filepath))
|
||||
|
||||
try:
|
||||
dist = Distribution()
|
||||
|
||||
filenames = dist.find_config_files() if find_others else []
|
||||
if filepath not in filenames:
|
||||
filenames.append(filepath)
|
||||
|
||||
_Distribution.parse_config_files(dist, filenames=filenames)
|
||||
|
||||
handlers = parse_configuration(
|
||||
dist, dist.command_options, ignore_option_errors=ignore_option_errors
|
||||
)
|
||||
|
||||
finally:
|
||||
os.chdir(current_directory)
|
||||
|
||||
return configuration_to_dict(handlers)
|
||||
|
||||
|
||||
def _get_option(target_obj, key):
|
||||
"""
|
||||
Given a target object and option key, get that option from
|
||||
the target object, either through a get_{key} method or
|
||||
from an attribute directly.
|
||||
"""
|
||||
getter_name = 'get_{key}'.format(**locals())
|
||||
by_attribute = functools.partial(getattr, target_obj, key)
|
||||
getter = getattr(target_obj, getter_name, by_attribute)
|
||||
return getter()
|
||||
|
||||
|
||||
def configuration_to_dict(handlers):
|
||||
"""Returns configuration data gathered by given handlers as a dict.
|
||||
|
||||
:param list[ConfigHandler] handlers: Handlers list,
|
||||
usually from parse_configuration()
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
config_dict = defaultdict(dict)
|
||||
|
||||
for handler in handlers:
|
||||
for option in handler.set_options:
|
||||
value = _get_option(handler.target_obj, option)
|
||||
config_dict[handler.section_prefix][option] = value
|
||||
|
||||
return config_dict
|
||||
|
||||
|
||||
def parse_configuration(distribution, command_options, ignore_option_errors=False):
|
||||
"""Performs additional parsing of configuration options
|
||||
for a distribution.
|
||||
|
||||
Returns a list of used option handlers.
|
||||
|
||||
:param Distribution distribution:
|
||||
:param dict command_options:
|
||||
:param bool ignore_option_errors: Whether to silently ignore
|
||||
options, values of which could not be resolved (e.g. due to exceptions
|
||||
in directives such as file:, attr:, etc.).
|
||||
If False exceptions are propagated as expected.
|
||||
:rtype: list
|
||||
"""
|
||||
options = ConfigOptionsHandler(distribution, command_options, ignore_option_errors)
|
||||
options.parse()
|
||||
|
||||
meta = ConfigMetadataHandler(
|
||||
distribution.metadata,
|
||||
command_options,
|
||||
ignore_option_errors,
|
||||
distribution.package_dir,
|
||||
)
|
||||
meta.parse()
|
||||
|
||||
return meta, options
|
||||
|
||||
|
||||
class ConfigHandler:
|
||||
"""Handles metadata supplied in configuration files."""
|
||||
|
||||
section_prefix = None
|
||||
"""Prefix for config sections handled by this handler.
|
||||
Must be provided by class heirs.
|
||||
|
||||
"""
|
||||
|
||||
aliases = {}
|
||||
"""Options aliases.
|
||||
For compatibility with various packages. E.g.: d2to1 and pbr.
|
||||
Note: `-` in keys is replaced with `_` by config parser.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, target_obj, options, ignore_option_errors=False):
|
||||
sections = {}
|
||||
|
||||
section_prefix = self.section_prefix
|
||||
for section_name, section_options in options.items():
|
||||
if not section_name.startswith(section_prefix):
|
||||
continue
|
||||
|
||||
section_name = section_name.replace(section_prefix, '').strip('.')
|
||||
sections[section_name] = section_options
|
||||
|
||||
self.ignore_option_errors = ignore_option_errors
|
||||
self.target_obj = target_obj
|
||||
self.sections = sections
|
||||
self.set_options = []
|
||||
|
||||
@property
|
||||
def parsers(self):
|
||||
"""Metadata item name to parser function mapping."""
|
||||
raise NotImplementedError(
|
||||
'%s must provide .parsers property' % self.__class__.__name__
|
||||
)
|
||||
|
||||
def __setitem__(self, option_name, value):
|
||||
unknown = tuple()
|
||||
target_obj = self.target_obj
|
||||
|
||||
# Translate alias into real name.
|
||||
option_name = self.aliases.get(option_name, option_name)
|
||||
|
||||
current_value = getattr(target_obj, option_name, unknown)
|
||||
|
||||
if current_value is unknown:
|
||||
raise KeyError(option_name)
|
||||
|
||||
if current_value:
|
||||
# Already inhabited. Skipping.
|
||||
return
|
||||
|
||||
skip_option = False
|
||||
parser = self.parsers.get(option_name)
|
||||
if parser:
|
||||
try:
|
||||
value = parser(value)
|
||||
|
||||
except Exception:
|
||||
skip_option = True
|
||||
if not self.ignore_option_errors:
|
||||
raise
|
||||
|
||||
if skip_option:
|
||||
return
|
||||
|
||||
setter = getattr(target_obj, 'set_%s' % option_name, None)
|
||||
if setter is None:
|
||||
setattr(target_obj, option_name, value)
|
||||
else:
|
||||
setter(value)
|
||||
|
||||
self.set_options.append(option_name)
|
||||
|
||||
@classmethod
|
||||
def _parse_list(cls, value, separator=','):
|
||||
"""Represents value as a list.
|
||||
|
||||
Value is split either by separator (defaults to comma) or by lines.
|
||||
|
||||
:param value:
|
||||
:param separator: List items separator character.
|
||||
:rtype: list
|
||||
"""
|
||||
if isinstance(value, list): # _get_parser_compound case
|
||||
return value
|
||||
|
||||
if '\n' in value:
|
||||
value = value.splitlines()
|
||||
else:
|
||||
value = value.split(separator)
|
||||
|
||||
return [chunk.strip() for chunk in value if chunk.strip()]
|
||||
|
||||
@classmethod
|
||||
def _parse_list_glob(cls, value, separator=','):
|
||||
"""Equivalent to _parse_list() but expands any glob patterns using glob().
|
||||
|
||||
However, unlike with glob() calls, the results remain relative paths.
|
||||
|
||||
:param value:
|
||||
:param separator: List items separator character.
|
||||
:rtype: list
|
||||
"""
|
||||
glob_characters = ('*', '?', '[', ']', '{', '}')
|
||||
values = cls._parse_list(value, separator=separator)
|
||||
expanded_values = []
|
||||
for value in values:
|
||||
|
||||
# Has globby characters?
|
||||
if any(char in value for char in glob_characters):
|
||||
# then expand the glob pattern while keeping paths *relative*:
|
||||
expanded_values.extend(sorted(
|
||||
os.path.relpath(path, os.getcwd())
|
||||
for path in iglob(os.path.abspath(value))))
|
||||
|
||||
else:
|
||||
# take the value as-is:
|
||||
expanded_values.append(value)
|
||||
|
||||
return expanded_values
|
||||
|
||||
@classmethod
|
||||
def _parse_dict(cls, value):
|
||||
"""Represents value as a dict.
|
||||
|
||||
:param value:
|
||||
:rtype: dict
|
||||
"""
|
||||
separator = '='
|
||||
result = {}
|
||||
for line in cls._parse_list(value):
|
||||
key, sep, val = line.partition(separator)
|
||||
if sep != separator:
|
||||
raise DistutilsOptionError(
|
||||
'Unable to parse option value to dict: %s' % value
|
||||
)
|
||||
result[key.strip()] = val.strip()
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _parse_bool(cls, value):
|
||||
"""Represents value as boolean.
|
||||
|
||||
:param value:
|
||||
:rtype: bool
|
||||
"""
|
||||
value = value.lower()
|
||||
return value in ('1', 'true', 'yes')
|
||||
|
||||
@classmethod
|
||||
def _exclude_files_parser(cls, key):
|
||||
"""Returns a parser function to make sure field inputs
|
||||
are not files.
|
||||
|
||||
Parses a value after getting the key so error messages are
|
||||
more informative.
|
||||
|
||||
:param key:
|
||||
:rtype: callable
|
||||
"""
|
||||
|
||||
def parser(value):
|
||||
exclude_directive = 'file:'
|
||||
if value.startswith(exclude_directive):
|
||||
raise ValueError(
|
||||
'Only strings are accepted for the {0} field, '
|
||||
'files are not accepted'.format(key)
|
||||
)
|
||||
return value
|
||||
|
||||
return parser
|
||||
|
||||
@classmethod
|
||||
def _parse_file(cls, value):
|
||||
"""Represents value as a string, allowing including text
|
||||
from nearest files using `file:` directive.
|
||||
|
||||
Directive is sandboxed and won't reach anything outside
|
||||
directory with setup.py.
|
||||
|
||||
Examples:
|
||||
file: README.rst, CHANGELOG.md, src/file.txt
|
||||
|
||||
:param str value:
|
||||
:rtype: str
|
||||
"""
|
||||
include_directive = 'file:'
|
||||
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
|
||||
if not value.startswith(include_directive):
|
||||
return value
|
||||
|
||||
spec = value[len(include_directive) :]
|
||||
filepaths = (os.path.abspath(path.strip()) for path in spec.split(','))
|
||||
return '\n'.join(
|
||||
cls._read_file(path)
|
||||
for path in filepaths
|
||||
if (cls._assert_local(path) or True) and os.path.isfile(path)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _assert_local(filepath):
|
||||
if not filepath.startswith(os.getcwd()):
|
||||
raise DistutilsOptionError('`file:` directive can not access %s' % filepath)
|
||||
|
||||
@staticmethod
|
||||
def _read_file(filepath):
|
||||
with io.open(filepath, encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
@classmethod
|
||||
def _parse_attr(cls, value, package_dir=None):
|
||||
"""Represents value as a module attribute.
|
||||
|
||||
Examples:
|
||||
attr: package.attr
|
||||
attr: package.module.attr
|
||||
|
||||
:param str value:
|
||||
:rtype: str
|
||||
"""
|
||||
attr_directive = 'attr:'
|
||||
if not value.startswith(attr_directive):
|
||||
return value
|
||||
|
||||
attrs_path = value.replace(attr_directive, '').strip().split('.')
|
||||
attr_name = attrs_path.pop()
|
||||
|
||||
module_name = '.'.join(attrs_path)
|
||||
module_name = module_name or '__init__'
|
||||
|
||||
parent_path = os.getcwd()
|
||||
if package_dir:
|
||||
if attrs_path[0] in package_dir:
|
||||
# A custom path was specified for the module we want to import
|
||||
custom_path = package_dir[attrs_path[0]]
|
||||
parts = custom_path.rsplit('/', 1)
|
||||
if len(parts) > 1:
|
||||
parent_path = os.path.join(os.getcwd(), parts[0])
|
||||
module_name = parts[1]
|
||||
else:
|
||||
module_name = custom_path
|
||||
elif '' in package_dir:
|
||||
# A custom parent directory was specified for all root modules
|
||||
parent_path = os.path.join(os.getcwd(), package_dir[''])
|
||||
|
||||
with patch_path(parent_path):
|
||||
try:
|
||||
# attempt to load value statically
|
||||
return getattr(StaticModule(module_name), attr_name)
|
||||
except Exception:
|
||||
# fallback to simple import
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
return getattr(module, attr_name)
|
||||
|
||||
@classmethod
|
||||
def _get_parser_compound(cls, *parse_methods):
|
||||
"""Returns parser function to represents value as a list.
|
||||
|
||||
Parses a value applying given methods one after another.
|
||||
|
||||
:param parse_methods:
|
||||
:rtype: callable
|
||||
"""
|
||||
|
||||
def parse(value):
|
||||
parsed = value
|
||||
|
||||
for method in parse_methods:
|
||||
parsed = method(parsed)
|
||||
|
||||
return parsed
|
||||
|
||||
return parse
|
||||
|
||||
@classmethod
|
||||
def _parse_section_to_dict(cls, section_options, values_parser=None):
|
||||
"""Parses section options into a dictionary.
|
||||
|
||||
Optionally applies a given parser to values.
|
||||
|
||||
:param dict section_options:
|
||||
:param callable values_parser:
|
||||
:rtype: dict
|
||||
"""
|
||||
value = {}
|
||||
values_parser = values_parser or (lambda val: val)
|
||||
for key, (_, val) in section_options.items():
|
||||
value[key] = values_parser(val)
|
||||
return value
|
||||
|
||||
def parse_section(self, section_options):
|
||||
"""Parses configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
for (name, (_, value)) in section_options.items():
|
||||
try:
|
||||
self[name] = value
|
||||
|
||||
except KeyError:
|
||||
pass # Keep silent for a new option may appear anytime.
|
||||
|
||||
def parse(self):
|
||||
"""Parses configuration file items from one
|
||||
or more related sections.
|
||||
|
||||
"""
|
||||
for section_name, section_options in self.sections.items():
|
||||
|
||||
method_postfix = ''
|
||||
if section_name: # [section.option] variant
|
||||
method_postfix = '_%s' % section_name
|
||||
|
||||
section_parser_method = getattr(
|
||||
self,
|
||||
# Dots in section names are translated into dunderscores.
|
||||
('parse_section%s' % method_postfix).replace('.', '__'),
|
||||
None,
|
||||
)
|
||||
|
||||
if section_parser_method is None:
|
||||
raise DistutilsOptionError(
|
||||
'Unsupported distribution option section: [%s.%s]'
|
||||
% (self.section_prefix, section_name)
|
||||
)
|
||||
|
||||
section_parser_method(section_options)
|
||||
|
||||
def _deprecated_config_handler(self, func, msg, warning_class):
|
||||
"""this function will wrap around parameters that are deprecated
|
||||
|
||||
:param msg: deprecation message
|
||||
:param warning_class: class of warning exception to be raised
|
||||
:param func: function to be wrapped around
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def config_handler(*args, **kwargs):
|
||||
warnings.warn(msg, warning_class)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return config_handler
|
||||
|
||||
|
||||
class ConfigMetadataHandler(ConfigHandler):
|
||||
|
||||
section_prefix = 'metadata'
|
||||
|
||||
aliases = {
|
||||
'home_page': 'url',
|
||||
'summary': 'description',
|
||||
'classifier': 'classifiers',
|
||||
'platform': 'platforms',
|
||||
}
|
||||
|
||||
strict_mode = False
|
||||
"""We need to keep it loose, to be partially compatible with
|
||||
`pbr` and `d2to1` packages which also uses `metadata` section.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, target_obj, options, ignore_option_errors=False, package_dir=None
|
||||
):
|
||||
super(ConfigMetadataHandler, self).__init__(
|
||||
target_obj, options, ignore_option_errors
|
||||
)
|
||||
self.package_dir = package_dir
|
||||
|
||||
@property
|
||||
def parsers(self):
|
||||
"""Metadata item name to parser function mapping."""
|
||||
parse_list = self._parse_list
|
||||
parse_file = self._parse_file
|
||||
parse_dict = self._parse_dict
|
||||
exclude_files_parser = self._exclude_files_parser
|
||||
|
||||
return {
|
||||
'platforms': parse_list,
|
||||
'keywords': parse_list,
|
||||
'provides': parse_list,
|
||||
'requires': self._deprecated_config_handler(
|
||||
parse_list,
|
||||
"The requires parameter is deprecated, please use "
|
||||
"install_requires for runtime dependencies.",
|
||||
DeprecationWarning,
|
||||
),
|
||||
'obsoletes': parse_list,
|
||||
'classifiers': self._get_parser_compound(parse_file, parse_list),
|
||||
'license': exclude_files_parser('license'),
|
||||
'license_file': self._deprecated_config_handler(
|
||||
exclude_files_parser('license_file'),
|
||||
"The license_file parameter is deprecated, "
|
||||
"use license_files instead.",
|
||||
DeprecationWarning,
|
||||
),
|
||||
'license_files': parse_list,
|
||||
'description': parse_file,
|
||||
'long_description': parse_file,
|
||||
'version': self._parse_version,
|
||||
'project_urls': parse_dict,
|
||||
}
|
||||
|
||||
def _parse_version(self, value):
|
||||
"""Parses `version` option value.
|
||||
|
||||
:param value:
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
version = self._parse_file(value)
|
||||
|
||||
if version != value:
|
||||
version = version.strip()
|
||||
# Be strict about versions loaded from file because it's easy to
|
||||
# accidentally include newlines and other unintended content
|
||||
if isinstance(parse(version), LegacyVersion):
|
||||
tmpl = (
|
||||
'Version loaded from {value} does not '
|
||||
'comply with PEP 440: {version}'
|
||||
)
|
||||
raise DistutilsOptionError(tmpl.format(**locals()))
|
||||
|
||||
return version
|
||||
|
||||
version = self._parse_attr(value, self.package_dir)
|
||||
|
||||
if callable(version):
|
||||
version = version()
|
||||
|
||||
if not isinstance(version, str):
|
||||
if hasattr(version, '__iter__'):
|
||||
version = '.'.join(map(str, version))
|
||||
else:
|
||||
version = '%s' % version
|
||||
|
||||
return version
|
||||
|
||||
|
||||
class ConfigOptionsHandler(ConfigHandler):
|
||||
|
||||
section_prefix = 'options'
|
||||
|
||||
@property
|
||||
def parsers(self):
|
||||
"""Metadata item name to parser function mapping."""
|
||||
parse_list = self._parse_list
|
||||
parse_list_semicolon = partial(self._parse_list, separator=';')
|
||||
parse_bool = self._parse_bool
|
||||
parse_dict = self._parse_dict
|
||||
parse_cmdclass = self._parse_cmdclass
|
||||
|
||||
return {
|
||||
'zip_safe': parse_bool,
|
||||
'include_package_data': parse_bool,
|
||||
'package_dir': parse_dict,
|
||||
'scripts': parse_list,
|
||||
'eager_resources': parse_list,
|
||||
'dependency_links': parse_list,
|
||||
'namespace_packages': parse_list,
|
||||
'install_requires': parse_list_semicolon,
|
||||
'setup_requires': parse_list_semicolon,
|
||||
'tests_require': parse_list_semicolon,
|
||||
'packages': self._parse_packages,
|
||||
'entry_points': self._parse_file,
|
||||
'py_modules': parse_list,
|
||||
'python_requires': SpecifierSet,
|
||||
'cmdclass': parse_cmdclass,
|
||||
}
|
||||
|
||||
def _parse_cmdclass(self, value):
|
||||
def resolve_class(qualified_class_name):
|
||||
idx = qualified_class_name.rfind('.')
|
||||
class_name = qualified_class_name[idx + 1 :]
|
||||
pkg_name = qualified_class_name[:idx]
|
||||
|
||||
module = __import__(pkg_name)
|
||||
|
||||
return getattr(module, class_name)
|
||||
|
||||
return {k: resolve_class(v) for k, v in self._parse_dict(value).items()}
|
||||
|
||||
def _parse_packages(self, value):
|
||||
"""Parses `packages` option value.
|
||||
|
||||
:param value:
|
||||
:rtype: list
|
||||
"""
|
||||
find_directives = ['find:', 'find_namespace:']
|
||||
trimmed_value = value.strip()
|
||||
|
||||
if trimmed_value not in find_directives:
|
||||
return self._parse_list(value)
|
||||
|
||||
findns = trimmed_value == find_directives[1]
|
||||
|
||||
# Read function arguments from a dedicated section.
|
||||
find_kwargs = self.parse_section_packages__find(
|
||||
self.sections.get('packages.find', {})
|
||||
)
|
||||
|
||||
if findns:
|
||||
from setuptools import find_namespace_packages as find_packages
|
||||
else:
|
||||
from setuptools import find_packages
|
||||
|
||||
return find_packages(**find_kwargs)
|
||||
|
||||
def parse_section_packages__find(self, section_options):
|
||||
"""Parses `packages.find` configuration file section.
|
||||
|
||||
To be used in conjunction with _parse_packages().
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
section_data = self._parse_section_to_dict(section_options, self._parse_list)
|
||||
|
||||
valid_keys = ['where', 'include', 'exclude']
|
||||
|
||||
find_kwargs = dict(
|
||||
[(k, v) for k, v in section_data.items() if k in valid_keys and v]
|
||||
)
|
||||
|
||||
where = find_kwargs.get('where')
|
||||
if where is not None:
|
||||
find_kwargs['where'] = where[0] # cast list to single val
|
||||
|
||||
return find_kwargs
|
||||
|
||||
def parse_section_entry_points(self, section_options):
|
||||
"""Parses `entry_points` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
parsed = self._parse_section_to_dict(section_options, self._parse_list)
|
||||
self['entry_points'] = parsed
|
||||
|
||||
def _parse_package_data(self, section_options):
|
||||
parsed = self._parse_section_to_dict(section_options, self._parse_list)
|
||||
|
||||
root = parsed.get('*')
|
||||
if root:
|
||||
parsed[''] = root
|
||||
del parsed['*']
|
||||
|
||||
return parsed
|
||||
|
||||
def parse_section_package_data(self, section_options):
|
||||
"""Parses `package_data` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
self['package_data'] = self._parse_package_data(section_options)
|
||||
|
||||
def parse_section_exclude_package_data(self, section_options):
|
||||
"""Parses `exclude_package_data` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
self['exclude_package_data'] = self._parse_package_data(section_options)
|
||||
|
||||
def parse_section_extras_require(self, section_options):
|
||||
"""Parses `extras_require` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
parse_list = partial(self._parse_list, separator=';')
|
||||
self['extras_require'] = self._parse_section_to_dict(
|
||||
section_options, parse_list
|
||||
)
|
||||
|
||||
def parse_section_data_files(self, section_options):
|
||||
"""Parses `data_files` configuration file section.
|
||||
|
||||
:param dict section_options:
|
||||
"""
|
||||
parsed = self._parse_section_to_dict(section_options, self._parse_list_glob)
|
||||
self['data_files'] = [(k, v) for k, v in parsed.items()]
|
||||
@@ -2,7 +2,8 @@ import sys
|
||||
import marshal
|
||||
import contextlib
|
||||
import dis
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
from setuptools.extern.packaging import version
|
||||
|
||||
from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE
|
||||
from . import _imp
|
||||
@@ -21,7 +22,7 @@ class Require:
|
||||
attribute=None, format=None):
|
||||
|
||||
if format is None and requested_version is not None:
|
||||
format = StrictVersion
|
||||
format = version.Version
|
||||
|
||||
if format is not None:
|
||||
requested_version = format(requested_version)
|
||||
@@ -40,7 +41,7 @@ class Require:
|
||||
def version_ok(self, version):
|
||||
"""Is 'version' sufficiently up-to-date?"""
|
||||
return self.attribute is None or self.format is None or \
|
||||
str(version) != "unknown" and version >= self.requested_version
|
||||
str(version) != "unknown" and self.format(version) >= self.requested_version
|
||||
|
||||
def get_version(self, paths=None, default="unknown"):
|
||||
"""Get version number of installed module, 'None', or 'default'
|
||||
@@ -78,7 +79,7 @@ class Require:
|
||||
version = self.get_version(paths)
|
||||
if version is None:
|
||||
return False
|
||||
return self.version_ok(version)
|
||||
return self.version_ok(str(version))
|
||||
|
||||
|
||||
def maybe_close(f):
|
||||
|
||||
@@ -19,17 +19,19 @@ from glob import iglob
|
||||
import itertools
|
||||
import textwrap
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
from collections import defaultdict
|
||||
from email import message_from_file
|
||||
|
||||
from distutils.errors import DistutilsOptionError, DistutilsSetupError
|
||||
from distutils.util import rfc822_escape
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
from setuptools.extern import packaging
|
||||
from setuptools.extern import ordered_set
|
||||
from setuptools.extern.more_itertools import unique_everseen
|
||||
from setuptools.extern.more_itertools import unique_everseen, partition
|
||||
|
||||
from ._importlib import metadata
|
||||
|
||||
from . import SetuptoolsDeprecationWarning
|
||||
|
||||
@@ -37,8 +39,13 @@ import setuptools
|
||||
import setuptools.command
|
||||
from setuptools import windows_support
|
||||
from setuptools.monkey import get_unpatched
|
||||
from setuptools.config import parse_configuration
|
||||
from setuptools.config import setupcfg, pyprojecttoml
|
||||
from setuptools.discovery import ConfigDiscovery
|
||||
|
||||
import pkg_resources
|
||||
from setuptools.extern.packaging import version
|
||||
from . import _reqs
|
||||
from . import _entry_points
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from email.message import Message
|
||||
@@ -55,7 +62,7 @@ def _get_unpatched(cls):
|
||||
def get_metadata_version(self):
|
||||
mv = getattr(self, 'metadata_version', None)
|
||||
if mv is None:
|
||||
mv = StrictVersion('2.1')
|
||||
mv = version.Version('2.1')
|
||||
self.metadata_version = mv
|
||||
return mv
|
||||
|
||||
@@ -94,7 +101,7 @@ def _read_list_from_msg(msg: "Message", field: str) -> Optional[List[str]]:
|
||||
|
||||
def _read_payload_from_msg(msg: "Message") -> Optional[str]:
|
||||
value = msg.get_payload().strip()
|
||||
if value == 'UNKNOWN':
|
||||
if value == 'UNKNOWN' or not value:
|
||||
return None
|
||||
return value
|
||||
|
||||
@@ -103,7 +110,7 @@ def read_pkg_file(self, file):
|
||||
"""Reads the metadata values from a file object."""
|
||||
msg = message_from_file(file)
|
||||
|
||||
self.metadata_version = StrictVersion(msg['metadata-version'])
|
||||
self.metadata_version = version.Version(msg['metadata-version'])
|
||||
self.name = _read_field_from_msg(msg, 'name')
|
||||
self.version = _read_field_from_msg(msg, 'version')
|
||||
self.description = _read_field_from_msg(msg, 'summary')
|
||||
@@ -113,15 +120,14 @@ def read_pkg_file(self, file):
|
||||
self.author_email = _read_field_from_msg(msg, 'author-email')
|
||||
self.maintainer_email = None
|
||||
self.url = _read_field_from_msg(msg, 'home-page')
|
||||
self.download_url = _read_field_from_msg(msg, 'download-url')
|
||||
self.license = _read_field_unescaped_from_msg(msg, 'license')
|
||||
|
||||
if 'download-url' in msg:
|
||||
self.download_url = _read_field_from_msg(msg, 'download-url')
|
||||
else:
|
||||
self.download_url = None
|
||||
|
||||
self.long_description = _read_field_unescaped_from_msg(msg, 'description')
|
||||
if self.long_description is None and self.metadata_version >= StrictVersion('2.1'):
|
||||
if (
|
||||
self.long_description is None and
|
||||
self.metadata_version >= version.Version('2.1')
|
||||
):
|
||||
self.long_description = _read_payload_from_msg(msg)
|
||||
self.description = _read_field_from_msg(msg, 'summary')
|
||||
|
||||
@@ -132,7 +138,7 @@ def read_pkg_file(self, file):
|
||||
self.classifiers = _read_list_from_msg(msg, 'classifier')
|
||||
|
||||
# PEP 314 - these fields only exist in 1.1
|
||||
if self.metadata_version == StrictVersion('1.1'):
|
||||
if self.metadata_version == version.Version('1.1'):
|
||||
self.requires = _read_list_from_msg(msg, 'requires')
|
||||
self.provides = _read_list_from_msg(msg, 'provides')
|
||||
self.obsoletes = _read_list_from_msg(msg, 'obsoletes')
|
||||
@@ -145,11 +151,14 @@ def read_pkg_file(self, file):
|
||||
|
||||
|
||||
def single_line(val):
|
||||
# quick and dirty validation for description pypa/setuptools#1390
|
||||
"""
|
||||
Quick and dirty validation for Summary pypa/setuptools#1390.
|
||||
"""
|
||||
if '\n' in val:
|
||||
# TODO after 2021-07-31: Replace with `raise ValueError("newlines not allowed")`
|
||||
# TODO: Replace with `raise ValueError("newlines not allowed")`
|
||||
# after reviewing #2893.
|
||||
warnings.warn("newlines not allowed and will break in the future")
|
||||
val = val.replace('\n', ' ')
|
||||
val = val.strip().split('\n')[0]
|
||||
return val
|
||||
|
||||
|
||||
@@ -164,10 +173,14 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
|
||||
write_field('Metadata-Version', str(version))
|
||||
write_field('Name', self.get_name())
|
||||
write_field('Version', self.get_version())
|
||||
write_field('Summary', single_line(self.get_description()))
|
||||
write_field('Home-page', self.get_url())
|
||||
|
||||
summary = self.get_description()
|
||||
if summary:
|
||||
write_field('Summary', single_line(summary))
|
||||
|
||||
optional_fields = (
|
||||
('Home-page', 'url'),
|
||||
('Download-URL', 'download_url'),
|
||||
('Author', 'author'),
|
||||
('Author-email', 'author_email'),
|
||||
('Maintainer', 'maintainer'),
|
||||
@@ -179,10 +192,10 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
|
||||
if attr_val is not None:
|
||||
write_field(field, attr_val)
|
||||
|
||||
license = rfc822_escape(self.get_license())
|
||||
write_field('License', license)
|
||||
if self.download_url:
|
||||
write_field('Download-URL', self.download_url)
|
||||
license = self.get_license()
|
||||
if license:
|
||||
write_field('License', rfc822_escape(license))
|
||||
|
||||
for project_url in self.project_urls.items():
|
||||
write_field('Project-URL', '%s, %s' % project_url)
|
||||
|
||||
@@ -190,7 +203,8 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
|
||||
if keywords:
|
||||
write_field('Keywords', keywords)
|
||||
|
||||
for platform in self.get_platforms():
|
||||
platforms = self.get_platforms() or []
|
||||
for platform in platforms:
|
||||
write_field('Platform', platform)
|
||||
|
||||
self._write_list(file, 'Classifier', self.get_classifiers())
|
||||
@@ -213,7 +227,11 @@ def write_pkg_file(self, file): # noqa: C901 # is too complex (14) # FIXME
|
||||
|
||||
self._write_list(file, 'License-File', self.license_files or [])
|
||||
|
||||
file.write("\n%s\n\n" % self.get_long_description())
|
||||
long_description = self.get_long_description()
|
||||
if long_description:
|
||||
file.write("\n%s" % long_description)
|
||||
if not long_description.endswith("\n"):
|
||||
file.write("\n")
|
||||
|
||||
|
||||
sequence = tuple, list
|
||||
@@ -221,7 +239,7 @@ sequence = tuple, list
|
||||
|
||||
def check_importable(dist, attr, value):
|
||||
try:
|
||||
ep = pkg_resources.EntryPoint.parse('x=' + value)
|
||||
ep = metadata.EntryPoint(value=value, name=None, group=None)
|
||||
assert not ep.extras
|
||||
except (TypeError, ValueError, AttributeError, AssertionError) as e:
|
||||
raise DistutilsSetupError(
|
||||
@@ -261,6 +279,11 @@ def check_nsp(dist, attr, value):
|
||||
nsp,
|
||||
parent,
|
||||
)
|
||||
msg = (
|
||||
"The namespace_packages parameter is deprecated, "
|
||||
"consider using implicit namespaces instead (PEP 420)."
|
||||
)
|
||||
warnings.warn(msg, SetuptoolsDeprecationWarning)
|
||||
|
||||
|
||||
def check_extras(dist, attr, value):
|
||||
@@ -279,7 +302,7 @@ def _check_extra(extra, reqs):
|
||||
name, sep, marker = extra.partition(':')
|
||||
if marker and pkg_resources.invalid_marker(marker):
|
||||
raise DistutilsSetupError("Invalid environment marker: " + marker)
|
||||
list(pkg_resources.parse_requirements(reqs))
|
||||
list(_reqs.parse(reqs))
|
||||
|
||||
|
||||
def assert_bool(dist, attr, value):
|
||||
@@ -299,7 +322,7 @@ def invalid_unless_false(dist, attr, value):
|
||||
def check_requirements(dist, attr, value):
|
||||
"""Verify that install_requires is a valid requirements list"""
|
||||
try:
|
||||
list(pkg_resources.parse_requirements(value))
|
||||
list(_reqs.parse(value))
|
||||
if isinstance(value, (dict, set)):
|
||||
raise TypeError("Unordered types are not allowed")
|
||||
except (TypeError, ValueError) as error:
|
||||
@@ -324,8 +347,8 @@ def check_specifier(dist, attr, value):
|
||||
def check_entry_points(dist, attr, value):
|
||||
"""Verify that entry_points map is parseable"""
|
||||
try:
|
||||
pkg_resources.EntryPoint.parse_map(value)
|
||||
except ValueError as e:
|
||||
_entry_points.load(value)
|
||||
except Exception as e:
|
||||
raise DistutilsSetupError(e) from e
|
||||
|
||||
|
||||
@@ -448,7 +471,7 @@ class Distribution(_Distribution):
|
||||
self.patch_missing_pkg_info(attrs)
|
||||
self.dependency_links = attrs.pop('dependency_links', [])
|
||||
self.setup_requires = attrs.pop('setup_requires', [])
|
||||
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
|
||||
for ep in metadata.entry_points(group='distutils.setup_keywords'):
|
||||
vars(self).setdefault(ep.name, None)
|
||||
_Distribution.__init__(
|
||||
self,
|
||||
@@ -459,6 +482,13 @@ class Distribution(_Distribution):
|
||||
},
|
||||
)
|
||||
|
||||
# Save the original dependencies before they are processed into the egg format
|
||||
self._orig_extras_require = {}
|
||||
self._orig_install_requires = []
|
||||
self._tmp_extras_require = defaultdict(ordered_set.OrderedSet)
|
||||
|
||||
self.set_defaults = ConfigDiscovery(self)
|
||||
|
||||
self._set_metadata_defaults(attrs)
|
||||
|
||||
self.metadata.version = self._normalize_version(
|
||||
@@ -466,6 +496,19 @@ class Distribution(_Distribution):
|
||||
)
|
||||
self._finalize_requires()
|
||||
|
||||
def _validate_metadata(self):
|
||||
required = {"name"}
|
||||
provided = {
|
||||
key
|
||||
for key in vars(self.metadata)
|
||||
if getattr(self.metadata, key, None) is not None
|
||||
}
|
||||
missing = required - provided
|
||||
|
||||
if missing:
|
||||
msg = f"Required package metadata is missing: {missing}"
|
||||
raise DistutilsSetupError(msg)
|
||||
|
||||
def _set_metadata_defaults(self, attrs):
|
||||
"""
|
||||
Fill-in missing metadata fields not supported by distutils.
|
||||
@@ -516,6 +559,8 @@ class Distribution(_Distribution):
|
||||
self.metadata.python_requires = self.python_requires
|
||||
|
||||
if getattr(self, 'extras_require', None):
|
||||
# Save original before it is messed by _convert_extras_requirements
|
||||
self._orig_extras_require = self._orig_extras_require or self.extras_require
|
||||
for extra in self.extras_require.keys():
|
||||
# Since this gets called multiple times at points where the
|
||||
# keys have become 'converted' extras, ensure that we are only
|
||||
@@ -524,6 +569,10 @@ class Distribution(_Distribution):
|
||||
if extra:
|
||||
self.metadata.provides_extras.add(extra)
|
||||
|
||||
if getattr(self, 'install_requires', None) and not self._orig_install_requires:
|
||||
# Save original before it is messed by _move_install_requirements_markers
|
||||
self._orig_install_requires = self.install_requires
|
||||
|
||||
self._convert_extras_requirements()
|
||||
self._move_install_requirements_markers()
|
||||
|
||||
@@ -534,11 +583,12 @@ class Distribution(_Distribution):
|
||||
`"extra:{marker}": ["barbazquux"]`.
|
||||
"""
|
||||
spec_ext_reqs = getattr(self, 'extras_require', None) or {}
|
||||
self._tmp_extras_require = defaultdict(list)
|
||||
tmp = defaultdict(ordered_set.OrderedSet)
|
||||
self._tmp_extras_require = getattr(self, '_tmp_extras_require', tmp)
|
||||
for section, v in spec_ext_reqs.items():
|
||||
# Do not strip empty sections.
|
||||
self._tmp_extras_require[section]
|
||||
for r in pkg_resources.parse_requirements(v):
|
||||
for r in _reqs.parse(v):
|
||||
suffix = self._suffix_for(r)
|
||||
self._tmp_extras_require[section + suffix].append(r)
|
||||
|
||||
@@ -564,7 +614,7 @@ class Distribution(_Distribution):
|
||||
return not req.marker
|
||||
|
||||
spec_inst_reqs = getattr(self, 'install_requires', None) or ()
|
||||
inst_reqs = list(pkg_resources.parse_requirements(spec_inst_reqs))
|
||||
inst_reqs = list(_reqs.parse(spec_inst_reqs))
|
||||
simple_reqs = filter(is_simple_req, inst_reqs)
|
||||
complex_reqs = itertools.filterfalse(is_simple_req, inst_reqs)
|
||||
self.install_requires = list(map(str, simple_reqs))
|
||||
@@ -572,7 +622,8 @@ class Distribution(_Distribution):
|
||||
for r in complex_reqs:
|
||||
self._tmp_extras_require[':' + str(r.marker)].append(r)
|
||||
self.extras_require = dict(
|
||||
(k, [str(r) for r in map(self._clean_req, v)])
|
||||
# list(dict.fromkeys(...)) ensures a list of unique strings
|
||||
(k, list(dict.fromkeys(str(r) for r in map(self._clean_req, v))))
|
||||
for k, v in self._tmp_extras_require.items()
|
||||
)
|
||||
|
||||
@@ -705,7 +756,10 @@ class Distribution(_Distribution):
|
||||
return opt
|
||||
|
||||
underscore_opt = opt.replace('-', '_')
|
||||
commands = distutils.command.__all__ + self._setuptools_commands()
|
||||
commands = list(itertools.chain(
|
||||
distutils.command.__all__,
|
||||
self._setuptools_commands(),
|
||||
))
|
||||
if (
|
||||
not section.startswith('options')
|
||||
and section != 'metadata'
|
||||
@@ -723,9 +777,8 @@ class Distribution(_Distribution):
|
||||
|
||||
def _setuptools_commands(self):
|
||||
try:
|
||||
dist = pkg_resources.get_distribution('setuptools')
|
||||
return list(dist.get_entry_map('distutils.commands'))
|
||||
except pkg_resources.DistributionNotFound:
|
||||
return metadata.distribution('setuptools').entry_points.names
|
||||
except metadata.PackageNotFoundError:
|
||||
# during bootstrapping, distribution doesn't exist
|
||||
return []
|
||||
|
||||
@@ -788,23 +841,39 @@ class Distribution(_Distribution):
|
||||
except ValueError as e:
|
||||
raise DistutilsOptionError(e) from e
|
||||
|
||||
def _get_project_config_files(self, filenames):
|
||||
"""Add default file and split between INI and TOML"""
|
||||
tomlfiles = []
|
||||
standard_project_metadata = Path(self.src_root or os.curdir, "pyproject.toml")
|
||||
if filenames is not None:
|
||||
parts = partition(lambda f: Path(f).suffix == ".toml", filenames)
|
||||
filenames = list(parts[0]) # 1st element => predicate is False
|
||||
tomlfiles = list(parts[1]) # 2nd element => predicate is True
|
||||
elif standard_project_metadata.exists():
|
||||
tomlfiles = [standard_project_metadata]
|
||||
return filenames, tomlfiles
|
||||
|
||||
def parse_config_files(self, filenames=None, ignore_option_errors=False):
|
||||
"""Parses configuration files from various levels
|
||||
and loads configuration.
|
||||
|
||||
"""
|
||||
self._parse_config_files(filenames=filenames)
|
||||
inifiles, tomlfiles = self._get_project_config_files(filenames)
|
||||
|
||||
parse_configuration(
|
||||
self._parse_config_files(filenames=inifiles)
|
||||
|
||||
setupcfg.parse_configuration(
|
||||
self, self.command_options, ignore_option_errors=ignore_option_errors
|
||||
)
|
||||
for filename in tomlfiles:
|
||||
pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
|
||||
|
||||
self._finalize_requires()
|
||||
self._finalize_license_files()
|
||||
|
||||
def fetch_build_eggs(self, requires):
|
||||
"""Resolve pre-setup requirements"""
|
||||
resolved_dists = pkg_resources.working_set.resolve(
|
||||
pkg_resources.parse_requirements(requires),
|
||||
_reqs.parse(requires),
|
||||
installer=self.fetch_build_egg,
|
||||
replace_conflicting=True,
|
||||
)
|
||||
@@ -824,7 +893,7 @@ class Distribution(_Distribution):
|
||||
def by_order(hook):
|
||||
return getattr(hook, 'order', 0)
|
||||
|
||||
defined = pkg_resources.iter_entry_points(group)
|
||||
defined = metadata.entry_points(group=group)
|
||||
filtered = itertools.filterfalse(self._removed, defined)
|
||||
loaded = map(lambda e: e.load(), filtered)
|
||||
for ep in sorted(loaded, key=by_order):
|
||||
@@ -845,10 +914,9 @@ class Distribution(_Distribution):
|
||||
return ep.name in removed
|
||||
|
||||
def _finalize_setup_keywords(self):
|
||||
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
|
||||
for ep in metadata.entry_points(group='distutils.setup_keywords'):
|
||||
value = getattr(self, ep.name, None)
|
||||
if value is not None:
|
||||
ep.require(installer=self.fetch_build_egg)
|
||||
ep.load()(self, ep.name, value)
|
||||
|
||||
def get_egg_cache_dir(self):
|
||||
@@ -881,27 +949,24 @@ class Distribution(_Distribution):
|
||||
if command in self.cmdclass:
|
||||
return self.cmdclass[command]
|
||||
|
||||
eps = pkg_resources.iter_entry_points('distutils.commands', command)
|
||||
eps = metadata.entry_points(group='distutils.commands', name=command)
|
||||
for ep in eps:
|
||||
ep.require(installer=self.fetch_build_egg)
|
||||
self.cmdclass[command] = cmdclass = ep.load()
|
||||
return cmdclass
|
||||
else:
|
||||
return _Distribution.get_command_class(self, command)
|
||||
|
||||
def print_commands(self):
|
||||
for ep in pkg_resources.iter_entry_points('distutils.commands'):
|
||||
for ep in metadata.entry_points(group='distutils.commands'):
|
||||
if ep.name not in self.cmdclass:
|
||||
# don't require extras as the commands won't be invoked
|
||||
cmdclass = ep.resolve()
|
||||
cmdclass = ep.load()
|
||||
self.cmdclass[ep.name] = cmdclass
|
||||
return _Distribution.print_commands(self)
|
||||
|
||||
def get_command_list(self):
|
||||
for ep in pkg_resources.iter_entry_points('distutils.commands'):
|
||||
for ep in metadata.entry_points(group='distutils.commands'):
|
||||
if ep.name not in self.cmdclass:
|
||||
# don't require extras as the commands won't be invoked
|
||||
cmdclass = ep.resolve()
|
||||
cmdclass = ep.load()
|
||||
self.cmdclass[ep.name] = cmdclass
|
||||
return _Distribution.get_command_list(self)
|
||||
|
||||
@@ -1144,6 +1209,13 @@ class Distribution(_Distribution):
|
||||
sys.stdout.detach(), encoding, errors, newline, line_buffering
|
||||
)
|
||||
|
||||
def run_command(self, command):
|
||||
self.set_defaults()
|
||||
# Postpone defaults until all explicit configuration is considered
|
||||
# (setup() args, config files, command line and plugins)
|
||||
|
||||
super().run_command(command)
|
||||
|
||||
|
||||
class DistDeprecationWarning(SetuptoolsDeprecationWarning):
|
||||
"""Class for warning about deprecations in dist in
|
||||
|
||||
@@ -3,10 +3,33 @@
|
||||
Provides exceptions used by setuptools modules.
|
||||
"""
|
||||
|
||||
from distutils.errors import DistutilsError
|
||||
from distutils import errors as _distutils_errors
|
||||
|
||||
|
||||
class RemovedCommandError(DistutilsError, RuntimeError):
|
||||
# Re-export errors from distutils to facilitate the migration to PEP632
|
||||
|
||||
ByteCompileError = _distutils_errors.DistutilsByteCompileError
|
||||
CCompilerError = _distutils_errors.CCompilerError
|
||||
ClassError = _distutils_errors.DistutilsClassError
|
||||
CompileError = _distutils_errors.CompileError
|
||||
ExecError = _distutils_errors.DistutilsExecError
|
||||
FileError = _distutils_errors.DistutilsFileError
|
||||
InternalError = _distutils_errors.DistutilsInternalError
|
||||
LibError = _distutils_errors.LibError
|
||||
LinkError = _distutils_errors.LinkError
|
||||
ModuleError = _distutils_errors.DistutilsModuleError
|
||||
OptionError = _distutils_errors.DistutilsOptionError
|
||||
PlatformError = _distutils_errors.DistutilsPlatformError
|
||||
PreprocessError = _distutils_errors.PreprocessError
|
||||
SetupError = _distutils_errors.DistutilsSetupError
|
||||
TemplateError = _distutils_errors.DistutilsTemplateError
|
||||
UnknownFileError = _distutils_errors.UnknownFileError
|
||||
|
||||
# The root error class in the hierarchy
|
||||
BaseError = _distutils_errors.DistutilsError
|
||||
|
||||
|
||||
class RemovedCommandError(BaseError, RuntimeError):
|
||||
"""Error used for commands that have been removed in setuptools.
|
||||
|
||||
Since ``setuptools`` is built on ``distutils``, simply removing a command
|
||||
@@ -14,3 +37,22 @@ class RemovedCommandError(DistutilsError, RuntimeError):
|
||||
error is raised if a command exists in ``distutils`` but has been actively
|
||||
removed in ``setuptools``.
|
||||
"""
|
||||
|
||||
|
||||
class PackageDiscoveryError(BaseError, RuntimeError):
|
||||
"""Impossible to perform automatic discovery of packages and/or modules.
|
||||
|
||||
The current project layout or given discovery options can lead to problems when
|
||||
scanning the project directory.
|
||||
|
||||
Setuptools might also refuse to complete auto-discovery if an error prone condition
|
||||
is detected (e.g. when a project is organised as a flat-layout but contains
|
||||
multiple directories that can be taken as top-level packages inside a single
|
||||
distribution [*]_). In these situations the users are encouraged to be explicit
|
||||
about which packages to include or to make the discovery parameters more specific.
|
||||
|
||||
.. [*] Since multi-package distributions are uncommon it is very likely that the
|
||||
developers did not intend for all the directories to be packaged, and are just
|
||||
leaving auxiliary code in the repository top-level, such as maintenance-related
|
||||
scripts.
|
||||
"""
|
||||
|
||||
@@ -28,13 +28,106 @@ _Extension = get_unpatched(distutils.core.Extension)
|
||||
|
||||
|
||||
class Extension(_Extension):
|
||||
"""Extension that uses '.c' files in place of '.pyx' files"""
|
||||
"""
|
||||
Describes a single extension module.
|
||||
|
||||
This means that all source files will be compiled into a single binary file
|
||||
``<module path>.<suffix>`` (with ``<module path>`` derived from ``name`` and
|
||||
``<suffix>`` defined by one of the values in
|
||||
``importlib.machinery.EXTENSION_SUFFIXES``).
|
||||
|
||||
In the case ``.pyx`` files are passed as ``sources and`` ``Cython`` is **not**
|
||||
installed in the build environment, ``setuptools`` may also try to look for the
|
||||
equivalent ``.cpp`` or ``.c`` files.
|
||||
|
||||
:arg str name:
|
||||
the full name of the extension, including any packages -- ie.
|
||||
*not* a filename or pathname, but Python dotted name
|
||||
|
||||
:arg list[str] sources:
|
||||
list of source filenames, relative to the distribution root
|
||||
(where the setup script lives), in Unix form (slash-separated)
|
||||
for portability. Source files may be C, C++, SWIG (.i),
|
||||
platform-specific resource files, or whatever else is recognized
|
||||
by the "build_ext" command as source for a Python extension.
|
||||
|
||||
:keyword list[str] include_dirs:
|
||||
list of directories to search for C/C++ header files (in Unix
|
||||
form for portability)
|
||||
|
||||
:keyword list[tuple[str, str|None]] define_macros:
|
||||
list of macros to define; each macro is defined using a 2-tuple:
|
||||
the first item corresponding to the name of the macro and the second
|
||||
item either a string with its value or None to
|
||||
define it without a particular value (equivalent of "#define
|
||||
FOO" in source or -DFOO on Unix C compiler command line)
|
||||
|
||||
:keyword list[str] undef_macros:
|
||||
list of macros to undefine explicitly
|
||||
|
||||
:keyword list[str] library_dirs:
|
||||
list of directories to search for C/C++ libraries at link time
|
||||
|
||||
:keyword list[str] libraries:
|
||||
list of library names (not filenames or paths) to link against
|
||||
|
||||
:keyword list[str] runtime_library_dirs:
|
||||
list of directories to search for C/C++ libraries at run time
|
||||
(for shared extensions, this is when the extension is loaded).
|
||||
Setting this will cause an exception during build on Windows
|
||||
platforms.
|
||||
|
||||
:keyword list[str] extra_objects:
|
||||
list of extra files to link with (eg. object files not implied
|
||||
by 'sources', static library that must be explicitly specified,
|
||||
binary resource files, etc.)
|
||||
|
||||
:keyword list[str] extra_compile_args:
|
||||
any extra platform- and compiler-specific information to use
|
||||
when compiling the source files in 'sources'. For platforms and
|
||||
compilers where "command line" makes sense, this is typically a
|
||||
list of command-line arguments, but for other platforms it could
|
||||
be anything.
|
||||
|
||||
:keyword list[str] extra_link_args:
|
||||
any extra platform- and compiler-specific information to use
|
||||
when linking object files together to create the extension (or
|
||||
to create a new static Python interpreter). Similar
|
||||
interpretation as for 'extra_compile_args'.
|
||||
|
||||
:keyword list[str] export_symbols:
|
||||
list of symbols to be exported from a shared extension. Not
|
||||
used on all platforms, and not generally necessary for Python
|
||||
extensions, which typically export exactly one symbol: "init" +
|
||||
extension_name.
|
||||
|
||||
:keyword list[str] swig_opts:
|
||||
any extra options to pass to SWIG if a source file has the .i
|
||||
extension.
|
||||
|
||||
:keyword list[str] depends:
|
||||
list of files that the extension depends on
|
||||
|
||||
:keyword str language:
|
||||
extension language (i.e. "c", "c++", "objc"). Will be detected
|
||||
from the source extensions if not provided.
|
||||
|
||||
:keyword bool optional:
|
||||
specifies that a build failure in the extension should not abort the
|
||||
build process, but simply not install the failing extension.
|
||||
|
||||
:keyword bool py_limited_api:
|
||||
opt-in flag for the usage of :doc:`Python's limited API <python:c-api/stable>`.
|
||||
|
||||
:raises setuptools.errors.PlatformError: if 'runtime_library_dirs' is
|
||||
specified on Windows. (since v63)
|
||||
"""
|
||||
|
||||
def __init__(self, name, sources, *args, **kw):
|
||||
# The *args is needed for compatibility as calls may use positional
|
||||
# arguments. py_limited_api may be set only via keyword.
|
||||
self.py_limited_api = kw.pop("py_limited_api", False)
|
||||
_Extension.__init__(self, name, sources, *args, **kw)
|
||||
super().__init__(name, sources, *args, **kw)
|
||||
|
||||
def _convert_pyx_sources_to_lang(self):
|
||||
"""
|
||||
|
||||
@@ -69,5 +69,8 @@ class VendorImporter:
|
||||
sys.meta_path.append(self)
|
||||
|
||||
|
||||
names = 'packaging', 'pyparsing', 'ordered_set', 'more_itertools',
|
||||
names = (
|
||||
'packaging', 'pyparsing', 'ordered_set', 'more_itertools', 'importlib_metadata',
|
||||
'zipp', 'importlib_resources', 'jaraco', 'typing_extensions', 'tomli',
|
||||
)
|
||||
VendorImporter(__name__, names, 'setuptools._vendor').install()
|
||||
|
||||
@@ -3,11 +3,13 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsError
|
||||
|
||||
import pkg_resources
|
||||
from setuptools.wheel import Wheel
|
||||
from ._deprecation_warning import SetuptoolsDeprecationWarning
|
||||
|
||||
|
||||
def _fixup_find_links(find_links):
|
||||
@@ -22,6 +24,11 @@ def fetch_build_egg(dist, req): # noqa: C901 # is too complex (16) # FIXME
|
||||
"""Fetch an egg needed for building.
|
||||
|
||||
Use pip/wheel to fetch/build a wheel."""
|
||||
warnings.warn(
|
||||
"setuptools.installer is deprecated. Requirements should "
|
||||
"be satisfied by a PEP 517 installer.",
|
||||
SetuptoolsDeprecationWarning,
|
||||
)
|
||||
# Warn if wheel is not available
|
||||
try:
|
||||
pkg_resources.get_distribution('wheel')
|
||||
|
||||
@@ -71,8 +71,6 @@ def patch_all():
|
||||
distutils.filelist.findall = setuptools.findall
|
||||
|
||||
needs_warehouse = (
|
||||
sys.version_info < (2, 7, 13)
|
||||
or
|
||||
(3, 4) < sys.version_info < (3, 4, 6)
|
||||
or
|
||||
(3, 5) < sys.version_info <= (3, 5, 3)
|
||||
@@ -143,7 +141,7 @@ def patch_for_msvc_specialized_compiler():
|
||||
"""
|
||||
Prepare the parameters for patch_func to patch indicated function.
|
||||
"""
|
||||
repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_'
|
||||
repl_prefix = 'msvc14_'
|
||||
repl_name = repl_prefix + func_name.lstrip('_')
|
||||
repl = getattr(msvc, repl_name)
|
||||
mod = import_module(mod_name)
|
||||
@@ -151,19 +149,9 @@ def patch_for_msvc_specialized_compiler():
|
||||
raise ImportError(func_name)
|
||||
return repl, mod, func_name
|
||||
|
||||
# Python 2.7 to 3.4
|
||||
msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler')
|
||||
|
||||
# Python 3.5+
|
||||
msvc14 = functools.partial(patch_params, 'distutils._msvccompiler')
|
||||
|
||||
try:
|
||||
# Patch distutils.msvc9compiler
|
||||
patch_func(*msvc9('find_vcvarsall'))
|
||||
patch_func(*msvc9('query_vcvarsall'))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Patch distutils._msvccompiler._get_vc_env
|
||||
patch_func(*msvc14('_get_vc_env'))
|
||||
|
||||
@@ -3,14 +3,6 @@ Improved support for Microsoft Visual C++ compilers.
|
||||
|
||||
Known supported compilers:
|
||||
--------------------------
|
||||
Microsoft Visual C++ 9.0:
|
||||
Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
|
||||
Microsoft Windows SDK 6.1 (x86, x64, ia64)
|
||||
Microsoft Windows SDK 7.0 (x86, x64, ia64)
|
||||
|
||||
Microsoft Visual C++ 10.0:
|
||||
Microsoft Windows SDK 7.1 (x86, x64, ia64)
|
||||
|
||||
Microsoft Visual C++ 14.X:
|
||||
Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
|
||||
Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
|
||||
@@ -48,100 +40,6 @@ else:
|
||||
|
||||
environ = dict()
|
||||
|
||||
_msvc9_suppress_errors = (
|
||||
# msvc9compiler isn't available on some platforms
|
||||
ImportError,
|
||||
|
||||
# msvc9compiler raises DistutilsPlatformError in some
|
||||
# environments. See #1118.
|
||||
distutils.errors.DistutilsPlatformError,
|
||||
)
|
||||
|
||||
try:
|
||||
from distutils.msvc9compiler import Reg
|
||||
except _msvc9_suppress_errors:
|
||||
pass
|
||||
|
||||
|
||||
def msvc9_find_vcvarsall(version):
|
||||
"""
|
||||
Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
|
||||
compiler build for Python
|
||||
(VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
|
||||
|
||||
Fall back to original behavior when the standalone compiler is not
|
||||
available.
|
||||
|
||||
Redirect the path of "vcvarsall.bat".
|
||||
|
||||
Parameters
|
||||
----------
|
||||
version: float
|
||||
Required Microsoft Visual C++ version.
|
||||
|
||||
Return
|
||||
------
|
||||
str
|
||||
vcvarsall.bat path
|
||||
"""
|
||||
vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
|
||||
key = vc_base % ('', version)
|
||||
try:
|
||||
# Per-user installs register the compiler path here
|
||||
productdir = Reg.get_value(key, "installdir")
|
||||
except KeyError:
|
||||
try:
|
||||
# All-user installs on a 64-bit system register here
|
||||
key = vc_base % ('Wow6432Node\\', version)
|
||||
productdir = Reg.get_value(key, "installdir")
|
||||
except KeyError:
|
||||
productdir = None
|
||||
|
||||
if productdir:
|
||||
vcvarsall = join(productdir, "vcvarsall.bat")
|
||||
if isfile(vcvarsall):
|
||||
return vcvarsall
|
||||
|
||||
return get_unpatched(msvc9_find_vcvarsall)(version)
|
||||
|
||||
|
||||
def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
|
||||
"""
|
||||
Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
|
||||
Microsoft Visual C++ 9.0 and 10.0 compilers.
|
||||
|
||||
Set environment without use of "vcvarsall.bat".
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ver: float
|
||||
Required Microsoft Visual C++ version.
|
||||
arch: str
|
||||
Target architecture.
|
||||
|
||||
Return
|
||||
------
|
||||
dict
|
||||
environment
|
||||
"""
|
||||
# Try to get environment from vcvarsall.bat (Classical way)
|
||||
try:
|
||||
orig = get_unpatched(msvc9_query_vcvarsall)
|
||||
return orig(ver, arch, *args, **kwargs)
|
||||
except distutils.errors.DistutilsPlatformError:
|
||||
# Pass error if Vcvarsall.bat is missing
|
||||
pass
|
||||
except ValueError:
|
||||
# Pass error if environment not set after executing vcvarsall.bat
|
||||
pass
|
||||
|
||||
# If error, try to set environment directly
|
||||
try:
|
||||
return EnvironmentInfo(arch, ver).return_env()
|
||||
except distutils.errors.DistutilsPlatformError as exc:
|
||||
_augment_exception(exc, ver, arch)
|
||||
raise
|
||||
|
||||
|
||||
def _msvc14_find_vc2015():
|
||||
"""Python 3.8 "distutils/_msvccompiler.py" backport"""
|
||||
|
||||
@@ -21,7 +21,7 @@ import setuptools
|
||||
from pkg_resources import (
|
||||
CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST,
|
||||
Environment, find_distributions, safe_name, safe_version,
|
||||
to_filename, Requirement, DEVELOP_DIST, EGG_DIST,
|
||||
to_filename, Requirement, DEVELOP_DIST, EGG_DIST, parse_version,
|
||||
)
|
||||
from distutils import log
|
||||
from distutils.errors import DistutilsError
|
||||
@@ -285,7 +285,7 @@ class PackageIndex(Environment):
|
||||
self, index_url="https://pypi.org/simple/", hosts=('*',),
|
||||
ca_bundle=None, verify_ssl=True, *args, **kw
|
||||
):
|
||||
Environment.__init__(self, *args, **kw)
|
||||
super().__init__(*args, **kw)
|
||||
self.index_url = index_url + "/" [:not index_url.endswith('/')]
|
||||
self.scanned_urls = {}
|
||||
self.fetched_urls = {}
|
||||
@@ -294,6 +294,14 @@ class PackageIndex(Environment):
|
||||
self.to_scan = []
|
||||
self.opener = urllib.request.urlopen
|
||||
|
||||
def add(self, dist):
|
||||
# ignore invalid versions
|
||||
try:
|
||||
parse_version(dist.version)
|
||||
except Exception:
|
||||
return
|
||||
return super().add(dist)
|
||||
|
||||
# FIXME: 'PackageIndex.process_url' is too complex (14)
|
||||
def process_url(self, url, retrieve=False): # noqa: C901
|
||||
"""Evaluate a URL as a possible download, and maybe retrieve it"""
|
||||
@@ -672,8 +680,7 @@ class PackageIndex(Environment):
|
||||
# Make sure the file has been downloaded to the temp dir.
|
||||
if os.path.dirname(filename) != tmpdir:
|
||||
dst = os.path.join(tmpdir, basename)
|
||||
from setuptools.command.easy_install import samefile
|
||||
if not samefile(filename, dst):
|
||||
if not (os.path.exists(dst) and os.path.samefile(filename, dst)):
|
||||
shutil.copy2(filename, dst)
|
||||
filename = dst
|
||||
|
||||
@@ -994,7 +1001,7 @@ class PyPIConfig(configparser.RawConfigParser):
|
||||
Load from ~/.pypirc
|
||||
"""
|
||||
defaults = dict.fromkeys(['username', 'password', 'repository'], '')
|
||||
configparser.RawConfigParser.__init__(self, defaults)
|
||||
super().__init__(defaults)
|
||||
|
||||
rc = os.path.join(os.path.expanduser('~'), '.pypirc')
|
||||
if os.path.exists(rc):
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
"""Wheels support."""
|
||||
|
||||
from distutils.util import get_platform
|
||||
from distutils import log
|
||||
import email
|
||||
import itertools
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import zipfile
|
||||
import contextlib
|
||||
|
||||
from distutils.util import get_platform
|
||||
|
||||
import pkg_resources
|
||||
import setuptools
|
||||
@@ -15,6 +16,7 @@ from pkg_resources import parse_version
|
||||
from setuptools.extern.packaging.tags import sys_tags
|
||||
from setuptools.extern.packaging.utils import canonicalize_name
|
||||
from setuptools.command.egg_info import write_requirements
|
||||
from setuptools.archive_util import _unpack_zipfile_obj
|
||||
|
||||
|
||||
WHEEL_NAME = re.compile(
|
||||
@@ -49,6 +51,19 @@ def unpack(src_dir, dst_dir):
|
||||
os.rmdir(dirpath)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def disable_info_traces():
|
||||
"""
|
||||
Temporarily disable info traces.
|
||||
"""
|
||||
from distutils import log
|
||||
saved = log.set_threshold(log.WARN)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
log.set_threshold(saved)
|
||||
|
||||
|
||||
class Wheel:
|
||||
|
||||
def __init__(self, filename):
|
||||
@@ -121,8 +136,7 @@ class Wheel:
|
||||
raise ValueError(
|
||||
'unsupported wheel format version: %s' % wheel_version)
|
||||
# Extract to target directory.
|
||||
os.mkdir(destination_eggdir)
|
||||
zf.extractall(destination_eggdir)
|
||||
_unpack_zipfile_obj(zf, destination_eggdir)
|
||||
# Convert metadata.
|
||||
dist_info = os.path.join(destination_eggdir, dist_info)
|
||||
dist = pkg_resources.Distribution.from_location(
|
||||
@@ -136,13 +150,13 @@ class Wheel:
|
||||
def raw_req(req):
|
||||
req.marker = None
|
||||
return str(req)
|
||||
install_requires = list(sorted(map(raw_req, dist.requires())))
|
||||
install_requires = list(map(raw_req, dist.requires()))
|
||||
extras_require = {
|
||||
extra: sorted(
|
||||
extra: [
|
||||
req
|
||||
for req in map(raw_req, dist.requires((extra,)))
|
||||
if req not in install_requires
|
||||
)
|
||||
]
|
||||
for extra in dist.extras
|
||||
}
|
||||
os.rename(dist_info, egg_info)
|
||||
@@ -156,17 +170,12 @@ class Wheel:
|
||||
extras_require=extras_require,
|
||||
),
|
||||
)
|
||||
# Temporarily disable info traces.
|
||||
log_threshold = log._global_log.threshold
|
||||
log.set_threshold(log.WARN)
|
||||
try:
|
||||
with disable_info_traces():
|
||||
write_requirements(
|
||||
setup_dist.get_command_obj('egg_info'),
|
||||
None,
|
||||
os.path.join(egg_info, 'requires.txt'),
|
||||
)
|
||||
finally:
|
||||
log.set_threshold(log_threshold)
|
||||
|
||||
@staticmethod
|
||||
def _move_data_entries(destination_eggdir, dist_data):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import platform
|
||||
import ctypes
|
||||
|
||||
|
||||
def windows_only(func):
|
||||
@@ -17,6 +16,7 @@ def hide_file(path):
|
||||
|
||||
`path` must be text.
|
||||
"""
|
||||
import ctypes
|
||||
__import__('ctypes.wintypes')
|
||||
SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW
|
||||
SetFileAttributes.argtypes = ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD
|
||||
|
||||
Reference in New Issue
Block a user