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