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