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 3a41ed6b288cee8d085373ad7fa02894e1903864 2019-01-23 17:30:35 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
385
387 """This is simply a class to represent a configure context. After
388 creating a SConf object, you can call any tests. After finished with your
389 tests, be sure to call the Finish() method, which returns the modified
390 environment.
391 Some words about caching: In most cases, it is not necessary to cache
392 Test results explicitly. Instead, we use the scons dependency checking
393 mechanism. For example, if one wants to compile a test program
394 (SConf.TryLink), the compiler is only called, if the program dependencies
395 have changed. However, if the program could not be compiled in a former
396 SConf run, we need to explicitly cache this error.
397 """
398
399 - def __init__(self, env, custom_tests = {}, conf_dir='$CONFIGUREDIR',
400 log_file='$CONFIGURELOG', config_h = None, _depth = 0):
401 """Constructor. Pass additional tests in the custom_tests-dictionary,
402 e.g. custom_tests={'CheckPrivate':MyPrivateTest}, where MyPrivateTest
403 defines a custom test.
404 Note also the conf_dir and log_file arguments (you may want to
405 build tests in the VariantDir, not in the SourceDir)
406 """
407 global SConfFS
408 if not SConfFS:
409 SConfFS = SCons.Node.FS.default_fs or \
410 SCons.Node.FS.FS(env.fs.pathTop)
411 if sconf_global is not None:
412 raise SCons.Errors.UserError
413 self.env = env
414 if log_file is not None:
415 log_file = SConfFS.File(env.subst(log_file))
416 self.logfile = log_file
417 self.logstream = None
418 self.lastTarget = None
419 self.depth = _depth
420 self.cached = 0
421
422
423 default_tests = {
424 'CheckCC' : CheckCC,
425 'CheckCXX' : CheckCXX,
426 'CheckSHCC' : CheckSHCC,
427 'CheckSHCXX' : CheckSHCXX,
428 'CheckFunc' : CheckFunc,
429 'CheckType' : CheckType,
430 'CheckTypeSize' : CheckTypeSize,
431 'CheckDeclaration' : CheckDeclaration,
432 'CheckHeader' : CheckHeader,
433 'CheckCHeader' : CheckCHeader,
434 'CheckCXXHeader' : CheckCXXHeader,
435 'CheckLib' : CheckLib,
436 'CheckLibWithHeader' : CheckLibWithHeader,
437 'CheckProg' : CheckProg,
438 }
439 self.AddTests(default_tests)
440 self.AddTests(custom_tests)
441 self.confdir = SConfFS.Dir(env.subst(conf_dir))
442 if config_h is not None:
443 config_h = SConfFS.File(config_h)
444 self.config_h = config_h
445 self._startup()
446
448 """Call this method after finished with your tests:
449 env = sconf.Finish()
450 """
451 self._shutdown()
452 return self.env
453
454 - def Define(self, name, value = None, comment = None):
455 """
456 Define a pre processor symbol name, with the optional given value in the
457 current config header.
458
459 If value is None (default), then #define name is written. If value is not
460 none, then #define name value is written.
461
462 comment is a string which will be put as a C comment in the header, to explain the meaning of the value
463 (appropriate C comments will be added automatically).
464 """
465 lines = []
466 if comment:
467 comment_str = "/* %s */" % comment
468 lines.append(comment_str)
469
470 if value is not None:
471 define_str = "#define %s %s" % (name, value)
472 else:
473 define_str = "#define %s" % name
474 lines.append(define_str)
475 lines.append('')
476
477 self.config_h_text = self.config_h_text + '\n'.join(lines)
478
531
533 """Wrapper function for handling piped spawns.
534
535 This looks to the calling interface (in Action.py) like a "normal"
536 spawn, but associates the call with the PSPAWN variable from
537 the construction environment and with the streams to which we
538 want the output logged. This gets slid into the construction
539 environment as the SPAWN variable so Action.py doesn't have to
540 know or care whether it's spawning a piped command or not.
541 """
542 return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream)
543
544
545 - def TryBuild(self, builder, text = None, extension = ""):
546 """Low level TryBuild implementation. Normally you don't need to
547 call that - you can use TryCompile / TryLink / TryRun instead
548 """
549 global _ac_build_counter
550
551
552
553 try:
554 self.pspawn = self.env['PSPAWN']
555 except KeyError:
556 raise SCons.Errors.UserError('Missing PSPAWN construction variable.')
557 try:
558 save_spawn = self.env['SPAWN']
559 except KeyError:
560 raise SCons.Errors.UserError('Missing SPAWN construction variable.')
561
562 nodesToBeBuilt = []
563
564 f = "conftest_" + str(_ac_build_counter)
565 pref = self.env.subst( builder.builder.prefix )
566 suff = self.env.subst( builder.builder.suffix )
567 target = self.confdir.File(pref + f + suff)
568
569 try:
570
571
572 self.env['SPAWN'] = self.pspawn_wrapper
573 sourcetext = self.env.Value(text)
574
575 if text is not None:
576 textFile = self.confdir.File(f + extension)
577 textFileNode = self.env.SConfSourceBuilder(target=textFile,
578 source=sourcetext)
579 nodesToBeBuilt.extend(textFileNode)
580 source = textFileNode
581 else:
582 source = None
583
584 nodes = builder(target = target, source = source)
585 if not SCons.Util.is_List(nodes):
586 nodes = [nodes]
587 nodesToBeBuilt.extend(nodes)
588 result = self.BuildNodes(nodesToBeBuilt)
589
590 finally:
591 self.env['SPAWN'] = save_spawn
592
593 _ac_build_counter = _ac_build_counter + 1
594 if result:
595 self.lastTarget = nodes[0]
596 else:
597 self.lastTarget = None
598
599 return result
600
601 - def TryAction(self, action, text = None, extension = ""):
602 """Tries to execute the given action with optional source file
603 contents <text> and optional source file extension <extension>,
604 Returns the status (0 : failed, 1 : ok) and the contents of the
605 output file.
606 """
607 builder = SCons.Builder.Builder(action=action)
608 self.env.Append( BUILDERS = {'SConfActionBuilder' : builder} )
609 ok = self.TryBuild(self.env.SConfActionBuilder, text, extension)
610 del self.env['BUILDERS']['SConfActionBuilder']
611 if ok:
612 outputStr = self.lastTarget.get_text_contents()
613 return (1, outputStr)
614 return (0, "")
615
617 """Compiles the program given in text to an env.Object, using extension
618 as file extension (e.g. '.c'). Returns 1, if compilation was
619 successful, 0 otherwise. The target is saved in self.lastTarget (for
620 further processing).
621 """
622 return self.TryBuild(self.env.Object, text, extension)
623
624 - def TryLink( self, text, extension ):
625 """Compiles the program given in text to an executable env.Program,
626 using extension as file extension (e.g. '.c'). Returns 1, if
627 compilation was successful, 0 otherwise. The target is saved in
628 self.lastTarget (for further processing).
629 """
630 return self.TryBuild(self.env.Program, text, extension )
631
632 - def TryRun(self, text, extension ):
633 """Compiles and runs the program given in text, using extension
634 as file extension (e.g. '.c'). Returns (1, outputStr) on success,
635 (0, '') otherwise. The target (a file containing the program's stdout)
636 is saved in self.lastTarget (for further processing).
637 """
638 ok = self.TryLink(text, extension)
639 if( ok ):
640 prog = self.lastTarget
641 pname = prog.get_internal_path()
642 output = self.confdir.File(os.path.basename(pname)+'.out')
643 node = self.env.Command(output, prog, [ [ pname, ">", "${TARGET}"] ])
644 ok = self.BuildNodes(node)
645 if ok:
646 outputStr = SCons.Util.to_str(output.get_contents())
647 return( 1, outputStr)
648 return (0, "")
649
651 """A wrapper around Tests (to ensure sanity)"""
653 self.test = test
654 self.sconf = sconf
656 if not self.sconf.active:
657 raise SCons.Errors.UserError
658 context = CheckContext(self.sconf)
659 ret = self.test(context, *args, **kw)
660 if self.sconf.config_h is not None:
661 self.sconf.config_h_text = self.sconf.config_h_text + context.config_h
662 context.Result("error: no result")
663 return ret
664
665 - def AddTest(self, test_name, test_instance):
666 """Adds test_class to this SConf instance. It can be called with
667 self.test_name(...)"""
668 setattr(self, test_name, SConfBase.TestWrapper(test_instance, self))
669
671 """Adds all the tests given in the tests dictionary to this SConf
672 instance
673 """
674 for name in list(tests.keys()):
675 self.AddTest(name, tests[name])
676
685
731
733 """Private method. Reset to non-piped spawn"""
734 global sconf_global, _ac_config_hs
735
736 if not self.active:
737 raise SCons.Errors.UserError("Finish may be called only once!")
738 if self.logstream is not None and not dryrun:
739 self.logstream.write("\n")
740 self.logstream.close()
741 self.logstream = None
742
743 blds = self.env['BUILDERS']
744 del blds['SConfSourceBuilder']
745 self.env.Replace( BUILDERS=blds )
746 self.active = 0
747 sconf_global = None
748 if not self.config_h is None:
749 _ac_config_hs[self.config_h] = self.config_h_text
750 self.env.fs = self.lastEnvFs
751
752 -class CheckContext(object):
753 """Provides a context for configure tests. Defines how a test writes to the
754 screen and log file.
755
756 A typical test is just a callable with an instance of CheckContext as
757 first argument:
758
759 def CheckCustom(context, ...):
760 context.Message('Checking my weird test ... ')
761 ret = myWeirdTestFunction(...)
762 context.Result(ret)
763
764 Often, myWeirdTestFunction will be one of
765 context.TryCompile/context.TryLink/context.TryRun. The results of
766 those are cached, for they are only rebuild, if the dependencies have
767 changed.
768 """
769
770 - def __init__(self, sconf):
771 """Constructor. Pass the corresponding SConf instance."""
772 self.sconf = sconf
773 self.did_show_result = 0
774
775
776 self.vardict = {}
777 self.havedict = {}
778 self.headerfilename = None
779 self.config_h = ""
780
781
782
783
784
785
786
787
788 - def Message(self, text):
789 """Inform about what we are doing right now, e.g.
790 'Checking for SOMETHING ... '
791 """
792 self.Display(text)
793 self.sconf.cached = 1
794 self.did_show_result = 0
795
796 - def Result(self, res):
797 """Inform about the result of the test. If res is not a string, displays
798 'yes' or 'no' depending on whether res is evaluated as true or false.
799 The result is only displayed when self.did_show_result is not set.
800 """
801 if isinstance(res, str):
802 text = res
803 elif res:
804 text = "yes"
805 else:
806 text = "no"
807
808 if self.did_show_result == 0:
809
810 self.Display(text + "\n")
811 self.did_show_result = 1
812
813 - def TryBuild(self, *args, **kw):
814 return self.sconf.TryBuild(*args, **kw)
815
816 - def TryAction(self, *args, **kw):
817 return self.sconf.TryAction(*args, **kw)
818
819 - def TryCompile(self, *args, **kw):
820 return self.sconf.TryCompile(*args, **kw)
821
822 - def TryLink(self, *args, **kw):
823 return self.sconf.TryLink(*args, **kw)
824
825 - def TryRun(self, *args, **kw):
826 return self.sconf.TryRun(*args, **kw)
827
828 - def __getattr__( self, attr ):
829 if( attr == 'env' ):
830 return self.sconf.env
831 elif( attr == 'lastTarget' ):
832 return self.sconf.lastTarget
833 else:
834 raise AttributeError("CheckContext instance has no attribute '%s'" % attr)
835
836
837
838 - def BuildProg(self, text, ext):
839 self.sconf.cached = 1
840
841 return not self.TryBuild(self.env.Program, text, ext)
842
843 - def CompileProg(self, text, ext):
844 self.sconf.cached = 1
845
846 return not self.TryBuild(self.env.Object, text, ext)
847
848 - def CompileSharedObject(self, text, ext):
849 self.sconf.cached = 1
850
851 return not self.TryBuild(self.env.SharedObject, text, ext)
852
853 - def RunProg(self, text, ext):
854 self.sconf.cached = 1
855
856 st, out = self.TryRun(text, ext)
857 return not st, out
858
859 - def AppendLIBS(self, lib_name_list):
860 oldLIBS = self.env.get( 'LIBS', [] )
861 self.env.Append(LIBS = lib_name_list)
862 return oldLIBS
863
864 - def PrependLIBS(self, lib_name_list):
865 oldLIBS = self.env.get( 'LIBS', [] )
866 self.env.Prepend(LIBS = lib_name_list)
867 return oldLIBS
868
869 - def SetLIBS(self, val):
870 oldLIBS = self.env.get( 'LIBS', [] )
871 self.env.Replace(LIBS = val)
872 return oldLIBS
873
874 - def Display(self, msg):
875 if self.sconf.cached:
876
877
878
879 msg = "(cached) " + msg
880 self.sconf.cached = 0
881 progress_display(msg, append_newline=0)
882 self.Log("scons: Configure: " + msg + "\n")
883
884 - def Log(self, msg):
885 if self.sconf.logstream is not None:
886 self.sconf.logstream.write(msg)
887
888
889
890
902
903
904 -def CheckFunc(context, function_name, header = None, language = None):
908
909 -def CheckType(context, type_name, includes = "", language = None):
914
915 -def CheckTypeSize(context, type_name, includes = "", language = None, expect = None):
916 res = SCons.Conftest.CheckTypeSize(context, type_name,
917 header = includes, language = language,
918 expect = expect)
919 context.did_show_result = 1
920 return res
921
928
930
931
932 if not SCons.Util.is_List(headers):
933 headers = [headers]
934 l = []
935 if leaveLast:
936 lastHeader = headers[-1]
937 headers = headers[:-1]
938 else:
939 lastHeader = None
940 for s in headers:
941 l.append("#include %s%s%s\n"
942 % (include_quotes[0], s, include_quotes[1]))
943 return ''.join(l), lastHeader
944
946 """
947 A test for a C or C++ header file.
948 """
949 prog_prefix, hdr_to_check = \
950 createIncludesFromHeaders(header, 1, include_quotes)
951 res = SCons.Conftest.CheckHeader(context, hdr_to_check, prog_prefix,
952 language = language,
953 include_quotes = include_quotes)
954 context.did_show_result = 1
955 return not res
956
961
966
971
976
977
978
980 """
981 A test for a C header file.
982 """
983 return CheckHeader(context, header, include_quotes, language = "C")
984
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 -def CheckLib(context, library = None, symbol = "main",
996 header = None, language = None, autoadd = 1):
997 """
998 A test for a library. See also CheckLibWithHeader.
999 Note that library may also be None to test whether the given symbol
1000 compiles without flags.
1001 """
1002
1003 if not library:
1004 library = [None]
1005
1006 if not SCons.Util.is_List(library):
1007 library = [library]
1008
1009
1010 res = SCons.Conftest.CheckLib(context, library, symbol, header = header,
1011 language = language, autoadd = autoadd)
1012 context.did_show_result = 1
1013 return not res
1014
1015
1016
1017
1020
1021 """
1022 Another (more sophisticated) test for a library.
1023 Checks, if library and header is available for language (may be 'C'
1024 or 'CXX'). Call maybe be a valid expression _with_ a trailing ';'.
1025 As in CheckLib, we support library=None, to test if the call compiles
1026 without extra link flags.
1027 """
1028 prog_prefix, dummy = \
1029 createIncludesFromHeaders(header, 0)
1030 if libs == []:
1031 libs = [None]
1032
1033 if not SCons.Util.is_List(libs):
1034 libs = [libs]
1035
1036 res = SCons.Conftest.CheckLib(context, libs, None, prog_prefix,
1037 call = call, language = language, autoadd = autoadd)
1038 context.did_show_result = 1
1039 return not res
1040
1042 """Simple check if a program exists in the path. Returns the path
1043 for the application, or None if not found.
1044 """
1045 res = SCons.Conftest.CheckProg(context, prog_name)
1046 context.did_show_result = 1
1047 return res
1048
1049
1050
1051
1052
1053
1054