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