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