renamed SCons.Tool.ninja -> SCons.Tool.ninja_tool and added alias in tool loading...
[scons.git] / SCons / Tool / __init__.py
blob23b7eeba2ca161992886ba75f2db9a11efee6c4a
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """SCons tool selection.
26 Looks for modules that define a callable object that can modify a
27 construction environment as appropriate for a given tool (or tool chain).
29 Note that because this subsystem just *selects* a callable that can
30 modify a construction environment, it's possible for people to define
31 their own "tool specification" in an arbitrary callable function. No
32 one needs to use or tie in to this subsystem in order to roll their own
33 tool specifications.
34 """
36 from __future__ import annotations
38 import sys
39 import os
40 import importlib.util
42 import SCons.Builder
43 import SCons.Errors
44 import SCons.Node.FS
45 import SCons.Scanner
46 import SCons.Scanner.C
47 import SCons.Scanner.D
48 import SCons.Scanner.Java
49 import SCons.Scanner.LaTeX
50 import SCons.Scanner.Prog
51 import SCons.Scanner.SWIG
52 from SCons.Tool.linkCommon import LibSymlinksActionFunction, LibSymlinksStrFun
54 DefaultToolpath = []
56 CScanner = SCons.Scanner.C.CScanner()
57 DScanner = SCons.Scanner.D.DScanner()
58 JavaScanner = SCons.Scanner.Java.JavaScanner()
59 LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner()
60 PDFLaTeXScanner = SCons.Scanner.LaTeX.PDFLaTeXScanner()
61 ProgramScanner = SCons.Scanner.Prog.ProgramScanner()
62 SourceFileScanner = SCons.Scanner.ScannerBase({}, name='SourceFileScanner')
63 SWIGScanner = SCons.Scanner.SWIG.SWIGScanner()
65 CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
66 ".h", ".H", ".hxx", ".hpp", ".hh",
67 ".F", ".fpp", ".FPP",
68 ".m", ".mm",
69 ".S", ".spp", ".SPP", ".sx"]
71 DSuffixes = ['.d']
73 IDLSuffixes = [".idl", ".IDL"]
75 LaTeXSuffixes = [".tex", ".ltx", ".latex"]
77 SWIGSuffixes = ['.i']
79 for suffix in CSuffixes:
80 SourceFileScanner.add_scanner(suffix, CScanner)
82 for suffix in DSuffixes:
83 SourceFileScanner.add_scanner(suffix, DScanner)
85 for suffix in SWIGSuffixes:
86 SourceFileScanner.add_scanner(suffix, SWIGScanner)
88 # FIXME: what should be done here? Two scanners scan the same extensions,
89 # but look for different files, e.g., "picture.eps" vs. "picture.pdf".
90 # The builders for DVI and PDF explicitly reference their scanners
91 # I think that means this is not needed???
92 for suffix in LaTeXSuffixes:
93 SourceFileScanner.add_scanner(suffix, LaTeXScanner)
94 SourceFileScanner.add_scanner(suffix, PDFLaTeXScanner)
96 # Tool aliases are needed for those tools whose module names also
97 # occur in the python standard library (This causes module shadowing and
98 # can break using python library functions under python3) or if the current tool/file names
99 # are not legal module names (violate python's identifier rules or are
100 # python language keywords).
101 TOOL_ALIASES = {
102 'gettext': 'gettext_tool',
103 'clang++': 'clangxx',
104 'as': 'asm',
105 'ninja' : 'ninja_tool'
109 class Tool:
110 def __init__(self, name, toolpath=None, **kwargs) -> None:
111 if toolpath is None:
112 toolpath = []
114 # Rename if there's a TOOL_ALIAS for this tool
115 self.name = TOOL_ALIASES.get(name, name)
116 self.toolpath = toolpath + DefaultToolpath
117 # remember these so we can merge them into the call
118 self.init_kw = kwargs
120 module = self._tool_module()
121 self.generate = module.generate
122 self.exists = module.exists
123 if hasattr(module, 'options'):
124 self.options = module.options
126 def _tool_module(self):
127 """Try to load a tool module.
129 This will hunt in the toolpath for both a Python file (toolname.py)
130 and a Python module (toolname directory), then try the regular
131 import machinery, then fallback to try a zipfile.
133 oldpythonpath = sys.path
134 sys.path = self.toolpath + sys.path
135 # These could be enabled under "if debug:"
136 # sys.stderr.write(f"Tool: {self.name}\n")
137 # sys.stderr.write(f"PATH: {sys.path}\n")
138 # sys.stderr.write(f"toolpath: {self.toolpath}\n")
139 # sys.stderr.write(f"SCONS.TOOL path: {sys.modules['SCons.Tool'].__path__}\n")
140 debug = False
141 spec = None
142 found_name = self.name
143 add_to_scons_tools_namespace = False
145 # Search for the tool module, but don't import it, yet.
147 # First look in the toolpath: these take priority.
148 # TODO: any reason to not just use find_spec here?
149 for path in self.toolpath:
150 sepname = self.name.replace('.', os.path.sep)
151 file_path = os.path.join(path, sepname + ".py")
152 file_package = os.path.join(path, sepname)
154 if debug: sys.stderr.write(f"Trying: {file_path} {file_package}\n")
156 if os.path.isfile(file_path):
157 spec = importlib.util.spec_from_file_location(self.name, file_path)
158 if debug: sys.stderr.write(f"file_Path: {file_path} FOUND\n")
159 break
160 elif os.path.isdir(file_package):
161 file_package = os.path.join(file_package, '__init__.py')
162 spec = importlib.util.spec_from_file_location(self.name, file_package)
163 if debug: sys.stderr.write(f"PACKAGE: {file_package} Found\n")
164 break
165 else:
166 continue
168 # Now look in the builtin tools (SCons.Tool package)
169 if spec is None:
170 if debug: sys.stderr.write(f"NO SPEC: {self.name}\n")
171 spec = importlib.util.find_spec("." + self.name, package='SCons.Tool')
172 if spec:
173 found_name = 'SCons.Tool.' + self.name
174 add_to_scons_tools_namespace = True
175 if debug: sys.stderr.write(f"Spec Found? .{self.name}: {spec}\n")
177 if spec is None:
178 # we are going to bail out here, format up stuff for the msg
179 sconstools = os.path.normpath(sys.modules['SCons.Tool'].__path__[0])
180 if self.toolpath:
181 sconstools = ", ".join(self.toolpath) + ", " + sconstools
182 msg = f"No tool module '{self.name}' found in {sconstools}"
183 raise SCons.Errors.UserError(msg)
185 # We have a module spec, so we're good to go.
186 module = importlib.util.module_from_spec(spec)
187 if module is None:
188 if debug: sys.stderr.write(f"MODULE IS NONE: {self.name}\n")
189 msg = f"Tool module '{self.name}' failed import"
190 raise SCons.Errors.SConsEnvironmentError(msg)
192 # Don't reload a tool we already loaded.
193 sys_modules_value = sys.modules.get(found_name, False)
195 found_module = None
196 if sys_modules_value and sys_modules_value.__file__ == spec.origin:
197 found_module = sys.modules[found_name]
198 else:
199 # Not sure what to do in the case that there already
200 # exists sys.modules[self.name] but the source file is
201 # different.. ?
202 sys.modules[found_name] = module
203 spec.loader.exec_module(module)
204 if add_to_scons_tools_namespace:
205 # If we found it in SCons.Tool, add it to the module
206 setattr(SCons.Tool, self.name, module)
207 found_module = module
209 if found_module is not None:
210 sys.path = oldpythonpath
211 return found_module
213 sys.path = oldpythonpath
215 # We try some other things here, but this is essentially dead code,
216 # because we bailed out above if we didn't find a module spec.
217 full_name = 'SCons.Tool.' + self.name
218 try:
219 return sys.modules[full_name]
220 except KeyError:
221 try:
222 # This support was added to enable running inside
223 # a py2exe bundle a long time ago - unclear if it's
224 # still needed. It is *not* intended to load individual
225 # tool modules stored in a zipfile.
226 import zipimport
228 tooldir = sys.modules['SCons.Tool'].__path__[0]
229 importer = zipimport.zipimporter(tooldir)
230 if not hasattr(importer, 'find_spec'):
231 # zipimport only added find_spec, exec_module in 3.10,
232 # unlike importlib, where they've been around since 3.4.
233 # If we don't have 'em, use the old way.
234 module = importer.load_module(full_name)
235 else:
236 spec = importer.find_spec(full_name)
237 module = importlib.util.module_from_spec(spec)
238 importer.exec_module(module)
239 sys.modules[full_name] = module
240 setattr(SCons.Tool, self.name, module)
241 return module
242 except zipimport.ZipImportError as e:
243 msg = "No tool named '{self.name}': {e}"
244 raise SCons.Errors.SConsEnvironmentError(msg)
246 def __call__(self, env, *args, **kw) -> None:
247 if self.init_kw is not None:
248 # Merge call kws into init kws;
249 # but don't bash self.init_kw.
250 if kw is not None:
251 call_kw = kw
252 kw = self.init_kw.copy()
253 kw.update(call_kw)
254 else:
255 kw = self.init_kw
256 env.AppendUnique(TOOLS=[self.name])
257 if hasattr(self, 'options'):
258 import SCons.Variables
259 if 'options' not in env:
260 from SCons.Script import ARGUMENTS
261 env['options'] = SCons.Variables.Variables(args=ARGUMENTS)
262 opts = env['options']
264 self.options(opts)
265 opts.Update(env)
267 self.generate(env, *args, **kw)
269 def __str__(self) -> str:
270 return self.name
273 LibSymlinksAction = SCons.Action.Action(LibSymlinksActionFunction, LibSymlinksStrFun)
276 ##########################################################################
277 # Create common executable program / library / object builders
279 def createProgBuilder(env):
280 """This is a utility function that creates the Program
281 Builder in an Environment if it is not there already.
283 If it is already there, we return the existing one.
286 try:
287 program = env['BUILDERS']['Program']
288 except KeyError:
289 import SCons.Defaults
290 program = SCons.Builder.Builder(action=SCons.Defaults.LinkAction,
291 emitter='$PROGEMITTER',
292 prefix='$PROGPREFIX',
293 suffix='$PROGSUFFIX',
294 src_suffix='$OBJSUFFIX',
295 src_builder='Object',
296 target_scanner=ProgramScanner)
297 env['BUILDERS']['Program'] = program
299 return program
302 def createStaticLibBuilder(env):
303 """This is a utility function that creates the StaticLibrary
304 Builder in an Environment if it is not there already.
306 If it is already there, we return the existing one.
309 try:
310 static_lib = env['BUILDERS']['StaticLibrary']
311 except KeyError:
312 action_list = [SCons.Action.Action("$ARCOM", "$ARCOMSTR")]
313 if env.get('RANLIB', False) or env.Detect('ranlib'):
314 ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR")
315 action_list.append(ranlib_action)
317 static_lib = SCons.Builder.Builder(action=action_list,
318 emitter='$LIBEMITTER',
319 prefix='$LIBPREFIX',
320 suffix='$LIBSUFFIX',
321 src_suffix='$OBJSUFFIX',
322 src_builder='StaticObject')
323 env['BUILDERS']['StaticLibrary'] = static_lib
324 env['BUILDERS']['Library'] = static_lib
326 return static_lib
329 def createSharedLibBuilder(env, shlib_suffix: str='$_SHLIBSUFFIX'):
330 """This is a utility function that creates the SharedLibrary
331 Builder in an Environment if it is not there already.
333 If it is already there, we return the existing one.
335 Args:
336 shlib_suffix: The suffix specified for the shared library builder
340 try:
341 shared_lib = env['BUILDERS']['SharedLibrary']
342 except KeyError:
343 import SCons.Defaults
344 action_list = [SCons.Defaults.SharedCheck,
345 SCons.Defaults.ShLinkAction,
346 LibSymlinksAction]
347 shared_lib = SCons.Builder.Builder(action=action_list,
348 emitter="$SHLIBEMITTER",
349 prefix="$SHLIBPREFIX",
350 suffix=shlib_suffix,
351 target_scanner=ProgramScanner,
352 src_suffix='$SHOBJSUFFIX',
353 src_builder='SharedObject')
354 env['BUILDERS']['SharedLibrary'] = shared_lib
356 return shared_lib
359 def createLoadableModuleBuilder(env, loadable_module_suffix: str='$_LDMODULESUFFIX'):
360 """This is a utility function that creates the LoadableModule
361 Builder in an Environment if it is not there already.
363 If it is already there, we return the existing one.
365 Args:
366 loadable_module_suffix: The suffix specified for the loadable module builder
370 try:
371 ld_module = env['BUILDERS']['LoadableModule']
372 except KeyError:
373 import SCons.Defaults
374 action_list = [SCons.Defaults.SharedCheck,
375 SCons.Defaults.LdModuleLinkAction,
376 LibSymlinksAction]
377 ld_module = SCons.Builder.Builder(action=action_list,
378 emitter="$LDMODULEEMITTER",
379 prefix="$LDMODULEPREFIX",
380 suffix=loadable_module_suffix,
381 target_scanner=ProgramScanner,
382 src_suffix='$SHOBJSUFFIX',
383 src_builder='SharedObject')
384 env['BUILDERS']['LoadableModule'] = ld_module
386 return ld_module
389 def createObjBuilders(env):
390 """This is a utility function that creates the StaticObject
391 and SharedObject Builders in an Environment if they
392 are not there already.
394 If they are there already, we return the existing ones.
396 This is a separate function because soooo many Tools
397 use this functionality.
399 The return is a 2-tuple of (StaticObject, SharedObject)
402 try:
403 static_obj = env['BUILDERS']['StaticObject']
404 except KeyError:
405 static_obj = SCons.Builder.Builder(action={},
406 emitter={},
407 prefix='$OBJPREFIX',
408 suffix='$OBJSUFFIX',
409 src_builder=['CFile', 'CXXFile'],
410 source_scanner=SourceFileScanner,
411 single_source=1)
412 env['BUILDERS']['StaticObject'] = static_obj
413 env['BUILDERS']['Object'] = static_obj
415 try:
416 shared_obj = env['BUILDERS']['SharedObject']
417 except KeyError:
418 shared_obj = SCons.Builder.Builder(action={},
419 emitter={},
420 prefix='$SHOBJPREFIX',
421 suffix='$SHOBJSUFFIX',
422 src_builder=['CFile', 'CXXFile'],
423 source_scanner=SourceFileScanner,
424 single_source=1)
425 env['BUILDERS']['SharedObject'] = shared_obj
427 return (static_obj, shared_obj)
430 def createCFileBuilders(env):
431 """This is a utility function that creates the CFile/CXXFile
432 Builders in an Environment if they
433 are not there already.
435 If they are there already, we return the existing ones.
437 This is a separate function because soooo many Tools
438 use this functionality.
440 The return is a 2-tuple of (CFile, CXXFile)
443 try:
444 c_file = env['BUILDERS']['CFile']
445 except KeyError:
446 c_file = SCons.Builder.Builder(action={},
447 emitter={},
448 suffix={None: '$CFILESUFFIX'})
449 env['BUILDERS']['CFile'] = c_file
451 env.SetDefault(CFILESUFFIX='.c')
453 try:
454 cxx_file = env['BUILDERS']['CXXFile']
455 except KeyError:
456 cxx_file = SCons.Builder.Builder(action={},
457 emitter={},
458 suffix={None: '$CXXFILESUFFIX'})
459 env['BUILDERS']['CXXFile'] = cxx_file
460 env.SetDefault(CXXFILESUFFIX='.cc')
462 return (c_file, cxx_file)
465 ##########################################################################
466 # Create common Java builders
468 def CreateJarBuilder(env):
469 """The Jar builder expects a list of class files
470 which it can package into a jar file.
472 The jar tool provides an interface for passing other types
473 of java files such as .java, directories or swig interfaces
474 and will build them to class files in which it can package
475 into the jar.
477 try:
478 java_jar = env['BUILDERS']['JarFile']
479 except KeyError:
480 fs = SCons.Node.FS.get_default_fs()
481 jar_com = SCons.Action.Action('$JARCOM', '$JARCOMSTR')
482 java_jar = SCons.Builder.Builder(action=jar_com,
483 suffix='$JARSUFFIX',
484 src_suffix='$JAVACLASSSUFFIX',
485 src_builder='JavaClassFile',
486 source_factory=fs.Entry)
487 env['BUILDERS']['JarFile'] = java_jar
488 return java_jar
491 def CreateJavaHBuilder(env):
492 try:
493 java_javah = env['BUILDERS']['JavaH']
494 except KeyError:
495 fs = SCons.Node.FS.get_default_fs()
496 java_javah_com = SCons.Action.Action('$JAVAHCOM', '$JAVAHCOMSTR')
497 java_javah = SCons.Builder.Builder(action=java_javah_com,
498 src_suffix='$JAVACLASSSUFFIX',
499 target_factory=fs.Entry,
500 source_factory=fs.File,
501 src_builder='JavaClassFile')
502 env['BUILDERS']['JavaH'] = java_javah
503 return java_javah
506 def CreateJavaClassFileBuilder(env):
507 try:
508 java_class_file = env['BUILDERS']['JavaClassFile']
509 except KeyError:
510 fs = SCons.Node.FS.get_default_fs()
511 javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
512 java_class_file = SCons.Builder.Builder(action=javac_com,
513 emitter={},
514 # suffix = '$JAVACLASSSUFFIX',
515 src_suffix='$JAVASUFFIX',
516 src_builder=['JavaFile'],
517 target_factory=fs.Entry,
518 source_factory=fs.File,
519 target_scanner=JavaScanner)
520 env['BUILDERS']['JavaClassFile'] = java_class_file
521 return java_class_file
524 def CreateJavaClassDirBuilder(env):
525 try:
526 java_class_dir = env['BUILDERS']['JavaClassDir']
527 except KeyError:
528 fs = SCons.Node.FS.get_default_fs()
529 javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
530 java_class_dir = SCons.Builder.Builder(action=javac_com,
531 emitter={},
532 target_factory=fs.Dir,
533 source_factory=fs.Dir,
534 target_scanner=JavaScanner)
535 env['BUILDERS']['JavaClassDir'] = java_class_dir
536 return java_class_dir
539 def CreateJavaFileBuilder(env):
540 try:
541 java_file = env['BUILDERS']['JavaFile']
542 except KeyError:
543 java_file = SCons.Builder.Builder(action={},
544 emitter={},
545 suffix={None: '$JAVASUFFIX'})
546 env['BUILDERS']['JavaFile'] = java_file
547 env['JAVASUFFIX'] = '.java'
548 return java_file
551 class ToolInitializerMethod:
553 This is added to a construction environment in place of a
554 method(s) normally called for a Builder (env.Object, env.StaticObject,
555 etc.). When called, it has its associated ToolInitializer
556 object search the specified list of tools and apply the first
557 one that exists to the construction environment. It then calls
558 whatever builder was (presumably) added to the construction
559 environment in place of this particular instance.
562 def __init__(self, name, initializer) -> None:
564 Note: we store the tool name as __name__ so it can be used by
565 the class that attaches this to a construction environment.
567 self.__name__ = name
568 self.initializer = initializer
570 def get_builder(self, env):
572 Returns the appropriate real Builder for this method name
573 after having the associated ToolInitializer object apply
574 the appropriate Tool module.
576 builder = getattr(env, self.__name__)
578 self.initializer.apply_tools(env)
580 builder = getattr(env, self.__name__)
581 if builder is self:
582 # There was no Builder added, which means no valid Tool
583 # for this name was found (or possibly there's a mismatch
584 # between the name we were called by and the Builder name
585 # added by the Tool module).
586 return None
588 self.initializer.remove_methods(env)
590 return builder
592 def __call__(self, env, *args, **kw):
595 builder = self.get_builder(env)
596 if builder is None:
597 return [], []
598 return builder(*args, **kw)
601 class ToolInitializer:
603 A class for delayed initialization of Tools modules.
605 Instances of this class associate a list of Tool modules with
606 a list of Builder method names that will be added by those Tool
607 modules. As part of instantiating this object for a particular
608 construction environment, we also add the appropriate
609 ToolInitializerMethod objects for the various Builder methods
610 that we want to use to delay Tool searches until necessary.
613 def __init__(self, env, tools, names) -> None:
614 if not SCons.Util.is_List(tools):
615 tools = [tools]
616 if not SCons.Util.is_List(names):
617 names = [names]
618 self.env = env
619 self.tools = tools
620 self.names = names
621 self.methods = {}
622 for name in names:
623 method = ToolInitializerMethod(name, self)
624 self.methods[name] = method
625 env.AddMethod(method)
627 def remove_methods(self, env) -> None:
629 Removes the methods that were added by the tool initialization
630 so we no longer copy and re-bind them when the construction
631 environment gets cloned.
633 for method in self.methods.values():
634 env.RemoveMethod(method)
636 def apply_tools(self, env) -> None:
638 Searches the list of associated Tool modules for one that
639 exists, and applies that to the construction environment.
641 for t in self.tools:
642 tool = SCons.Tool.Tool(t)
643 if tool.exists(env):
644 env.Tool(tool)
645 return
647 # If we fall through here, there was no tool module found.
648 # This is where we can put an informative error message
649 # about the inability to find the tool. We'll start doing
650 # this as we cut over more pre-defined Builder+Tools to use
651 # the ToolInitializer class.
654 def Initializers(env) -> None:
655 ToolInitializer(env, ['install'], ['_InternalInstall', '_InternalInstallAs', '_InternalInstallVersionedLib'])
657 def Install(self, *args, **kw):
658 return self._InternalInstall(*args, **kw)
660 def InstallAs(self, *args, **kw):
661 return self._InternalInstallAs(*args, **kw)
663 def InstallVersionedLib(self, *args, **kw):
664 return self._InternalInstallVersionedLib(*args, **kw)
666 env.AddMethod(Install)
667 env.AddMethod(InstallAs)
668 env.AddMethod(InstallVersionedLib)
671 def FindTool(tools, env):
672 for tool in tools:
673 t = Tool(tool)
674 if t.exists(env):
675 return tool
676 return None
679 def FindAllTools(tools, env):
680 def ToolExists(tool, env=env):
681 return Tool(tool).exists(env)
683 return list(filter(ToolExists, tools))
686 def tool_list(platform, env):
687 other_plat_tools = []
688 # XXX this logic about what tool to prefer on which platform
689 # should be moved into either the platform files or
690 # the tool files themselves.
691 # The search orders here are described in the man page. If you
692 # change these search orders, update the man page as well.
693 if str(platform) == 'win32':
694 "prefer Microsoft tools on Windows"
695 linkers = ['mslink', 'gnulink', 'ilink', 'linkloc', 'ilink32']
696 c_compilers = ['msvc', 'mingw', 'gcc', 'clang', 'intelc', 'icl', 'icc', 'cc', 'bcc32']
697 cxx_compilers = ['msvc', 'intelc', 'icc', 'g++', 'clang++', 'cxx', 'bcc32']
698 assemblers = ['masm', 'nasm', 'gas', '386asm']
699 fortran_compilers = ['gfortran', 'g77', 'ifl', 'cvf', 'f95', 'f90', 'fortran']
700 ars = ['mslib', 'ar', 'tlib']
701 other_plat_tools = ['msvs', 'midl', 'wix']
702 elif str(platform) == 'os2':
703 "prefer IBM tools on OS/2"
704 linkers = ['ilink', 'gnulink', ] # 'mslink']
705 c_compilers = ['icc', 'gcc', ] # 'msvc', 'cc']
706 cxx_compilers = ['icc', 'g++', ] # 'msvc', 'cxx']
707 assemblers = ['nasm', ] # 'masm', 'gas']
708 fortran_compilers = ['ifl', 'g77']
709 ars = ['ar', ] # 'mslib']
710 elif str(platform) == 'irix':
711 "prefer MIPSPro on IRIX"
712 linkers = ['sgilink', 'gnulink']
713 c_compilers = ['sgicc', 'gcc', 'cc']
714 cxx_compilers = ['sgicxx', 'g++', 'cxx']
715 assemblers = ['as', 'gas']
716 fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
717 ars = ['sgiar']
718 elif str(platform) == 'sunos':
719 "prefer Forte tools on SunOS"
720 linkers = ['sunlink', 'gnulink']
721 c_compilers = ['suncc', 'gcc', 'cc']
722 cxx_compilers = ['suncxx', 'g++', 'cxx']
723 assemblers = ['as', 'gas']
724 fortran_compilers = ['sunf95', 'sunf90', 'sunf77', 'f95', 'f90', 'f77',
725 'gfortran', 'g77', 'fortran']
726 ars = ['sunar']
727 elif str(platform) == 'hpux':
728 "prefer aCC tools on HP-UX"
729 linkers = ['hplink', 'gnulink']
730 c_compilers = ['hpcc', 'gcc', 'cc']
731 cxx_compilers = ['hpcxx', 'g++', 'cxx']
732 assemblers = ['as', 'gas']
733 fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
734 ars = ['ar']
735 elif str(platform) == 'aix':
736 "prefer AIX Visual Age tools on AIX"
737 linkers = ['aixlink', 'gnulink']
738 c_compilers = ['aixcc', 'gcc', 'cc']
739 cxx_compilers = ['aixcxx', 'g++', 'cxx']
740 assemblers = ['as', 'gas']
741 fortran_compilers = ['f95', 'f90', 'aixf77', 'g77', 'fortran']
742 ars = ['ar']
743 elif str(platform) == 'darwin':
744 "prefer GNU tools on Mac OS X, except for some linkers and IBM tools"
745 linkers = ['applelink', 'gnulink']
746 c_compilers = ['gcc', 'cc']
747 cxx_compilers = ['g++', 'cxx']
748 assemblers = ['as']
749 fortran_compilers = ['gfortran', 'f95', 'f90', 'g77']
750 ars = ['ar']
751 elif str(platform) == 'cygwin':
752 "prefer GNU tools on Cygwin, except for a platform-specific linker"
753 linkers = ['cyglink', 'mslink', 'ilink']
754 c_compilers = ['gcc', 'msvc', 'intelc', 'icc', 'cc']
755 cxx_compilers = ['g++', 'msvc', 'intelc', 'icc', 'cxx']
756 assemblers = ['gas', 'nasm', 'masm']
757 fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
758 ars = ['ar', 'mslib']
759 else:
760 "prefer GNU tools on all other platforms"
761 linkers = ['gnulink', 'ilink']
762 c_compilers = ['gcc', 'clang', 'intelc', 'icc', 'cc']
763 cxx_compilers = ['g++', 'clang++', 'intelc', 'icc', 'cxx']
764 assemblers = ['gas', 'nasm', 'masm']
765 fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
766 ars = ['ar', ]
768 if not str(platform) == 'win32':
769 other_plat_tools += ['m4', 'rpm']
771 c_compiler = FindTool(c_compilers, env) or c_compilers[0]
773 # XXX this logic about what tool provides what should somehow be
774 # moved into the tool files themselves.
775 if c_compiler and c_compiler == 'mingw':
776 # MinGW contains a linker, C compiler, C++ compiler,
777 # Fortran compiler, archiver and assembler:
778 cxx_compiler = None
779 linker = None
780 assembler = None
781 fortran_compiler = None
782 ar = None
783 else:
784 # Don't use g++ if the C compiler has built-in C++ support:
785 if c_compiler in ('msvc', 'intelc', 'icc'):
786 cxx_compiler = None
787 else:
788 cxx_compiler = FindTool(cxx_compilers, env) or cxx_compilers[0]
789 linker = FindTool(linkers, env) or linkers[0]
790 assembler = FindTool(assemblers, env) or assemblers[0]
791 fortran_compiler = FindTool(fortran_compilers, env) or fortran_compilers[0]
792 ar = FindTool(ars, env) or ars[0]
794 d_compilers = ['dmd', 'ldc', 'gdc']
795 d_compiler = FindTool(d_compilers, env) or d_compilers[0]
797 other_tools = FindAllTools(other_plat_tools + [
798 # TODO: merge 'install' into 'filesystem' and
799 # make 'filesystem' the default
800 'filesystem',
801 # Parser generators
802 'lex', 'yacc',
803 # Foreign function interface
804 'rpcgen', 'swig',
805 # Java
806 'jar', 'javac', 'javah', 'rmic',
807 # TeX
808 'dvipdf', 'dvips', 'gs',
809 'tex', 'latex', 'pdflatex', 'pdftex',
810 # Archivers
811 'tar', 'zip',
812 # File builders (text)
813 'textfile',
814 ], env)
816 tools = [
817 linker,
818 c_compiler,
819 cxx_compiler,
820 fortran_compiler,
821 assembler,
823 d_compiler,
824 ] + other_tools
826 return [x for x in tools if x]
829 def find_program_path(env, key_program, default_paths=None, add_path: bool=False) -> str | None:
831 Find the location of a tool using various means.
833 Mainly for windows where tools aren't all installed in /usr/bin, etc.
835 Args:
836 env: Current Construction Environment.
837 key_program: Tool to locate.
838 default_paths: List of additional paths this tool might be found in.
839 add_path: If true, add path found if it was from *default_paths*.
841 # First search in the SCons path
842 path = env.WhereIs(key_program)
843 if path:
844 return path
846 # Then in the OS path
847 path = SCons.Util.WhereIs(key_program)
848 if path:
849 if add_path:
850 env.AppendENVPath('PATH', os.path.dirname(path))
851 return path
853 # Finally, add the defaults and check again.
854 if default_paths is None:
855 return path
857 save_path = env['ENV']['PATH']
858 for p in default_paths:
859 env.AppendENVPath('PATH', p)
860 path = env.WhereIs(key_program)
862 # By default, do not change ['ENV']['PATH'] permananetly
863 # leave that to the caller, unless add_path is true.
864 env['ENV']['PATH'] = save_path
865 if path and add_path:
866 env.AppendENVPath('PATH', os.path.dirname(path))
868 return path
870 # Local Variables:
871 # tab-width:4
872 # indent-tabs-mode:nil
873 # End:
874 # vim: set expandtab tabstop=4 shiftwidth=4: