1 | n/a | """distutils.command.config |
---|
2 | n/a | |
---|
3 | n/a | Implements the Distutils 'config' command, a (mostly) empty command class |
---|
4 | n/a | that exists mainly to be sub-classed by specific module distributions and |
---|
5 | n/a | applications. The idea is that while every "config" command is different, |
---|
6 | n/a | at least they're all named the same, and users always see "config" in the |
---|
7 | n/a | list of standard commands. Also, this is a good place to put common |
---|
8 | n/a | configure-like tasks: "try to compile this C code", or "figure out where |
---|
9 | n/a | this header file lives". |
---|
10 | n/a | """ |
---|
11 | n/a | |
---|
12 | n/a | import os, re |
---|
13 | n/a | |
---|
14 | n/a | from distutils.core import Command |
---|
15 | n/a | from distutils.errors import DistutilsExecError |
---|
16 | n/a | from distutils.sysconfig import customize_compiler |
---|
17 | n/a | from distutils import log |
---|
18 | n/a | |
---|
19 | n/a | LANG_EXT = {"c": ".c", "c++": ".cxx"} |
---|
20 | n/a | |
---|
21 | n/a | class config(Command): |
---|
22 | n/a | |
---|
23 | n/a | description = "prepare to build" |
---|
24 | n/a | |
---|
25 | n/a | user_options = [ |
---|
26 | n/a | ('compiler=', None, |
---|
27 | n/a | "specify the compiler type"), |
---|
28 | n/a | ('cc=', None, |
---|
29 | n/a | "specify the compiler executable"), |
---|
30 | n/a | ('include-dirs=', 'I', |
---|
31 | n/a | "list of directories to search for header files"), |
---|
32 | n/a | ('define=', 'D', |
---|
33 | n/a | "C preprocessor macros to define"), |
---|
34 | n/a | ('undef=', 'U', |
---|
35 | n/a | "C preprocessor macros to undefine"), |
---|
36 | n/a | ('libraries=', 'l', |
---|
37 | n/a | "external C libraries to link with"), |
---|
38 | n/a | ('library-dirs=', 'L', |
---|
39 | n/a | "directories to search for external C libraries"), |
---|
40 | n/a | |
---|
41 | n/a | ('noisy', None, |
---|
42 | n/a | "show every action (compile, link, run, ...) taken"), |
---|
43 | n/a | ('dump-source', None, |
---|
44 | n/a | "dump generated source files before attempting to compile them"), |
---|
45 | n/a | ] |
---|
46 | n/a | |
---|
47 | n/a | |
---|
48 | n/a | # The three standard command methods: since the "config" command |
---|
49 | n/a | # does nothing by default, these are empty. |
---|
50 | n/a | |
---|
51 | n/a | def initialize_options(self): |
---|
52 | n/a | self.compiler = None |
---|
53 | n/a | self.cc = None |
---|
54 | n/a | self.include_dirs = None |
---|
55 | n/a | self.libraries = None |
---|
56 | n/a | self.library_dirs = None |
---|
57 | n/a | |
---|
58 | n/a | # maximal output for now |
---|
59 | n/a | self.noisy = 1 |
---|
60 | n/a | self.dump_source = 1 |
---|
61 | n/a | |
---|
62 | n/a | # list of temporary files generated along-the-way that we have |
---|
63 | n/a | # to clean at some point |
---|
64 | n/a | self.temp_files = [] |
---|
65 | n/a | |
---|
66 | n/a | def finalize_options(self): |
---|
67 | n/a | if self.include_dirs is None: |
---|
68 | n/a | self.include_dirs = self.distribution.include_dirs or [] |
---|
69 | n/a | elif isinstance(self.include_dirs, str): |
---|
70 | n/a | self.include_dirs = self.include_dirs.split(os.pathsep) |
---|
71 | n/a | |
---|
72 | n/a | if self.libraries is None: |
---|
73 | n/a | self.libraries = [] |
---|
74 | n/a | elif isinstance(self.libraries, str): |
---|
75 | n/a | self.libraries = [self.libraries] |
---|
76 | n/a | |
---|
77 | n/a | if self.library_dirs is None: |
---|
78 | n/a | self.library_dirs = [] |
---|
79 | n/a | elif isinstance(self.library_dirs, str): |
---|
80 | n/a | self.library_dirs = self.library_dirs.split(os.pathsep) |
---|
81 | n/a | |
---|
82 | n/a | def run(self): |
---|
83 | n/a | pass |
---|
84 | n/a | |
---|
85 | n/a | # Utility methods for actual "config" commands. The interfaces are |
---|
86 | n/a | # loosely based on Autoconf macros of similar names. Sub-classes |
---|
87 | n/a | # may use these freely. |
---|
88 | n/a | |
---|
89 | n/a | def _check_compiler(self): |
---|
90 | n/a | """Check that 'self.compiler' really is a CCompiler object; |
---|
91 | n/a | if not, make it one. |
---|
92 | n/a | """ |
---|
93 | n/a | # We do this late, and only on-demand, because this is an expensive |
---|
94 | n/a | # import. |
---|
95 | n/a | from distutils.ccompiler import CCompiler, new_compiler |
---|
96 | n/a | if not isinstance(self.compiler, CCompiler): |
---|
97 | n/a | self.compiler = new_compiler(compiler=self.compiler, |
---|
98 | n/a | dry_run=self.dry_run, force=1) |
---|
99 | n/a | customize_compiler(self.compiler) |
---|
100 | n/a | if self.include_dirs: |
---|
101 | n/a | self.compiler.set_include_dirs(self.include_dirs) |
---|
102 | n/a | if self.libraries: |
---|
103 | n/a | self.compiler.set_libraries(self.libraries) |
---|
104 | n/a | if self.library_dirs: |
---|
105 | n/a | self.compiler.set_library_dirs(self.library_dirs) |
---|
106 | n/a | |
---|
107 | n/a | def _gen_temp_sourcefile(self, body, headers, lang): |
---|
108 | n/a | filename = "_configtest" + LANG_EXT[lang] |
---|
109 | n/a | file = open(filename, "w") |
---|
110 | n/a | if headers: |
---|
111 | n/a | for header in headers: |
---|
112 | n/a | file.write("#include <%s>\n" % header) |
---|
113 | n/a | file.write("\n") |
---|
114 | n/a | file.write(body) |
---|
115 | n/a | if body[-1] != "\n": |
---|
116 | n/a | file.write("\n") |
---|
117 | n/a | file.close() |
---|
118 | n/a | return filename |
---|
119 | n/a | |
---|
120 | n/a | def _preprocess(self, body, headers, include_dirs, lang): |
---|
121 | n/a | src = self._gen_temp_sourcefile(body, headers, lang) |
---|
122 | n/a | out = "_configtest.i" |
---|
123 | n/a | self.temp_files.extend([src, out]) |
---|
124 | n/a | self.compiler.preprocess(src, out, include_dirs=include_dirs) |
---|
125 | n/a | return (src, out) |
---|
126 | n/a | |
---|
127 | n/a | def _compile(self, body, headers, include_dirs, lang): |
---|
128 | n/a | src = self._gen_temp_sourcefile(body, headers, lang) |
---|
129 | n/a | if self.dump_source: |
---|
130 | n/a | dump_file(src, "compiling '%s':" % src) |
---|
131 | n/a | (obj,) = self.compiler.object_filenames([src]) |
---|
132 | n/a | self.temp_files.extend([src, obj]) |
---|
133 | n/a | self.compiler.compile([src], include_dirs=include_dirs) |
---|
134 | n/a | return (src, obj) |
---|
135 | n/a | |
---|
136 | n/a | def _link(self, body, headers, include_dirs, libraries, library_dirs, |
---|
137 | n/a | lang): |
---|
138 | n/a | (src, obj) = self._compile(body, headers, include_dirs, lang) |
---|
139 | n/a | prog = os.path.splitext(os.path.basename(src))[0] |
---|
140 | n/a | self.compiler.link_executable([obj], prog, |
---|
141 | n/a | libraries=libraries, |
---|
142 | n/a | library_dirs=library_dirs, |
---|
143 | n/a | target_lang=lang) |
---|
144 | n/a | |
---|
145 | n/a | if self.compiler.exe_extension is not None: |
---|
146 | n/a | prog = prog + self.compiler.exe_extension |
---|
147 | n/a | self.temp_files.append(prog) |
---|
148 | n/a | |
---|
149 | n/a | return (src, obj, prog) |
---|
150 | n/a | |
---|
151 | n/a | def _clean(self, *filenames): |
---|
152 | n/a | if not filenames: |
---|
153 | n/a | filenames = self.temp_files |
---|
154 | n/a | self.temp_files = [] |
---|
155 | n/a | log.info("removing: %s", ' '.join(filenames)) |
---|
156 | n/a | for filename in filenames: |
---|
157 | n/a | try: |
---|
158 | n/a | os.remove(filename) |
---|
159 | n/a | except OSError: |
---|
160 | n/a | pass |
---|
161 | n/a | |
---|
162 | n/a | |
---|
163 | n/a | # XXX these ignore the dry-run flag: what to do, what to do? even if |
---|
164 | n/a | # you want a dry-run build, you still need some sort of configuration |
---|
165 | n/a | # info. My inclination is to make it up to the real config command to |
---|
166 | n/a | # consult 'dry_run', and assume a default (minimal) configuration if |
---|
167 | n/a | # true. The problem with trying to do it here is that you'd have to |
---|
168 | n/a | # return either true or false from all the 'try' methods, neither of |
---|
169 | n/a | # which is correct. |
---|
170 | n/a | |
---|
171 | n/a | # XXX need access to the header search path and maybe default macros. |
---|
172 | n/a | |
---|
173 | n/a | def try_cpp(self, body=None, headers=None, include_dirs=None, lang="c"): |
---|
174 | n/a | """Construct a source file from 'body' (a string containing lines |
---|
175 | n/a | of C/C++ code) and 'headers' (a list of header files to include) |
---|
176 | n/a | and run it through the preprocessor. Return true if the |
---|
177 | n/a | preprocessor succeeded, false if there were any errors. |
---|
178 | n/a | ('body' probably isn't of much use, but what the heck.) |
---|
179 | n/a | """ |
---|
180 | n/a | from distutils.ccompiler import CompileError |
---|
181 | n/a | self._check_compiler() |
---|
182 | n/a | ok = True |
---|
183 | n/a | try: |
---|
184 | n/a | self._preprocess(body, headers, include_dirs, lang) |
---|
185 | n/a | except CompileError: |
---|
186 | n/a | ok = False |
---|
187 | n/a | |
---|
188 | n/a | self._clean() |
---|
189 | n/a | return ok |
---|
190 | n/a | |
---|
191 | n/a | def search_cpp(self, pattern, body=None, headers=None, include_dirs=None, |
---|
192 | n/a | lang="c"): |
---|
193 | n/a | """Construct a source file (just like 'try_cpp()'), run it through |
---|
194 | n/a | the preprocessor, and return true if any line of the output matches |
---|
195 | n/a | 'pattern'. 'pattern' should either be a compiled regex object or a |
---|
196 | n/a | string containing a regex. If both 'body' and 'headers' are None, |
---|
197 | n/a | preprocesses an empty file -- which can be useful to determine the |
---|
198 | n/a | symbols the preprocessor and compiler set by default. |
---|
199 | n/a | """ |
---|
200 | n/a | self._check_compiler() |
---|
201 | n/a | src, out = self._preprocess(body, headers, include_dirs, lang) |
---|
202 | n/a | |
---|
203 | n/a | if isinstance(pattern, str): |
---|
204 | n/a | pattern = re.compile(pattern) |
---|
205 | n/a | |
---|
206 | n/a | file = open(out) |
---|
207 | n/a | match = False |
---|
208 | n/a | while True: |
---|
209 | n/a | line = file.readline() |
---|
210 | n/a | if line == '': |
---|
211 | n/a | break |
---|
212 | n/a | if pattern.search(line): |
---|
213 | n/a | match = True |
---|
214 | n/a | break |
---|
215 | n/a | |
---|
216 | n/a | file.close() |
---|
217 | n/a | self._clean() |
---|
218 | n/a | return match |
---|
219 | n/a | |
---|
220 | n/a | def try_compile(self, body, headers=None, include_dirs=None, lang="c"): |
---|
221 | n/a | """Try to compile a source file built from 'body' and 'headers'. |
---|
222 | n/a | Return true on success, false otherwise. |
---|
223 | n/a | """ |
---|
224 | n/a | from distutils.ccompiler import CompileError |
---|
225 | n/a | self._check_compiler() |
---|
226 | n/a | try: |
---|
227 | n/a | self._compile(body, headers, include_dirs, lang) |
---|
228 | n/a | ok = True |
---|
229 | n/a | except CompileError: |
---|
230 | n/a | ok = False |
---|
231 | n/a | |
---|
232 | n/a | log.info(ok and "success!" or "failure.") |
---|
233 | n/a | self._clean() |
---|
234 | n/a | return ok |
---|
235 | n/a | |
---|
236 | n/a | def try_link(self, body, headers=None, include_dirs=None, libraries=None, |
---|
237 | n/a | library_dirs=None, lang="c"): |
---|
238 | n/a | """Try to compile and link a source file, built from 'body' and |
---|
239 | n/a | 'headers', to executable form. Return true on success, false |
---|
240 | n/a | otherwise. |
---|
241 | n/a | """ |
---|
242 | n/a | from distutils.ccompiler import CompileError, LinkError |
---|
243 | n/a | self._check_compiler() |
---|
244 | n/a | try: |
---|
245 | n/a | self._link(body, headers, include_dirs, |
---|
246 | n/a | libraries, library_dirs, lang) |
---|
247 | n/a | ok = True |
---|
248 | n/a | except (CompileError, LinkError): |
---|
249 | n/a | ok = False |
---|
250 | n/a | |
---|
251 | n/a | log.info(ok and "success!" or "failure.") |
---|
252 | n/a | self._clean() |
---|
253 | n/a | return ok |
---|
254 | n/a | |
---|
255 | n/a | def try_run(self, body, headers=None, include_dirs=None, libraries=None, |
---|
256 | n/a | library_dirs=None, lang="c"): |
---|
257 | n/a | """Try to compile, link to an executable, and run a program |
---|
258 | n/a | built from 'body' and 'headers'. Return true on success, false |
---|
259 | n/a | otherwise. |
---|
260 | n/a | """ |
---|
261 | n/a | from distutils.ccompiler import CompileError, LinkError |
---|
262 | n/a | self._check_compiler() |
---|
263 | n/a | try: |
---|
264 | n/a | src, obj, exe = self._link(body, headers, include_dirs, |
---|
265 | n/a | libraries, library_dirs, lang) |
---|
266 | n/a | self.spawn([exe]) |
---|
267 | n/a | ok = True |
---|
268 | n/a | except (CompileError, LinkError, DistutilsExecError): |
---|
269 | n/a | ok = False |
---|
270 | n/a | |
---|
271 | n/a | log.info(ok and "success!" or "failure.") |
---|
272 | n/a | self._clean() |
---|
273 | n/a | return ok |
---|
274 | n/a | |
---|
275 | n/a | |
---|
276 | n/a | # -- High-level methods -------------------------------------------- |
---|
277 | n/a | # (these are the ones that are actually likely to be useful |
---|
278 | n/a | # when implementing a real-world config command!) |
---|
279 | n/a | |
---|
280 | n/a | def check_func(self, func, headers=None, include_dirs=None, |
---|
281 | n/a | libraries=None, library_dirs=None, decl=0, call=0): |
---|
282 | n/a | """Determine if function 'func' is available by constructing a |
---|
283 | n/a | source file that refers to 'func', and compiles and links it. |
---|
284 | n/a | If everything succeeds, returns true; otherwise returns false. |
---|
285 | n/a | |
---|
286 | n/a | The constructed source file starts out by including the header |
---|
287 | n/a | files listed in 'headers'. If 'decl' is true, it then declares |
---|
288 | n/a | 'func' (as "int func()"); you probably shouldn't supply 'headers' |
---|
289 | n/a | and set 'decl' true in the same call, or you might get errors about |
---|
290 | n/a | a conflicting declarations for 'func'. Finally, the constructed |
---|
291 | n/a | 'main()' function either references 'func' or (if 'call' is true) |
---|
292 | n/a | calls it. 'libraries' and 'library_dirs' are used when |
---|
293 | n/a | linking. |
---|
294 | n/a | """ |
---|
295 | n/a | self._check_compiler() |
---|
296 | n/a | body = [] |
---|
297 | n/a | if decl: |
---|
298 | n/a | body.append("int %s ();" % func) |
---|
299 | n/a | body.append("int main () {") |
---|
300 | n/a | if call: |
---|
301 | n/a | body.append(" %s();" % func) |
---|
302 | n/a | else: |
---|
303 | n/a | body.append(" %s;" % func) |
---|
304 | n/a | body.append("}") |
---|
305 | n/a | body = "\n".join(body) + "\n" |
---|
306 | n/a | |
---|
307 | n/a | return self.try_link(body, headers, include_dirs, |
---|
308 | n/a | libraries, library_dirs) |
---|
309 | n/a | |
---|
310 | n/a | def check_lib(self, library, library_dirs=None, headers=None, |
---|
311 | n/a | include_dirs=None, other_libraries=[]): |
---|
312 | n/a | """Determine if 'library' is available to be linked against, |
---|
313 | n/a | without actually checking that any particular symbols are provided |
---|
314 | n/a | by it. 'headers' will be used in constructing the source file to |
---|
315 | n/a | be compiled, but the only effect of this is to check if all the |
---|
316 | n/a | header files listed are available. Any libraries listed in |
---|
317 | n/a | 'other_libraries' will be included in the link, in case 'library' |
---|
318 | n/a | has symbols that depend on other libraries. |
---|
319 | n/a | """ |
---|
320 | n/a | self._check_compiler() |
---|
321 | n/a | return self.try_link("int main (void) { }", headers, include_dirs, |
---|
322 | n/a | [library] + other_libraries, library_dirs) |
---|
323 | n/a | |
---|
324 | n/a | def check_header(self, header, include_dirs=None, library_dirs=None, |
---|
325 | n/a | lang="c"): |
---|
326 | n/a | """Determine if the system header file named by 'header_file' |
---|
327 | n/a | exists and can be found by the preprocessor; return true if so, |
---|
328 | n/a | false otherwise. |
---|
329 | n/a | """ |
---|
330 | n/a | return self.try_cpp(body="/* No body */", headers=[header], |
---|
331 | n/a | include_dirs=include_dirs) |
---|
332 | n/a | |
---|
333 | n/a | |
---|
334 | n/a | def dump_file(filename, head=None): |
---|
335 | n/a | """Dumps a file content into log.info. |
---|
336 | n/a | |
---|
337 | n/a | If head is not None, will be dumped before the file content. |
---|
338 | n/a | """ |
---|
339 | n/a | if head is None: |
---|
340 | n/a | log.info('%s', filename) |
---|
341 | n/a | else: |
---|
342 | n/a | log.info(head) |
---|
343 | n/a | file = open(filename) |
---|
344 | n/a | try: |
---|
345 | n/a | log.info(file.read()) |
---|
346 | n/a | finally: |
---|
347 | n/a | file.close() |
---|