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