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