Package SCons :: Module SConf
[hide private]
[frames] | no frames]

Source Code for Module SCons.SConf

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