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 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
62 SCons.Conftest.LogInputFiles = 0
63 SCons.Conftest.LogErrorMessages = 0
64
65
66 build_type = None
67 build_types = ['clean', 'help']
68
72
73
74 dryrun = 0
75
76 AUTO=0
77 FORCE=1
78 CACHE=2
79 cache_mode = AUTO
80
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
99
100 SConfFS = None
101
102 _ac_build_counter = 0
103 _ac_config_logs = {}
104 _ac_config_hs = {}
105 sconf_global = None
106
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
121 return "scons: Configure: creating " + str(target[0])
122
123
125 if len(_ac_config_hs) == 0:
126 return False
127 else:
128 return True
129
138
139
142 SCons.Warnings.enableWarningClass(SConfWarning)
143
144
148
158
164
165
171 return (str(target[0]) + ' <-\n |' +
172 source[0].get_contents().decode().replace( '\n', "\n |" ) )
173
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
185
189
190
192 """
193 'Sniffer' for a file-like writable object. Similar to the unix tool tee.
194 """
196 self.orig = orig
197 self.s = io.StringIO()
198
200 if self.orig:
201 self.orig.write(str)
202 try:
203 self.s.write(str)
204 except TypeError as e:
205
206 self.s.write(str.decode())
207
209 for l in lines:
210 self.write(l + '\n')
211
213 """
214 Return everything written to orig since the Streamer was created.
215 """
216 return self.s.getvalue()
217
219 if self.orig:
220 self.orig.flush()
221 self.s.flush()
222
223
225 """
226 This is almost the same as SCons.Script.BuildTask. Handles SConfErrors
227 correctly and knows about the current cache_mode.
228 """
232
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
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 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
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
440
441
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
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
565
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
586
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
605
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
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
658 - def TryLink( self, text, extension ):
659 """Compiles the program given in text to an executable env.Program,
660 using extension as file extension (e.g. '.c'). Returns 1, if
661 compilation was successful, 0 otherwise. The target is saved in
662 self.lastTarget (for further processing).
663 """
664 return self.TryBuild(self.env.Program, text, extension )
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
685 """A wrapper around Tests (to ensure sanity)"""
687 self.test = test
688 self.sconf = sconf
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
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
719
765
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
778
779
780 if cache_mode == FORCE:
781 self.env.Decider(self.original_env.decide_source)
782
783
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
818 self.vardict = {}
819 self.havedict = {}
820 self.headerfilename = None
821 self.config_h = ""
822
823
824
825
826
827
828
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
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
864 - def TryLink(self, *args, **kw):
865 return self.sconf.TryLink(*args, **kw)
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
879
880 - def BuildProg(self, text, ext):
881 self.sconf.cached = 1
882
883 return not self.TryBuild(self.env.Program, text, ext)
884
885 - def CompileProg(self, text, ext):
886 self.sconf.cached = 1
887
888 return not self.TryBuild(self.env.Object, text, ext)
889
890 - def CompileSharedObject(self, text, ext):
891 self.sconf.cached = 1
892
893 return not self.TryBuild(self.env.SharedObject, text, ext)
894
895 - def RunProg(self, text, ext):
896 self.sconf.cached = 1
897
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
919
920
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
931
932
944
945
946 -def CheckFunc(context, function_name, header = None, language = None):
950
951 -def CheckType(context, type_name, includes = "", language = None):
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
970
972
973
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
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
1003
1008
1013
1018
1019
1020
1022 """
1023 A test for a C header file.
1024 """
1025 return CheckHeader(context, header, include_quotes, language = "C")
1026
1027
1028
1029
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
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
1058
1059
1062
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
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
1092
1093
1094
1095
1096