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
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 __revision__ = "src/engine/SCons/SConf.py rel_2.4.0:3365:9259ea1c13d7 2015/09/21 14:03:43 bdbaddog"
38
39 import SCons.compat
40
41 import io
42 import os
43 import re
44 import sys
45 import traceback
46
47 import SCons.Action
48 import SCons.Builder
49 import SCons.Errors
50 import SCons.Job
51 import SCons.Node.FS
52 import SCons.Taskmaster
53 import SCons.Util
54 import SCons.Warnings
55 import SCons.Conftest
56
57 from SCons.Debug import Trace
58
59
60 SCons.Conftest.LogInputFiles = 0
61 SCons.Conftest.LogErrorMessages = 0
62
63
64 build_type = None
65 build_types = ['clean', 'help']
66
70
71
72 dryrun = 0
73
74 AUTO=0
75 FORCE=1
76 CACHE=2
77 cache_mode = AUTO
78
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
92 progress_display = SCons.Util.display
97
98 SConfFS = None
99
100 _ac_build_counter = 0
101 _ac_config_logs = {}
102 _ac_config_hs = {}
103 sconf_global = None
104
106 t = open(str(target[0]), "w")
107 defname = re.sub('[^A-Za-z0-9_]', '_', str(target[0]).upper())
108 t.write("""#ifndef %(DEFNAME)s_SEEN
109 #define %(DEFNAME)s_SEEN
110
111 """ % {'DEFNAME' : defname})
112 t.write(source[0].get_contents())
113 t.write("""
114 #endif /* %(DEFNAME)s_SEEN */
115 """ % {'DEFNAME' : defname})
116 t.close()
117
119 return "scons: Configure: creating " + str(target[0])
120
121
123 if len(_ac_config_hs) == 0:
124 return False
125 else:
126 return True
127
136
137
140 SCons.Warnings.enableWarningClass(SConfWarning)
141
142
146
156
162
163
169 return (str(target[0]) + ' <-\n |' +
170 source[0].get_contents().replace( '\n', "\n |" ) )
171
173 """
174 Special build info for targets of configure tests. Additional members
175 are result (did the builder succeed last time?) and string, which
176 contains messages of the original build phase.
177 """
178 __slots__ = ('result', 'string')
179
183
187
188
190 """
191 'Sniffer' for a file-like writable object. Similar to the unix tool tee.
192 """
194 self.orig = orig
195 self.s = io.StringIO()
196
198 if self.orig:
199 self.orig.write(str)
200 try:
201 self.s.write(str)
202 except TypeError as e:
203
204 self.s.write(str.decode())
205
207 for l in lines:
208 self.write(l + '\n')
209
211 """
212 Return everything written to orig since the Streamer was created.
213 """
214 return self.s.getvalue()
215
217 if self.orig:
218 self.orig.flush()
219 self.s.flush()
220
221
223 """
224 This is almost the same as SCons.Script.BuildTask. Handles SConfErrors
225 correctly and knows about the current cache_mode.
226 """
230
232 """
233 Logs the original builder messages, given the SConfBuildInfo instance
234 bi.
235 """
236 if not isinstance(bi, SConfBuildInfo):
237 SCons.Warnings.warn(SConfWarning,
238 "The stored build information has an unexpected class: %s" % bi.__class__)
239 else:
240 self.display("The original builder output was:\n" +
241 (" |" + str(bi.string)).replace("\n", "\n |"))
242
244
245
246 exc_type = self.exc_info()[0]
247 if issubclass(exc_type, SConfError):
248 raise
249 elif issubclass(exc_type, SCons.Errors.BuildError):
250
251
252
253 self.exc_clear()
254 else:
255 self.display('Caught exception while building "%s":\n' %
256 self.targets[0])
257 try:
258 excepthook = sys.excepthook
259 except AttributeError:
260
261 def excepthook(type, value, tb):
262 traceback.print_tb(tb)
263 print type, value
264 excepthook(*self.exc_info())
265 return SCons.Taskmaster.Task.failed(self)
266
301
342 if env.decide_source.func_code is not force_build.func_code:
343 env.Decider(force_build)
344 env['PSTDOUT'] = env['PSTDERR'] = s
345 try:
346 sconf.cached = 0
347 self.targets[0].build()
348 finally:
349 sys.stdout = sys.stderr = env['PSTDOUT'] = \
350 env['PSTDERR'] = sconf.logstream
351 except KeyboardInterrupt:
352 raise
353 except SystemExit:
354 exc_value = sys.exc_info()[1]
355 raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code)
356 except Exception, e:
357 for t in self.targets:
358
359
360 binfo = SConfBuildInfo()
361 binfo.merge(t.get_binfo())
362 binfo.set_build_result(1, s.getvalue())
363 sconsign_entry = SCons.SConsign.SConsignEntry()
364 sconsign_entry.binfo = binfo
365
366
367
368
369
370
371
372 sconsign = t.dir.sconsign()
373 sconsign.set_entry(t.name, sconsign_entry)
374 sconsign.merge()
375 raise e
376 else:
377 for t in self.targets:
378
379
380 binfo = SConfBuildInfo()
381 binfo.merge(t.get_binfo())
382 binfo.set_build_result(0, s.getvalue())
383 sconsign_entry = SCons.SConsign.SConsignEntry()
384 sconsign_entry.binfo = binfo
385
386
387
388
389
390
391
392 sconsign = t.dir.sconsign()
393 sconsign.set_entry(t.name, sconsign_entry)
394 sconsign.merge()
395
397 """This is simply a class to represent a configure context. After
398 creating a SConf object, you can call any tests. After finished with your
399 tests, be sure to call the Finish() method, which returns the modified
400 environment.
401 Some words about caching: In most cases, it is not necessary to cache
402 Test results explicitely. Instead, we use the scons dependency checking
403 mechanism. For example, if one wants to compile a test program
404 (SConf.TryLink), the compiler is only called, if the program dependencies
405 have changed. However, if the program could not be compiled in a former
406 SConf run, we need to explicitely cache this error.
407 """
408
409 - def __init__(self, env, custom_tests = {}, conf_dir='$CONFIGUREDIR',
410 log_file='$CONFIGURELOG', config_h = None, _depth = 0):
411 """Constructor. Pass additional tests in the custom_tests-dictinary,
412 e.g. custom_tests={'CheckPrivate':MyPrivateTest}, where MyPrivateTest
413 defines a custom test.
414 Note also the conf_dir and log_file arguments (you may want to
415 build tests in the VariantDir, not in the SourceDir)
416 """
417 global SConfFS
418 if not SConfFS:
419 SConfFS = SCons.Node.FS.default_fs or \
420 SCons.Node.FS.FS(env.fs.pathTop)
421 if sconf_global is not None:
422 raise SCons.Errors.UserError
423 self.env = env
424 if log_file is not None:
425 log_file = SConfFS.File(env.subst(log_file))
426 self.logfile = log_file
427 self.logstream = None
428 self.lastTarget = None
429 self.depth = _depth
430 self.cached = 0
431
432
433 default_tests = {
434 'CheckCC' : CheckCC,
435 'CheckCXX' : CheckCXX,
436 'CheckSHCC' : CheckSHCC,
437 'CheckSHCXX' : CheckSHCXX,
438 'CheckFunc' : CheckFunc,
439 'CheckType' : CheckType,
440 'CheckTypeSize' : CheckTypeSize,
441 'CheckDeclaration' : CheckDeclaration,
442 'CheckHeader' : CheckHeader,
443 'CheckCHeader' : CheckCHeader,
444 'CheckCXXHeader' : CheckCXXHeader,
445 'CheckLib' : CheckLib,
446 'CheckLibWithHeader' : CheckLibWithHeader,
447 }
448 self.AddTests(default_tests)
449 self.AddTests(custom_tests)
450 self.confdir = SConfFS.Dir(env.subst(conf_dir))
451 if config_h is not None:
452 config_h = SConfFS.File(config_h)
453 self.config_h = config_h
454 self._startup()
455
457 """Call this method after finished with your tests:
458 env = sconf.Finish()
459 """
460 self._shutdown()
461 return self.env
462
463 - def Define(self, name, value = None, comment = None):
464 """
465 Define a pre processor symbol name, with the optional given value in the
466 current config header.
467
468 If value is None (default), then #define name is written. If value is not
469 none, then #define name value is written.
470
471 comment is a string which will be put as a C comment in the
472 header, to explain the meaning of the value (appropriate C comments /* and
473 */ will be put automatically."""
474 lines = []
475 if comment:
476 comment_str = "/* %s */" % comment
477 lines.append(comment_str)
478
479 if value is not None:
480 define_str = "#define %s %s" % (name, value)
481 else:
482 define_str = "#define %s" % name
483 lines.append(define_str)
484 lines.append('')
485
486 self.config_h_text = self.config_h_text + '\n'.join(lines)
487
540
542 """Wrapper function for handling piped spawns.
543
544 This looks to the calling interface (in Action.py) like a "normal"
545 spawn, but associates the call with the PSPAWN variable from
546 the construction environment and with the streams to which we
547 want the output logged. This gets slid into the construction
548 environment as the SPAWN variable so Action.py doesn't have to
549 know or care whether it's spawning a piped command or not.
550 """
551 return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream)
552
553
554 - def TryBuild(self, builder, text = None, extension = ""):
555 """Low level TryBuild implementation. Normally you don't need to
556 call that - you can use TryCompile / TryLink / TryRun instead
557 """
558 global _ac_build_counter
559
560
561
562 try:
563 self.pspawn = self.env['PSPAWN']
564 except KeyError:
565 raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
566 try:
567 save_spawn = self.env['SPAWN']
568 except KeyError:
569 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
570
571 nodesToBeBuilt = []
572
573 f = "conftest_" + str(_ac_build_counter)
574 pref = self.env.subst( builder.builder.prefix )
575 suff = self.env.subst( builder.builder.suffix )
576 target = self.confdir.File(pref + f + suff)
577
578 try:
579
580
581 self.env['SPAWN'] = self.pspawn_wrapper
582 sourcetext = self.env.Value(text)
583
584 if text is not None:
585 textFile = self.confdir.File(f + extension)
586 textFileNode = self.env.SConfSourceBuilder(target=textFile,
587 source=sourcetext)
588 nodesToBeBuilt.extend(textFileNode)
589 source = textFileNode
590 else:
591 source = None
592
593 nodes = builder(target = target, source = source)
594 if not SCons.Util.is_List(nodes):
595 nodes = [nodes]
596 nodesToBeBuilt.extend(nodes)
597 result = self.BuildNodes(nodesToBeBuilt)
598
599 finally:
600 self.env['SPAWN'] = save_spawn
601
602 _ac_build_counter = _ac_build_counter + 1
603 if result:
604 self.lastTarget = nodes[0]
605 else:
606 self.lastTarget = None
607
608 return result
609
610 - def TryAction(self, action, text = None, extension = ""):
611 """Tries to execute the given action with optional source file
612 contents <text> and optional source file extension <extension>,
613 Returns the status (0 : failed, 1 : ok) and the contents of the
614 output file.
615 """
616 builder = SCons.Builder.Builder(action=action)
617 self.env.Append( BUILDERS = {'SConfActionBuilder' : builder} )
618 ok = self.TryBuild(self.env.SConfActionBuilder, text, extension)
619 del self.env['BUILDERS']['SConfActionBuilder']
620 if ok:
621 outputStr = self.lastTarget.get_contents()
622 return (1, outputStr)
623 return (0, "")
624
626 """Compiles the program given in text to an env.Object, using extension
627 as file extension (e.g. '.c'). Returns 1, if compilation was
628 successful, 0 otherwise. The target is saved in self.lastTarget (for
629 further processing).
630 """
631 return self.TryBuild(self.env.Object, text, extension)
632
633 - def TryLink( self, text, extension ):
634 """Compiles the program given in text to an executable env.Program,
635 using extension as file extension (e.g. '.c'). Returns 1, if
636 compilation was successful, 0 otherwise. The target is saved in
637 self.lastTarget (for further processing).
638 """
639 return self.TryBuild(self.env.Program, text, extension )
640
641 - def TryRun(self, text, extension ):
642 """Compiles and runs the program given in text, using extension
643 as file extension (e.g. '.c'). Returns (1, outputStr) on success,
644 (0, '') otherwise. The target (a file containing the program's stdout)
645 is saved in self.lastTarget (for further processing).
646 """
647 ok = self.TryLink(text, extension)
648 if( ok ):
649 prog = self.lastTarget
650 pname = prog.get_internal_path()
651 output = self.confdir.File(os.path.basename(pname)+'.out')
652 node = self.env.Command(output, prog, [ [ pname, ">", "${TARGET}"] ])
653 ok = self.BuildNodes(node)
654 if ok:
655 outputStr = output.get_contents()
656 return( 1, outputStr)
657 return (0, "")
658
660 """A wrapper around Tests (to ensure sanity)"""
662 self.test = test
663 self.sconf = sconf
665 if not self.sconf.active:
666 raise SCons.Errors.UserError
667 context = CheckContext(self.sconf)
668 ret = self.test(context, *args, **kw)
669 if self.sconf.config_h is not None:
670 self.sconf.config_h_text = self.sconf.config_h_text + context.config_h
671 context.Result("error: no result")
672 return ret
673
674 - def AddTest(self, test_name, test_instance):
675 """Adds test_class to this SConf instance. It can be called with
676 self.test_name(...)"""
677 setattr(self, test_name, SConfBase.TestWrapper(test_instance, self))
678
680 """Adds all the tests given in the tests dictionary to this SConf
681 instance
682 """
683 for name in tests.keys():
684 self.AddTest(name, tests[name])
685
694
740
742 """Private method. Reset to non-piped spawn"""
743 global sconf_global, _ac_config_hs
744
745 if not self.active:
746 raise SCons.Errors.UserError("Finish may be called only once!")
747 if self.logstream is not None and not dryrun:
748 self.logstream.write("\n")
749 self.logstream.close()
750 self.logstream = None
751
752 blds = self.env['BUILDERS']
753 del blds['SConfSourceBuilder']
754 self.env.Replace( BUILDERS=blds )
755 self.active = 0
756 sconf_global = None
757 if not self.config_h is None:
758 _ac_config_hs[self.config_h] = self.config_h_text
759 self.env.fs = self.lastEnvFs
760
761 -class CheckContext(object):
762 """Provides a context for configure tests. Defines how a test writes to the
763 screen and log file.
764
765 A typical test is just a callable with an instance of CheckContext as
766 first argument:
767
768 def CheckCustom(context, ...)
769 context.Message('Checking my weird test ... ')
770 ret = myWeirdTestFunction(...)
771 context.Result(ret)
772
773 Often, myWeirdTestFunction will be one of
774 context.TryCompile/context.TryLink/context.TryRun. The results of
775 those are cached, for they are only rebuild, if the dependencies have
776 changed.
777 """
778
779 - def __init__(self, sconf):
780 """Constructor. Pass the corresponding SConf instance."""
781 self.sconf = sconf
782 self.did_show_result = 0
783
784
785 self.vardict = {}
786 self.havedict = {}
787 self.headerfilename = None
788 self.config_h = ""
789
790
791
792
793
794
795
796
797 - def Message(self, text):
798 """Inform about what we are doing right now, e.g.
799 'Checking for SOMETHING ... '
800 """
801 self.Display(text)
802 self.sconf.cached = 1
803 self.did_show_result = 0
804
805 - def Result(self, res):
806 """Inform about the result of the test. If res is not a string, displays
807 'yes' or 'no' depending on whether res is evaluated as true or false.
808 The result is only displayed when self.did_show_result is not set.
809 """
810 if isinstance(res, str):
811 text = res
812 elif res:
813 text = "yes"
814 else:
815 text = "no"
816
817 if self.did_show_result == 0:
818
819 self.Display(text + "\n")
820 self.did_show_result = 1
821
822 - def TryBuild(self, *args, **kw):
823 return self.sconf.TryBuild(*args, **kw)
824
825 - def TryAction(self, *args, **kw):
826 return self.sconf.TryAction(*args, **kw)
827
828 - def TryCompile(self, *args, **kw):
829 return self.sconf.TryCompile(*args, **kw)
830
831 - def TryLink(self, *args, **kw):
832 return self.sconf.TryLink(*args, **kw)
833
834 - def TryRun(self, *args, **kw):
835 return self.sconf.TryRun(*args, **kw)
836
837 - def __getattr__( self, attr ):
838 if( attr == 'env' ):
839 return self.sconf.env
840 elif( attr == 'lastTarget' ):
841 return self.sconf.lastTarget
842 else:
843 raise AttributeError("CheckContext instance has no attribute '%s'" % attr)
844
845
846
847 - def BuildProg(self, text, ext):
848 self.sconf.cached = 1
849
850 return not self.TryBuild(self.env.Program, text, ext)
851
852 - def CompileProg(self, text, ext):
853 self.sconf.cached = 1
854
855 return not self.TryBuild(self.env.Object, text, ext)
856
857 - def CompileSharedObject(self, text, ext):
858 self.sconf.cached = 1
859
860 return not self.TryBuild(self.env.SharedObject, text, ext)
861
862 - def RunProg(self, text, ext):
863 self.sconf.cached = 1
864
865 st, out = self.TryRun(text, ext)
866 return not st, out
867
868 - def AppendLIBS(self, lib_name_list):
869 oldLIBS = self.env.get( 'LIBS', [] )
870 self.env.Append(LIBS = lib_name_list)
871 return oldLIBS
872
873 - def PrependLIBS(self, lib_name_list):
874 oldLIBS = self.env.get( 'LIBS', [] )
875 self.env.Prepend(LIBS = lib_name_list)
876 return oldLIBS
877
878 - def SetLIBS(self, val):
879 oldLIBS = self.env.get( 'LIBS', [] )
880 self.env.Replace(LIBS = val)
881 return oldLIBS
882
883 - def Display(self, msg):
884 if self.sconf.cached:
885
886
887
888 msg = "(cached) " + msg
889 self.sconf.cached = 0
890 progress_display(msg, append_newline=0)
891 self.Log("scons: Configure: " + msg + "\n")
892
893 - def Log(self, msg):
894 if self.sconf.logstream is not None:
895 self.sconf.logstream.write(msg)
896
897
898
899
911
912
913 -def CheckFunc(context, function_name, header = None, language = None):
917
918 -def CheckType(context, type_name, includes = "", language = None):
923
924 -def CheckTypeSize(context, type_name, includes = "", language = None, expect = None):
925 res = SCons.Conftest.CheckTypeSize(context, type_name,
926 header = includes, language = language,
927 expect = expect)
928 context.did_show_result = 1
929 return res
930
937
939
940
941 if not SCons.Util.is_List(headers):
942 headers = [headers]
943 l = []
944 if leaveLast:
945 lastHeader = headers[-1]
946 headers = headers[:-1]
947 else:
948 lastHeader = None
949 for s in headers:
950 l.append("#include %s%s%s\n"
951 % (include_quotes[0], s, include_quotes[1]))
952 return ''.join(l), lastHeader
953
955 """
956 A test for a C or C++ header file.
957 """
958 prog_prefix, hdr_to_check = \
959 createIncludesFromHeaders(header, 1, include_quotes)
960 res = SCons.Conftest.CheckHeader(context, hdr_to_check, prog_prefix,
961 language = language,
962 include_quotes = include_quotes)
963 context.did_show_result = 1
964 return not res
965
970
975
980
985
986
987
989 """
990 A test for a C header file.
991 """
992 return CheckHeader(context, header, include_quotes, language = "C")
993
994
995
996
998 """
999 A test for a C++ header file.
1000 """
1001 return CheckHeader(context, header, include_quotes, language = "C++")
1002
1003
1004 -def CheckLib(context, library = None, symbol = "main",
1005 header = None, language = None, autoadd = 1):
1006 """
1007 A test for a library. See also CheckLibWithHeader.
1008 Note that library may also be None to test whether the given symbol
1009 compiles without flags.
1010 """
1011
1012 if library == []:
1013 library = [None]
1014
1015 if not SCons.Util.is_List(library):
1016 library = [library]
1017
1018
1019 res = SCons.Conftest.CheckLib(context, library, symbol, header = header,
1020 language = language, autoadd = autoadd)
1021 context.did_show_result = 1
1022 return not res
1023
1024
1025
1026
1029
1030 """
1031 Another (more sophisticated) test for a library.
1032 Checks, if library and header is available for language (may be 'C'
1033 or 'CXX'). Call maybe be a valid expression _with_ a trailing ';'.
1034 As in CheckLib, we support library=None, to test if the call compiles
1035 without extra link flags.
1036 """
1037 prog_prefix, dummy = \
1038 createIncludesFromHeaders(header, 0)
1039 if libs == []:
1040 libs = [None]
1041
1042 if not SCons.Util.is_List(libs):
1043 libs = [libs]
1044
1045 res = SCons.Conftest.CheckLib(context, libs, None, prog_prefix,
1046 call = call, language = language, autoadd = autoadd)
1047 context.did_show_result = 1
1048 return not res
1049
1050
1051
1052
1053
1054
1055