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