Merge pull request #4655 from bdbaddog/fix_new_ninja_package
[scons.git] / SCons / Tool / MSCommon / MSVC / SetupEnvDefault.py
blob76b9ed654b4a8fe97b5121563bb62a764cff8557
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 """
25 Determine if and/or when an error/warning should be issued when there
26 are no versions of msvc installed. If there is at least one version of
27 msvc installed, these routines do (almost) nothing.
29 Notes:
30 * When msvc is the default compiler because there are no compilers
31 installed, a build may fail due to the cl.exe command not being
32 recognized. Currently, there is no easy way to detect during
33 msvc initialization if the default environment will be used later
34 to build a program and/or library. There is no error/warning
35 as there are legitimate SCons uses that do not require a c compiler.
36 * An error is indicated by returning a non-empty tool list from the
37 function register_iserror.
38 """
40 import re
42 from .. common import (
43 debug,
46 from . import Dispatcher
47 Dispatcher.register_modulename(__name__)
50 class _Data:
52 separator = r';'
54 need_init = True
56 @classmethod
57 def reset(cls) -> None:
58 debug('msvc default:init')
59 cls.n_setup = 0 # number of calls to msvc_setup_env_once
60 cls.default_ismsvc = False # is msvc the default compiler
61 cls.default_tools_re_list = [] # list of default tools regular expressions
62 cls.msvc_tools_init = set() # tools registered via msvc_exists
63 cls.msvc_tools = None # tools registered via msvc_setup_env_once
64 cls.msvc_installed = False # is msvc installed (vcs_installed > 0)
65 cls.msvc_nodefault = False # is there a default version of msvc
66 cls.need_init = True # reset initialization indicator
68 def _initialize(env, msvc_exists_func) -> None:
69 if _Data.need_init:
70 _Data.reset()
71 _Data.need_init = False
72 _Data.msvc_installed = msvc_exists_func(env)
73 debug('msvc default:msvc_installed=%s', _Data.msvc_installed)
75 def register_tool(env, tool, msvc_exists_func):
76 if _Data.need_init:
77 _initialize(env, msvc_exists_func)
78 if _Data.msvc_installed:
79 return None
80 if not tool:
81 return None
82 if _Data.n_setup == 0:
83 if tool not in _Data.msvc_tools_init:
84 _Data.msvc_tools_init.add(tool)
85 debug('msvc default:tool=%s, msvc_tools_init=%s', tool, _Data.msvc_tools_init)
86 return None
87 if tool not in _Data.msvc_tools:
88 _Data.msvc_tools.add(tool)
89 debug('msvc default:tool=%s, msvc_tools=%s', tool, _Data.msvc_tools)
91 def register_setup(env, msvc_exists_func) -> None:
92 if _Data.need_init:
93 _initialize(env, msvc_exists_func)
94 _Data.n_setup += 1
95 if not _Data.msvc_installed:
96 _Data.msvc_tools = set(_Data.msvc_tools_init)
97 if _Data.n_setup == 1:
98 tool_list = env.get('TOOLS', None)
99 if tool_list and tool_list[0] == 'default':
100 if len(tool_list) > 1 and tool_list[1] in _Data.msvc_tools:
101 # msvc tools are the default compiler
102 _Data.default_ismsvc = True
103 _Data.msvc_nodefault = False
104 debug(
105 'msvc default:n_setup=%d, msvc_installed=%s, default_ismsvc=%s',
106 _Data.n_setup, _Data.msvc_installed, _Data.default_ismsvc
109 def set_nodefault() -> None:
110 # default msvc version, msvc not installed
111 _Data.msvc_nodefault = True
112 debug('msvc default:msvc_nodefault=%s', _Data.msvc_nodefault)
114 def register_iserror(env, tool, msvc_exists_func):
116 register_tool(env, tool, msvc_exists_func)
118 if _Data.msvc_installed:
119 # msvc installed
120 return None
122 if not _Data.msvc_nodefault:
123 # msvc version specified
124 return None
126 tool_list = env.get('TOOLS', None)
127 if not tool_list:
128 # tool list is empty
129 return None
131 debug(
132 'msvc default:n_setup=%s, default_ismsvc=%s, msvc_tools=%s, tool_list=%s',
133 _Data.n_setup, _Data.default_ismsvc, _Data.msvc_tools, tool_list
136 if not _Data.default_ismsvc:
138 # Summary:
139 # * msvc is not installed
140 # * msvc version not specified (default)
141 # * msvc is not the default compiler
143 # construct tools set
144 tools_set = set(tool_list)
146 else:
148 if _Data.n_setup == 1:
149 # first setup and msvc is default compiler:
150 # build default tools regex for current tool state
151 tools = _Data.separator.join(tool_list)
152 tools_nchar = len(tools)
153 debug('msvc default:add regex:nchar=%d, tools=%s', tools_nchar, tools)
154 re_default_tools = re.compile(re.escape(tools))
155 _Data.default_tools_re_list.insert(0, (tools_nchar, re_default_tools))
156 # early exit: no error for default environment when msvc is not installed
157 return None
159 # Summary:
160 # * msvc is not installed
161 # * msvc version not specified (default)
162 # * environment tools list is not empty
163 # * default tools regex list constructed
164 # * msvc tools set constructed
166 # Algorithm using tools string and sets:
167 # * convert environment tools list to a string
168 # * iteratively remove default tools sequences via regex
169 # substition list built from longest sequence (first)
170 # to shortest sequence (last)
171 # * build environment tools set with remaining tools
172 # * compute intersection of environment tools and msvc tools sets
173 # * if the intersection is:
174 # empty - no error: default tools and/or no additional msvc tools
175 # not empty - error: user specified one or more msvc tool(s)
177 # This will not produce an error or warning when there are no
178 # msvc installed instances nor any other recognized compilers
179 # and the default environment is needed for a build. The msvc
180 # compiler is forcibly added to the environment tools list when
181 # there are no compilers installed on win32. In this case, cl.exe
182 # will not be found on the path resulting in a failed build.
184 # construct tools string
185 tools = _Data.separator.join(tool_list)
186 tools_nchar = len(tools)
188 debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
190 # iteratively remove default tool sequences (longest to shortest)
191 if not _Data.default_tools_re_list:
192 debug('default_tools_re_list=%s', _Data.default_tools_re_list)
193 else:
194 re_nchar_min, re_tools_min = _Data.default_tools_re_list[-1]
195 if tools_nchar >= re_nchar_min and re_tools_min.search(tools):
196 # minimum characters satisfied and minimum pattern exists
197 for re_nchar, re_default_tool in _Data.default_tools_re_list:
198 if tools_nchar < re_nchar:
199 # not enough characters for pattern
200 continue
201 tools = re_default_tool.sub('', tools).strip(_Data.separator)
202 tools_nchar = len(tools)
203 debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
204 if tools_nchar < re_nchar_min or not re_tools_min.search(tools):
205 # less than minimum characters or minimum pattern does not exist
206 break
208 # construct non-default list(s) tools set
209 tools_set = {msvc_tool for msvc_tool in tools.split(_Data.separator) if msvc_tool}
211 debug('msvc default:tools=%s', tools_set)
212 if not tools_set:
213 return None
215 # compute intersection of remaining tools set and msvc tools set
216 tools_found = _Data.msvc_tools.intersection(tools_set)
217 debug('msvc default:tools_exist=%s', tools_found)
218 if not tools_found:
219 return None
221 # construct in same order as tools list
222 tools_found_list = []
223 seen_tool = set()
224 for tool in tool_list:
225 if tool not in seen_tool:
226 seen_tool.add(tool)
227 if tool in tools_found:
228 tools_found_list.append(tool)
230 # return tool list in order presented
231 return tools_found_list
233 def reset() -> None:
234 debug('')
235 _Data.reset()