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