http://scons.tigris.org/issues/show_bug.cgi?id=2345
[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 SCons.compat
32
33 import io
34 import os
35 import re
36 import sys
37 import traceback
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_]', '_', str(target[0]).upper())
100     t.write("""#ifndef %(DEFNAME)s_SEEN
101 #define %(DEFNAME)s_SEEN
102
103 """ % {'DEFNAME' : defname})
104     t.write(source[0].get_contents())
105     t.write("""
106 #endif /* %(DEFNAME)s_SEEN */
107 """ % {'DEFNAME' : defname})
108     t.close()
109
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             source[0].get_contents().replace( '\n', "\n  |" ) )
157
158 # python 2.2 introduces bool
159 BooleanTypes = [int, bool]
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 = io.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         self.env = env
402         if log_file is not None:
403             log_file = SConfFS.File(env.subst(log_file))
404         self.logfile = log_file
405         self.logstream = None
406         self.lastTarget = None
407         self.depth = _depth
408         self.cached = 0 # will be set, if all test results are cached
409
410         # add default tests
411         default_tests = {
412                  'CheckCC'            : CheckCC,
413                  'CheckCXX'           : CheckCXX,
414                  'CheckSHCC'          : CheckSHCC,
415                  'CheckSHCXX'         : CheckSHCXX,
416                  'CheckFunc'          : CheckFunc,
417                  'CheckType'          : CheckType,
418                  'CheckTypeSize'      : CheckTypeSize,
419                  'CheckDeclaration'   : CheckDeclaration,
420                  'CheckHeader'        : CheckHeader,
421                  'CheckCHeader'       : CheckCHeader,
422                  'CheckCXXHeader'     : CheckCXXHeader,
423                  'CheckLib'           : CheckLib,
424                  'CheckLibWithHeader' : CheckLibWithHeader,
425                }
426         self.AddTests(default_tests)
427         self.AddTests(custom_tests)
428         self.confdir = SConfFS.Dir(env.subst(conf_dir))
429         if config_h is not None:
430             config_h = SConfFS.File(config_h)
431         self.config_h = config_h
432         self._startup()
433
434     def Finish(self):
435         """Call this method after finished with your tests:
436                 env = sconf.Finish()
437         """
438         self._shutdown()
439         return self.env
440
441     def Define(self, name, value = None, comment = None):
442         """
443         Define a pre processor symbol name, with the optional given value in the
444         current config header.
445
446         If value is None (default), then #define name is written. If value is not
447         none, then #define name value is written.
448         
449         comment is a string which will be put as a C comment in the
450         header, to explain the meaning of the value (appropriate C comments /* and
451         */ will be put automatically."""
452         lines = []
453         if comment:
454             comment_str = "/* %s */" % comment
455             lines.append(comment_str)
456
457         if value is not None:
458             define_str = "#define %s %s" % (name, value)
459         else:
460             define_str = "#define %s" % name
461         lines.append(define_str)
462         lines.append('')
463
464         self.config_h_text = self.config_h_text + '\n'.join(lines)
465
466     def BuildNodes(self, nodes):
467         """
468         Tries to build the given nodes immediately. Returns 1 on success,
469         0 on error.
470         """
471         if self.logstream is not None:
472             # override stdout / stderr to write in log file
473             oldStdout = sys.stdout
474             sys.stdout = self.logstream
475             oldStderr = sys.stderr
476             sys.stderr = self.logstream
477
478         # the engine assumes the current path is the SConstruct directory ...
479         old_fs_dir = SConfFS.getcwd()
480         old_os_dir = os.getcwd()
481         SConfFS.chdir(SConfFS.Top, change_os_dir=1)
482
483         # Because we take responsibility here for writing out our
484         # own .sconsign info (see SConfBuildTask.execute(), above),
485         # we override the store_info() method with a null place-holder
486         # so we really control how it gets written.
487         for n in nodes:
488             n.store_info = n.do_not_store_info
489
490         ret = 1
491
492         try:
493             # ToDo: use user options for calc
494             save_max_drift = SConfFS.get_max_drift()
495             SConfFS.set_max_drift(0)
496             tm = SCons.Taskmaster.Taskmaster(nodes, SConfBuildTask)
497             # we don't want to build tests in parallel
498             jobs = SCons.Job.Jobs(1, tm )
499             jobs.run()
500             for n in nodes:
501                 state = n.get_state()
502                 if (state != SCons.Node.executed and
503                     state != SCons.Node.up_to_date):
504                     # the node could not be built. we return 0 in this case
505                     ret = 0
506         finally:
507             SConfFS.set_max_drift(save_max_drift)
508             os.chdir(old_os_dir)
509             SConfFS.chdir(old_fs_dir, change_os_dir=0)
510             if self.logstream is not None:
511                 # restore stdout / stderr
512                 sys.stdout = oldStdout
513                 sys.stderr = oldStderr
514         return ret
515
516     def pspawn_wrapper(self, sh, escape, cmd, args, env):
517         """Wrapper function for handling piped spawns.
518
519         This looks to the calling interface (in Action.py) like a "normal"
520         spawn, but associates the call with the PSPAWN variable from
521         the construction environment and with the streams to which we
522         want the output logged.  This gets slid into the construction
523         environment as the SPAWN variable so Action.py doesn't have to
524         know or care whether it's spawning a piped command or not.
525         """
526         return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream)
527
528
529     def TryBuild(self, builder, text = None, extension = ""):
530         """Low level TryBuild implementation. Normally you don't need to
531         call that - you can use TryCompile / TryLink / TryRun instead
532         """
533         global _ac_build_counter
534
535         # Make sure we have a PSPAWN value, and save the current
536         # SPAWN value.
537         try:
538             self.pspawn = self.env['PSPAWN']
539         except KeyError:
540             raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
541         try:
542             save_spawn = self.env['SPAWN']
543         except KeyError:
544             raise SCons.Errors.UserError('Missing SPAWN construction variable.')
545
546         nodesToBeBuilt = []
547
548         f = "conftest_" + str(_ac_build_counter)
549         pref = self.env.subst( builder.builder.prefix )
550         suff = self.env.subst( builder.builder.suffix )
551         target = self.confdir.File(pref + f + suff)
552
553         try:
554             # Slide our wrapper into the construction environment as
555             # the SPAWN function.
556             self.env['SPAWN'] = self.pspawn_wrapper
557             sourcetext = self.env.Value(text)
558
559             if text is not None:
560                 textFile = self.confdir.File(f + extension)
561                 textFileNode = self.env.SConfSourceBuilder(target=textFile,
562                                                            source=sourcetext)
563                 nodesToBeBuilt.extend(textFileNode)
564                 source = textFileNode
565             else:
566                 source = None
567
568             nodes = builder(target = target, source = source)
569             if not SCons.Util.is_List(nodes):
570                 nodes = [nodes]
571             nodesToBeBuilt.extend(nodes)
572             result = self.BuildNodes(nodesToBeBuilt)
573
574         finally:
575             self.env['SPAWN'] = save_spawn
576
577         _ac_build_counter = _ac_build_counter + 1
578         if result:
579             self.lastTarget = nodes[0]
580         else:
581             self.lastTarget = None
582
583         return result
584
585     def TryAction(self, action, text = None, extension = ""):
586         """Tries to execute the given action with optional source file
587         contents <text> and optional source file extension <extension>,
588         Returns the status (0 : failed, 1 : ok) and the contents of the
589         output file.
590         """
591         builder = SCons.Builder.Builder(action=action)
592         self.env.Append( BUILDERS = {'SConfActionBuilder' : builder} )
593         ok = self.TryBuild(self.env.SConfActionBuilder, text, extension)
594         del self.env['BUILDERS']['SConfActionBuilder']
595         if ok:
596             outputStr = self.lastTarget.get_contents()
597             return (1, outputStr)
598         return (0, "")
599
600     def TryCompile( self, text, extension):
601         """Compiles the program given in text to an env.Object, using extension
602         as file extension (e.g. '.c'). Returns 1, if compilation was
603         successful, 0 otherwise. The target is saved in self.lastTarget (for
604         further processing).
605         """
606         return self.TryBuild(self.env.Object, text, extension)
607
608     def TryLink( self, text, extension ):
609         """Compiles the program given in text to an executable env.Program,
610         using extension as file extension (e.g. '.c'). Returns 1, if
611         compilation was successful, 0 otherwise. The target is saved in
612         self.lastTarget (for further processing).
613         """
614         return self.TryBuild(self.env.Program, text, extension )
615
616     def TryRun(self, text, extension ):
617         """Compiles and runs the program given in text, using extension
618         as file extension (e.g. '.c'). Returns (1, outputStr) on success,
619         (0, '') otherwise. The target (a file containing the program's stdout)
620         is saved in self.lastTarget (for further processing).
621         """
622         ok = self.TryLink(text, extension)
623         if( ok ):
624             prog = self.lastTarget
625             pname = prog.path
626             output = self.confdir.File(os.path.basename(pname)+'.out')
627             node = self.env.Command(output, prog, [ [ pname, ">", "${TARGET}"] ])
628             ok = self.BuildNodes(node)
629             if ok:
630                 outputStr = output.get_contents()
631                 return( 1, outputStr)
632         return (0, "")
633
634     class TestWrapper:
635         """A wrapper around Tests (to ensure sanity)"""
636         def __init__(self, test, sconf):
637             self.test = test
638             self.sconf = sconf
639         def __call__(self, *args, **kw):
640             if not self.sconf.active:
641                 raise SCons.Errors.UserError
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: