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