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