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