Remove hardcoded libpython binaries and add debug step
All checks were successful
build / build-linux (push) Successful in 16s
All checks were successful
build / build-linux (push) Successful in 16s
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,340 @@
|
||||
"""distutils.cygwinccompiler
|
||||
|
||||
Provides the CygwinCCompiler class, a subclass of UnixCCompiler that
|
||||
handles the Cygwin port of the GNU C compiler to Windows. It also contains
|
||||
the Mingw32CCompiler class which handles the mingw32 port of GCC (same as
|
||||
cygwin in no-cygwin mode).
|
||||
"""
|
||||
|
||||
import copy
|
||||
import os
|
||||
import pathlib
|
||||
import shlex
|
||||
import sys
|
||||
import warnings
|
||||
from subprocess import check_output
|
||||
|
||||
from ...errors import (
|
||||
DistutilsExecError,
|
||||
DistutilsPlatformError,
|
||||
)
|
||||
from ...file_util import write_file
|
||||
from ...sysconfig import get_config_vars
|
||||
from ...version import LooseVersion, suppress_known_deprecation
|
||||
from . import unix
|
||||
from .errors import (
|
||||
CompileError,
|
||||
Error,
|
||||
)
|
||||
|
||||
|
||||
def get_msvcr():
|
||||
"""No longer needed, but kept for backward compatibility."""
|
||||
return []
|
||||
|
||||
|
||||
_runtime_library_dirs_msg = (
|
||||
"Unable to set runtime library search path on Windows, "
|
||||
"usually indicated by `runtime_library_dirs` parameter to Extension"
|
||||
)
|
||||
|
||||
|
||||
class Compiler(unix.Compiler):
|
||||
"""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.a"
|
||||
dylib_lib_extension = ".dll"
|
||||
static_lib_format = "lib%s%s"
|
||||
shared_lib_format = "lib%s%s"
|
||||
dylib_lib_format = "cyg%s%s"
|
||||
exe_extension = ".exe"
|
||||
|
||||
def __init__(self, verbose=False, dry_run=False, force=False):
|
||||
super().__init__(verbose, dry_run, force)
|
||||
|
||||
status, details = check_config_h()
|
||||
self.debug_print(f"Python's GCC status: {status} (details: {details})")
|
||||
if status is not CONFIG_H_OK:
|
||||
self.warn(
|
||||
"Python's pyconfig.h doesn't seem to support your compiler. "
|
||||
f"Reason: {details}. "
|
||||
"Compiling may fail because of undefined preprocessor macros."
|
||||
)
|
||||
|
||||
self.cc, self.cxx = get_config_vars('CC', 'CXX')
|
||||
|
||||
# Override 'CC' and 'CXX' environment variables for
|
||||
# building using MINGW compiler for MSVC python.
|
||||
self.cc = os.environ.get('CC', self.cc or 'gcc')
|
||||
self.cxx = os.environ.get('CXX', self.cxx or 'g++')
|
||||
|
||||
self.linker_dll = self.cc
|
||||
self.linker_dll_cxx = self.cxx
|
||||
shared_option = "-shared"
|
||||
|
||||
self.set_executables(
|
||||
compiler=f'{self.cc} -mcygwin -O -Wall',
|
||||
compiler_so=f'{self.cc} -mcygwin -mdll -O -Wall',
|
||||
compiler_cxx=f'{self.cxx} -mcygwin -O -Wall',
|
||||
compiler_so_cxx=f'{self.cxx} -mcygwin -mdll -O -Wall',
|
||||
linker_exe=f'{self.cc} -mcygwin',
|
||||
linker_so=f'{self.linker_dll} -mcygwin {shared_option}',
|
||||
linker_exe_cxx=f'{self.cxx} -mcygwin',
|
||||
linker_so_cxx=f'{self.linker_dll_cxx} -mcygwin {shared_option}',
|
||||
)
|
||||
|
||||
self.dll_libraries = get_msvcr()
|
||||
|
||||
@property
|
||||
def gcc_version(self):
|
||||
# Older numpy depended 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."""
|
||||
if ext in ('.rc', '.res'):
|
||||
# gcc needs '.res' and '.rc' compiled to object files !!!
|
||||
try:
|
||||
self.spawn(["windres", "-i", src, "-o", obj])
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
else: # for other files use the C-compiler
|
||||
try:
|
||||
if self.detect_language(src) == 'c++':
|
||||
self.spawn(
|
||||
self.compiler_so_cxx
|
||||
+ cc_args
|
||||
+ [src, '-o', obj]
|
||||
+ extra_postargs
|
||||
)
|
||||
else:
|
||||
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=False,
|
||||
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"
|
||||
):
|
||||
# (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
|
||||
# UnixCCompiler, and this is not what we want.)
|
||||
|
||||
# we want to put some files in the same directory as the
|
||||
# object files are, build_temp doesn't help much
|
||||
# where are the object files
|
||||
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)
|
||||
)
|
||||
|
||||
# generate the filenames for these files
|
||||
def_file = os.path.join(temp_dir, dll_name + ".def")
|
||||
|
||||
# Generate .def file
|
||||
contents = [f"LIBRARY {os.path.basename(output_filename)}", "EXPORTS"]
|
||||
contents.extend(export_symbols)
|
||||
self.execute(write_file, (def_file, contents), f"writing {def_file}")
|
||||
|
||||
# next add options for 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
|
||||
# (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 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")
|
||||
|
||||
super().link(
|
||||
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 _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 MinGW32Compiler(Compiler):
|
||||
"""Handles the Mingw32 port of the GNU C compiler to Windows."""
|
||||
|
||||
compiler_type = 'mingw32'
|
||||
|
||||
def __init__(self, verbose=False, dry_run=False, force=False):
|
||||
super().__init__(verbose, dry_run, force)
|
||||
|
||||
shared_option = "-shared"
|
||||
|
||||
if is_cygwincc(self.cc):
|
||||
raise Error('Cygwin gcc cannot be used with --compiler=mingw32')
|
||||
|
||||
self.set_executables(
|
||||
compiler=f'{self.cc} -O -Wall',
|
||||
compiler_so=f'{self.cc} -shared -O -Wall',
|
||||
compiler_so_cxx=f'{self.cxx} -shared -O -Wall',
|
||||
compiler_cxx=f'{self.cxx} -O -Wall',
|
||||
linker_exe=f'{self.cc}',
|
||||
linker_so=f'{self.linker_dll} {shared_option}',
|
||||
linker_exe_cxx=f'{self.cxx}',
|
||||
linker_so_cxx=f'{self.linker_dll_cxx} {shared_option}',
|
||||
)
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Returns a tuple (status, details), where 'status' is one of the following
|
||||
constants:
|
||||
|
||||
- CONFIG_H_OK: all is well, go ahead and compile
|
||||
- CONFIG_H_NOTOK: doesn't look good
|
||||
- CONFIG_H_UNCERTAIN: not sure -- unable to read pyconfig.h
|
||||
|
||||
'details' is a human-readable string explaining the situation.
|
||||
|
||||
Note there are two ways to conclude "OK": either 'sys.version' contains
|
||||
the string "GCC" (implying that this Python was built with GCC), or the
|
||||
installed "pyconfig.h" contains the string "__GNUC__".
|
||||
"""
|
||||
|
||||
# XXX since this function also checks sys.version, it's not strictly a
|
||||
# "pyconfig.h" check -- should probably be renamed...
|
||||
|
||||
from distutils import sysconfig
|
||||
|
||||
# if sys.version contains GCC then python was compiled with GCC, and the
|
||||
# pyconfig.h file should be OK
|
||||
if "GCC" in sys.version:
|
||||
return CONFIG_H_OK, "sys.version mentions 'GCC'"
|
||||
|
||||
# Clang would also work
|
||||
if "Clang" in sys.version:
|
||||
return CONFIG_H_OK, "sys.version mentions 'Clang'"
|
||||
|
||||
# let's see if __GNUC__ is mentioned in python.h
|
||||
fn = sysconfig.get_config_h_filename()
|
||||
try:
|
||||
config_h = pathlib.Path(fn).read_text(encoding='utf-8')
|
||||
except OSError as exc:
|
||||
return (CONFIG_H_UNCERTAIN, f"couldn't read '{fn}': {exc.strerror}")
|
||||
else:
|
||||
substring = '__GNUC__'
|
||||
if substring in config_h:
|
||||
code = CONFIG_H_OK
|
||||
mention_inflected = 'mentions'
|
||||
else:
|
||||
code = CONFIG_H_NOTOK
|
||||
mention_inflected = 'does not mention'
|
||||
return code, f"{fn!r} {mention_inflected} {substring!r}"
|
||||
|
||||
|
||||
def is_cygwincc(cc):
|
||||
"""Try to determine if the compiler that would be used is from cygwin."""
|
||||
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.
|
||||
"""
|
||||
@@ -0,0 +1,24 @@
|
||||
class Error(Exception):
|
||||
"""Some compile/link operation failed."""
|
||||
|
||||
|
||||
class PreprocessError(Error):
|
||||
"""Failure to preprocess one or more C/C++ files."""
|
||||
|
||||
|
||||
class CompileError(Error):
|
||||
"""Failure to compile one or more C/C++ source files."""
|
||||
|
||||
|
||||
class LibError(Error):
|
||||
"""Failure to create a static library from one or more C/C++ object
|
||||
files."""
|
||||
|
||||
|
||||
class LinkError(Error):
|
||||
"""Failure to link one or more C/C++ object files into an executable
|
||||
or shared library file."""
|
||||
|
||||
|
||||
class UnknownFileType(Error):
|
||||
"""Attempt to process an unknown file type."""
|
||||
@@ -0,0 +1,614 @@
|
||||
"""distutils._msvccompiler
|
||||
|
||||
Contains MSVCCompiler, an implementation of the abstract CCompiler class
|
||||
for Microsoft Visual Studio 2015.
|
||||
|
||||
This module requires VS 2015 or later.
|
||||
"""
|
||||
|
||||
# Written by Perry Stoll
|
||||
# hacked by Robin Becker and Thomas Heller to do a better job of
|
||||
# finding DevStudio (through the registry)
|
||||
# ported to VS 2005 and VS 2008 by Christian Heimes
|
||||
# ported to VS 2015 by Steve Dower
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import subprocess
|
||||
import unittest.mock as mock
|
||||
import warnings
|
||||
from collections.abc import Iterable
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
import winreg
|
||||
|
||||
from itertools import count
|
||||
|
||||
from ..._log import log
|
||||
from ...errors import (
|
||||
DistutilsExecError,
|
||||
DistutilsPlatformError,
|
||||
)
|
||||
from ...util import get_host_platform, get_platform
|
||||
from . import base
|
||||
from .base import gen_lib_options
|
||||
from .errors import (
|
||||
CompileError,
|
||||
LibError,
|
||||
LinkError,
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
except OSError:
|
||||
log.debug("Visual C++ is not registered")
|
||||
return None, None
|
||||
|
||||
best_version = 0
|
||||
best_dir = None
|
||||
with key:
|
||||
for i in count():
|
||||
try:
|
||||
v, vc_dir, vt = winreg.EnumValue(key, i)
|
||||
except OSError:
|
||||
break
|
||||
if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir):
|
||||
try:
|
||||
version = int(float(v))
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
if version >= 14 and version > best_version:
|
||||
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"
|
||||
|
||||
The version is returned to avoid unnecessarily changing the function
|
||||
result. It may be ignored when the path is not None.
|
||||
|
||||
If vswhere.exe is not available, by definition, VS 2017 is not
|
||||
installed.
|
||||
"""
|
||||
root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles")
|
||||
if not root:
|
||||
return None, None
|
||||
|
||||
variant = 'arm64' if get_platform() == 'win-arm64' else 'x86.x64'
|
||||
suitable_components = (
|
||||
f"Microsoft.VisualStudio.Component.VC.Tools.{variant}",
|
||||
"Microsoft.VisualStudio.Workload.WDExpress",
|
||||
)
|
||||
|
||||
for component in suitable_components:
|
||||
# Workaround for `-requiresAny` (only available on VS 2017 > 15.6)
|
||||
with contextlib.suppress(
|
||||
subprocess.CalledProcessError, OSError, UnicodeDecodeError
|
||||
):
|
||||
path = (
|
||||
subprocess.check_output([
|
||||
os.path.join(
|
||||
root, "Microsoft Visual Studio", "Installer", "vswhere.exe"
|
||||
),
|
||||
"-latest",
|
||||
"-prerelease",
|
||||
"-requires",
|
||||
component,
|
||||
"-property",
|
||||
"installationPath",
|
||||
"-products",
|
||||
"*",
|
||||
])
|
||||
.decode(encoding="mbcs", errors="strict")
|
||||
.strip()
|
||||
)
|
||||
|
||||
path = os.path.join(path, "VC", "Auxiliary", "Build")
|
||||
if os.path.isdir(path):
|
||||
return 15, path
|
||||
|
||||
return None, None # no suitable component found
|
||||
|
||||
|
||||
PLAT_SPEC_TO_RUNTIME = {
|
||||
'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()
|
||||
|
||||
if not best_dir:
|
||||
best_version, best_dir = _find_vc2015()
|
||||
|
||||
if not best_dir:
|
||||
log.debug("No suitable Visual C++ version found")
|
||||
return None, None
|
||||
|
||||
vcvarsall = os.path.join(best_dir, "vcvarsall.bat")
|
||||
if not os.path.isfile(vcvarsall):
|
||||
log.debug("%s cannot be found", vcvarsall)
|
||||
return None, None
|
||||
|
||||
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()}
|
||||
|
||||
vcvarsall, _ = _find_vcvarsall(plat_spec)
|
||||
if not vcvarsall:
|
||||
raise DistutilsPlatformError(
|
||||
'Microsoft Visual C++ 14.0 or greater is required. '
|
||||
'Get it with "Microsoft C++ Build Tools": '
|
||||
'https://visualstudio.microsoft.com/visual-cpp-build-tools/'
|
||||
)
|
||||
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
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(f"Error executing {exc.cmd}")
|
||||
|
||||
env = {
|
||||
key.lower(): value
|
||||
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.
|
||||
|
||||
Tries to find the program in several places: first, one of the
|
||||
MSVC program search paths from the registry; next, the directories
|
||||
in the PATH environment variable. If any of those work, return an
|
||||
absolute path that is known to exist. If none of them work, just
|
||||
return the original program name, 'exe'.
|
||||
"""
|
||||
if not paths:
|
||||
paths = os.getenv('path').split(os.pathsep)
|
||||
for p in paths:
|
||||
fn = os.path.join(os.path.abspath(p), exe)
|
||||
if os.path.isfile(fn):
|
||||
return fn
|
||||
return exe
|
||||
|
||||
|
||||
_vcvars_names = {
|
||||
'win32': 'x86',
|
||||
'win-amd64': 'amd64',
|
||||
'win-arm32': 'arm',
|
||||
'win-arm64': 'arm64',
|
||||
}
|
||||
|
||||
|
||||
def _get_vcvars_spec(host_platform, platform):
|
||||
"""
|
||||
Given a host platform and platform, determine the spec for vcvarsall.
|
||||
|
||||
Uses the native MSVC host if the host platform would need expensive
|
||||
emulation for x86.
|
||||
|
||||
>>> _get_vcvars_spec('win-arm64', 'win32')
|
||||
'arm64_x86'
|
||||
>>> _get_vcvars_spec('win-arm64', 'win-amd64')
|
||||
'arm64_amd64'
|
||||
|
||||
Otherwise, always cross-compile from x86 to work with the
|
||||
lighter-weight MSVC installs that do not include native 64-bit tools.
|
||||
|
||||
>>> _get_vcvars_spec('win32', 'win32')
|
||||
'x86'
|
||||
>>> _get_vcvars_spec('win-arm32', 'win-arm32')
|
||||
'x86_arm'
|
||||
>>> _get_vcvars_spec('win-amd64', 'win-arm64')
|
||||
'x86_arm64'
|
||||
"""
|
||||
if host_platform != 'win-arm64':
|
||||
host_platform = 'win32'
|
||||
vc_hp = _vcvars_names[host_platform]
|
||||
vc_plat = _vcvars_names[platform]
|
||||
return vc_hp if vc_hp == vc_plat else f'{vc_hp}_{vc_plat}'
|
||||
|
||||
|
||||
class Compiler(base.Compiler):
|
||||
"""Concrete class that implements an interface to Microsoft Visual C++,
|
||||
as defined by the CCompiler abstract class."""
|
||||
|
||||
compiler_type = 'msvc'
|
||||
|
||||
# Just set this so CCompiler's constructor doesn't barf. We currently
|
||||
# don't use the 'set_executables()' bureaucracy provided by CCompiler,
|
||||
# as it really isn't necessary for this sort of single-compiler class.
|
||||
# Would be nice to have a consistent interface with UnixCCompiler,
|
||||
# though, so it's worth thinking about.
|
||||
executables = {}
|
||||
|
||||
# Private class data (need to distinguish C from C++ source for compiler)
|
||||
_c_extensions = ['.c']
|
||||
_cpp_extensions = ['.cc', '.cpp', '.cxx']
|
||||
_rc_extensions = ['.rc']
|
||||
_mc_extensions = ['.mc']
|
||||
|
||||
# Needed for the filename generation methods provided by the
|
||||
# base class, CCompiler.
|
||||
src_extensions = _c_extensions + _cpp_extensions + _rc_extensions + _mc_extensions
|
||||
res_extension = '.res'
|
||||
obj_extension = '.obj'
|
||||
static_lib_extension = '.lib'
|
||||
shared_lib_extension = '.dll'
|
||||
static_lib_format = shared_lib_format = '%s%s'
|
||||
exe_extension = '.exe'
|
||||
|
||||
def __init__(self, verbose=False, dry_run=False, force=False) -> None:
|
||||
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: str | None = None) -> None:
|
||||
# multi-init means we would need to check platform same each time...
|
||||
assert not self.initialized, "don't init multiple times"
|
||||
if plat_name is None:
|
||||
plat_name = get_platform()
|
||||
# sanity check for platforms to prevent obscure errors later.
|
||||
if plat_name not in _vcvars_names:
|
||||
raise DistutilsPlatformError(
|
||||
f"--plat-name must be one of {tuple(_vcvars_names)}"
|
||||
)
|
||||
|
||||
plat_spec = _get_vcvars_spec(get_host_platform(), plat_name)
|
||||
|
||||
vc_env = _get_vc_env(plat_spec)
|
||||
if not vc_env:
|
||||
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
|
||||
|
||||
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_debug = [
|
||||
'/nologo',
|
||||
'/Od',
|
||||
'/MDd',
|
||||
'/Zi',
|
||||
'/W3',
|
||||
'/D_DEBUG',
|
||||
]
|
||||
|
||||
ldflags = ['/nologo', '/INCREMENTAL:NO', '/LTCG']
|
||||
|
||||
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_static = [*ldflags]
|
||||
self.ldflags_static_debug = [*ldflags_debug]
|
||||
|
||||
self._ldflags = {
|
||||
(base.Compiler.EXECUTABLE, None): self.ldflags_exe,
|
||||
(base.Compiler.EXECUTABLE, False): self.ldflags_exe,
|
||||
(base.Compiler.EXECUTABLE, True): self.ldflags_exe_debug,
|
||||
(base.Compiler.SHARED_OBJECT, None): self.ldflags_shared,
|
||||
(base.Compiler.SHARED_OBJECT, False): self.ldflags_shared,
|
||||
(base.Compiler.SHARED_OBJECT, True): self.ldflags_shared_debug,
|
||||
(base.Compiler.SHARED_LIBRARY, None): self.ldflags_static,
|
||||
(base.Compiler.SHARED_LIBRARY, False): self.ldflags_static,
|
||||
(base.Compiler.SHARED_LIBRARY, True): self.ldflags_static_debug,
|
||||
}
|
||||
|
||||
self.initialized = True
|
||||
|
||||
# -- Worker methods ------------------------------------------------
|
||||
|
||||
@property
|
||||
def out_extensions(self) -> dict[str, str]:
|
||||
return {
|
||||
**super().out_extensions,
|
||||
**{
|
||||
ext: self.res_extension
|
||||
for ext in self._rc_extensions + self._mc_extensions
|
||||
},
|
||||
}
|
||||
|
||||
def compile( # noqa: C901
|
||||
self,
|
||||
sources,
|
||||
output_dir=None,
|
||||
macros=None,
|
||||
include_dirs=None,
|
||||
debug=False,
|
||||
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
|
||||
)
|
||||
macros, objects, extra_postargs, pp_opts, build = compile_info
|
||||
|
||||
compile_opts = extra_preargs or []
|
||||
compile_opts.append('/c')
|
||||
if debug:
|
||||
compile_opts.extend(self.compile_options_debug)
|
||||
else:
|
||||
compile_opts.extend(self.compile_options)
|
||||
|
||||
add_cpp_opts = False
|
||||
|
||||
for obj in objects:
|
||||
try:
|
||||
src, ext = build[obj]
|
||||
except KeyError:
|
||||
continue
|
||||
if debug:
|
||||
# pass the full pathname to MSVC in debug mode,
|
||||
# this allows the debugger to find the source file
|
||||
# without asking the user to browse for it
|
||||
src = os.path.abspath(src)
|
||||
|
||||
if ext in self._c_extensions:
|
||||
input_opt = f"/Tc{src}"
|
||||
elif ext in self._cpp_extensions:
|
||||
input_opt = f"/Tp{src}"
|
||||
add_cpp_opts = True
|
||||
elif ext in self._rc_extensions:
|
||||
# compile .RC to .RES file
|
||||
input_opt = src
|
||||
output_opt = "/fo" + obj
|
||||
try:
|
||||
self.spawn([self.rc] + pp_opts + [output_opt, input_opt])
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
continue
|
||||
elif ext in self._mc_extensions:
|
||||
# Compile .MC to .RC file to .RES file.
|
||||
# * '-h dir' specifies the directory for the
|
||||
# generated include file
|
||||
# * '-r dir' specifies the target directory of the
|
||||
# generated RC file and the binary message resource
|
||||
# it includes
|
||||
#
|
||||
# For now (since there are no options to change this),
|
||||
# we use the source-directory for the include file and
|
||||
# the build directory for the RC file and message
|
||||
# resources. This works at least for win32all.
|
||||
h_dir = os.path.dirname(src)
|
||||
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')
|
||||
# then compile .RC to .RES 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(f"Don't know how to compile {src} to {obj}")
|
||||
|
||||
args = [self.cc] + compile_opts + pp_opts
|
||||
if add_cpp_opts:
|
||||
args.append('/EHsc')
|
||||
args.extend((input_opt, "/Fo" + obj))
|
||||
args.extend(extra_postargs)
|
||||
|
||||
try:
|
||||
self.spawn(args)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
return objects
|
||||
|
||||
def create_static_lib(
|
||||
self,
|
||||
objects: list[str] | tuple[str, ...],
|
||||
output_libname: str,
|
||||
output_dir: str | None = None,
|
||||
debug: bool = False,
|
||||
target_lang: str | None = None,
|
||||
) -> 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)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
lib_args = objects + ['/OUT:' + output_filename]
|
||||
if debug:
|
||||
pass # XXX what goes here?
|
||||
try:
|
||||
log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args))
|
||||
self.spawn([self.lib] + lib_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise LibError(msg)
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
def link(
|
||||
self,
|
||||
target_desc: str,
|
||||
objects: list[str] | tuple[str, ...],
|
||||
output_filename: str,
|
||||
output_dir: str | None = None,
|
||||
libraries: list[str] | tuple[str, ...] | None = None,
|
||||
library_dirs: list[str] | tuple[str, ...] | None = None,
|
||||
runtime_library_dirs: list[str] | tuple[str, ...] | None = None,
|
||||
export_symbols: Iterable[str] | None = None,
|
||||
debug: bool = False,
|
||||
extra_preargs: list[str] | None = None,
|
||||
extra_postargs: Iterable[str] | None = None,
|
||||
build_temp: str | os.PathLike[str] | None = None,
|
||||
target_lang: str | None = None,
|
||||
) -> 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)
|
||||
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)
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
if self._need_link(objects, output_filename):
|
||||
ldflags = self._ldflags[target_desc, debug]
|
||||
|
||||
export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])]
|
||||
|
||||
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
|
||||
# needed! Make sure they are generated in the temporary build
|
||||
# directory. Since they have different names for debug and release
|
||||
# builds, they can go into the same directory.
|
||||
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)
|
||||
|
||||
if extra_preargs:
|
||||
ld_args[:0] = extra_preargs
|
||||
if extra_postargs:
|
||||
ld_args.extend(extra_postargs)
|
||||
|
||||
output_dir = os.path.dirname(os.path.abspath(output_filename))
|
||||
self.mkpath(output_dir)
|
||||
try:
|
||||
log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args))
|
||||
self.spawn([self.linker] + ld_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise LinkError(msg)
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
def spawn(self, cmd):
|
||||
env = dict(os.environ, PATH=self._paths)
|
||||
with self._fallback_spawn(cmd, env) as fallback:
|
||||
return super().spawn(cmd, env=env)
|
||||
return fallback.value
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _fallback_spawn(self, cmd, env):
|
||||
"""
|
||||
Discovered in pypa/distutils#15, some tools monkeypatch the compiler,
|
||||
so the 'env' kwarg causes a TypeError. Detect this condition and
|
||||
restore the legacy, unsafe behavior.
|
||||
"""
|
||||
bag = type('Bag', (), {})()
|
||||
try:
|
||||
yield bag
|
||||
except TypeError as exc:
|
||||
if "unexpected keyword argument 'env'" not in str(exc):
|
||||
raise
|
||||
else:
|
||||
return
|
||||
warnings.warn("Fallback spawn triggered. Please update distutils monkeypatch.")
|
||||
with mock.patch.dict('os.environ', env):
|
||||
bag.value = super().spawn(cmd)
|
||||
|
||||
# -- Miscellaneous methods -----------------------------------------
|
||||
# These are all used by the 'gen_lib_options() function, in
|
||||
# ccompiler.py.
|
||||
|
||||
def library_dir_option(self, dir):
|
||||
return "/LIBPATH:" + dir
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
raise DistutilsPlatformError(
|
||||
"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=False):
|
||||
# Prefer a debugging library if found (and requested), but deal
|
||||
# with it if we don't have one.
|
||||
if debug:
|
||||
try_names = [lib + "_d", lib]
|
||||
else:
|
||||
try_names = [lib]
|
||||
for dir in dirs:
|
||||
for name in try_names:
|
||||
libfile = os.path.join(dir, self.library_filename(name))
|
||||
if os.path.isfile(libfile):
|
||||
return libfile
|
||||
else:
|
||||
# Oops, didn't find it in *any* of 'dirs'
|
||||
return None
|
||||
@@ -0,0 +1,83 @@
|
||||
import platform
|
||||
import sysconfig
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
from .. import base
|
||||
|
||||
pytestmark = pytest.mark.usefixtures('suppress_path_mangle')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def c_file(tmp_path):
|
||||
c_file = tmp_path / 'foo.c'
|
||||
gen_headers = ('Python.h',)
|
||||
is_windows = platform.system() == "Windows"
|
||||
plat_headers = ('windows.h',) * is_windows
|
||||
all_headers = gen_headers + plat_headers
|
||||
headers = '\n'.join(f'#include <{header}>\n' for header in all_headers)
|
||||
payload = (
|
||||
textwrap.dedent(
|
||||
"""
|
||||
#headers
|
||||
void PyInit_foo(void) {}
|
||||
"""
|
||||
)
|
||||
.lstrip()
|
||||
.replace('#headers', headers)
|
||||
)
|
||||
c_file.write_text(payload, encoding='utf-8')
|
||||
return c_file
|
||||
|
||||
|
||||
def test_set_include_dirs(c_file):
|
||||
"""
|
||||
Extensions should build even if set_include_dirs is invoked.
|
||||
In particular, compiler-specific paths should not be overridden.
|
||||
"""
|
||||
compiler = base.new_compiler()
|
||||
python = sysconfig.get_paths()['include']
|
||||
compiler.set_include_dirs([python])
|
||||
compiler.compile([c_file])
|
||||
|
||||
# do it again, setting include dirs after any initialization
|
||||
compiler.set_include_dirs([python])
|
||||
compiler.compile([c_file])
|
||||
|
||||
|
||||
def test_has_function_prototype():
|
||||
# Issue https://github.com/pypa/setuptools/issues/3648
|
||||
# Test prototype-generating behavior.
|
||||
|
||||
compiler = base.new_compiler()
|
||||
|
||||
# Every C implementation should have these.
|
||||
assert compiler.has_function('abort')
|
||||
assert compiler.has_function('exit')
|
||||
with pytest.deprecated_call(match='includes is deprecated'):
|
||||
# abort() is a valid expression with the <stdlib.h> prototype.
|
||||
assert compiler.has_function('abort', includes=['stdlib.h'])
|
||||
with pytest.deprecated_call(match='includes is deprecated'):
|
||||
# But exit() is not valid with the actual prototype in scope.
|
||||
assert not compiler.has_function('exit', includes=['stdlib.h'])
|
||||
# And setuptools_does_not_exist is not declared or defined at all.
|
||||
assert not compiler.has_function('setuptools_does_not_exist')
|
||||
with pytest.deprecated_call(match='includes is deprecated'):
|
||||
assert not compiler.has_function(
|
||||
'setuptools_does_not_exist', includes=['stdio.h']
|
||||
)
|
||||
|
||||
|
||||
def test_include_dirs_after_multiple_compile_calls(c_file):
|
||||
"""
|
||||
Calling compile multiple times should not change the include dirs
|
||||
(regression test for setuptools issue #3591).
|
||||
"""
|
||||
compiler = base.new_compiler()
|
||||
python = sysconfig.get_paths()['include']
|
||||
compiler.set_include_dirs([python])
|
||||
compiler.compile([c_file])
|
||||
assert compiler.include_dirs == [python]
|
||||
compiler.compile([c_file])
|
||||
assert compiler.include_dirs == [python]
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Tests for distutils.cygwinccompiler."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from distutils import sysconfig
|
||||
from distutils.tests import support
|
||||
|
||||
import pytest
|
||||
|
||||
from .. import cygwin
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def stuff(request, monkeypatch, distutils_managed_tempdir):
|
||||
self = request.instance
|
||||
self.python_h = os.path.join(self.mkdtemp(), 'python.h')
|
||||
monkeypatch.setattr(sysconfig, 'get_config_h_filename', self._get_config_h_filename)
|
||||
monkeypatch.setattr(sys, 'version', sys.version)
|
||||
|
||||
|
||||
class TestCygwinCCompiler(support.TempdirManager):
|
||||
def _get_config_h_filename(self):
|
||||
return self.python_h
|
||||
|
||||
@pytest.mark.skipif('sys.platform != "cygwin"')
|
||||
@pytest.mark.skipif('not os.path.exists("/usr/lib/libbash.dll.a")')
|
||||
def test_find_library_file(self):
|
||||
from distutils.cygwinccompiler import CygwinCCompiler
|
||||
|
||||
compiler = CygwinCCompiler()
|
||||
link_name = "bash"
|
||||
linkable_file = compiler.find_library_file(["/usr/lib"], link_name)
|
||||
assert linkable_file is not None
|
||||
assert os.path.exists(linkable_file)
|
||||
assert linkable_file == f"/usr/lib/lib{link_name:s}.dll.a"
|
||||
|
||||
@pytest.mark.skipif('sys.platform != "cygwin"')
|
||||
def test_runtime_library_dir_option(self):
|
||||
from distutils.cygwinccompiler import CygwinCCompiler
|
||||
|
||||
compiler = CygwinCCompiler()
|
||||
assert compiler.runtime_library_dir_option('/foo') == []
|
||||
|
||||
def test_check_config_h(self):
|
||||
# check_config_h looks for "GCC" in sys.version first
|
||||
# returns CONFIG_H_OK if found
|
||||
sys.version = (
|
||||
'2.6.1 (r261:67515, Dec 6 2008, 16:42:21) \n[GCC '
|
||||
'4.0.1 (Apple Computer, Inc. build 5370)]'
|
||||
)
|
||||
|
||||
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_OK
|
||||
|
||||
# then it tries to see if it can find "__GNUC__" in pyconfig.h
|
||||
sys.version = 'something without the *CC word'
|
||||
|
||||
# if the file doesn't exist it returns CONFIG_H_UNCERTAIN
|
||||
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_UNCERTAIN
|
||||
|
||||
# if it exists but does not contain __GNUC__, it returns CONFIG_H_NOTOK
|
||||
self.write_file(self.python_h, 'xxx')
|
||||
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_NOTOK
|
||||
|
||||
# and CONFIG_H_OK if __GNUC__ is found
|
||||
self.write_file(self.python_h, 'xxx __GNUC__ xxx')
|
||||
assert cygwin.check_config_h()[0] == cygwin.CONFIG_H_OK
|
||||
|
||||
def test_get_msvcr(self):
|
||||
assert cygwin.get_msvcr() == []
|
||||
|
||||
@pytest.mark.skipif('sys.platform != "cygwin"')
|
||||
def test_dll_libraries_not_none(self):
|
||||
from distutils.cygwinccompiler import CygwinCCompiler
|
||||
|
||||
compiler = CygwinCCompiler()
|
||||
assert compiler.dll_libraries is not None
|
||||
@@ -0,0 +1,48 @@
|
||||
from distutils import sysconfig
|
||||
from distutils.errors import DistutilsPlatformError
|
||||
from distutils.util import is_mingw, split_quoted
|
||||
|
||||
import pytest
|
||||
|
||||
from .. import cygwin, errors
|
||||
|
||||
|
||||
class TestMinGW32Compiler:
|
||||
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
|
||||
def test_compiler_type(self):
|
||||
compiler = cygwin.MinGW32Compiler()
|
||||
assert compiler.compiler_type == 'mingw32'
|
||||
|
||||
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
|
||||
def test_set_executables(self, monkeypatch):
|
||||
monkeypatch.setenv('CC', 'cc')
|
||||
monkeypatch.setenv('CXX', 'c++')
|
||||
|
||||
compiler = cygwin.MinGW32Compiler()
|
||||
|
||||
assert compiler.compiler == split_quoted('cc -O -Wall')
|
||||
assert compiler.compiler_so == split_quoted('cc -shared -O -Wall')
|
||||
assert compiler.compiler_cxx == split_quoted('c++ -O -Wall')
|
||||
assert compiler.linker_exe == split_quoted('cc')
|
||||
assert compiler.linker_so == split_quoted('cc -shared')
|
||||
|
||||
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
|
||||
def test_runtime_library_dir_option(self):
|
||||
compiler = cygwin.MinGW32Compiler()
|
||||
with pytest.raises(DistutilsPlatformError):
|
||||
compiler.runtime_library_dir_option('/usr/lib')
|
||||
|
||||
@pytest.mark.skipif(not is_mingw(), reason='not on mingw')
|
||||
def test_cygwincc_error(self, monkeypatch):
|
||||
monkeypatch.setattr(cygwin, 'is_cygwincc', lambda _: True)
|
||||
|
||||
with pytest.raises(errors.Error):
|
||||
cygwin.MinGW32Compiler()
|
||||
|
||||
@pytest.mark.skipif('sys.platform == "cygwin"')
|
||||
def test_customize_compiler_with_msvc_python(self):
|
||||
# In case we have an MSVC Python build, but still want to use
|
||||
# MinGW32Compiler, then customize_compiler() shouldn't fail at least.
|
||||
# https://github.com/pypa/setuptools/issues/4456
|
||||
compiler = cygwin.MinGW32Compiler()
|
||||
sysconfig.customize_compiler(compiler)
|
||||
@@ -0,0 +1,136 @@
|
||||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
import threading
|
||||
import unittest.mock as mock
|
||||
from distutils.errors import DistutilsPlatformError
|
||||
from distutils.tests import support
|
||||
from distutils.util import get_platform
|
||||
|
||||
import pytest
|
||||
|
||||
from .. import msvc
|
||||
|
||||
needs_winreg = pytest.mark.skipif('not hasattr(msvc, "winreg")')
|
||||
|
||||
|
||||
class Testmsvccompiler(support.TempdirManager):
|
||||
def test_no_compiler(self, monkeypatch):
|
||||
# makes sure query_vcvarsall raises
|
||||
# a DistutilsPlatformError if the compiler
|
||||
# is not found
|
||||
def _find_vcvarsall(plat_spec):
|
||||
return None, None
|
||||
|
||||
monkeypatch.setattr(msvc, '_find_vcvarsall', _find_vcvarsall)
|
||||
|
||||
with pytest.raises(DistutilsPlatformError):
|
||||
msvc._get_vc_env(
|
||||
'wont find this version',
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not sysconfig.get_platform().startswith("win"),
|
||||
reason="Only run test for non-mingw Windows platforms",
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"plat_name, expected",
|
||||
[
|
||||
("win-arm64", "win-arm64"),
|
||||
("win-amd64", "win-amd64"),
|
||||
(None, get_platform()),
|
||||
],
|
||||
)
|
||||
def test_cross_platform_compilation_paths(self, monkeypatch, plat_name, expected):
|
||||
"""
|
||||
Ensure a specified target platform is passed to _get_vcvars_spec.
|
||||
"""
|
||||
compiler = msvc.Compiler()
|
||||
|
||||
def _get_vcvars_spec(host_platform, platform):
|
||||
assert platform == expected
|
||||
|
||||
monkeypatch.setattr(msvc, '_get_vcvars_spec', _get_vcvars_spec)
|
||||
compiler.initialize(plat_name)
|
||||
|
||||
@needs_winreg
|
||||
def test_get_vc_env_unicode(self):
|
||||
test_var = 'ṰḖṤṪ┅ṼẨṜ'
|
||||
test_value = '₃⁴₅'
|
||||
|
||||
# Ensure we don't early exit from _get_vc_env
|
||||
old_distutils_use_sdk = os.environ.pop('DISTUTILS_USE_SDK', None)
|
||||
os.environ[test_var] = test_value
|
||||
try:
|
||||
env = msvc._get_vc_env('x86')
|
||||
assert test_var.lower() in env
|
||||
assert test_value == env[test_var.lower()]
|
||||
finally:
|
||||
os.environ.pop(test_var)
|
||||
if old_distutils_use_sdk:
|
||||
os.environ['DISTUTILS_USE_SDK'] = old_distutils_use_sdk
|
||||
|
||||
@needs_winreg
|
||||
@pytest.mark.parametrize('ver', (2015, 2017))
|
||||
def test_get_vc(self, ver):
|
||||
# This function cannot be mocked, so pass if VC is found
|
||||
# and skip otherwise.
|
||||
lookup = getattr(msvc, f'_find_vc{ver}')
|
||||
expected_version = {2015: 14, 2017: 15}[ver]
|
||||
version, path = lookup()
|
||||
if not version:
|
||||
pytest.skip(f"VS {ver} is not installed")
|
||||
assert version >= expected_version
|
||||
assert os.path.isdir(path)
|
||||
|
||||
|
||||
class CheckThread(threading.Thread):
|
||||
exc_info = None
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
super().run()
|
||||
except Exception:
|
||||
self.exc_info = sys.exc_info()
|
||||
|
||||
def __bool__(self):
|
||||
return not self.exc_info
|
||||
|
||||
|
||||
class TestSpawn:
|
||||
def test_concurrent_safe(self):
|
||||
"""
|
||||
Concurrent calls to spawn should have consistent results.
|
||||
"""
|
||||
compiler = msvc.Compiler()
|
||||
compiler._paths = "expected"
|
||||
inner_cmd = 'import os; assert os.environ["PATH"] == "expected"'
|
||||
command = [sys.executable, '-c', inner_cmd]
|
||||
|
||||
threads = [
|
||||
CheckThread(target=compiler.spawn, args=[command]) for n in range(100)
|
||||
]
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
assert all(threads)
|
||||
|
||||
def test_concurrent_safe_fallback(self):
|
||||
"""
|
||||
If CCompiler.spawn has been monkey-patched without support
|
||||
for an env, it should still execute.
|
||||
"""
|
||||
from distutils import ccompiler
|
||||
|
||||
compiler = msvc.Compiler()
|
||||
compiler._paths = "expected"
|
||||
|
||||
def CCompiler_spawn(self, cmd):
|
||||
"A spawn without an env argument."
|
||||
assert os.environ["PATH"] == "expected"
|
||||
|
||||
with mock.patch.object(ccompiler.CCompiler, 'spawn', CCompiler_spawn):
|
||||
compiler.spawn(["n/a"])
|
||||
|
||||
assert os.environ.get("PATH") != "expected"
|
||||
@@ -0,0 +1,413 @@
|
||||
"""Tests for distutils.unixccompiler."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest.mock as mock
|
||||
from distutils import sysconfig
|
||||
from distutils.compat import consolidate_linker_args
|
||||
from distutils.errors import DistutilsPlatformError
|
||||
from distutils.tests import support
|
||||
from distutils.tests.compat.py39 import EnvironmentVarGuard
|
||||
from distutils.util import _clear_cached_macosx_ver
|
||||
|
||||
import pytest
|
||||
|
||||
from .. import unix
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def save_values(monkeypatch):
|
||||
monkeypatch.setattr(sys, 'platform', sys.platform)
|
||||
monkeypatch.setattr(sysconfig, 'get_config_var', sysconfig.get_config_var)
|
||||
monkeypatch.setattr(sysconfig, 'get_config_vars', sysconfig.get_config_vars)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def compiler_wrapper(request):
|
||||
class CompilerWrapper(unix.Compiler):
|
||||
def rpath_foo(self):
|
||||
return self.runtime_library_dir_option('/foo')
|
||||
|
||||
request.instance.cc = CompilerWrapper()
|
||||
|
||||
|
||||
class TestUnixCCompiler(support.TempdirManager):
|
||||
@pytest.mark.skipif('platform.system == "Windows"')
|
||||
def test_runtime_libdir_option(self): # noqa: C901
|
||||
# Issue #5900; GitHub Issue #37
|
||||
#
|
||||
# Ensure RUNPATH is added to extension modules with RPATH if
|
||||
# GNU ld is used
|
||||
|
||||
# darwin
|
||||
sys.platform = 'darwin'
|
||||
darwin_ver_var = 'MACOSX_DEPLOYMENT_TARGET'
|
||||
darwin_rpath_flag = '-Wl,-rpath,/foo'
|
||||
darwin_lib_flag = '-L/foo'
|
||||
|
||||
# (macOS version from syscfg, macOS version from env var) -> flag
|
||||
# Version value of None generates two tests: as None and as empty string
|
||||
# Expected flag value of None means an mismatch exception is expected
|
||||
darwin_test_cases = [
|
||||
((None, None), darwin_lib_flag),
|
||||
((None, '11'), darwin_rpath_flag),
|
||||
(('10', None), darwin_lib_flag),
|
||||
(('10.3', None), darwin_lib_flag),
|
||||
(('10.3.1', None), darwin_lib_flag),
|
||||
(('10.5', None), darwin_rpath_flag),
|
||||
(('10.5.1', None), darwin_rpath_flag),
|
||||
(('10.3', '10.3'), darwin_lib_flag),
|
||||
(('10.3', '10.5'), darwin_rpath_flag),
|
||||
(('10.5', '10.3'), darwin_lib_flag),
|
||||
(('10.5', '11'), darwin_rpath_flag),
|
||||
(('10.4', '10'), None),
|
||||
]
|
||||
|
||||
def make_darwin_gcv(syscfg_macosx_ver):
|
||||
def gcv(var):
|
||||
if var == darwin_ver_var:
|
||||
return syscfg_macosx_ver
|
||||
return "xxx"
|
||||
|
||||
return gcv
|
||||
|
||||
def do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag):
|
||||
env = os.environ
|
||||
msg = f"macOS version = (sysconfig={syscfg_macosx_ver!r}, env={env_macosx_ver!r})"
|
||||
|
||||
# Save
|
||||
old_gcv = sysconfig.get_config_var
|
||||
old_env_macosx_ver = env.get(darwin_ver_var)
|
||||
|
||||
# Setup environment
|
||||
_clear_cached_macosx_ver()
|
||||
sysconfig.get_config_var = make_darwin_gcv(syscfg_macosx_ver)
|
||||
if env_macosx_ver is not None:
|
||||
env[darwin_ver_var] = env_macosx_ver
|
||||
elif darwin_ver_var in env:
|
||||
env.pop(darwin_ver_var)
|
||||
|
||||
# Run the test
|
||||
if expected_flag is not None:
|
||||
assert self.cc.rpath_foo() == expected_flag, msg
|
||||
else:
|
||||
with pytest.raises(
|
||||
DistutilsPlatformError, match=darwin_ver_var + r' mismatch'
|
||||
):
|
||||
self.cc.rpath_foo()
|
||||
|
||||
# Restore
|
||||
if old_env_macosx_ver is not None:
|
||||
env[darwin_ver_var] = old_env_macosx_ver
|
||||
elif darwin_ver_var in env:
|
||||
env.pop(darwin_ver_var)
|
||||
sysconfig.get_config_var = old_gcv
|
||||
_clear_cached_macosx_ver()
|
||||
|
||||
for macosx_vers, expected_flag in darwin_test_cases:
|
||||
syscfg_macosx_ver, env_macosx_ver = macosx_vers
|
||||
do_darwin_test(syscfg_macosx_ver, env_macosx_ver, expected_flag)
|
||||
# Bonus test cases with None interpreted as empty string
|
||||
if syscfg_macosx_ver is None:
|
||||
do_darwin_test("", env_macosx_ver, expected_flag)
|
||||
if env_macosx_ver is None:
|
||||
do_darwin_test(syscfg_macosx_ver, "", expected_flag)
|
||||
if syscfg_macosx_ver is None and env_macosx_ver is None:
|
||||
do_darwin_test("", "", expected_flag)
|
||||
|
||||
old_gcv = sysconfig.get_config_var
|
||||
|
||||
# hp-ux
|
||||
sys.platform = 'hp-ux'
|
||||
|
||||
def gcv(v):
|
||||
return 'xxx'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == ['+s', '-L/foo']
|
||||
|
||||
def gcv(v):
|
||||
return 'gcc'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == ['-Wl,+s', '-L/foo']
|
||||
|
||||
def gcv(v):
|
||||
return 'g++'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == ['-Wl,+s', '-L/foo']
|
||||
|
||||
sysconfig.get_config_var = old_gcv
|
||||
|
||||
# GCC GNULD
|
||||
sys.platform = 'bar'
|
||||
|
||||
def gcv(v):
|
||||
if v == 'CC':
|
||||
return 'gcc'
|
||||
elif v == 'GNULD':
|
||||
return 'yes'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == consolidate_linker_args([
|
||||
'-Wl,--enable-new-dtags',
|
||||
'-Wl,-rpath,/foo',
|
||||
])
|
||||
|
||||
def gcv(v):
|
||||
if v == 'CC':
|
||||
return 'gcc -pthread -B /bar'
|
||||
elif v == 'GNULD':
|
||||
return 'yes'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == consolidate_linker_args([
|
||||
'-Wl,--enable-new-dtags',
|
||||
'-Wl,-rpath,/foo',
|
||||
])
|
||||
|
||||
# GCC non-GNULD
|
||||
sys.platform = 'bar'
|
||||
|
||||
def gcv(v):
|
||||
if v == 'CC':
|
||||
return 'gcc'
|
||||
elif v == 'GNULD':
|
||||
return 'no'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == '-Wl,-R/foo'
|
||||
|
||||
# GCC GNULD with fully qualified configuration prefix
|
||||
# see #7617
|
||||
sys.platform = 'bar'
|
||||
|
||||
def gcv(v):
|
||||
if v == 'CC':
|
||||
return 'x86_64-pc-linux-gnu-gcc-4.4.2'
|
||||
elif v == 'GNULD':
|
||||
return 'yes'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == consolidate_linker_args([
|
||||
'-Wl,--enable-new-dtags',
|
||||
'-Wl,-rpath,/foo',
|
||||
])
|
||||
|
||||
# non-GCC GNULD
|
||||
sys.platform = 'bar'
|
||||
|
||||
def gcv(v):
|
||||
if v == 'CC':
|
||||
return 'cc'
|
||||
elif v == 'GNULD':
|
||||
return 'yes'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == consolidate_linker_args([
|
||||
'-Wl,--enable-new-dtags',
|
||||
'-Wl,-rpath,/foo',
|
||||
])
|
||||
|
||||
# non-GCC non-GNULD
|
||||
sys.platform = 'bar'
|
||||
|
||||
def gcv(v):
|
||||
if v == 'CC':
|
||||
return 'cc'
|
||||
elif v == 'GNULD':
|
||||
return 'no'
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
assert self.cc.rpath_foo() == '-Wl,-R/foo'
|
||||
|
||||
@pytest.mark.skipif('platform.system == "Windows"')
|
||||
def test_cc_overrides_ldshared(self):
|
||||
# Issue #18080:
|
||||
# ensure that setting CC env variable also changes default linker
|
||||
def gcv(v):
|
||||
if v == 'LDSHARED':
|
||||
return 'gcc-4.2 -bundle -undefined dynamic_lookup '
|
||||
return 'gcc-4.2'
|
||||
|
||||
def gcvs(*args, _orig=sysconfig.get_config_vars):
|
||||
if args:
|
||||
return list(map(sysconfig.get_config_var, args))
|
||||
return _orig()
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
sysconfig.get_config_vars = gcvs
|
||||
with EnvironmentVarGuard() as env:
|
||||
env['CC'] = 'my_cc'
|
||||
del env['LDSHARED']
|
||||
sysconfig.customize_compiler(self.cc)
|
||||
assert self.cc.linker_so[0] == 'my_cc'
|
||||
|
||||
@pytest.mark.skipif('platform.system == "Windows"')
|
||||
def test_cxx_commands_used_are_correct(self):
|
||||
def gcv(v):
|
||||
if v == 'LDSHARED':
|
||||
return 'ccache gcc-4.2 -bundle -undefined dynamic_lookup'
|
||||
elif v == 'LDCXXSHARED':
|
||||
return 'ccache g++-4.2 -bundle -undefined dynamic_lookup'
|
||||
elif v == 'CXX':
|
||||
return 'ccache g++-4.2'
|
||||
elif v == 'CC':
|
||||
return 'ccache gcc-4.2'
|
||||
return ''
|
||||
|
||||
def gcvs(*args, _orig=sysconfig.get_config_vars):
|
||||
if args:
|
||||
return list(map(sysconfig.get_config_var, args))
|
||||
return _orig() # pragma: no cover
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
sysconfig.get_config_vars = gcvs
|
||||
with (
|
||||
mock.patch.object(self.cc, 'spawn', return_value=None) as mock_spawn,
|
||||
mock.patch.object(self.cc, '_need_link', return_value=True),
|
||||
mock.patch.object(self.cc, 'mkpath', return_value=None),
|
||||
EnvironmentVarGuard() as env,
|
||||
):
|
||||
# override environment overrides in case they're specified by CI
|
||||
del env['CXX']
|
||||
del env['LDCXXSHARED']
|
||||
|
||||
sysconfig.customize_compiler(self.cc)
|
||||
assert self.cc.linker_so_cxx[0:2] == ['ccache', 'g++-4.2']
|
||||
assert self.cc.linker_exe_cxx[0:2] == ['ccache', 'g++-4.2']
|
||||
self.cc.link(None, [], 'a.out', target_lang='c++')
|
||||
call_args = mock_spawn.call_args[0][0]
|
||||
expected = ['ccache', 'g++-4.2', '-bundle', '-undefined', 'dynamic_lookup']
|
||||
assert call_args[:5] == expected
|
||||
|
||||
self.cc.link_executable([], 'a.out', target_lang='c++')
|
||||
call_args = mock_spawn.call_args[0][0]
|
||||
expected = ['ccache', 'g++-4.2', '-o', self.cc.executable_filename('a.out')]
|
||||
assert call_args[:4] == expected
|
||||
|
||||
env['LDCXXSHARED'] = 'wrapper g++-4.2 -bundle -undefined dynamic_lookup'
|
||||
env['CXX'] = 'wrapper g++-4.2'
|
||||
sysconfig.customize_compiler(self.cc)
|
||||
assert self.cc.linker_so_cxx[0:2] == ['wrapper', 'g++-4.2']
|
||||
assert self.cc.linker_exe_cxx[0:2] == ['wrapper', 'g++-4.2']
|
||||
self.cc.link(None, [], 'a.out', target_lang='c++')
|
||||
call_args = mock_spawn.call_args[0][0]
|
||||
expected = ['wrapper', 'g++-4.2', '-bundle', '-undefined', 'dynamic_lookup']
|
||||
assert call_args[:5] == expected
|
||||
|
||||
self.cc.link_executable([], 'a.out', target_lang='c++')
|
||||
call_args = mock_spawn.call_args[0][0]
|
||||
expected = [
|
||||
'wrapper',
|
||||
'g++-4.2',
|
||||
'-o',
|
||||
self.cc.executable_filename('a.out'),
|
||||
]
|
||||
assert call_args[:4] == expected
|
||||
|
||||
@pytest.mark.skipif('platform.system == "Windows"')
|
||||
@pytest.mark.usefixtures('disable_macos_customization')
|
||||
def test_cc_overrides_ldshared_for_cxx_correctly(self):
|
||||
"""
|
||||
Ensure that setting CC env variable also changes default linker
|
||||
correctly when building C++ extensions.
|
||||
|
||||
pypa/distutils#126
|
||||
"""
|
||||
|
||||
def gcv(v):
|
||||
if v == 'LDSHARED':
|
||||
return 'gcc-4.2 -bundle -undefined dynamic_lookup '
|
||||
elif v == 'LDCXXSHARED':
|
||||
return 'g++-4.2 -bundle -undefined dynamic_lookup '
|
||||
elif v == 'CXX':
|
||||
return 'g++-4.2'
|
||||
elif v == 'CC':
|
||||
return 'gcc-4.2'
|
||||
return ''
|
||||
|
||||
def gcvs(*args, _orig=sysconfig.get_config_vars):
|
||||
if args:
|
||||
return list(map(sysconfig.get_config_var, args))
|
||||
return _orig()
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
sysconfig.get_config_vars = gcvs
|
||||
with (
|
||||
mock.patch.object(self.cc, 'spawn', return_value=None) as mock_spawn,
|
||||
mock.patch.object(self.cc, '_need_link', return_value=True),
|
||||
mock.patch.object(self.cc, 'mkpath', return_value=None),
|
||||
EnvironmentVarGuard() as env,
|
||||
):
|
||||
env['CC'] = 'ccache my_cc'
|
||||
env['CXX'] = 'my_cxx'
|
||||
del env['LDSHARED']
|
||||
sysconfig.customize_compiler(self.cc)
|
||||
assert self.cc.linker_so[0:2] == ['ccache', 'my_cc']
|
||||
self.cc.link(None, [], 'a.out', target_lang='c++')
|
||||
call_args = mock_spawn.call_args[0][0]
|
||||
expected = ['my_cxx', '-bundle', '-undefined', 'dynamic_lookup']
|
||||
assert call_args[:4] == expected
|
||||
|
||||
@pytest.mark.skipif('platform.system == "Windows"')
|
||||
def test_explicit_ldshared(self):
|
||||
# Issue #18080:
|
||||
# ensure that setting CC env variable does not change
|
||||
# explicit LDSHARED setting for linker
|
||||
def gcv(v):
|
||||
if v == 'LDSHARED':
|
||||
return 'gcc-4.2 -bundle -undefined dynamic_lookup '
|
||||
return 'gcc-4.2'
|
||||
|
||||
def gcvs(*args, _orig=sysconfig.get_config_vars):
|
||||
if args:
|
||||
return list(map(sysconfig.get_config_var, args))
|
||||
return _orig()
|
||||
|
||||
sysconfig.get_config_var = gcv
|
||||
sysconfig.get_config_vars = gcvs
|
||||
with EnvironmentVarGuard() as env:
|
||||
env['CC'] = 'my_cc'
|
||||
env['LDSHARED'] = 'my_ld -bundle -dynamic'
|
||||
sysconfig.customize_compiler(self.cc)
|
||||
assert self.cc.linker_so[0] == 'my_ld'
|
||||
|
||||
def test_has_function(self):
|
||||
# Issue https://github.com/pypa/distutils/issues/64:
|
||||
# ensure that setting output_dir does not raise
|
||||
# FileNotFoundError: [Errno 2] No such file or directory: 'a.out'
|
||||
self.cc.output_dir = 'scratch'
|
||||
os.chdir(self.mkdtemp())
|
||||
self.cc.has_function('abort')
|
||||
|
||||
def test_find_library_file(self, monkeypatch):
|
||||
compiler = unix.Compiler()
|
||||
compiler._library_root = lambda dir: dir
|
||||
monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d)
|
||||
|
||||
libname = 'libabc.dylib' if sys.platform != 'cygwin' else 'cygabc.dll'
|
||||
dirs = ('/foo/bar/missing', '/foo/bar/existing')
|
||||
assert (
|
||||
compiler.find_library_file(dirs, 'abc').replace('\\', '/')
|
||||
== f'/foo/bar/existing/{libname}'
|
||||
)
|
||||
assert (
|
||||
compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')
|
||||
== f'/foo/bar/existing/{libname}'
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
os.path,
|
||||
'exists',
|
||||
lambda d: 'existing' in d and '.a' in d and '.dll.a' not in d,
|
||||
)
|
||||
assert (
|
||||
compiler.find_library_file(dirs, 'abc').replace('\\', '/')
|
||||
== '/foo/bar/existing/libabc.a'
|
||||
)
|
||||
assert (
|
||||
compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')
|
||||
== '/foo/bar/existing/libabc.a'
|
||||
)
|
||||
@@ -0,0 +1,422 @@
|
||||
"""distutils.unixccompiler
|
||||
|
||||
Contains the UnixCCompiler class, a subclass of CCompiler that handles
|
||||
the "typical" Unix-style command-line C compiler:
|
||||
* macros defined with -Dname[=value]
|
||||
* macros undefined with -Uname
|
||||
* include search directories specified with -Idir
|
||||
* libraries specified with -lllib
|
||||
* library search directories specified with -Ldir
|
||||
* compile handled by 'cc' (or similar) executable with -c option:
|
||||
compiles .c to .o
|
||||
* link static library handled by 'ar' command (possibly with 'ranlib')
|
||||
* link shared library handled by 'cc -shared'
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
|
||||
from ... import sysconfig
|
||||
from ..._log import log
|
||||
from ..._macos_compat import compiler_fixup
|
||||
from ..._modified import newer
|
||||
from ...compat import consolidate_linker_args
|
||||
from ...errors import DistutilsExecError
|
||||
from . import base
|
||||
from .base import _Macro, gen_lib_options, gen_preprocess_options
|
||||
from .errors import (
|
||||
CompileError,
|
||||
LibError,
|
||||
LinkError,
|
||||
)
|
||||
|
||||
# XXX Things not currently handled:
|
||||
# * optimization/debug/warning flags; we just use whatever's in Python's
|
||||
# Makefile and live with it. Is this adequate? If not, we might
|
||||
# have to have a bunch of subclasses GNUCCompiler, SGICCompiler,
|
||||
# SunCCompiler, and I suspect down that road lies madness.
|
||||
# * even if we don't know a warning flag from an optimization flag,
|
||||
# we need some way for outsiders to feed preprocessor/compiler/linker
|
||||
# flags in to us -- eg. a sysadmin might want to mandate certain flags
|
||||
# via a site config file, or a user might want to set something for
|
||||
# compiling this module distribution only via the setup.py command
|
||||
# line, whatever. As long as these options come from something on the
|
||||
# current system, they can be as system-dependent as they like, and we
|
||||
# should just happily stuff them into the preprocessor/compiler/linker
|
||||
# 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 Compiler(base.Compiler):
|
||||
compiler_type = 'unix'
|
||||
|
||||
# These are used by CCompiler in two places: the constructor sets
|
||||
# instance attributes 'preprocessor', 'compiler', etc. from them, and
|
||||
# 'set_executable()' allows any of these to be set. The defaults here
|
||||
# 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': ["c++"],
|
||||
'compiler_so_cxx': ["c++"],
|
||||
'linker_so': ["cc", "-shared"],
|
||||
'linker_so_cxx': ["c++", "-shared"],
|
||||
'linker_exe': ["cc"],
|
||||
'linker_exe_cxx': ["c++", "-shared"],
|
||||
'archiver': ["ar", "-cr"],
|
||||
'ranlib': None,
|
||||
}
|
||||
|
||||
if sys.platform[:6] == "darwin":
|
||||
executables['ranlib'] = ["ranlib"]
|
||||
|
||||
# Needed for the filename generation methods provided by the base
|
||||
# class, CCompiler. NB. whoever instantiates/uses a particular
|
||||
# UnixCCompiler instance should set 'shared_lib_ext' -- we set a
|
||||
# reasonable common default here, but it's not necessarily used on all
|
||||
# Unices!
|
||||
|
||||
src_extensions = [".c", ".C", ".cc", ".cxx", ".cpp", ".m"]
|
||||
obj_extension = ".o"
|
||||
static_lib_extension = ".a"
|
||||
shared_lib_extension = ".so"
|
||||
dylib_lib_extension = ".dylib"
|
||||
xcode_stub_lib_extension = ".tbd"
|
||||
static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s"
|
||||
xcode_stub_lib_format = dylib_lib_format
|
||||
if sys.platform == "cygwin":
|
||||
exe_extension = ".exe"
|
||||
shared_lib_extension = ".dll.a"
|
||||
dylib_lib_extension = ".dll"
|
||||
dylib_lib_format = "cyg%s%s"
|
||||
|
||||
def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs):
|
||||
"""Remove standard library path from rpath"""
|
||||
libraries, library_dirs, runtime_library_dirs = super()._fix_lib_args(
|
||||
libraries, library_dirs, runtime_library_dirs
|
||||
)
|
||||
libdir = sysconfig.get_config_var('LIBDIR')
|
||||
if (
|
||||
runtime_library_dirs
|
||||
and libdir.startswith("/usr/lib")
|
||||
and (libdir in runtime_library_dirs)
|
||||
):
|
||||
runtime_library_dirs.remove(libdir)
|
||||
return libraries, library_dirs, runtime_library_dirs
|
||||
|
||||
def preprocess(
|
||||
self,
|
||||
source: str | os.PathLike[str],
|
||||
output_file: str | os.PathLike[str] | None = None,
|
||||
macros: list[_Macro] | None = None,
|
||||
include_dirs: list[str] | tuple[str, ...] | None = None,
|
||||
extra_preargs: list[str] | None = None,
|
||||
extra_postargs: Iterable[str] | None = 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)
|
||||
pp_args = self.preprocessor + pp_opts
|
||||
if output_file:
|
||||
pp_args.extend(['-o', output_file])
|
||||
if extra_preargs:
|
||||
pp_args[:0] = extra_preargs
|
||||
if extra_postargs:
|
||||
pp_args.extend(extra_postargs)
|
||||
pp_args.append(source)
|
||||
|
||||
# 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))
|
||||
|
||||
try:
|
||||
self.spawn(pp_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
|
||||
compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs)
|
||||
compiler_so_cxx = compiler_fixup(self.compiler_so_cxx, cc_args + extra_postargs)
|
||||
try:
|
||||
if self.detect_language(src) == 'c++':
|
||||
self.spawn(
|
||||
compiler_so_cxx + cc_args + [src, '-o', obj] + extra_postargs
|
||||
)
|
||||
else:
|
||||
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=False, target_lang=None
|
||||
):
|
||||
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):
|
||||
self.mkpath(os.path.dirname(output_filename))
|
||||
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
|
||||
# platform intelligence here to skip ranlib if it's not
|
||||
# needed -- or maybe Python's configure script took care of
|
||||
# it for us, hence the check for leading colon.
|
||||
if self.ranlib:
|
||||
try:
|
||||
self.spawn(self.ranlib + [output_filename])
|
||||
except DistutilsExecError as msg:
|
||||
raise LibError(msg)
|
||||
else:
|
||||
log.debug("skipping %s (up-to-date)", output_filename)
|
||||
|
||||
def link(
|
||||
self,
|
||||
target_desc,
|
||||
objects: list[str] | tuple[str, ...],
|
||||
output_filename,
|
||||
output_dir: str | None = None,
|
||||
libraries: list[str] | tuple[str, ...] | None = None,
|
||||
library_dirs: list[str] | tuple[str, ...] | None = None,
|
||||
runtime_library_dirs: list[str] | tuple[str, ...] | None = None,
|
||||
export_symbols=None,
|
||||
debug=False,
|
||||
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)
|
||||
libraries, library_dirs, runtime_library_dirs = fixed_args
|
||||
|
||||
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]
|
||||
if debug:
|
||||
ld_args[:0] = ['-g']
|
||||
if extra_preargs:
|
||||
ld_args[:0] = extra_preargs
|
||||
if extra_postargs:
|
||||
ld_args.extend(extra_postargs)
|
||||
self.mkpath(os.path.dirname(output_filename))
|
||||
try:
|
||||
# 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 == base.Compiler.EXECUTABLE
|
||||
target_cxx = target_lang == "c++"
|
||||
linker = (
|
||||
(self.linker_exe_cxx if target_cxx else self.linker_exe)
|
||||
if building_exe
|
||||
else (self.linker_so_cxx if target_cxx else self.linker_so)
|
||||
)[:]
|
||||
|
||||
if target_cxx and self.compiler_cxx:
|
||||
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_cxx)
|
||||
|
||||
params = _linker_params(linker_na, linker_exe_ne)
|
||||
linker = env + aix + compiler_cxx_ne + params
|
||||
|
||||
linker = compiler_fixup(linker, ld_args)
|
||||
|
||||
self.spawn(linker + ld_args)
|
||||
except DistutilsExecError as msg:
|
||||
raise LinkError(msg)
|
||||
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.
|
||||
|
||||
def library_dir_option(self, dir):
|
||||
return "-L" + dir
|
||||
|
||||
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: str) -> str | list[str]: # type: ignore[override] # Fixed in pypa/distutils#339
|
||||
# XXX Hackish, at the very least. See Python bug #445902:
|
||||
# https://bugs.python.org/issue445902
|
||||
# Linkers on different platforms need different options to
|
||||
# specify that directories need to be added to the list of
|
||||
# directories searched for dependencies when a dynamic library
|
||||
# is sought. GCC on GNU systems (Linux, FreeBSD, ...) has to
|
||||
# be told to pass the -R option through to the linker, whereas
|
||||
# other compilers and gcc on other systems just know this.
|
||||
# Other compilers may need something slightly different. At
|
||||
# this time, there's no way to determine this information from
|
||||
# the configuration data stored in the Python installation, so
|
||||
# we use this hack.
|
||||
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
|
||||
return "-L" + dir
|
||||
elif sys.platform[:7] == "freebsd":
|
||||
return "-Wl,-rpath=" + dir
|
||||
elif sys.platform[:5] == "hp-ux":
|
||||
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
|
||||
if sysconfig.get_config_var("GNULD") == "yes":
|
||||
return consolidate_linker_args([
|
||||
# Force RUNPATH instead of RPATH
|
||||
"-Wl,--enable-new-dtags",
|
||||
"-Wl,-rpath," + dir,
|
||||
])
|
||||
else:
|
||||
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=False):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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 = itertools.starmap(os.path.join, itertools.product(roots, lib_names))
|
||||
|
||||
found = filter(os.path.exists, searched)
|
||||
|
||||
# Return None if it could not be found in any dir.
|
||||
return next(found, None)
|
||||
@@ -0,0 +1,230 @@
|
||||
"""distutils.zosccompiler
|
||||
|
||||
Contains the selection of the c & c++ compilers on z/OS. There are several
|
||||
different c compilers on z/OS, all of them are optional, so the correct
|
||||
one needs to be chosen based on the users input. This is compatible with
|
||||
the following compilers:
|
||||
|
||||
IBM C/C++ For Open Enterprise Languages on z/OS 2.0
|
||||
IBM Open XL C/C++ 1.1 for z/OS
|
||||
IBM XL C/C++ V2.4.1 for z/OS 2.4 and 2.5
|
||||
IBM z/OS XL C/C++
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from ... import sysconfig
|
||||
from ...errors import DistutilsExecError
|
||||
from . import unix
|
||||
from .errors import CompileError
|
||||
|
||||
_cc_args = {
|
||||
'ibm-openxl': [
|
||||
'-m64',
|
||||
'-fvisibility=default',
|
||||
'-fzos-le-char-mode=ascii',
|
||||
'-fno-short-enums',
|
||||
],
|
||||
'ibm-xlclang': [
|
||||
'-q64',
|
||||
'-qexportall',
|
||||
'-qascii',
|
||||
'-qstrict',
|
||||
'-qnocsect',
|
||||
'-Wa,asa,goff',
|
||||
'-Wa,xplink',
|
||||
'-qgonumber',
|
||||
'-qenum=int',
|
||||
'-Wc,DLL',
|
||||
],
|
||||
'ibm-xlc': [
|
||||
'-q64',
|
||||
'-qexportall',
|
||||
'-qascii',
|
||||
'-qstrict',
|
||||
'-qnocsect',
|
||||
'-Wa,asa,goff',
|
||||
'-Wa,xplink',
|
||||
'-qgonumber',
|
||||
'-qenum=int',
|
||||
'-Wc,DLL',
|
||||
'-qlanglvl=extc99',
|
||||
],
|
||||
}
|
||||
|
||||
_cxx_args = {
|
||||
'ibm-openxl': [
|
||||
'-m64',
|
||||
'-fvisibility=default',
|
||||
'-fzos-le-char-mode=ascii',
|
||||
'-fno-short-enums',
|
||||
],
|
||||
'ibm-xlclang': [
|
||||
'-q64',
|
||||
'-qexportall',
|
||||
'-qascii',
|
||||
'-qstrict',
|
||||
'-qnocsect',
|
||||
'-Wa,asa,goff',
|
||||
'-Wa,xplink',
|
||||
'-qgonumber',
|
||||
'-qenum=int',
|
||||
'-Wc,DLL',
|
||||
],
|
||||
'ibm-xlc': [
|
||||
'-q64',
|
||||
'-qexportall',
|
||||
'-qascii',
|
||||
'-qstrict',
|
||||
'-qnocsect',
|
||||
'-Wa,asa,goff',
|
||||
'-Wa,xplink',
|
||||
'-qgonumber',
|
||||
'-qenum=int',
|
||||
'-Wc,DLL',
|
||||
'-qlanglvl=extended0x',
|
||||
],
|
||||
}
|
||||
|
||||
_asm_args = {
|
||||
'ibm-openxl': ['-fasm', '-fno-integrated-as', '-Wa,--ASA', '-Wa,--GOFF'],
|
||||
'ibm-xlclang': [],
|
||||
'ibm-xlc': [],
|
||||
}
|
||||
|
||||
_ld_args = {
|
||||
'ibm-openxl': [],
|
||||
'ibm-xlclang': ['-Wl,dll', '-q64'],
|
||||
'ibm-xlc': ['-Wl,dll', '-q64'],
|
||||
}
|
||||
|
||||
|
||||
# Python on z/OS is built with no compiler specific options in it's CFLAGS.
|
||||
# But each compiler requires it's own specific options to build successfully,
|
||||
# though some of the options are common between them
|
||||
class Compiler(unix.Compiler):
|
||||
src_extensions = ['.c', '.C', '.cc', '.cxx', '.cpp', '.m', '.s']
|
||||
_cpp_extensions = ['.cc', '.cpp', '.cxx', '.C']
|
||||
_asm_extensions = ['.s']
|
||||
|
||||
def _get_zos_compiler_name(self):
|
||||
zos_compiler_names = [
|
||||
os.path.basename(binary)
|
||||
for envvar in ('CC', 'CXX', 'LDSHARED')
|
||||
if (binary := os.environ.get(envvar, None))
|
||||
]
|
||||
if len(zos_compiler_names) == 0:
|
||||
return 'ibm-openxl'
|
||||
|
||||
zos_compilers = {}
|
||||
for compiler in (
|
||||
'ibm-clang',
|
||||
'ibm-clang64',
|
||||
'ibm-clang++',
|
||||
'ibm-clang++64',
|
||||
'clang',
|
||||
'clang++',
|
||||
'clang-14',
|
||||
):
|
||||
zos_compilers[compiler] = 'ibm-openxl'
|
||||
|
||||
for compiler in ('xlclang', 'xlclang++', 'njsc', 'njsc++'):
|
||||
zos_compilers[compiler] = 'ibm-xlclang'
|
||||
|
||||
for compiler in ('xlc', 'xlC', 'xlc++'):
|
||||
zos_compilers[compiler] = 'ibm-xlc'
|
||||
|
||||
return zos_compilers.get(zos_compiler_names[0], 'ibm-openxl')
|
||||
|
||||
def __init__(self, verbose=False, dry_run=False, force=False):
|
||||
super().__init__(verbose, dry_run, force)
|
||||
self.zos_compiler = self._get_zos_compiler_name()
|
||||
sysconfig.customize_compiler(self)
|
||||
|
||||
def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):
|
||||
local_args = []
|
||||
if ext in self._cpp_extensions:
|
||||
compiler = self.compiler_cxx
|
||||
local_args.extend(_cxx_args[self.zos_compiler])
|
||||
elif ext in self._asm_extensions:
|
||||
compiler = self.compiler_so
|
||||
local_args.extend(_cc_args[self.zos_compiler])
|
||||
local_args.extend(_asm_args[self.zos_compiler])
|
||||
else:
|
||||
compiler = self.compiler_so
|
||||
local_args.extend(_cc_args[self.zos_compiler])
|
||||
local_args.extend(cc_args)
|
||||
|
||||
try:
|
||||
self.spawn(compiler + local_args + [src, '-o', obj] + extra_postargs)
|
||||
except DistutilsExecError as msg:
|
||||
raise CompileError(msg)
|
||||
|
||||
def runtime_library_dir_option(self, dir):
|
||||
return '-L' + dir
|
||||
|
||||
def link(
|
||||
self,
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir=None,
|
||||
libraries=None,
|
||||
library_dirs=None,
|
||||
runtime_library_dirs=None,
|
||||
export_symbols=None,
|
||||
debug=False,
|
||||
extra_preargs=None,
|
||||
extra_postargs=None,
|
||||
build_temp=None,
|
||||
target_lang=None,
|
||||
):
|
||||
# For a built module to use functions from cpython, it needs to use Pythons
|
||||
# side deck file. The side deck is located beside the libpython3.xx.so
|
||||
ldversion = sysconfig.get_config_var('LDVERSION')
|
||||
if sysconfig.python_build:
|
||||
side_deck_path = os.path.join(
|
||||
sysconfig.get_config_var('abs_builddir'),
|
||||
f'libpython{ldversion}.x',
|
||||
)
|
||||
else:
|
||||
side_deck_path = os.path.join(
|
||||
sysconfig.get_config_var('installed_base'),
|
||||
sysconfig.get_config_var('platlibdir'),
|
||||
f'libpython{ldversion}.x',
|
||||
)
|
||||
|
||||
if os.path.exists(side_deck_path):
|
||||
if extra_postargs:
|
||||
extra_postargs.append(side_deck_path)
|
||||
else:
|
||||
extra_postargs = [side_deck_path]
|
||||
|
||||
# Check and replace libraries included side deck files
|
||||
if runtime_library_dirs:
|
||||
for dir in runtime_library_dirs:
|
||||
for library in libraries[:]:
|
||||
library_side_deck = os.path.join(dir, f'{library}.x')
|
||||
if os.path.exists(library_side_deck):
|
||||
libraries.remove(library)
|
||||
extra_postargs.append(library_side_deck)
|
||||
break
|
||||
|
||||
# Any required ld args for the given compiler
|
||||
extra_postargs.extend(_ld_args[self.zos_compiler])
|
||||
|
||||
super().link(
|
||||
target_desc,
|
||||
objects,
|
||||
output_filename,
|
||||
output_dir,
|
||||
libraries,
|
||||
library_dirs,
|
||||
runtime_library_dirs,
|
||||
export_symbols,
|
||||
debug,
|
||||
extra_preargs,
|
||||
extra_postargs,
|
||||
build_temp,
|
||||
target_lang,
|
||||
)
|
||||
Reference in New Issue
Block a user