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.
42 from typing
import Tuple
47 import SCons
.Taskmaster
.Job
49 import SCons
.Taskmaster
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
63 build_types
= ['clean', 'help']
65 def SetBuildType(buildtype
) -> None:
67 build_type
= buildtype
69 # to be set, if we are in dry-run mode
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)
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",
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
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())
115 #endif /* %(DEFNAME)s_SEEN */
116 """ % {'DEFNAME' : defname
})
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:
129 def CreateConfigHBuilder(env
) -> None:
130 """Called if necessary just before the building targets phase begins."""
131 action
= SCons
.Action
.Action(_createConfigH
,
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
):
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
)
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())
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:
195 'Sniffer' for a file-like writable object. Similar to the unix tool tee.
197 def __init__(self
, orig
) -> None:
199 self
.s
= io
.StringIO()
201 def write(self
, str) -> None:
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:
216 Return everything written to orig since the Streamer was created.
218 return self
.s
.getvalue()
220 def flush(self
) -> None:
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
):
245 "The stored build information has an unexpected class: %s" % bi
.__class
__
248 self
.display("The original builder output was:\n" +
249 (" |" + str(bi
.string
)).replace("\n", "\n |"))
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
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.
264 self
.display('Caught exception while building "%s":\n' %
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
274 # cachable is False if some nodes are not in our cache
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)')
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():
292 if T
: Trace(': changed %s' % changed
)
293 cached_error
= cached_error
or bi
.result
295 if T
: Trace(': else')
296 # the node hasn't been built in a SConf context or doesn't
299 changed
= ( t
.get_state() != SCons
.Node
.up_to_date
)
300 if T
: Trace(': changed %s' % changed
)
302 return (not changed
, cached_error
, cachable
)
305 if not self
.targets
[0].has_builder():
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
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
)
328 raise ConfigureDryRunError(self
.targets
[0])
330 # note stdout and stderr are the same here
331 s
= sys
.stdout
= sys
.stderr
= Streamer(sys
.stdout
)
333 env
= self
.targets
[0].get_build_env()
334 env
['PSTDOUT'] = env
['PSTDERR'] = s
337 self
.targets
[0].build()
339 sys
.stdout
= sys
.stderr
= env
['PSTDOUT'] = \
340 env
['PSTDERR'] = sconf
.logstream
341 except KeyboardInterrupt:
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
360 sconsign
= t
.dir.sconsign()
361 sconsign
.set_entry(t
.name
, sconsign_entry
)
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
378 sconsign
= t
.dir.sconsign()
379 sconsign
.set_entry(t
.name
, sconsign_entry
)
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()
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
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)
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
,
436 env_decider
=env
.decide_source
) -> bool:
438 env_decider(dependency
, target
, prev_ni
, repo_node
)
439 except Exception as e
:
443 if self
.env
.decide_source
.__code
__ is not force_build
.__code
__:
444 self
.env
.Decider(force_build
)
449 # print("Override env:%s"%env)
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
464 self
.cached
= 0 # will be set, if all test results are cached
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
493 """Call this method after finished with your tests:
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).
513 comment_str
= "/* %s */" % comment
514 lines
.append(comment_str
)
516 if value
is not None:
517 define_str
= "#define %s %s" % (name
, value
)
519 define_str
= "#define %s" % name
520 lines
.append(define_str
)
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,
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.
547 _set_conftest_node(n
)
549 if not hasattr(n
, 'attributes'):
550 n
.attributes
= SCons
.Node
.Node
.Attrs()
551 n
.attributes
.keep_targetinfo
= 1
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
)
563 if not hasattr(c
, 'attributes'):
564 c
.attributes
= SCons
.Node
.Node
.Attrs()
565 c
.attributes
.keep_targetinfo
= 1
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
)
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
585 SConfFS
.set_max_drift(save_max_drift
)
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
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
615 self
.pspawn
= self
.env
['PSPAWN']
617 raise SCons
.Errors
.UserError('Missing PSPAWN construction variable.')
619 save_spawn
= self
.env
['SPAWN']
621 raise SCons
.Errors
.UserError('Missing SPAWN construction variable.')
624 sourcetext
= self
.env
.Value(text
)
625 _set_conftest_node(sourcetext
)
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
,
638 nodesToBeBuilt
.extend(textFileNode
)
641 target
= textFile
.File(f
+ "SConfActionsContentDummyTarget")
642 _set_conftest_node(target
)
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
)
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
):
664 nodesToBeBuilt
.extend(nodes
)
665 result
= self
.BuildNodes(nodesToBeBuilt
)
668 self
.env
['SPAWN'] = save_spawn
671 self
.lastTarget
= nodes
[0]
673 self
.lastTarget
= None
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
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']
688 outputStr
= self
.lastTarget
.get_text_contents()
689 return (1, outputStr
)
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
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
)
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
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
)
728 outputStr
= SCons
.Util
.to_str(output
.get_contents())
729 return( 1, outputStr
)
733 """A wrapper around Tests (to ensure sanity)"""
734 def __init__(self
, test
, sconf
) -> None:
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")
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
756 for name
in tests
.keys():
757 self
.AddTest(name
, tests
[name
])
759 def _createDir( self
, node
):
762 if not os
.path
.isdir( dirName
):
763 raise ConfigureDryRunError(dirName
)
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
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
784 if self
.logfile
in _ac_config_logs
:
787 _ac_config_logs
[self
.logfile
] = None
789 fp
= open(str(self
.logfile
), log_mode
)
791 def conflog_cleanup(logf
) -> None:
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
)
808 self
.logstream
= None
809 # we use a special builder to create source files from TEXT
810 action
= SCons
.Action
.Action(_createSource
,
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
, "")
816 # only one SConf instance should be active at a time ...
820 """Private method. Reset to non-piped spawn"""
821 global sconf_global
, _ac_config_hs
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
)
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
848 """Provides a context for configure tests. Defines how a test writes to the
851 A typical test is just a callable with an instance of CheckContext as
854 def CheckCustom(context, ...):
855 context.Message('Checking my weird test ... ')
856 ret = myWeirdTestFunction(...)
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
865 def __init__(self
, sconf
) -> None:
866 """Constructor. Pass the corresponding SConf instance."""
868 self
.did_show_result
= 0
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 ... '
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):
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
):
925 return self
.sconf
.env
926 elif attr
== 'lastTarget':
927 return self
.sconf
.lastTarget
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
)
954 def AppendLIBS(self
, lib_name_list
, unique
: bool=False):
955 oldLIBS
= self
.env
.get( 'LIBS', [] )
957 self
.env
.AppendUnique(LIBS
= lib_name_list
)
959 self
.env
.Append(LIBS
= lib_name_list
)
962 def PrependLIBS(self
, lib_name_list
, unique
: bool=False):
963 oldLIBS
= self
.env
.get( 'LIBS', [] )
965 self
.env
.PrependUnique(LIBS
= lib_name_list
)
967 self
.env
.Prepend(LIBS
= lib_name_list
)
970 def SetLIBS(self
, val
):
971 oldLIBS
= self
.env
.get( 'LIBS', [] )
972 self
.env
.Replace(LIBS
= val
)
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
:
1000 return SConfBase(*args
, **kw
)
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
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
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
,
1020 context
.did_show_result
= 1
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
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
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
):
1044 lastHeader
= headers
[-1]
1045 headers
= headers
[:-1]
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
1065 def CheckCC(context
) -> bool:
1066 res
= SCons
.Conftest
.CheckCC(context
)
1067 context
.did_show_result
= 1
1070 def CheckCXX(context
) -> bool:
1071 res
= SCons
.Conftest
.CheckCXX(context
)
1072 context
.did_show_result
= 1
1075 def CheckSHCC(context
) -> bool:
1076 res
= SCons
.Conftest
.CheckSHCC(context
)
1077 context
.did_show_result
= 1
1080 def CheckSHCXX(context
) -> bool:
1081 res
= SCons
.Conftest
.CheckSHCXX(context
)
1082 context
.did_show_result
= 1
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.
1115 if not SCons
.Util
.is_List(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
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)
1142 if not SCons
.Util
.is_List(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
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
1161 # indent-tabs-mode:nil
1163 # vim: set expandtab tabstop=4 shiftwidth=4: