codekingpro/portable-devtools
114k
1"""distutils.unixccompiler2 3Contains the UnixCCompiler class, a subclass of CCompiler that handles4the "typical" Unix-style command-line C compiler:5 * macros defined with -Dname[=value]6 * macros undefined with -Uname7 * include search directories specified with -Idir8 * libraries specified with -lllib9 * library search directories specified with -Ldir10 * compile handled by 'cc' (or similar) executable with -c option:11 compiles .c to .o12 * link static library handled by 'ar' command (possibly with 'ranlib')13 * link shared library handled by 'cc -shared'14"""15 16from __future__ import annotations17 18import itertools19import os20import re21import shlex22import sys23from collections.abc import Iterable24 25from ... import sysconfig26from ..._log import log27from ..._macos_compat import compiler_fixup28from ..._modified import newer29from ...compat import consolidate_linker_args30from ...errors import DistutilsExecError31from . import base32from .base import _Macro, gen_lib_options, gen_preprocess_options33from .errors import (34 CompileError,35 LibError,36 LinkError,37)38 39# XXX Things not currently handled:40# * optimization/debug/warning flags; we just use whatever's in Python's41# Makefile and live with it. Is this adequate? If not, we might42# have to have a bunch of subclasses GNUCCompiler, SGICCompiler,43# SunCCompiler, and I suspect down that road lies madness.44# * even if we don't know a warning flag from an optimization flag,45# we need some way for outsiders to feed preprocessor/compiler/linker46# flags in to us -- eg. a sysadmin might want to mandate certain flags47# via a site config file, or a user might want to set something for48# compiling this module distribution only via the setup.py command49# line, whatever. As long as these options come from something on the50# current system, they can be as system-dependent as they like, and we51# should just happily stuff them into the preprocessor/compiler/linker52# options and carry on.53 54 55def _split_env(cmd):56 """57 For macOS, split command into 'env' portion (if any)58 and the rest of the linker command.59 60 >>> _split_env(['a', 'b', 'c'])61 ([], ['a', 'b', 'c'])62 >>> _split_env(['/usr/bin/env', 'A=3', 'gcc'])63 (['/usr/bin/env', 'A=3'], ['gcc'])64 """65 pivot = 066 if os.path.basename(cmd[0]) == "env":67 pivot = 168 while '=' in cmd[pivot]:69 pivot += 170 return cmd[:pivot], cmd[pivot:]71 72 73def _split_aix(cmd):74 """75 AIX platforms prefix the compiler with the ld_so_aix76 script, so split that from the linker command.77 78 >>> _split_aix(['a', 'b', 'c'])79 ([], ['a', 'b', 'c'])80 >>> _split_aix(['/bin/foo/ld_so_aix', 'gcc'])81 (['/bin/foo/ld_so_aix'], ['gcc'])82 """83 pivot = os.path.basename(cmd[0]) == 'ld_so_aix'84 return cmd[:pivot], cmd[pivot:]85 86 87def _linker_params(linker_cmd, compiler_cmd):88 """89 The linker command usually begins with the compiler90 command (possibly multiple elements), followed by zero or more91 params for shared library building.92 93 If the LDSHARED env variable overrides the linker command,94 however, the commands may not match.95 96 Return the best guess of the linker parameters by stripping97 the linker command. If the compiler command does not98 match the linker command, assume the linker command is99 just the first element.100 101 >>> _linker_params('gcc foo bar'.split(), ['gcc'])102 ['foo', 'bar']103 >>> _linker_params('gcc foo bar'.split(), ['other'])104 ['foo', 'bar']105 >>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split())106 ['foo', 'bar']107 >>> _linker_params(['gcc'], ['gcc'])108 []109 """110 c_len = len(compiler_cmd)111 pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1112 return linker_cmd[pivot:]113 114 115class Compiler(base.Compiler):116 compiler_type = 'unix'117 118 # These are used by CCompiler in two places: the constructor sets119 # instance attributes 'preprocessor', 'compiler', etc. from them, and120 # 'set_executable()' allows any of these to be set. The defaults here121 # are pretty generic; they will probably have to be set by an outsider122 # (eg. using information discovered by the sysconfig about building123 # Python extensions).124 executables = {125 'preprocessor': None,126 'compiler': ["cc"],127 'compiler_so': ["cc"],128 'compiler_cxx': ["c++"],129 'compiler_so_cxx': ["c++"],130 'linker_so': ["cc", "-shared"],131 'linker_so_cxx': ["c++", "-shared"],132 'linker_exe': ["cc"],133 'linker_exe_cxx': ["c++", "-shared"],134 'archiver': ["ar", "-cr"],135 'ranlib': None,136 }137 138 if sys.platform[:6] == "darwin":139 executables['ranlib'] = ["ranlib"]140 141 # Needed for the filename generation methods provided by the base142 # class, CCompiler. NB. whoever instantiates/uses a particular143 # UnixCCompiler instance should set 'shared_lib_ext' -- we set a144 # reasonable common default here, but it's not necessarily used on all145 # Unices!146 147 src_extensions = [".c", ".C", ".cc", ".cxx", ".cpp", ".m"]148 obj_extension = ".o"149 static_lib_extension = ".a"150 shared_lib_extension = ".so"151 dylib_lib_extension = ".dylib"152 xcode_stub_lib_extension = ".tbd"153 static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s"154 xcode_stub_lib_format = dylib_lib_format155 if sys.platform == "cygwin":156 exe_extension = ".exe"157 shared_lib_extension = ".dll.a"158 dylib_lib_extension = ".dll"159 dylib_lib_format = "cyg%s%s"160 161 def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs):162 """Remove standard library path from rpath"""163 libraries, library_dirs, runtime_library_dirs = super()._fix_lib_args(164 libraries, library_dirs, runtime_library_dirs165 )166 libdir = sysconfig.get_config_var('LIBDIR')167 if (168 runtime_library_dirs169 and libdir.startswith("/usr/lib")170 and (libdir in runtime_library_dirs)171 ):172 runtime_library_dirs.remove(libdir)173 return libraries, library_dirs, runtime_library_dirs174 175 def preprocess(176 self,177 source: str | os.PathLike[str],178 output_file: str | os.PathLike[str] | None = None,179 macros: list[_Macro] | None = None,180 include_dirs: list[str] | tuple[str, ...] | None = None,181 extra_preargs: list[str] | None = None,182 extra_postargs: Iterable[str] | None = None,183 ):184 fixed_args = self._fix_compile_args(None, macros, include_dirs)185 ignore, macros, include_dirs = fixed_args186 pp_opts = gen_preprocess_options(macros, include_dirs)187 pp_args = self.preprocessor + pp_opts188 if output_file:189 pp_args.extend(['-o', output_file])190 if extra_preargs:191 pp_args[:0] = extra_preargs192 if extra_postargs:193 pp_args.extend(extra_postargs)194 pp_args.append(source)195 196 # reasons to preprocess:197 # - force is indicated198 # - output is directed to stdout199 # - source file is newer than the target200 preprocess = self.force or output_file is None or newer(source, output_file)201 if not preprocess:202 return203 204 if output_file:205 self.mkpath(os.path.dirname(output_file))206 207 try:208 self.spawn(pp_args)209 except DistutilsExecError as msg:210 raise CompileError(msg)211 212 def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):213 compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs)214 compiler_so_cxx = compiler_fixup(self.compiler_so_cxx, cc_args + extra_postargs)215 try:216 if self.detect_language(src) == 'c++':217 self.spawn(218 compiler_so_cxx + cc_args + [src, '-o', obj] + extra_postargs219 )220 else:221 self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs)222 except DistutilsExecError as msg:223 raise CompileError(msg)224 225 def create_static_lib(226 self, objects, output_libname, output_dir=None, debug=False, target_lang=None227 ):228 objects, output_dir = self._fix_object_args(objects, output_dir)229 230 output_filename = self.library_filename(output_libname, output_dir=output_dir)231 232 if self._need_link(objects, output_filename):233 self.mkpath(os.path.dirname(output_filename))234 self.spawn(self.archiver + [output_filename] + objects + self.objects)235 236 # Not many Unices required ranlib anymore -- SunOS 4.x is, I237 # think the only major Unix that does. Maybe we need some238 # platform intelligence here to skip ranlib if it's not239 # needed -- or maybe Python's configure script took care of240 # it for us, hence the check for leading colon.241 if self.ranlib:242 try:243 self.spawn(self.ranlib + [output_filename])244 except DistutilsExecError as msg:245 raise LibError(msg)246 else:247 log.debug("skipping %s (up-to-date)", output_filename)248 249 def link(250 self,251 target_desc,252 objects: list[str] | tuple[str, ...],253 output_filename,254 output_dir: str | None = None,255 libraries: list[str] | tuple[str, ...] | None = None,256 library_dirs: list[str] | tuple[str, ...] | None = None,257 runtime_library_dirs: list[str] | tuple[str, ...] | None = None,258 export_symbols=None,259 debug=False,260 extra_preargs=None,261 extra_postargs=None,262 build_temp=None,263 target_lang=None,264 ):265 objects, output_dir = self._fix_object_args(objects, output_dir)266 fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)267 libraries, library_dirs, runtime_library_dirs = fixed_args268 269 lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)270 if not isinstance(output_dir, (str, type(None))):271 raise TypeError("'output_dir' must be a string or None")272 if output_dir is not None:273 output_filename = os.path.join(output_dir, output_filename)274 275 if self._need_link(objects, output_filename):276 ld_args = objects + self.objects + lib_opts + ['-o', output_filename]277 if debug:278 ld_args[:0] = ['-g']279 if extra_preargs:280 ld_args[:0] = extra_preargs281 if extra_postargs:282 ld_args.extend(extra_postargs)283 self.mkpath(os.path.dirname(output_filename))284 try:285 # Select a linker based on context: linker_exe when286 # building an executable or linker_so (with shared options)287 # when building a shared library.288 building_exe = target_desc == base.Compiler.EXECUTABLE289 target_cxx = target_lang == "c++"290 linker = (291 (self.linker_exe_cxx if target_cxx else self.linker_exe)292 if building_exe293 else (self.linker_so_cxx if target_cxx else self.linker_so)294 )[:]295 296 if target_cxx and self.compiler_cxx:297 env, linker_ne = _split_env(linker)298 aix, linker_na = _split_aix(linker_ne)299 _, compiler_cxx_ne = _split_env(self.compiler_cxx)300 _, linker_exe_ne = _split_env(self.linker_exe_cxx)301 302 params = _linker_params(linker_na, linker_exe_ne)303 linker = env + aix + compiler_cxx_ne + params304 305 linker = compiler_fixup(linker, ld_args)306 307 self.spawn(linker + ld_args)308 except DistutilsExecError as msg:309 raise LinkError(msg)310 else:311 log.debug("skipping %s (up-to-date)", output_filename)312 313 # -- Miscellaneous methods -----------------------------------------314 # These are all used by the 'gen_lib_options() function, in315 # ccompiler.py.316 317 def library_dir_option(self, dir):318 return "-L" + dir319 320 def _is_gcc(self):321 cc_var = sysconfig.get_config_var("CC")322 compiler = os.path.basename(shlex.split(cc_var)[0])323 return "gcc" in compiler or "g++" in compiler324 325 def runtime_library_dir_option(self, dir: str) -> str | list[str]: # type: ignore[override] # Fixed in pypa/distutils#339326 # XXX Hackish, at the very least. See Python bug #445902:327 # https://bugs.python.org/issue445902328 # Linkers on different platforms need different options to329 # specify that directories need to be added to the list of330 # directories searched for dependencies when a dynamic library331 # is sought. GCC on GNU systems (Linux, FreeBSD, ...) has to332 # be told to pass the -R option through to the linker, whereas333 # other compilers and gcc on other systems just know this.334 # Other compilers may need something slightly different. At335 # this time, there's no way to determine this information from336 # the configuration data stored in the Python installation, so337 # we use this hack.338 if sys.platform[:6] == "darwin":339 from distutils.util import get_macosx_target_ver, split_version340 341 macosx_target_ver = get_macosx_target_ver()342 if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]:343 return "-Wl,-rpath," + dir344 else: # no support for -rpath on earlier macOS versions345 return "-L" + dir346 elif sys.platform[:7] == "freebsd":347 return "-Wl,-rpath=" + dir348 elif sys.platform[:5] == "hp-ux":349 return [350 "-Wl,+s" if self._is_gcc() else "+s",351 "-L" + dir,352 ]353 354 # For all compilers, `-Wl` is the presumed way to pass a355 # compiler option to the linker356 if sysconfig.get_config_var("GNULD") == "yes":357 return consolidate_linker_args([358 # Force RUNPATH instead of RPATH359 "-Wl,--enable-new-dtags",360 "-Wl,-rpath," + dir,361 ])362 else:363 return "-Wl,-R" + dir364 365 def library_option(self, lib):366 return "-l" + lib367 368 @staticmethod369 def _library_root(dir):370 """371 macOS users can specify an alternate SDK using'-isysroot'.372 Calculate the SDK root if it is specified.373 374 Note that, as of Xcode 7, Apple SDKs may contain textual stub375 libraries with .tbd extensions rather than the normal .dylib376 shared libraries installed in /. The Apple compiler tool377 chain handles this transparently but it can cause problems378 for programs that are being built with an SDK and searching379 for specific libraries. Callers of find_library_file need to380 keep in mind that the base filename of the returned SDK library381 file might have a different extension from that of the library382 file installed on the running system, for example:383 /Applications/Xcode.app/Contents/Developer/Platforms/384 MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/385 usr/lib/libedit.tbd386 vs387 /usr/lib/libedit.dylib388 """389 cflags = sysconfig.get_config_var('CFLAGS')390 match = re.search(r'-isysroot\s*(\S+)', cflags)391 392 apply_root = (393 sys.platform == 'darwin'394 and match395 and (396 dir.startswith('/System/')397 or (dir.startswith('/usr/') and not dir.startswith('/usr/local/'))398 )399 )400 401 return os.path.join(match.group(1), dir[1:]) if apply_root else dir402 403 def find_library_file(self, dirs, lib, debug=False):404 """405 Second-guess the linker with not much hard406 data to go on: GCC seems to prefer the shared library, so407 assume that *all* Unix C compilers do,408 ignoring even GCC's "-static" option.409 """410 lib_names = (411 self.library_filename(lib, lib_type=type)412 for type in 'dylib xcode_stub shared static'.split()413 )414 415 roots = map(self._library_root, dirs)416 417 searched = itertools.starmap(os.path.join, itertools.product(roots, lib_names))418 419 found = filter(os.path.exists, searched)420 421 # Return None if it could not be found in any dir.422 return next(found, None)423 