Updates to PR 4374 from mwichmann to correct config file hash changes
[scons.git] / SCons / SConf.py
blobe522c8bb7af431f709703987bd4b3f7dd90ba462
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """Autoconf-like configuration support.
26 In other words, SConf allows to run tests on the build machine to detect
27 capabilities of system and do some things based on result: generate config
28 files, header files for C/C++, update variables in environment.
30 Tests on the build system can detect if compiler sees header files, if
31 libraries are installed, if some command line options are supported etc.
32 """
34 import SCons.compat
36 import atexit
37 import io
38 import os
39 import re
40 import sys
41 import traceback
42 from typing import Tuple
44 import SCons.Action
45 import SCons.Builder
46 import SCons.Errors
47 import SCons.Taskmaster.Job
48 import SCons.Node.FS
49 import SCons.Taskmaster
50 import SCons.Util
51 import SCons.Warnings
52 import SCons.Conftest
54 from SCons.Debug import Trace
55 from collections import defaultdict
57 # Turn off the Conftest error logging
58 SCons.Conftest.LogInputFiles = 0
59 SCons.Conftest.LogErrorMessages = 0
61 # Set
62 build_type = None
63 build_types = ['clean', 'help']
65 def SetBuildType(buildtype) -> None:
66 global build_type
67 build_type = buildtype
69 # to be set, if we are in dry-run mode
70 dryrun = 0
72 AUTO=0 # use SCons dependency scanning for up-to-date checks
73 FORCE=1 # force all tests to be rebuilt
74 CACHE=2 # force all tests to be taken from cache (raise an error, if necessary)
75 cache_mode = AUTO
77 def _set_conftest_node(node) -> None:
78 node.attributes.conftest_node = 1
80 def SetCacheMode(mode):
81 """Set the Configure cache mode. mode must be one of "auto", "force",
82 or "cache"."""
83 global cache_mode
84 if mode == "auto":
85 cache_mode = AUTO
86 elif mode == "force":
87 cache_mode = FORCE
88 elif mode == "cache":
89 cache_mode = CACHE
90 else:
91 raise ValueError("SCons.SConf.SetCacheMode: Unknown mode " + mode)
93 progress_display = SCons.Util.display # will be overwritten by SCons.Script
94 def SetProgressDisplay(display) -> None:
95 """Set the progress display to use (called from SCons.Script)"""
96 global progress_display
97 progress_display = display
99 SConfFS = None
101 _ac_build_counter = defaultdict(int)
102 _ac_config_logs = {} # all config.log files created in this build
103 _ac_config_hs = {} # all config.h files created in this build
104 sconf_global = None # current sconf object
106 def _createConfigH(target, source, env) -> None:
107 t = open(str(target[0]), "w")
108 defname = re.sub('[^A-Za-z0-9_]', '_', str(target[0]).upper())
109 t.write("""#ifndef %(DEFNAME)s_SEEN
110 #define %(DEFNAME)s_SEEN
112 """ % {'DEFNAME' : defname})
113 t.write(source[0].get_contents().decode())
114 t.write("""
115 #endif /* %(DEFNAME)s_SEEN */
116 """ % {'DEFNAME' : defname})
117 t.close()
119 def _stringConfigH(target, source, env):
120 return "scons: Configure: creating " + str(target[0])
123 def NeedConfigHBuilder() -> bool:
124 if len(_ac_config_hs) == 0:
125 return False
126 else:
127 return True
129 def CreateConfigHBuilder(env) -> None:
130 """Called if necessary just before the building targets phase begins."""
131 action = SCons.Action.Action(_createConfigH,
132 _stringConfigH)
133 sconfigHBld = SCons.Builder.Builder(action=action)
134 env.Append( BUILDERS={'SConfigHBuilder':sconfigHBld} )
135 for k, v in _ac_config_hs.items():
136 env.SConfigHBuilder(k, env.Value(v))
139 class SConfWarning(SCons.Warnings.SConsWarning):
140 pass
141 SCons.Warnings.enableWarningClass(SConfWarning)
143 # some error definitions
144 class SConfError(SCons.Errors.UserError):
145 def __init__(self,msg) -> None:
146 super().__init__(msg)
148 class ConfigureDryRunError(SConfError):
149 """Raised when a file or directory needs to be updated during a Configure
150 process, but the user requested a dry-run"""
151 def __init__(self,target) -> None:
152 if not isinstance(target, SCons.Node.FS.File):
153 msg = 'Cannot create configure directory "%s" within a dry-run.' % str(target)
154 else:
155 msg = 'Cannot update configure test "%s" within a dry-run.' % str(target)
156 super().__init__(msg)
158 class ConfigureCacheError(SConfError):
159 """Raised when a use explicitely requested the cache feature, but the test
160 is run the first time."""
161 def __init__(self,target) -> None:
162 super().__init__('"%s" is not yet built and cache is forced.' % str(target))
165 # define actions for building text files
166 def _createSource(target, source, env) -> None:
167 fd = open(str(target[0]), "w")
168 fd.write(source[0].get_contents().decode())
169 fd.close()
172 def _stringSource( target, source, env ):
173 return (str(target[0]) + ' <-\n |' +
174 source[0].get_contents().decode().replace( '\n', "\n |" ) )
176 class SConfBuildInfo(SCons.Node.FS.FileBuildInfo):
178 Special build info for targets of configure tests. Additional members
179 are result (did the builder succeed last time?) and string, which
180 contains messages of the original build phase.
182 __slots__ = ('result', 'string')
184 def __init__(self) -> None:
185 self.result = None # -> 0/None -> no error, != 0 error
186 self.string = None # the stdout / stderr output when building the target
188 def set_build_result(self, result, string) -> None:
189 self.result = result
190 self.string = string
193 class Streamer:
195 'Sniffer' for a file-like writable object. Similar to the unix tool tee.
197 def __init__(self, orig) -> None:
198 self.orig = orig
199 self.s = io.StringIO()
201 def write(self, str) -> None:
202 if self.orig:
203 self.orig.write(str)
204 try:
205 self.s.write(str)
206 except TypeError as e:
207 # "unicode argument expected" bug in IOStream (python 2.x)
208 self.s.write(str.decode())
210 def writelines(self, lines) -> None:
211 for l in lines:
212 self.write(l + '\n')
214 def getvalue(self):
216 Return everything written to orig since the Streamer was created.
218 return self.s.getvalue()
220 def flush(self) -> None:
221 if self.orig:
222 self.orig.flush()
223 self.s.flush()
226 class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
228 This is almost the same as SCons.Script.BuildTask. Handles SConfErrors
229 correctly and knows about the current cache_mode.
231 non_sconf_nodes = set()
233 def display(self, message) -> None:
234 if sconf_global.logstream:
235 sconf_global.logstream.write("scons: Configure: " + message + "\n")
237 def display_cached_string(self, bi) -> None:
239 Logs the original builder messages, given the SConfBuildInfo instance
242 if not isinstance(bi, SConfBuildInfo):
243 SCons.Warnings.warn(
244 SConfWarning,
245 "The stored build information has an unexpected class: %s" % bi.__class__
247 else:
248 self.display("The original builder output was:\n" +
249 (" |" + str(bi.string)).replace("\n", "\n |"))
251 def failed(self):
252 # check, if the reason was a ConfigureDryRunError or a
253 # ConfigureCacheError and if yes, reraise the exception
254 exc_type = self.exc_info()[0]
255 if issubclass(exc_type, SConfError):
256 # TODO pylint E0704: bare raise not inside except
257 raise
258 elif issubclass(exc_type, SCons.Errors.BuildError):
259 # we ignore Build Errors (occurs, when a test doesn't pass)
260 # Clear the exception to prevent the contained traceback
261 # to build a reference cycle.
262 self.exc_clear()
263 else:
264 self.display('Caught exception while building "%s":\n' %
265 self.targets[0])
266 sys.excepthook(*self.exc_info())
267 return SCons.Taskmaster.Task.failed(self)
269 def collect_node_states(self) -> Tuple[bool, bool, bool]:
270 # returns (is_up_to_date, cached_error, cachable)
271 # where is_up_to_date is True if the node(s) are up_to_date
272 # cached_error is True if the node(s) are up_to_date, but the
273 # build will fail
274 # cachable is False if some nodes are not in our cache
275 T = 0
276 changed = False
277 cached_error = False
278 cachable = True
279 for t in self.targets:
280 if T: Trace('%s' % t)
281 bi = t.get_stored_info().binfo
282 if isinstance(bi, SConfBuildInfo):
283 if T: Trace(': SConfBuildInfo')
284 if cache_mode == CACHE:
285 t.set_state(SCons.Node.up_to_date)
286 if T: Trace(': set_state(up_to-date)')
287 else:
288 if T: Trace(': get_state() %s' % t.get_state())
289 if T: Trace(': changed() %s' % t.changed())
290 if t.get_state() != SCons.Node.up_to_date and t.changed():
291 changed = True
292 if T: Trace(': changed %s' % changed)
293 cached_error = cached_error or bi.result
294 else:
295 if T: Trace(': else')
296 # the node hasn't been built in a SConf context or doesn't
297 # exist
298 cachable = False
299 changed = ( t.get_state() != SCons.Node.up_to_date )
300 if T: Trace(': changed %s' % changed)
301 if T: Trace('\n')
302 return (not changed, cached_error, cachable)
304 def execute(self):
305 if not self.targets[0].has_builder():
306 return
308 sconf = sconf_global
310 is_up_to_date, cached_error, cachable = self.collect_node_states()
312 if cache_mode == CACHE and not cachable:
313 raise ConfigureCacheError(self.targets[0])
314 elif cache_mode == FORCE:
315 is_up_to_date = False
317 if cached_error and is_up_to_date:
318 self.display("Building \"%s\" failed in a previous run and all "
319 "its sources are up to date." % str(self.targets[0]))
320 binfo = self.targets[0].get_stored_info().binfo
321 self.display_cached_string(binfo)
322 raise SCons.Errors.BuildError # will be 'caught' in self.failed
323 elif is_up_to_date:
324 self.display("\"%s\" is up to date." % str(self.targets[0]))
325 binfo = self.targets[0].get_stored_info().binfo
326 self.display_cached_string(binfo)
327 elif dryrun:
328 raise ConfigureDryRunError(self.targets[0])
329 else:
330 # note stdout and stderr are the same here
331 s = sys.stdout = sys.stderr = Streamer(sys.stdout)
332 try:
333 env = self.targets[0].get_build_env()
334 env['PSTDOUT'] = env['PSTDERR'] = s
335 try:
336 sconf.cached = 0
337 self.targets[0].build()
338 finally:
339 sys.stdout = sys.stderr = env['PSTDOUT'] = \
340 env['PSTDERR'] = sconf.logstream
341 except KeyboardInterrupt:
342 raise
343 except SystemExit:
344 exc_value = sys.exc_info()[1]
345 raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code)
346 except Exception as e:
347 for t in self.targets:
348 binfo = SConfBuildInfo()
349 binfo.merge(t.get_binfo())
350 binfo.set_build_result(1, s.getvalue())
351 sconsign_entry = SCons.SConsign.SConsignEntry()
352 sconsign_entry.binfo = binfo
353 #sconsign_entry.ninfo = self.get_ninfo()
354 # We'd like to do this as follows:
355 # t.store_info(binfo)
356 # However, we need to store it as an SConfBuildInfo
357 # object, and store_info() will turn it into a
358 # regular FileNodeInfo if the target is itself a
359 # regular File.
360 sconsign = t.dir.sconsign()
361 sconsign.set_entry(t.name, sconsign_entry)
362 sconsign.merge()
363 raise e
364 else:
365 for t in self.targets:
366 binfo = SConfBuildInfo()
367 binfo.merge(t.get_binfo())
368 binfo.set_build_result(0, s.getvalue())
369 sconsign_entry = SCons.SConsign.SConsignEntry()
370 sconsign_entry.binfo = binfo
371 #sconsign_entry.ninfo = self.get_ninfo()
372 # We'd like to do this as follows:
373 # t.store_info(binfo)
374 # However, we need to store it as an SConfBuildInfo
375 # object, and store_info() will turn it into a
376 # regular FileNodeInfo if the target is itself a
377 # regular File.
378 sconsign = t.dir.sconsign()
379 sconsign.set_entry(t.name, sconsign_entry)
380 sconsign.merge()
382 def make_ready_current(self) -> None:
383 # We're overriding make_ready_current() call to add to the list
384 # of nodes used by this task, filtering out any nodes created
385 # by the checker for it's own purpose.
386 self.non_sconf_nodes.update([t for t in self.targets if not t.is_conftest()])
387 super().make_ready_current()
388 make_ready = make_ready_current
390 def postprocess(self) -> None:
391 # We're done executing this task, so now we'll go through all the
392 # nodes used by this task which aren't nodes created for
393 # Configure checkers, but rather are existing or built files
394 # and reset their node info.
395 # If we do not reset their node info, any changes in these
396 # nodes will not trigger builds in the normal build process
397 for node in self.non_sconf_nodes:
398 node.ninfo = node.new_ninfo()
399 super().postprocess()
401 class SConfBase:
402 """This is simply a class to represent a configure context. After
403 creating a SConf object, you can call any tests. After finished with your
404 tests, be sure to call the Finish() method, which returns the modified
405 environment.
406 Some words about caching: In most cases, it is not necessary to cache
407 Test results explicitly. Instead, we use the scons dependency checking
408 mechanism. For example, if one wants to compile a test program
409 (SConf.TryLink), the compiler is only called, if the program dependencies
410 have changed. However, if the program could not be compiled in a former
411 SConf run, we need to explicitly cache this error.
414 def __init__(self, env, custom_tests = {}, conf_dir: str='$CONFIGUREDIR',
415 log_file: str='$CONFIGURELOG', config_h = None, _depth: int = 0) -> None:
416 """Constructor. Pass additional tests in the custom_tests-dictionary,
417 e.g. custom_tests={'CheckPrivate':MyPrivateTest}, where MyPrivateTest
418 defines a custom test.
419 Note also the conf_dir and log_file arguments (you may want to
420 build tests in the VariantDir, not in the SourceDir)
422 global SConfFS
424 # Now create isolated override so setting source_decider doesn't affect parent Environment
425 if cache_mode == FORCE:
426 self.original_env = env
427 self.env = env.Clone()
429 # Set up the Decider() to force rebuilds by saying
430 # that every source has changed. Note that we still
431 # call the environment's underlying source decider so
432 # that the correct .sconsign info will get calculated
433 # and keep the build state consistent.
434 def force_build(dependency, target, prev_ni,
435 repo_node=None,
436 env_decider=env.decide_source) -> bool:
437 try:
438 env_decider(dependency, target, prev_ni, repo_node)
439 except Exception as e:
440 raise e
441 return True
443 if self.env.decide_source.__code__ is not force_build.__code__:
444 self.env.Decider(force_build)
446 else:
447 self.env = env
449 # print("Override env:%s"%env)
451 if not SConfFS:
452 SConfFS = SCons.Node.FS.default_fs or \
453 SCons.Node.FS.FS(env.fs.pathTop)
454 if sconf_global is not None:
455 raise SCons.Errors.UserError("""Configure() called while another Configure() exists.
456 Please call .Finish() before creating and second Configure() context""")
458 if log_file is not None:
459 log_file = SConfFS.File(env.subst(log_file))
460 self.logfile = log_file
461 self.logstream = None
462 self.lastTarget = None
463 self.depth = _depth
464 self.cached = 0 # will be set, if all test results are cached
466 # add default tests
467 default_tests = {
468 'CheckCC' : CheckCC,
469 'CheckCXX' : CheckCXX,
470 'CheckSHCC' : CheckSHCC,
471 'CheckSHCXX' : CheckSHCXX,
472 'CheckFunc' : CheckFunc,
473 'CheckType' : CheckType,
474 'CheckTypeSize' : CheckTypeSize,
475 'CheckMember' : CheckMember,
476 'CheckDeclaration' : CheckDeclaration,
477 'CheckHeader' : CheckHeader,
478 'CheckCHeader' : CheckCHeader,
479 'CheckCXXHeader' : CheckCXXHeader,
480 'CheckLib' : CheckLib,
481 'CheckLibWithHeader' : CheckLibWithHeader,
482 'CheckProg' : CheckProg,
484 self.AddTests(default_tests)
485 self.AddTests(custom_tests)
486 self.confdir = SConfFS.Dir(env.subst(conf_dir))
487 if config_h is not None:
488 config_h = SConfFS.File(config_h)
489 self.config_h = config_h
490 self._startup()
492 def Finish(self):
493 """Call this method after finished with your tests:
494 env = sconf.Finish()
496 self._shutdown()
498 return self.env
500 def Define(self, name, value = None, comment = None) -> None:
502 Define a pre processor symbol name, with the optional given value in the
503 current config header.
505 If value is None (default), then #define name is written. If value is not
506 none, then #define name value is written.
508 comment is a string which will be put as a C comment in the header, to explain the meaning of the value
509 (appropriate C comments will be added automatically).
511 lines = []
512 if comment:
513 comment_str = "/* %s */" % comment
514 lines.append(comment_str)
516 if value is not None:
517 define_str = "#define %s %s" % (name, value)
518 else:
519 define_str = "#define %s" % name
520 lines.append(define_str)
521 lines.append('')
523 self.config_h_text = self.config_h_text + '\n'.join(lines)
525 def BuildNodes(self, nodes):
527 Tries to build the given nodes immediately. Returns 1 on success,
528 0 on error.
530 if self.logstream is not None:
531 # override stdout / stderr to write in log file
532 oldStdout = sys.stdout
533 sys.stdout = self.logstream
534 oldStderr = sys.stderr
535 sys.stderr = self.logstream
537 # the engine assumes the current path is the SConstruct directory ...
538 old_fs_dir = SConfFS.getcwd()
539 old_os_dir = os.getcwd()
540 SConfFS.chdir(SConfFS.Top, change_os_dir=True)
542 # Because we take responsibility here for writing out our
543 # own .sconsign info (see SConfBuildTask.execute(), above),
544 # we override the store_info() method with a null place-holder
545 # so we really control how it gets written.
546 for n in nodes:
547 _set_conftest_node(n)
548 n.store_info = 0
549 if not hasattr(n, 'attributes'):
550 n.attributes = SCons.Node.Node.Attrs()
551 n.attributes.keep_targetinfo = 1
553 if True:
554 # Some checkers have intermediate files (for example anything that compiles a c file into a program to run
555 # Those files need to be set to not release their target info, otherwise taskmaster will throw a
556 # Nonetype not callable
557 for c in n.children(scan=False):
558 # Keep debug code here.
559 # print("Checking [%s] for builders and then setting keep_targetinfo"%c)
560 _set_conftest_node(c)
561 if c.has_builder():
562 n.store_info = 0
563 if not hasattr(c, 'attributes'):
564 c.attributes = SCons.Node.Node.Attrs()
565 c.attributes.keep_targetinfo = 1
566 # pass
568 ret = 1
570 try:
571 # ToDo: use user options for calc
572 save_max_drift = SConfFS.get_max_drift()
573 SConfFS.set_max_drift(0)
574 tm = SCons.Taskmaster.Taskmaster(nodes, SConfBuildTask)
575 # we don't want to build tests in parallel
576 jobs = SCons.Taskmaster.Job.Jobs(1, tm)
577 jobs.run()
578 for n in nodes:
579 state = n.get_state()
580 if (state != SCons.Node.executed and
581 state != SCons.Node.up_to_date):
582 # the node could not be built. we return 0 in this case
583 ret = 0
584 finally:
585 SConfFS.set_max_drift(save_max_drift)
586 os.chdir(old_os_dir)
587 SConfFS.chdir(old_fs_dir, change_os_dir=False)
588 if self.logstream is not None:
589 # restore stdout / stderr
590 sys.stdout = oldStdout
591 sys.stderr = oldStderr
592 return ret
594 def pspawn_wrapper(self, sh, escape, cmd, args, env):
595 """Wrapper function for handling piped spawns.
597 This looks to the calling interface (in Action.py) like a "normal"
598 spawn, but associates the call with the PSPAWN variable from
599 the construction environment and with the streams to which we
600 want the output logged. This gets slid into the construction
601 environment as the SPAWN variable so Action.py doesn't have to
602 know or care whether it's spawning a piped command or not.
604 return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream)
606 def TryBuild(self, builder, text=None, extension: str=""):
607 """Low level TryBuild implementation. Normally you don't need to
608 call that - you can use TryCompile / TryLink / TryRun instead
610 global _ac_build_counter
612 # Make sure we have a PSPAWN value, and save the current
613 # SPAWN value.
614 try:
615 self.pspawn = self.env['PSPAWN']
616 except KeyError:
617 raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
618 try:
619 save_spawn = self.env['SPAWN']
620 except KeyError:
621 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
623 nodesToBeBuilt = []
624 sourcetext = self.env.Value(text)
625 _set_conftest_node(sourcetext)
626 f = "conftest"
628 if text is not None:
629 textSig = SCons.Util.hash_signature(sourcetext)
630 textSigCounter = str(_ac_build_counter[textSig])
631 _ac_build_counter[textSig] += 1
633 f = "_".join([f, textSig, textSigCounter])
634 textFile = self.confdir.File(f + extension)
635 _set_conftest_node(textFile)
636 textFileNode = self.env.SConfSourceBuilder(target=textFile,
637 source=sourcetext)
638 nodesToBeBuilt.extend(textFileNode)
640 source = textFile
641 target = textFile.File(f + "SConfActionsContentDummyTarget")
642 _set_conftest_node(target)
643 else:
644 source = None
645 target = None
647 action = builder.builder.action.get_contents(target=target, source=[source], env=self.env)
648 actionsig = SCons.Util.hash_signature(action)
649 f = "_".join([f, actionsig])
651 pref = self.env.subst( builder.builder.prefix )
652 suff = self.env.subst( builder.builder.suffix )
653 target = self.confdir.File(pref + f + suff)
654 _set_conftest_node(target)
656 try:
657 # Slide our wrapper into the construction environment as
658 # the SPAWN function.
659 self.env['SPAWN'] = self.pspawn_wrapper
661 nodes = builder(target = target, source = source, SCONF_NODE=True)
662 if not SCons.Util.is_List(nodes):
663 nodes = [nodes]
664 nodesToBeBuilt.extend(nodes)
665 result = self.BuildNodes(nodesToBeBuilt)
667 finally:
668 self.env['SPAWN'] = save_spawn
670 if result:
671 self.lastTarget = nodes[0]
672 else:
673 self.lastTarget = None
675 return result
677 def TryAction(self, action, text = None, extension: str = ""):
678 """Tries to execute the given action with optional source file
679 contents <text> and optional source file extension <extension>,
680 Returns the status (0 : failed, 1 : ok) and the contents of the
681 output file.
683 builder = SCons.Builder.Builder(action=action)
684 self.env.Append( BUILDERS = {'SConfActionBuilder' : builder} )
685 ok = self.TryBuild(self.env.SConfActionBuilder, text, extension)
686 del self.env['BUILDERS']['SConfActionBuilder']
687 if ok:
688 outputStr = self.lastTarget.get_text_contents()
689 return (1, outputStr)
690 return (0, "")
692 def TryCompile( self, text, extension):
693 """Compiles the program given in text to an env.Object, using extension
694 as file extension (e.g. '.c'). Returns 1, if compilation was
695 successful, 0 otherwise. The target is saved in self.lastTarget (for
696 further processing).
698 return self.TryBuild(self.env.Object, text, extension)
700 def TryLink( self, text, extension ):
701 """Compiles the program given in text to an executable env.Program,
702 using extension as file extension (e.g. '.c'). Returns 1, if
703 compilation was successful, 0 otherwise. The target is saved in
704 self.lastTarget (for further processing).
706 return self.TryBuild(self.env.Program, text, extension )
708 def TryRun(self, text, extension ):
709 """Compiles and runs the program given in text, using extension
710 as file extension (e.g. '.c'). Returns (1, outputStr) on success,
711 (0, '') otherwise. The target (a file containing the program's stdout)
712 is saved in self.lastTarget (for further processing).
714 ok = self.TryLink(text, extension)
715 if ok:
716 prog = self.lastTarget
717 pname = prog.get_internal_path()
718 if sys.platform == "win32" and os.sep == "/":
719 # msys might have a Python where os.sep='/' on Windows.
720 # That builds a path in the env.Command below which breaks
721 # if the SHELL used is cmd because 'pname' will always have
722 # an os.sep in it.
723 pname = pname.replace(os.sep, os.altsep)
724 output = self.confdir.File(os.path.basename(pname)+'.out')
725 node = self.env.Command(output, prog, [ [ pname, ">", "${TARGET}"] ])
726 ok = self.BuildNodes(node)
727 if ok:
728 outputStr = SCons.Util.to_str(output.get_contents())
729 return( 1, outputStr)
730 return (0, "")
732 class TestWrapper:
733 """A wrapper around Tests (to ensure sanity)"""
734 def __init__(self, test, sconf) -> None:
735 self.test = test
736 self.sconf = sconf
737 def __call__(self, *args, **kw):
738 if not self.sconf.active:
739 raise SCons.Errors.UserError
740 context = CheckContext(self.sconf)
741 ret = self.test(context, *args, **kw)
742 if self.sconf.config_h is not None:
743 self.sconf.config_h_text = self.sconf.config_h_text + context.config_h
744 context.Result("error: no result")
745 return ret
747 def AddTest(self, test_name, test_instance) -> None:
748 """Adds test_class to this SConf instance. It can be called with
749 self.test_name(...)"""
750 setattr(self, test_name, SConfBase.TestWrapper(test_instance, self))
752 def AddTests(self, tests) -> None:
753 """Adds all the tests given in the tests dictionary to this SConf
754 instance
756 for name in tests.keys():
757 self.AddTest(name, tests[name])
759 def _createDir( self, node ):
760 dirName = str(node)
761 if dryrun:
762 if not os.path.isdir( dirName ):
763 raise ConfigureDryRunError(dirName)
764 else:
765 if not os.path.isdir( dirName ):
766 os.makedirs( dirName )
768 def _startup(self) -> None:
769 """Private method. Set up logstream, and set the environment
770 variables necessary for a piped build
772 global _ac_config_logs
773 global sconf_global
774 global SConfFS
776 self.lastEnvFs = self.env.fs
777 self.env.fs = SConfFS
778 self._createDir(self.confdir)
779 self.confdir.up().add_ignore( [self.confdir] )
781 if self.logfile is not None and not dryrun:
782 # truncate logfile, if SConf.Configure is called for the first time
783 # in a build
784 if self.logfile in _ac_config_logs:
785 log_mode = "a"
786 else:
787 _ac_config_logs[self.logfile] = None
788 log_mode = "w"
789 fp = open(str(self.logfile), log_mode)
791 def conflog_cleanup(logf) -> None:
792 logf.close()
794 atexit.register(conflog_cleanup, fp)
795 self.logstream = SCons.Util.Unbuffered(fp)
796 # logfile may stay in a build directory, so we tell
797 # the build system not to override it with an eventually
798 # existing file with the same name in the source directory
799 self.logfile.dir.add_ignore([self.logfile])
801 tb = traceback.extract_stack()[-3-self.depth]
802 old_fs_dir = SConfFS.getcwd()
803 SConfFS.chdir(SConfFS.Top, change_os_dir=False)
804 self.logstream.write('file %s,line %d:\n\tConfigure(confdir = %s)\n' %
805 (tb[0], tb[1], str(self.confdir)) )
806 SConfFS.chdir(old_fs_dir)
807 else:
808 self.logstream = None
809 # we use a special builder to create source files from TEXT
810 action = SCons.Action.Action(_createSource,
811 _stringSource)
812 sconfSrcBld = SCons.Builder.Builder(action=action)
813 self.env.Append( BUILDERS={'SConfSourceBuilder':sconfSrcBld} )
814 self.config_h_text = _ac_config_hs.get(self.config_h, "")
815 self.active = 1
816 # only one SConf instance should be active at a time ...
817 sconf_global = self
819 def _shutdown(self):
820 """Private method. Reset to non-piped spawn"""
821 global sconf_global, _ac_config_hs
823 if not self.active:
824 raise SCons.Errors.UserError("Finish may be called only once!")
825 if self.logstream is not None and not dryrun:
826 self.logstream.write("\n")
827 self.logstream.close()
828 self.logstream = None
830 # Now reset the decider if we changed it due to --config=force
831 # We saved original Environment passed in and cloned it to isolate
832 # it from being changed.
833 if cache_mode == FORCE:
834 self.env.Decider(self.original_env.decide_source)
836 # remove the SConfSourceBuilder from the environment
837 blds = self.env['BUILDERS']
838 del blds['SConfSourceBuilder']
839 self.env.Replace( BUILDERS=blds )
841 self.active = 0
842 sconf_global = None
843 if self.config_h is not None:
844 _ac_config_hs[self.config_h] = self.config_h_text
845 self.env.fs = self.lastEnvFs
847 class CheckContext:
848 """Provides a context for configure tests. Defines how a test writes to the
849 screen and log file.
851 A typical test is just a callable with an instance of CheckContext as
852 first argument:
854 def CheckCustom(context, ...):
855 context.Message('Checking my weird test ... ')
856 ret = myWeirdTestFunction(...)
857 context.Result(ret)
859 Often, myWeirdTestFunction will be one of
860 context.TryCompile/context.TryLink/context.TryRun. The results of
861 those are cached, for they are only rebuild, if the dependencies have
862 changed.
865 def __init__(self, sconf) -> None:
866 """Constructor. Pass the corresponding SConf instance."""
867 self.sconf = sconf
868 self.did_show_result = 0
870 # for Conftest.py:
871 self.vardict = {}
872 self.havedict = {}
873 self.headerfilename = None
874 self.config_h = "" # config_h text will be stored here
875 # we don't regenerate the config.h file after each test. That means,
876 # that tests won't be able to include the config.h file, and so
877 # they can't do an #ifdef HAVE_XXX_H. This shouldn't be a major
878 # issue, though. If it turns out, that we need to include config.h
879 # in tests, we must ensure, that the dependencies are worked out
880 # correctly. Note that we can't use Conftest.py's support for config.h,
881 # cause we will need to specify a builder for the config.h file ...
883 def Message(self, text) -> None:
884 """Inform about what we are doing right now, e.g.
885 'Checking for SOMETHING ... '
887 self.Display(text)
888 self.sconf.cached = 1
889 self.did_show_result = 0
891 def Result(self, res) -> None:
892 """Inform about the result of the test. If res is not a string, displays
893 'yes' or 'no' depending on whether res is evaluated as true or false.
894 The result is only displayed when self.did_show_result is not set.
896 if isinstance(res, str):
897 text = res
898 elif res:
899 text = "yes"
900 else:
901 text = "no"
903 if self.did_show_result == 0:
904 # Didn't show result yet, do it now.
905 self.Display(text + "\n")
906 self.did_show_result = 1
908 def TryBuild(self, *args, **kw):
909 return self.sconf.TryBuild(*args, **kw)
911 def TryAction(self, *args, **kw):
912 return self.sconf.TryAction(*args, **kw)
914 def TryCompile(self, *args, **kw):
915 return self.sconf.TryCompile(*args, **kw)
917 def TryLink(self, *args, **kw):
918 return self.sconf.TryLink(*args, **kw)
920 def TryRun(self, *args, **kw):
921 return self.sconf.TryRun(*args, **kw)
923 def __getattr__( self, attr ):
924 if attr == 'env':
925 return self.sconf.env
926 elif attr == 'lastTarget':
927 return self.sconf.lastTarget
928 else:
929 raise AttributeError("CheckContext instance has no attribute '%s'" % attr)
931 #### Stuff used by Conftest.py (look there for explanations).
933 def BuildProg(self, text, ext) -> bool:
934 self.sconf.cached = 1
935 # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
936 return not self.TryBuild(self.env.Program, text, ext)
938 def CompileProg(self, text, ext) -> bool:
939 self.sconf.cached = 1
940 # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
941 return not self.TryBuild(self.env.Object, text, ext)
943 def CompileSharedObject(self, text, ext) -> bool:
944 self.sconf.cached = 1
945 # TODO: should use self.vardict for $SHCC, $CPPFLAGS, etc.
946 return not self.TryBuild(self.env.SharedObject, text, ext)
948 def RunProg(self, text, ext):
949 self.sconf.cached = 1
950 # TODO: should use self.vardict for $CC, $CPPFLAGS, etc.
951 st, out = self.TryRun(text, ext)
952 return not st, out
954 def AppendLIBS(self, lib_name_list, unique: bool=False):
955 oldLIBS = self.env.get( 'LIBS', [] )
956 if unique:
957 self.env.AppendUnique(LIBS = lib_name_list)
958 else:
959 self.env.Append(LIBS = lib_name_list)
960 return oldLIBS
962 def PrependLIBS(self, lib_name_list, unique: bool=False):
963 oldLIBS = self.env.get( 'LIBS', [] )
964 if unique:
965 self.env.PrependUnique(LIBS = lib_name_list)
966 else:
967 self.env.Prepend(LIBS = lib_name_list)
968 return oldLIBS
970 def SetLIBS(self, val):
971 oldLIBS = self.env.get( 'LIBS', [] )
972 self.env.Replace(LIBS = val)
973 return oldLIBS
975 def Display(self, msg) -> None:
976 if self.sconf.cached:
977 # We assume that Display is called twice for each test here
978 # once for the Checking for ... message and once for the result.
979 # The self.sconf.cached flag can only be set between those calls
980 msg = "(cached) " + msg
981 self.sconf.cached = 0
982 progress_display(msg, append_newline=0)
983 self.Log("scons: Configure: " + msg + "\n")
985 def Log(self, msg) -> None:
986 if self.sconf.logstream is not None:
987 self.sconf.logstream.write(msg)
989 #### End of stuff used by Conftest.py.
992 def SConf(*args, **kw):
993 if kw.get(build_type, True):
994 kw['_depth'] = kw.get('_depth', 0) + 1
995 for bt in build_types:
996 try:
997 del kw[bt]
998 except KeyError:
999 pass
1000 return SConfBase(*args, **kw)
1001 else:
1002 return SCons.Util.Null()
1005 def CheckFunc(context, function_name, header = None, language = None) -> bool:
1006 res = SCons.Conftest.CheckFunc(context, function_name, header = header, language = language)
1007 context.did_show_result = 1
1008 return not res
1010 def CheckType(context, type_name, includes: str = "", language = None) -> bool:
1011 res = SCons.Conftest.CheckType(context, type_name,
1012 header = includes, language = language)
1013 context.did_show_result = 1
1014 return not res
1016 def CheckTypeSize(context, type_name, includes: str = "", language = None, expect = None):
1017 res = SCons.Conftest.CheckTypeSize(context, type_name,
1018 header = includes, language = language,
1019 expect = expect)
1020 context.did_show_result = 1
1021 return res
1023 def CheckDeclaration(context, declaration, includes: str = "", language = None) -> bool:
1024 res = SCons.Conftest.CheckDeclaration(context, declaration,
1025 includes = includes,
1026 language = language)
1027 context.did_show_result = 1
1028 return not res
1030 def CheckMember(context, aggregate_member, header = None, language = None) -> bool:
1031 '''Returns the status (False : failed, True : ok).'''
1032 res = SCons.Conftest.CheckMember(context, aggregate_member, header=header, language=language)
1033 context.did_show_result = 1
1034 return not res
1037 def createIncludesFromHeaders(headers, leaveLast, include_quotes: str = '""'):
1038 # used by CheckHeader and CheckLibWithHeader to produce C - #include
1039 # statements from the specified header (list)
1040 if not SCons.Util.is_List(headers):
1041 headers = [headers]
1042 l = []
1043 if leaveLast:
1044 lastHeader = headers[-1]
1045 headers = headers[:-1]
1046 else:
1047 lastHeader = None
1048 for s in headers:
1049 l.append("#include %s%s%s\n"
1050 % (include_quotes[0], s, include_quotes[1]))
1051 return ''.join(l), lastHeader
1053 def CheckHeader(context, header, include_quotes: str = '<>', language = None) -> bool:
1055 A test for a C or C++ header file.
1057 prog_prefix, hdr_to_check = \
1058 createIncludesFromHeaders(header, 1, include_quotes)
1059 res = SCons.Conftest.CheckHeader(context, hdr_to_check, prog_prefix,
1060 language = language,
1061 include_quotes = include_quotes)
1062 context.did_show_result = 1
1063 return not res
1065 def CheckCC(context) -> bool:
1066 res = SCons.Conftest.CheckCC(context)
1067 context.did_show_result = 1
1068 return not res
1070 def CheckCXX(context) -> bool:
1071 res = SCons.Conftest.CheckCXX(context)
1072 context.did_show_result = 1
1073 return not res
1075 def CheckSHCC(context) -> bool:
1076 res = SCons.Conftest.CheckSHCC(context)
1077 context.did_show_result = 1
1078 return not res
1080 def CheckSHCXX(context) -> bool:
1081 res = SCons.Conftest.CheckSHCXX(context)
1082 context.did_show_result = 1
1083 return not res
1085 # Bram: Make this function obsolete? CheckHeader() is more generic.
1087 def CheckCHeader(context, header, include_quotes: str = '""'):
1089 A test for a C header file.
1091 return CheckHeader(context, header, include_quotes, language = "C")
1094 # Bram: Make this function obsolete? CheckHeader() is more generic.
1096 def CheckCXXHeader(context, header, include_quotes: str = '""'):
1098 A test for a C++ header file.
1100 return CheckHeader(context, header, include_quotes, language = "C++")
1103 def CheckLib(context, library = None, symbol: str = "main",
1104 header = None, language = None, autoadd: bool=True,
1105 append: bool=True, unique: bool=False) -> bool:
1107 A test for a library. See also CheckLibWithHeader.
1108 Note that library may also be None to test whether the given symbol
1109 compiles without flags.
1112 if not library:
1113 library = [None]
1115 if not SCons.Util.is_List(library):
1116 library = [library]
1118 # ToDo: accept path for the library
1119 res = SCons.Conftest.CheckLib(context, library, symbol, header = header,
1120 language = language, autoadd = autoadd,
1121 append=append, unique=unique)
1122 context.did_show_result = True
1123 return not res
1125 # XXX
1126 # Bram: Can only include one header and can't use #ifdef HAVE_HEADER_H.
1128 def CheckLibWithHeader(context, libs, header, language,
1129 call = None, autoadd: bool=True, append: bool=True, unique: bool=False) -> bool:
1130 # ToDo: accept path for library. Support system header files.
1132 Another (more sophisticated) test for a library.
1133 Checks, if library and header is available for language (may be 'C'
1134 or 'CXX'). Call maybe be a valid expression _with_ a trailing ';'.
1135 As in CheckLib, we support library=None, to test if the call compiles
1136 without extra link flags.
1138 prog_prefix, dummy = createIncludesFromHeaders(header, 0)
1139 if not libs:
1140 libs = [None]
1142 if not SCons.Util.is_List(libs):
1143 libs = [libs]
1145 res = SCons.Conftest.CheckLib(context, libs, None, prog_prefix,
1146 call = call, language = language, autoadd=autoadd,
1147 append=append, unique=unique)
1148 context.did_show_result = 1
1149 return not res
1151 def CheckProg(context, prog_name):
1152 """Simple check if a program exists in the path. Returns the path
1153 for the application, or None if not found.
1155 res = SCons.Conftest.CheckProg(context, prog_name)
1156 context.did_show_result = 1
1157 return res
1159 # Local Variables:
1160 # tab-width:4
1161 # indent-tabs-mode:nil
1162 # End:
1163 # vim: set expandtab tabstop=4 shiftwidth=4: