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 sys23 24from . import sysconfig25from .compat import consolidate_linker_args26from ._log import log27from ._macos_compat import compiler_fixup28from ._modified import newer29from .ccompiler import CCompiler, gen_lib_options, gen_preprocess_options30from .errors import CompileError, DistutilsExecError, LibError, LinkError31 32# XXX Things not currently handled:33# * optimization/debug/warning flags; we just use whatever's in Python's34# Makefile and live with it. Is this adequate? If not, we might35# have to have a bunch of subclasses GNUCCompiler, SGICCompiler,36# SunCCompiler, and I suspect down that road lies madness.37# * even if we don't know a warning flag from an optimization flag,38# we need some way for outsiders to feed preprocessor/compiler/linker39# flags in to us -- eg. a sysadmin might want to mandate certain flags40# via a site config file, or a user might want to set something for41# compiling this module distribution only via the setup.py command42# line, whatever. As long as these options come from something on the43# current system, they can be as system-dependent as they like, and we44# should just happily stuff them into the preprocessor/compiler/linker45# options and carry on.46 47 48def _split_env(cmd):49 """50 For macOS, split command into 'env' portion (if any)51 and the rest of the linker command.52 53 >>> _split_env(['a', 'b', 'c'])54 ([], ['a', 'b', 'c'])55 >>> _split_env(['/usr/bin/env', 'A=3', 'gcc'])56 (['/usr/bin/env', 'A=3'], ['gcc'])57 """58 pivot = 059 if os.path.basename(cmd[0]) == "env":60 pivot = 161 while '=' in cmd[pivot]:62 pivot += 163 return cmd[:pivot], cmd[pivot:]64 65 66def _split_aix(cmd):67 """68 AIX platforms prefix the compiler with the ld_so_aix69 script, so split that from the linker command.70 71 >>> _split_aix(['a', 'b', 'c'])72 ([], ['a', 'b', 'c'])73 >>> _split_aix(['/bin/foo/ld_so_aix', 'gcc'])74 (['/bin/foo/ld_so_aix'], ['gcc'])75 """76 pivot = os.path.basename(cmd[0]) == 'ld_so_aix'77 return cmd[:pivot], cmd[pivot:]78 79 80def _linker_params(linker_cmd, compiler_cmd):81 """82 The linker command usually begins with the compiler83 command (possibly multiple elements), followed by zero or more84 params for shared library building.85 86 If the LDSHARED env variable overrides the linker command,87 however, the commands may not match.88 89 Return the best guess of the linker parameters by stripping90 the linker command. If the compiler command does not91 match the linker command, assume the linker command is92 just the first element.93 94 >>> _linker_params('gcc foo bar'.split(), ['gcc'])95 ['foo', 'bar']96 >>> _linker_params('gcc foo bar'.split(), ['other'])97 ['foo', 'bar']98 >>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split())99 ['foo', 'bar']100 >>> _linker_params(['gcc'], ['gcc'])101 []102 """103 c_len = len(compiler_cmd)104 pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1105 return linker_cmd[pivot:]106 107 108class UnixCCompiler(CCompiler):109 compiler_type = 'unix'110 111 # These are used by CCompiler in two places: the constructor sets112 # instance attributes 'preprocessor', 'compiler', etc. from them, and113 # 'set_executable()' allows any of these to be set. The defaults here114 # are pretty generic; they will probably have to be set by an outsider115 # (eg. using information discovered by the sysconfig about building116 # Python extensions).117 executables = {118 'preprocessor': None,119 'compiler': ["cc"],120 'compiler_so': ["cc"],121 'compiler_cxx': ["cc"],122 'linker_so': ["cc", "-shared"],123 'linker_exe': ["cc"],124 'archiver': ["ar", "-cr"],125 'ranlib': None,126 }127 128 if sys.platform[:6] == "darwin":129 executables['ranlib'] = ["ranlib"]130 131 # Needed for the filename generation methods provided by the base132 # class, CCompiler. NB. whoever instantiates/uses a particular133 # UnixCCompiler instance should set 'shared_lib_ext' -- we set a134 # reasonable common default here, but it's not necessarily used on all135 # Unices!136 137 src_extensions = [".c", ".C", ".cc", ".cxx", ".cpp", ".m"]138 obj_extension = ".o"139 static_lib_extension = ".a"140 shared_lib_extension = ".so"141 dylib_lib_extension = ".dylib"142 xcode_stub_lib_extension = ".tbd"143 static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s"144 xcode_stub_lib_format = dylib_lib_format145 if sys.platform == "cygwin":146 exe_extension = ".exe"147 148 def preprocess(149 self,150 source,151 output_file=None,152 macros=None,153 include_dirs=None,154 extra_preargs=None,155 extra_postargs=None,156 ):157 fixed_args = self._fix_compile_args(None, macros, include_dirs)158 ignore, macros, include_dirs = fixed_args159 pp_opts = gen_preprocess_options(macros, include_dirs)160 pp_args = self.preprocessor + pp_opts161 if output_file:162 pp_args.extend(['-o', output_file])163 if extra_preargs:164 pp_args[:0] = extra_preargs165 if extra_postargs:166 pp_args.extend(extra_postargs)167 pp_args.append(source)168 169 # reasons to preprocess:170 # - force is indicated171 # - output is directed to stdout172 # - source file is newer than the target173 preprocess = self.force or output_file is None or newer(source, output_file)174 if not preprocess:175 return176 177 if output_file:178 self.mkpath(os.path.dirname(output_file))179 180 try:181 self.spawn(pp_args)182 except DistutilsExecError as msg:183 raise CompileError(msg)184 185 def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts):186 compiler_so = compiler_fixup(self.compiler_so, cc_args + extra_postargs)187 try:188 self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs)189 except DistutilsExecError as msg:190 raise CompileError(msg)191 192 def create_static_lib(193 self, objects, output_libname, output_dir=None, debug=0, target_lang=None194 ):195 objects, output_dir = self._fix_object_args(objects, output_dir)196 197 output_filename = self.library_filename(output_libname, output_dir=output_dir)198 199 if self._need_link(objects, output_filename):200 self.mkpath(os.path.dirname(output_filename))201 self.spawn(self.archiver + [output_filename] + objects + self.objects)202 203 # Not many Unices required ranlib anymore -- SunOS 4.x is, I204 # think the only major Unix that does. Maybe we need some205 # platform intelligence here to skip ranlib if it's not206 # needed -- or maybe Python's configure script took care of207 # it for us, hence the check for leading colon.208 if self.ranlib:209 try:210 self.spawn(self.ranlib + [output_filename])211 except DistutilsExecError as msg:212 raise LibError(msg)213 else:214 log.debug("skipping %s (up-to-date)", output_filename)215 216 def link(217 self,218 target_desc,219 objects,220 output_filename,221 output_dir=None,222 libraries=None,223 library_dirs=None,224 runtime_library_dirs=None,225 export_symbols=None,226 debug=0,227 extra_preargs=None,228 extra_postargs=None,229 build_temp=None,230 target_lang=None,231 ):232 objects, output_dir = self._fix_object_args(objects, output_dir)233 fixed_args = self._fix_lib_args(libraries, library_dirs, runtime_library_dirs)234 libraries, library_dirs, runtime_library_dirs = fixed_args235 236 lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, libraries)237 if not isinstance(output_dir, (str, type(None))):238 raise TypeError("'output_dir' must be a string or None")239 if output_dir is not None:240 output_filename = os.path.join(output_dir, output_filename)241 242 if self._need_link(objects, output_filename):243 ld_args = objects + self.objects + lib_opts + ['-o', output_filename]244 if debug:245 ld_args[:0] = ['-g']246 if extra_preargs:247 ld_args[:0] = extra_preargs248 if extra_postargs:249 ld_args.extend(extra_postargs)250 self.mkpath(os.path.dirname(output_filename))251 try:252 # Select a linker based on context: linker_exe when253 # building an executable or linker_so (with shared options)254 # when building a shared library.255 building_exe = target_desc == CCompiler.EXECUTABLE256 linker = (self.linker_exe if building_exe else self.linker_so)[:]257 258 if target_lang == "c++" and self.compiler_cxx:259 env, linker_ne = _split_env(linker)260 aix, linker_na = _split_aix(linker_ne)261 _, compiler_cxx_ne = _split_env(self.compiler_cxx)262 _, linker_exe_ne = _split_env(self.linker_exe)263 264 params = _linker_params(linker_na, linker_exe_ne)265 linker = env + aix + compiler_cxx_ne + params266 267 linker = compiler_fixup(linker, ld_args)268 269 self.spawn(linker + ld_args)270 except DistutilsExecError as msg:271 raise LinkError(msg)272 else:273 log.debug("skipping %s (up-to-date)", output_filename)274 275 # -- Miscellaneous methods -----------------------------------------276 # These are all used by the 'gen_lib_options() function, in277 # ccompiler.py.278 279 def library_dir_option(self, dir):280 return "-L" + dir281 282 def _is_gcc(self):283 cc_var = sysconfig.get_config_var("CC")284 compiler = os.path.basename(shlex.split(cc_var)[0])285 return "gcc" in compiler or "g++" in compiler286 287 def runtime_library_dir_option(self, dir: str) -> str | list[str]:288 # XXX Hackish, at the very least. See Python bug #445902:289 # https://bugs.python.org/issue445902290 # Linkers on different platforms need different options to291 # specify that directories need to be added to the list of292 # directories searched for dependencies when a dynamic library293 # is sought. GCC on GNU systems (Linux, FreeBSD, ...) has to294 # be told to pass the -R option through to the linker, whereas295 # other compilers and gcc on other systems just know this.296 # Other compilers may need something slightly different. At297 # this time, there's no way to determine this information from298 # the configuration data stored in the Python installation, so299 # we use this hack.300 if sys.platform[:6] == "darwin":301 from distutils.util import get_macosx_target_ver, split_version302 303 macosx_target_ver = get_macosx_target_ver()304 if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]:305 return "-Wl,-rpath," + dir306 else: # no support for -rpath on earlier macOS versions307 return "-L" + dir308 elif sys.platform[:7] == "freebsd":309 return "-Wl,-rpath=" + dir310 elif sys.platform[:5] == "hp-ux":311 return [312 "-Wl,+s" if self._is_gcc() else "+s",313 "-L" + dir,314 ]315 316 # For all compilers, `-Wl` is the presumed way to pass a317 # compiler option to the linker318 if sysconfig.get_config_var("GNULD") == "yes":319 return consolidate_linker_args([320 # Force RUNPATH instead of RPATH321 "-Wl,--enable-new-dtags",322 "-Wl,-rpath," + dir,323 ])324 else:325 return "-Wl,-R" + dir326 327 def library_option(self, lib):328 return "-l" + lib329 330 @staticmethod331 def _library_root(dir):332 """333 macOS users can specify an alternate SDK using'-isysroot'.334 Calculate the SDK root if it is specified.335 336 Note that, as of Xcode 7, Apple SDKs may contain textual stub337 libraries with .tbd extensions rather than the normal .dylib338 shared libraries installed in /. The Apple compiler tool339 chain handles this transparently but it can cause problems340 for programs that are being built with an SDK and searching341 for specific libraries. Callers of find_library_file need to342 keep in mind that the base filename of the returned SDK library343 file might have a different extension from that of the library344 file installed on the running system, for example:345 /Applications/Xcode.app/Contents/Developer/Platforms/346 MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/347 usr/lib/libedit.tbd348 vs349 /usr/lib/libedit.dylib350 """351 cflags = sysconfig.get_config_var('CFLAGS')352 match = re.search(r'-isysroot\s*(\S+)', cflags)353 354 apply_root = (355 sys.platform == 'darwin'356 and match357 and (358 dir.startswith('/System/')359 or (dir.startswith('/usr/') and not dir.startswith('/usr/local/'))360 )361 )362 363 return os.path.join(match.group(1), dir[1:]) if apply_root else dir364 365 def find_library_file(self, dirs, lib, debug=0):366 r"""367 Second-guess the linker with not much hard368 data to go on: GCC seems to prefer the shared library, so369 assume that *all* Unix C compilers do,370 ignoring even GCC's "-static" option.371 372 >>> compiler = UnixCCompiler()373 >>> compiler._library_root = lambda dir: dir374 >>> monkeypatch = getfixture('monkeypatch')375 >>> monkeypatch.setattr(os.path, 'exists', lambda d: 'existing' in d)376 >>> dirs = ('/foo/bar/missing', '/foo/bar/existing')377 >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')378 '/foo/bar/existing/libabc.dylib'379 >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')380 '/foo/bar/existing/libabc.dylib'381 >>> monkeypatch.setattr(os.path, 'exists',382 ... lambda d: 'existing' in d and '.a' in d)383 >>> compiler.find_library_file(dirs, 'abc').replace('\\', '/')384 '/foo/bar/existing/libabc.a'385 >>> compiler.find_library_file(reversed(dirs), 'abc').replace('\\', '/')386 '/foo/bar/existing/libabc.a'387 """388 lib_names = (389 self.library_filename(lib, lib_type=type)390 for type in 'dylib xcode_stub shared static'.split()391 )392 393 roots = map(self._library_root, dirs)394 395 searched = itertools.starmap(os.path.join, itertools.product(roots, lib_names))396 397 found = filter(os.path.exists, searched)398 399 # Return None if it could not be found in any dir.400 return next(found, None)401 