1"""distutils.unixccompiler 2 3Contains the UnixCCompiler class, a subclass of CCompiler that handles 4the "typical" Unix-style command-line C compiler: 5 * macros defined with -Dname[=value] 6 * macros undefined with -Uname 7 * include search directories specified with -Idir 8 * libraries specified with -lllib 9 * library search directories specified with -Ldir 10 * compile handled by 'cc' (or similar) executable with -c option: 11 compiles .c to .o 12 * link static library handled by 'ar' command (possibly with 'ranlib') 13 * link shared library handled by 'cc -shared' 14""" 15 16import os, sys, re, shlex 17 18from distutils import sysconfig 19from distutils.dep_util import newer 20from distutils.ccompiler import \ 21 CCompiler, gen_preprocess_options, gen_lib_options 22from distutils.errors import \ 23 DistutilsExecError, CompileError, LibError, LinkError 24from distutils import log 25from ._macos_compat import compiler_fixup 26 27# XXX Things not currently handled: 28# * optimization/debug/warning flags; we just use whatever's in Python's 29# Makefile and live with it. Is this adequate? If not, we might 30# have to have a bunch of subclasses GNUCCompiler, SGICCompiler, 31# SunCCompiler, and I suspect down that road lies madness. 32# * even if we don't know a warning flag from an optimization flag, 33# we need some way for outsiders to feed preprocessor/compiler/linker 34# flags in to us -- eg. a sysadmin might want to mandate certain flags 35# via a site config file, or a user might want to set something for 36# compiling this module distribution only via the setup.py command 37# line, whatever. As long as these options come from something on the 38# current system, they can be as system-dependent as they like, and we 39# should just happily stuff them into the preprocessor/compiler/linker 40# options and carry on. 41 42 43def _split_env(cmd): 44 """ 45 For macOS, split command into 'env' portion (if any) 46 and the rest of the linker command. 47 48 >>> _split_env(['a', 'b', 'c']) 49 ([], ['a', 'b', 'c']) 50 >>> _split_env(['/usr/bin/env', 'A=3', 'gcc']) 51 (['/usr/bin/env', 'A=3'], ['gcc']) 52 """ 53 pivot = 0 54 if os.path.basename(cmd[0]) == "env": 55 pivot = 1 56 while '=' in cmd[pivot]: 57 pivot += 1 58 return cmd[:pivot], cmd[pivot:] 59 60 61def _split_aix(cmd): 62 """ 63 AIX platforms prefix the compiler with the ld_so_aix 64 script, so split that from the linker command. 65 66 >>> _split_aix(['a', 'b', 'c']) 67 ([], ['a', 'b', 'c']) 68 >>> _split_aix(['/bin/foo/ld_so_aix', 'gcc']) 69 (['/bin/foo/ld_so_aix'], ['gcc']) 70 """ 71 pivot = os.path.basename(cmd[0]) == 'ld_so_aix' 72 return cmd[:pivot], cmd[pivot:] 73 74 75def _linker_params(linker_cmd, compiler_cmd): 76 """ 77 The linker command usually begins with the compiler 78 command (possibly multiple elements), followed by zero or more 79 params for shared library building. 80 81 If the LDSHARED env variable overrides the linker command, 82 however, the commands may not match. 83 84 Return the best guess of the linker parameters by stripping 85 the linker command. If the compiler command does not 86 match the linker command, assume the linker command is 87 just the first element. 88 89 >>> _linker_params('gcc foo bar'.split(), ['gcc']) 90 ['foo', 'bar'] 91 >>> _linker_params('gcc foo bar'.split(), ['other']) 92 ['foo', 'bar'] 93 >>> _linker_params('ccache gcc foo bar'.split(), 'ccache gcc'.split()) 94 ['foo', 'bar'] 95 >>> _linker_params(['gcc'], ['gcc']) 96 [] 97 """ 98 c_len = len(compiler_cmd) 99 pivot = c_len if linker_cmd[:c_len] == compiler_cmd else 1 100 return linker_cmd[pivot:] 101 102 103class UnixCCompiler(CCompiler): 104 105 compiler_type = 'unix' 106 107 # These are used by CCompiler in two places: the constructor sets 108 # instance attributes 'preprocessor', 'compiler', etc. from them, and 109 # 'set_executable()' allows any of these to be set. The defaults here 110 # are pretty generic; they will probably have to be set by an outsider 111 # (eg. using information discovered by the sysconfig about building 112 # Python extensions). 113 executables = {'preprocessor' : None, 114 'compiler' : ["cc"], 115 'compiler_so' : ["cc"], 116 'compiler_cxx' : ["cc"], 117 'linker_so' : ["cc", "-shared"], 118 'linker_exe' : ["cc"], 119 'archiver' : ["ar", "-cr"], 120 'ranlib' : None, 121 } 122 123 if sys.platform[:6] == "darwin": 124 executables['ranlib'] = ["ranlib"] 125 126 # Needed for the filename generation methods provided by the base 127 # class, CCompiler. NB. whoever instantiates/uses a particular 128 # UnixCCompiler instance should set 'shared_lib_ext' -- we set a 129 # reasonable common default here, but it's not necessarily used on all 130 # Unices! 131 132 src_extensions = [".c",".C",".cc",".cxx",".cpp",".m"] 133 obj_extension = ".o" 134 static_lib_extension = ".a" 135 shared_lib_extension = ".so" 136 dylib_lib_extension = ".dylib" 137 xcode_stub_lib_extension = ".tbd" 138 static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s" 139 xcode_stub_lib_format = dylib_lib_format 140 if sys.platform == "cygwin": 141 exe_extension = ".exe" 142 143 def preprocess(self, source, output_file=None, macros=None, 144 include_dirs=None, extra_preargs=None, extra_postargs=None): 145 fixed_args = self._fix_compile_args(None, macros, include_dirs) 146 ignore, macros, include_dirs = fixed_args 147 pp_opts = gen_preprocess_options(macros, include_dirs) 148 pp_args = self.preprocessor + pp_opts 149 if output_file: 150 pp_args.extend(['-o', output_file]) 151 if extra_preargs: 152 pp_args[:0] = extra_preargs 153 if extra_postargs: 154 pp_args.extend(extra_postargs) 155 pp_args.append(source) 156 157 # We need to preprocess: either we're being forced to, or we're 158 # generating output to stdout, or there's a target output file and 159 # the source file is newer than the target (or the target doesn't 160 # exist). 161 if self.force or output_file is None or newer(source, output_file): 162 if output_file: 163 self.mkpath(os.path.dirname(output_file)) 164 try: 165 self.spawn(pp_args) 166 except DistutilsExecError as msg: 167 raise CompileError(msg) 168 169 def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): 170 compiler_so = compiler_fixup( 171 self.compiler_so, cc_args + extra_postargs) 172 try: 173 self.spawn(compiler_so + cc_args + [src, '-o', obj] + 174 extra_postargs) 175 except DistutilsExecError as msg: 176 raise CompileError(msg) 177 178 def create_static_lib(self, objects, output_libname, 179 output_dir=None, debug=0, target_lang=None): 180 objects, output_dir = self._fix_object_args(objects, output_dir) 181 182 output_filename = \ 183 self.library_filename(output_libname, output_dir=output_dir) 184 185 if self._need_link(objects, output_filename): 186 self.mkpath(os.path.dirname(output_filename)) 187 self.spawn(self.archiver + 188 [output_filename] + 189 objects + self.objects) 190 191 # Not many Unices required ranlib anymore -- SunOS 4.x is, I 192 # think the only major Unix that does. Maybe we need some 193 # platform intelligence here to skip ranlib if it's not 194 # needed -- or maybe Python's configure script took care of 195 # it for us, hence the check for leading colon. 196 if self.ranlib: 197 try: 198 self.spawn(self.ranlib + [output_filename]) 199 except DistutilsExecError as msg: 200 raise LibError(msg) 201 else: 202 log.debug("skipping %s (up-to-date)", output_filename) 203 204 def link(self, target_desc, objects, 205 output_filename, output_dir=None, libraries=None, 206 library_dirs=None, runtime_library_dirs=None, 207 export_symbols=None, debug=0, extra_preargs=None, 208 extra_postargs=None, build_temp=None, target_lang=None): 209 objects, output_dir = self._fix_object_args(objects, output_dir) 210 fixed_args = self._fix_lib_args(libraries, library_dirs, 211 runtime_library_dirs) 212 libraries, library_dirs, runtime_library_dirs = fixed_args 213 214 lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs, 215 libraries) 216 if not isinstance(output_dir, (str, type(None))): 217 raise TypeError("'output_dir' must be a string or None") 218 if output_dir is not None: 219 output_filename = os.path.join(output_dir, output_filename) 220 221 if self._need_link(objects, output_filename): 222 ld_args = (objects + self.objects + 223 lib_opts + ['-o', output_filename]) 224 if debug: 225 ld_args[:0] = ['-g'] 226 if extra_preargs: 227 ld_args[:0] = extra_preargs 228 if extra_postargs: 229 ld_args.extend(extra_postargs) 230 self.mkpath(os.path.dirname(output_filename)) 231 try: 232 # Select a linker based on context: linker_exe when 233 # building an executable or linker_so (with shared options) 234 # when building a shared library. 235 building_exe = target_desc == CCompiler.EXECUTABLE 236 linker = (self.linker_exe if building_exe else self.linker_so)[:] 237 238 if target_lang == "c++" and self.compiler_cxx: 239 env, linker_ne = _split_env(linker) 240 aix, linker_na = _split_aix(linker_ne) 241 _, compiler_cxx_ne = _split_env(self.compiler_cxx) 242 _, linker_exe_ne = _split_env(self.linker_exe) 243 244 params = _linker_params(linker_na, linker_exe_ne) 245 linker = env + aix + compiler_cxx_ne + params 246 247 linker = compiler_fixup(linker, ld_args) 248 249 self.spawn(linker + ld_args) 250 except DistutilsExecError as msg: 251 raise LinkError(msg) 252 else: 253 log.debug("skipping %s (up-to-date)", output_filename) 254 255 # -- Miscellaneous methods ----------------------------------------- 256 # These are all used by the 'gen_lib_options() function, in 257 # ccompiler.py. 258 259 def library_dir_option(self, dir): 260 return "-L" + dir 261 262 def _is_gcc(self, compiler_name): 263 return "gcc" in compiler_name or "g++" in compiler_name 264 265 def runtime_library_dir_option(self, dir): 266 # XXX Hackish, at the very least. See Python bug #445902: 267 # http://sourceforge.net/tracker/index.php 268 # ?func=detail&aid=445902&group_id=5470&atid=105470 269 # Linkers on different platforms need different options to 270 # specify that directories need to be added to the list of 271 # directories searched for dependencies when a dynamic library 272 # is sought. GCC on GNU systems (Linux, FreeBSD, ...) has to 273 # be told to pass the -R option through to the linker, whereas 274 # other compilers and gcc on other systems just know this. 275 # Other compilers may need something slightly different. At 276 # this time, there's no way to determine this information from 277 # the configuration data stored in the Python installation, so 278 # we use this hack. 279 compiler = os.path.basename(shlex.split(sysconfig.get_config_var("CC"))[0]) 280 if sys.platform[:6] == "darwin": 281 from distutils.util import get_macosx_target_ver, split_version 282 macosx_target_ver = get_macosx_target_ver() 283 if macosx_target_ver and split_version(macosx_target_ver) >= [10, 5]: 284 return "-Wl,-rpath," + dir 285 else: # no support for -rpath on earlier macOS versions 286 return "-L" + dir 287 elif sys.platform[:7] == "freebsd": 288 return "-Wl,-rpath=" + dir 289 elif sys.platform[:5] == "hp-ux": 290 if self._is_gcc(compiler): 291 return ["-Wl,+s", "-L" + dir] 292 return ["+s", "-L" + dir] 293 294 # For all compilers, `-Wl` is the presumed way to 295 # pass a compiler option to the linker and `-R` is 296 # the way to pass an RPATH. 297 if sysconfig.get_config_var("GNULD") == "yes": 298 # GNU ld needs an extra option to get a RUNPATH 299 # instead of just an RPATH. 300 return "-Wl,--enable-new-dtags,-R" + dir 301 else: 302 return "-Wl,-R" + dir 303 304 def library_option(self, lib): 305 return "-l" + lib 306 307 def find_library_file(self, dirs, lib, debug=0): 308 shared_f = self.library_filename(lib, lib_type='shared') 309 dylib_f = self.library_filename(lib, lib_type='dylib') 310 xcode_stub_f = self.library_filename(lib, lib_type='xcode_stub') 311 static_f = self.library_filename(lib, lib_type='static') 312 313 if sys.platform == 'darwin': 314 # On OSX users can specify an alternate SDK using 315 # '-isysroot', calculate the SDK root if it is specified 316 # (and use it further on) 317 # 318 # Note that, as of Xcode 7, Apple SDKs may contain textual stub 319 # libraries with .tbd extensions rather than the normal .dylib 320 # shared libraries installed in /. The Apple compiler tool 321 # chain handles this transparently but it can cause problems 322 # for programs that are being built with an SDK and searching 323 # for specific libraries. Callers of find_library_file need to 324 # keep in mind that the base filename of the returned SDK library 325 # file might have a different extension from that of the library 326 # file installed on the running system, for example: 327 # /Applications/Xcode.app/Contents/Developer/Platforms/ 328 # MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/ 329 # usr/lib/libedit.tbd 330 # vs 331 # /usr/lib/libedit.dylib 332 cflags = sysconfig.get_config_var('CFLAGS') 333 m = re.search(r'-isysroot\s*(\S+)', cflags) 334 if m is None: 335 sysroot = '/' 336 else: 337 sysroot = m.group(1) 338 339 340 341 for dir in dirs: 342 shared = os.path.join(dir, shared_f) 343 dylib = os.path.join(dir, dylib_f) 344 static = os.path.join(dir, static_f) 345 xcode_stub = os.path.join(dir, xcode_stub_f) 346 347 if sys.platform == 'darwin' and ( 348 dir.startswith('/System/') or ( 349 dir.startswith('/usr/') and not dir.startswith('/usr/local/'))): 350 351 shared = os.path.join(sysroot, dir[1:], shared_f) 352 dylib = os.path.join(sysroot, dir[1:], dylib_f) 353 static = os.path.join(sysroot, dir[1:], static_f) 354 xcode_stub = os.path.join(sysroot, dir[1:], xcode_stub_f) 355 356 # We're second-guessing the linker here, with not much hard 357 # data to go on: GCC seems to prefer the shared library, so I'm 358 # assuming that *all* Unix C compilers do. And of course I'm 359 # ignoring even GCC's "-static" option. So sue me. 360 if os.path.exists(dylib): 361 return dylib 362 elif os.path.exists(xcode_stub): 363 return xcode_stub 364 elif os.path.exists(shared): 365 return shared 366 elif os.path.exists(static): 367 return static 368 369 # Oops, didn't find it in *any* of 'dirs' 370 return None 371