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 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
61 SCons.Conftest.LogInputFiles = 0
62 SCons.Conftest.LogErrorMessages = 0
63
64
65 build_type = None
66 build_types = ['clean', 'help']
67
71
72
73 dryrun = 0
74
75 AUTO=0
76 FORCE=1
77 CACHE=2
78 cache_mode = AUTO
79
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
98
99 SConfFS = None
100
101 _ac_build_counter = 0
102 _ac_config_logs = {}
103 _ac_config_hs = {}
104 sconf_global = None
105
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
120 return "scons: Configure: creating " + str(target[0])
121
122
124 if len(_ac_config_hs) == 0:
125 return False
126 else:
127 return True
128
137
138
141 SCons.Warnings.enableWarningClass(SConfWarning)
142
143
147
157
163
164
170 return (str(target[0]) + ' <-\n |' +
171 source[0].get_contents().decode().replace( '\n', "\n |" ) )
172
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
184
188
189
191 """
192 'Sniffer' for a file-like writable object. Similar to the unix tool tee.
193 """
195 self.orig = orig
196 self.s = io.StringIO()
197
199 if self.orig:
200 self.orig.write(str)
201 try:
202 self.s.write(str)
203 except TypeError as e:
204
205 self.s.write(str.decode())
206
208 for l in lines:
209 self.write(l + '\n')
210
212 """
213 Return everything written to orig since the Streamer was created.
214 """
215 return self.s.getvalue()
216
218 if self.orig:
219 self.orig.flush()
220 self.s.flush()
221
222
224 """
225 This is almost the same as SCons.Script.BuildTask. Handles SConfErrors
226 correctly and knows about the current cache_mode.
227 """
231
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
261
296
374
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
399 if cache_mode == FORCE:
400 self.original_env = env
401 self.env = env.Clone()
402
403
404
405
406
407
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
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
438
439
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
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
563
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
584
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
603
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
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
656 - def TryLink( self, text, extension ):
657 """Compiles the program given in text to an executable env.Program,
658 using extension as file extension (e.g. '.c'). Returns 1, if
659 compilation was successful, 0 otherwise. The target is saved in
660 self.lastTarget (for further processing).
661 """
662 return self.TryBuild(self.env.Program, text, extension )
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
683 """A wrapper around Tests (to ensure sanity)"""
685 self.test = test
686 self.sconf = sconf
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
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
717
763
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
776
777
778 if cache_mode == FORCE:
779 self.env.Decider(self.original_env.decide_source)
780
781
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
816 self.vardict = {}
817 self.havedict = {}
818 self.headerfilename = None
819 self.config_h = ""
820
821
822
823
824
825
826
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
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
862 - def TryLink(self, *args, **kw):
863 return self.sconf.TryLink(*args, **kw)
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
877
878 - def BuildProg(self, text, ext):
879 self.sconf.cached = 1
880
881 return not self.TryBuild(self.env.Program, text, ext)
882
883 - def CompileProg(self, text, ext):
884 self.sconf.cached = 1
885
886 return not self.TryBuild(self.env.Object, text, ext)
887
888 - def CompileSharedObject(self, text, ext):
889 self.sconf.cached = 1
890
891 return not self.TryBuild(self.env.SharedObject, text, ext)
892
893 - def RunProg(self, text, ext):
894 self.sconf.cached = 1
895
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
917
918
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
929
930
942
943
944 -def CheckFunc(context, function_name, header = None, language = None):
948
949 -def CheckType(context, type_name, includes = "", language = None):
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
968
970
971
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
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
1001
1006
1011
1016
1017
1018
1020 """
1021 A test for a C header file.
1022 """
1023 return CheckHeader(context, header, include_quotes, language = "C")
1024
1025
1026
1027
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
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
1056
1057
1060
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
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
1090
1091
1092
1093
1094