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