1 | # Copyright (c) 2012 Google Inc. All rights reserved.
|
---|
2 | # Use of this source code is governed by a BSD-style license that can be
|
---|
3 | # found in the LICENSE file.
|
---|
4 |
|
---|
5 | """
|
---|
6 | This module helps emulate Visual Studio 2008 behavior on top of other
|
---|
7 | build systems, primarily ninja.
|
---|
8 | """
|
---|
9 |
|
---|
10 | import os
|
---|
11 | import re
|
---|
12 | import subprocess
|
---|
13 | import sys
|
---|
14 |
|
---|
15 | from gyp.common import OrderedSet
|
---|
16 | import gyp.MSVSUtil
|
---|
17 | import gyp.MSVSVersion
|
---|
18 |
|
---|
19 | PY3 = bytes != str
|
---|
20 |
|
---|
21 | windows_quoter_regex = re.compile(r'(\\*)"')
|
---|
22 |
|
---|
23 |
|
---|
24 | def QuoteForRspFile(arg):
|
---|
25 | """Quote a command line argument so that it appears as one argument when
|
---|
26 | processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for
|
---|
27 | Windows programs)."""
|
---|
28 | # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment
|
---|
29 | # threads. This is actually the quoting rules for CommandLineToArgvW, not
|
---|
30 | # for the shell, because the shell doesn't do anything in Windows. This
|
---|
31 | # works more or less because most programs (including the compiler, etc.)
|
---|
32 | # use that function to handle command line arguments.
|
---|
33 |
|
---|
34 | # Use a heuristic to try to find args that are paths, and normalize them
|
---|
35 | if arg.find("/") > 0 or arg.count("/") > 1:
|
---|
36 | arg = os.path.normpath(arg)
|
---|
37 |
|
---|
38 | # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes
|
---|
39 | # preceding it, and results in n backslashes + the quote. So we substitute
|
---|
40 | # in 2* what we match, +1 more, plus the quote.
|
---|
41 | arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg)
|
---|
42 |
|
---|
43 | # %'s also need to be doubled otherwise they're interpreted as batch
|
---|
44 | # positional arguments. Also make sure to escape the % so that they're
|
---|
45 | # passed literally through escaping so they can be singled to just the
|
---|
46 | # original %. Otherwise, trying to pass the literal representation that
|
---|
47 | # looks like an environment variable to the shell (e.g. %PATH%) would fail.
|
---|
48 | arg = arg.replace("%", "%%")
|
---|
49 |
|
---|
50 | # These commands are used in rsp files, so no escaping for the shell (via ^)
|
---|
51 | # is necessary.
|
---|
52 |
|
---|
53 | # Finally, wrap the whole thing in quotes so that the above quote rule
|
---|
54 | # applies and whitespace isn't a word break.
|
---|
55 | return '"' + arg + '"'
|
---|
56 |
|
---|
57 |
|
---|
58 | def EncodeRspFileList(args):
|
---|
59 | """Process a list of arguments using QuoteCmdExeArgument."""
|
---|
60 | # Note that the first argument is assumed to be the command. Don't add
|
---|
61 | # quotes around it because then built-ins like 'echo', etc. won't work.
|
---|
62 | # Take care to normpath only the path in the case of 'call ../x.bat' because
|
---|
63 | # otherwise the whole thing is incorrectly interpreted as a path and not
|
---|
64 | # normalized correctly.
|
---|
65 | if not args:
|
---|
66 | return ""
|
---|
67 | if args[0].startswith("call "):
|
---|
68 | call, program = args[0].split(" ", 1)
|
---|
69 | program = call + " " + os.path.normpath(program)
|
---|
70 | else:
|
---|
71 | program = os.path.normpath(args[0])
|
---|
72 | return program + " " + " ".join(QuoteForRspFile(arg) for arg in args[1:])
|
---|
73 |
|
---|
74 |
|
---|
75 | def _GenericRetrieve(root, default, path):
|
---|
76 | """Given a list of dictionary keys |path| and a tree of dicts |root|, find
|
---|
77 | value at path, or return |default| if any of the path doesn't exist."""
|
---|
78 | if not root:
|
---|
79 | return default
|
---|
80 | if not path:
|
---|
81 | return root
|
---|
82 | return _GenericRetrieve(root.get(path[0]), default, path[1:])
|
---|
83 |
|
---|
84 |
|
---|
85 | def _AddPrefix(element, prefix):
|
---|
86 | """Add |prefix| to |element| or each subelement if element is iterable."""
|
---|
87 | if element is None:
|
---|
88 | return element
|
---|
89 | # Note, not Iterable because we don't want to handle strings like that.
|
---|
90 | if isinstance(element, list) or isinstance(element, tuple):
|
---|
91 | return [prefix + e for e in element]
|
---|
92 | else:
|
---|
93 | return prefix + element
|
---|
94 |
|
---|
95 |
|
---|
96 | def _DoRemapping(element, map):
|
---|
97 | """If |element| then remap it through |map|. If |element| is iterable then
|
---|
98 | each item will be remapped. Any elements not found will be removed."""
|
---|
99 | if map is not None and element is not None:
|
---|
100 | if not callable(map):
|
---|
101 | map = map.get # Assume it's a dict, otherwise a callable to do the remap.
|
---|
102 | if isinstance(element, list) or isinstance(element, tuple):
|
---|
103 | element = filter(None, [map(elem) for elem in element])
|
---|
104 | else:
|
---|
105 | element = map(element)
|
---|
106 | return element
|
---|
107 |
|
---|
108 |
|
---|
109 | def _AppendOrReturn(append, element):
|
---|
110 | """If |append| is None, simply return |element|. If |append| is not None,
|
---|
111 | then add |element| to it, adding each item in |element| if it's a list or
|
---|
112 | tuple."""
|
---|
113 | if append is not None and element is not None:
|
---|
114 | if isinstance(element, list) or isinstance(element, tuple):
|
---|
115 | append.extend(element)
|
---|
116 | else:
|
---|
117 | append.append(element)
|
---|
118 | else:
|
---|
119 | return element
|
---|
120 |
|
---|
121 |
|
---|
122 | def _FindDirectXInstallation():
|
---|
123 | """Try to find an installation location for the DirectX SDK. Check for the
|
---|
124 | standard environment variable, and if that doesn't exist, try to find
|
---|
125 | via the registry. May return None if not found in either location."""
|
---|
126 | # Return previously calculated value, if there is one
|
---|
127 | if hasattr(_FindDirectXInstallation, "dxsdk_dir"):
|
---|
128 | return _FindDirectXInstallation.dxsdk_dir
|
---|
129 |
|
---|
130 | dxsdk_dir = os.environ.get("DXSDK_DIR")
|
---|
131 | if not dxsdk_dir:
|
---|
132 | # Setup params to pass to and attempt to launch reg.exe.
|
---|
133 | cmd = ["reg.exe", "query", r"HKLM\Software\Microsoft\DirectX", "/s"]
|
---|
134 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
---|
135 | stdout = p.communicate()[0]
|
---|
136 | if PY3:
|
---|
137 | stdout = stdout.decode("utf-8")
|
---|
138 | for line in stdout.splitlines():
|
---|
139 | if "InstallPath" in line:
|
---|
140 | dxsdk_dir = line.split(" ")[3] + "\\"
|
---|
141 |
|
---|
142 | # Cache return value
|
---|
143 | _FindDirectXInstallation.dxsdk_dir = dxsdk_dir
|
---|
144 | return dxsdk_dir
|
---|
145 |
|
---|
146 |
|
---|
147 | def GetGlobalVSMacroEnv(vs_version):
|
---|
148 | """Get a dict of variables mapping internal VS macro names to their gyp
|
---|
149 | equivalents. Returns all variables that are independent of the target."""
|
---|
150 | env = {}
|
---|
151 | # '$(VSInstallDir)' and '$(VCInstallDir)' are available when and only when
|
---|
152 | # Visual Studio is actually installed.
|
---|
153 | if vs_version.Path():
|
---|
154 | env["$(VSInstallDir)"] = vs_version.Path()
|
---|
155 | env["$(VCInstallDir)"] = os.path.join(vs_version.Path(), "VC") + "\\"
|
---|
156 | # Chromium uses DXSDK_DIR in include/lib paths, but it may or may not be
|
---|
157 | # set. This happens when the SDK is sync'd via src-internal, rather than
|
---|
158 | # by typical end-user installation of the SDK. If it's not set, we don't
|
---|
159 | # want to leave the unexpanded variable in the path, so simply strip it.
|
---|
160 | dxsdk_dir = _FindDirectXInstallation()
|
---|
161 | env["$(DXSDK_DIR)"] = dxsdk_dir if dxsdk_dir else ""
|
---|
162 | # Try to find an installation location for the Windows DDK by checking
|
---|
163 | # the WDK_DIR environment variable, may be None.
|
---|
164 | env["$(WDK_DIR)"] = os.environ.get("WDK_DIR", "")
|
---|
165 | return env
|
---|
166 |
|
---|
167 |
|
---|
168 | def ExtractSharedMSVSSystemIncludes(configs, generator_flags):
|
---|
169 | """Finds msvs_system_include_dirs that are common to all targets, removes
|
---|
170 | them from all targets, and returns an OrderedSet containing them."""
|
---|
171 | all_system_includes = OrderedSet(configs[0].get("msvs_system_include_dirs", []))
|
---|
172 | for config in configs[1:]:
|
---|
173 | system_includes = config.get("msvs_system_include_dirs", [])
|
---|
174 | all_system_includes = all_system_includes & OrderedSet(system_includes)
|
---|
175 | if not all_system_includes:
|
---|
176 | return None
|
---|
177 | # Expand macros in all_system_includes.
|
---|
178 | env = GetGlobalVSMacroEnv(GetVSVersion(generator_flags))
|
---|
179 | expanded_system_includes = OrderedSet(
|
---|
180 | [ExpandMacros(include, env) for include in all_system_includes]
|
---|
181 | )
|
---|
182 | if any(["$" in include for include in expanded_system_includes]):
|
---|
183 | # Some path relies on target-specific variables, bail.
|
---|
184 | return None
|
---|
185 |
|
---|
186 | # Remove system includes shared by all targets from the targets.
|
---|
187 | for config in configs:
|
---|
188 | includes = config.get("msvs_system_include_dirs", [])
|
---|
189 | if includes: # Don't insert a msvs_system_include_dirs key if not needed.
|
---|
190 | # This must check the unexpanded includes list:
|
---|
191 | new_includes = [i for i in includes if i not in all_system_includes]
|
---|
192 | config["msvs_system_include_dirs"] = new_includes
|
---|
193 | return expanded_system_includes
|
---|
194 |
|
---|
195 |
|
---|
196 | class MsvsSettings(object):
|
---|
197 | """A class that understands the gyp 'msvs_...' values (especially the
|
---|
198 | msvs_settings field). They largely correpond to the VS2008 IDE DOM. This
|
---|
199 | class helps map those settings to command line options."""
|
---|
200 |
|
---|
201 | def __init__(self, spec, generator_flags):
|
---|
202 | self.spec = spec
|
---|
203 | self.vs_version = GetVSVersion(generator_flags)
|
---|
204 |
|
---|
205 | supported_fields = [
|
---|
206 | ("msvs_configuration_attributes", dict),
|
---|
207 | ("msvs_settings", dict),
|
---|
208 | ("msvs_system_include_dirs", list),
|
---|
209 | ("msvs_disabled_warnings", list),
|
---|
210 | ("msvs_precompiled_header", str),
|
---|
211 | ("msvs_precompiled_source", str),
|
---|
212 | ("msvs_configuration_platform", str),
|
---|
213 | ("msvs_target_platform", str),
|
---|
214 | ]
|
---|
215 | configs = spec["configurations"]
|
---|
216 | for field, default in supported_fields:
|
---|
217 | setattr(self, field, {})
|
---|
218 | for configname, config in configs.items():
|
---|
219 | getattr(self, field)[configname] = config.get(field, default())
|
---|
220 |
|
---|
221 | self.msvs_cygwin_dirs = spec.get("msvs_cygwin_dirs", ["."])
|
---|
222 |
|
---|
223 | unsupported_fields = [
|
---|
224 | "msvs_prebuild",
|
---|
225 | "msvs_postbuild",
|
---|
226 | ]
|
---|
227 | unsupported = []
|
---|
228 | for field in unsupported_fields:
|
---|
229 | for config in configs.values():
|
---|
230 | if field in config:
|
---|
231 | unsupported += [
|
---|
232 | "%s not supported (target %s)." % (field, spec["target_name"])
|
---|
233 | ]
|
---|
234 | if unsupported:
|
---|
235 | raise Exception("\n".join(unsupported))
|
---|
236 |
|
---|
237 | def GetExtension(self):
|
---|
238 | """Returns the extension for the target, with no leading dot.
|
---|
239 |
|
---|
240 | Uses 'product_extension' if specified, otherwise uses MSVS defaults based on
|
---|
241 | the target type.
|
---|
242 | """
|
---|
243 | ext = self.spec.get("product_extension", None)
|
---|
244 | if ext:
|
---|
245 | return ext
|
---|
246 | return gyp.MSVSUtil.TARGET_TYPE_EXT.get(self.spec["type"], "")
|
---|
247 |
|
---|
248 | def GetVSMacroEnv(self, base_to_build=None, config=None):
|
---|
249 | """Get a dict of variables mapping internal VS macro names to their gyp
|
---|
250 | equivalents."""
|
---|
251 | target_arch = self.GetArch(config)
|
---|
252 | if target_arch == "x86":
|
---|
253 | target_platform = "Win32"
|
---|
254 | else:
|
---|
255 | target_platform = target_arch
|
---|
256 | target_name = self.spec.get("product_prefix", "") + self.spec.get(
|
---|
257 | "product_name", self.spec["target_name"]
|
---|
258 | )
|
---|
259 | target_dir = base_to_build + "\\" if base_to_build else ""
|
---|
260 | target_ext = "." + self.GetExtension()
|
---|
261 | target_file_name = target_name + target_ext
|
---|
262 |
|
---|
263 | replacements = {
|
---|
264 | "$(InputName)": "${root}",
|
---|
265 | "$(InputPath)": "${source}",
|
---|
266 | "$(IntDir)": "$!INTERMEDIATE_DIR",
|
---|
267 | "$(OutDir)\\": target_dir,
|
---|
268 | "$(PlatformName)": target_platform,
|
---|
269 | "$(ProjectDir)\\": "",
|
---|
270 | "$(ProjectName)": self.spec["target_name"],
|
---|
271 | "$(TargetDir)\\": target_dir,
|
---|
272 | "$(TargetExt)": target_ext,
|
---|
273 | "$(TargetFileName)": target_file_name,
|
---|
274 | "$(TargetName)": target_name,
|
---|
275 | "$(TargetPath)": os.path.join(target_dir, target_file_name),
|
---|
276 | }
|
---|
277 | replacements.update(GetGlobalVSMacroEnv(self.vs_version))
|
---|
278 | return replacements
|
---|
279 |
|
---|
280 | def ConvertVSMacros(self, s, base_to_build=None, config=None):
|
---|
281 | """Convert from VS macro names to something equivalent."""
|
---|
282 | env = self.GetVSMacroEnv(base_to_build, config=config)
|
---|
283 | return ExpandMacros(s, env)
|
---|
284 |
|
---|
285 | def AdjustLibraries(self, libraries):
|
---|
286 | """Strip -l from library if it's specified with that."""
|
---|
287 | libs = [lib[2:] if lib.startswith("-l") else lib for lib in libraries]
|
---|
288 | return [
|
---|
289 | lib + ".lib"
|
---|
290 | if not lib.lower().endswith(".lib") and not lib.lower().endswith(".obj")
|
---|
291 | else lib
|
---|
292 | for lib in libs
|
---|
293 | ]
|
---|
294 |
|
---|
295 | def _GetAndMunge(self, field, path, default, prefix, append, map):
|
---|
296 | """Retrieve a value from |field| at |path| or return |default|. If
|
---|
297 | |append| is specified, and the item is found, it will be appended to that
|
---|
298 | object instead of returned. If |map| is specified, results will be
|
---|
299 | remapped through |map| before being returned or appended."""
|
---|
300 | result = _GenericRetrieve(field, default, path)
|
---|
301 | result = _DoRemapping(result, map)
|
---|
302 | result = _AddPrefix(result, prefix)
|
---|
303 | return _AppendOrReturn(append, result)
|
---|
304 |
|
---|
305 | class _GetWrapper(object):
|
---|
306 | def __init__(self, parent, field, base_path, append=None):
|
---|
307 | self.parent = parent
|
---|
308 | self.field = field
|
---|
309 | self.base_path = [base_path]
|
---|
310 | self.append = append
|
---|
311 |
|
---|
312 | def __call__(self, name, map=None, prefix="", default=None):
|
---|
313 | return self.parent._GetAndMunge(
|
---|
314 | self.field,
|
---|
315 | self.base_path + [name],
|
---|
316 | default=default,
|
---|
317 | prefix=prefix,
|
---|
318 | append=self.append,
|
---|
319 | map=map,
|
---|
320 | )
|
---|
321 |
|
---|
322 | def GetArch(self, config):
|
---|
323 | """Get architecture based on msvs_configuration_platform and
|
---|
324 | msvs_target_platform. Returns either 'x86' or 'x64'."""
|
---|
325 | configuration_platform = self.msvs_configuration_platform.get(config, "")
|
---|
326 | platform = self.msvs_target_platform.get(config, "")
|
---|
327 | if not platform: # If no specific override, use the configuration's.
|
---|
328 | platform = configuration_platform
|
---|
329 | # Map from platform to architecture.
|
---|
330 | return {"Win32": "x86", "x64": "x64", "ARM64": "arm64"}.get(platform, "x86")
|
---|
331 |
|
---|
332 | def _TargetConfig(self, config):
|
---|
333 | """Returns the target-specific configuration."""
|
---|
334 | # There's two levels of architecture/platform specification in VS. The
|
---|
335 | # first level is globally for the configuration (this is what we consider
|
---|
336 | # "the" config at the gyp level, which will be something like 'Debug' or
|
---|
337 | # 'Release'), VS2015 and later only use this level
|
---|
338 | if self.vs_version.short_name >= 2015:
|
---|
339 | return config
|
---|
340 | # and a second target-specific configuration, which is an
|
---|
341 | # override for the global one. |config| is remapped here to take into
|
---|
342 | # account the local target-specific overrides to the global configuration.
|
---|
343 | arch = self.GetArch(config)
|
---|
344 | if arch == "x64" and not config.endswith("_x64"):
|
---|
345 | config += "_x64"
|
---|
346 | if arch == "x86" and config.endswith("_x64"):
|
---|
347 | config = config.rsplit("_", 1)[0]
|
---|
348 | return config
|
---|
349 |
|
---|
350 | def _Setting(self, path, config, default=None, prefix="", append=None, map=None):
|
---|
351 | """_GetAndMunge for msvs_settings."""
|
---|
352 | return self._GetAndMunge(
|
---|
353 | self.msvs_settings[config], path, default, prefix, append, map
|
---|
354 | )
|
---|
355 |
|
---|
356 | def _ConfigAttrib(
|
---|
357 | self, path, config, default=None, prefix="", append=None, map=None
|
---|
358 | ):
|
---|
359 | """_GetAndMunge for msvs_configuration_attributes."""
|
---|
360 | return self._GetAndMunge(
|
---|
361 | self.msvs_configuration_attributes[config],
|
---|
362 | path,
|
---|
363 | default,
|
---|
364 | prefix,
|
---|
365 | append,
|
---|
366 | map,
|
---|
367 | )
|
---|
368 |
|
---|
369 | def AdjustIncludeDirs(self, include_dirs, config):
|
---|
370 | """Updates include_dirs to expand VS specific paths, and adds the system
|
---|
371 | include dirs used for platform SDK and similar."""
|
---|
372 | config = self._TargetConfig(config)
|
---|
373 | includes = include_dirs + self.msvs_system_include_dirs[config]
|
---|
374 | includes.extend(
|
---|
375 | self._Setting(
|
---|
376 | ("VCCLCompilerTool", "AdditionalIncludeDirectories"), config, default=[]
|
---|
377 | )
|
---|
378 | )
|
---|
379 | return [self.ConvertVSMacros(p, config=config) for p in includes]
|
---|
380 |
|
---|
381 | def AdjustMidlIncludeDirs(self, midl_include_dirs, config):
|
---|
382 | """Updates midl_include_dirs to expand VS specific paths, and adds the
|
---|
383 | system include dirs used for platform SDK and similar."""
|
---|
384 | config = self._TargetConfig(config)
|
---|
385 | includes = midl_include_dirs + self.msvs_system_include_dirs[config]
|
---|
386 | includes.extend(
|
---|
387 | self._Setting(
|
---|
388 | ("VCMIDLTool", "AdditionalIncludeDirectories"), config, default=[]
|
---|
389 | )
|
---|
390 | )
|
---|
391 | return [self.ConvertVSMacros(p, config=config) for p in includes]
|
---|
392 |
|
---|
393 | def GetComputedDefines(self, config):
|
---|
394 | """Returns the set of defines that are injected to the defines list based
|
---|
395 | on other VS settings."""
|
---|
396 | config = self._TargetConfig(config)
|
---|
397 | defines = []
|
---|
398 | if self._ConfigAttrib(["CharacterSet"], config) == "1":
|
---|
399 | defines.extend(("_UNICODE", "UNICODE"))
|
---|
400 | if self._ConfigAttrib(["CharacterSet"], config) == "2":
|
---|
401 | defines.append("_MBCS")
|
---|
402 | defines.extend(
|
---|
403 | self._Setting(
|
---|
404 | ("VCCLCompilerTool", "PreprocessorDefinitions"), config, default=[]
|
---|
405 | )
|
---|
406 | )
|
---|
407 | return defines
|
---|
408 |
|
---|
409 | def GetCompilerPdbName(self, config, expand_special):
|
---|
410 | """Get the pdb file name that should be used for compiler invocations, or
|
---|
411 | None if there's no explicit name specified."""
|
---|
412 | config = self._TargetConfig(config)
|
---|
413 | pdbname = self._Setting(("VCCLCompilerTool", "ProgramDataBaseFileName"), config)
|
---|
414 | if pdbname:
|
---|
415 | pdbname = expand_special(self.ConvertVSMacros(pdbname))
|
---|
416 | return pdbname
|
---|
417 |
|
---|
418 | def GetMapFileName(self, config, expand_special):
|
---|
419 | """Gets the explicitly overridden map file name for a target or returns None
|
---|
420 | if it's not set."""
|
---|
421 | config = self._TargetConfig(config)
|
---|
422 | map_file = self._Setting(("VCLinkerTool", "MapFileName"), config)
|
---|
423 | if map_file:
|
---|
424 | map_file = expand_special(self.ConvertVSMacros(map_file, config=config))
|
---|
425 | return map_file
|
---|
426 |
|
---|
427 | def GetOutputName(self, config, expand_special):
|
---|
428 | """Gets the explicitly overridden output name for a target or returns None
|
---|
429 | if it's not overridden."""
|
---|
430 | config = self._TargetConfig(config)
|
---|
431 | type = self.spec["type"]
|
---|
432 | root = "VCLibrarianTool" if type == "static_library" else "VCLinkerTool"
|
---|
433 | # TODO(scottmg): Handle OutputDirectory without OutputFile.
|
---|
434 | output_file = self._Setting((root, "OutputFile"), config)
|
---|
435 | if output_file:
|
---|
436 | output_file = expand_special(
|
---|
437 | self.ConvertVSMacros(output_file, config=config)
|
---|
438 | )
|
---|
439 | return output_file
|
---|
440 |
|
---|
441 | def GetPDBName(self, config, expand_special, default):
|
---|
442 | """Gets the explicitly overridden pdb name for a target or returns
|
---|
443 | default if it's not overridden, or if no pdb will be generated."""
|
---|
444 | config = self._TargetConfig(config)
|
---|
445 | output_file = self._Setting(("VCLinkerTool", "ProgramDatabaseFile"), config)
|
---|
446 | generate_debug_info = self._Setting(
|
---|
447 | ("VCLinkerTool", "GenerateDebugInformation"), config
|
---|
448 | )
|
---|
449 | if generate_debug_info == "true":
|
---|
450 | if output_file:
|
---|
451 | return expand_special(self.ConvertVSMacros(output_file, config=config))
|
---|
452 | else:
|
---|
453 | return default
|
---|
454 | else:
|
---|
455 | return None
|
---|
456 |
|
---|
457 | def GetNoImportLibrary(self, config):
|
---|
458 | """If NoImportLibrary: true, ninja will not expect the output to include
|
---|
459 | an import library."""
|
---|
460 | config = self._TargetConfig(config)
|
---|
461 | noimplib = self._Setting(("NoImportLibrary",), config)
|
---|
462 | return noimplib == "true"
|
---|
463 |
|
---|
464 | def GetAsmflags(self, config):
|
---|
465 | """Returns the flags that need to be added to ml invocations."""
|
---|
466 | config = self._TargetConfig(config)
|
---|
467 | asmflags = []
|
---|
468 | safeseh = self._Setting(("MASM", "UseSafeExceptionHandlers"), config)
|
---|
469 | if safeseh == "true":
|
---|
470 | asmflags.append("/safeseh")
|
---|
471 | return asmflags
|
---|
472 |
|
---|
473 | def GetCflags(self, config):
|
---|
474 | """Returns the flags that need to be added to .c and .cc compilations."""
|
---|
475 | config = self._TargetConfig(config)
|
---|
476 | cflags = []
|
---|
477 | cflags.extend(["/wd" + w for w in self.msvs_disabled_warnings[config]])
|
---|
478 | cl = self._GetWrapper(
|
---|
479 | self, self.msvs_settings[config], "VCCLCompilerTool", append=cflags
|
---|
480 | )
|
---|
481 | cl(
|
---|
482 | "Optimization",
|
---|
483 | map={"0": "d", "1": "1", "2": "2", "3": "x"},
|
---|
484 | prefix="/O",
|
---|
485 | default="2",
|
---|
486 | )
|
---|
487 | cl("InlineFunctionExpansion", prefix="/Ob")
|
---|
488 | cl("DisableSpecificWarnings", prefix="/wd")
|
---|
489 | cl("StringPooling", map={"true": "/GF"})
|
---|
490 | cl("EnableFiberSafeOptimizations", map={"true": "/GT"})
|
---|
491 | cl("OmitFramePointers", map={"false": "-", "true": ""}, prefix="/Oy")
|
---|
492 | cl("EnableIntrinsicFunctions", map={"false": "-", "true": ""}, prefix="/Oi")
|
---|
493 | cl("FavorSizeOrSpeed", map={"1": "t", "2": "s"}, prefix="/O")
|
---|
494 | cl(
|
---|
495 | "FloatingPointModel",
|
---|
496 | map={"0": "precise", "1": "strict", "2": "fast"},
|
---|
497 | prefix="/fp:",
|
---|
498 | default="0",
|
---|
499 | )
|
---|
500 | cl("CompileAsManaged", map={"false": "", "true": "/clr"})
|
---|
501 | cl("WholeProgramOptimization", map={"true": "/GL"})
|
---|
502 | cl("WarningLevel", prefix="/W")
|
---|
503 | cl("WarnAsError", map={"true": "/WX"})
|
---|
504 | cl(
|
---|
505 | "CallingConvention",
|
---|
506 | map={"0": "d", "1": "r", "2": "z", "3": "v"},
|
---|
507 | prefix="/G",
|
---|
508 | )
|
---|
509 | cl("DebugInformationFormat", map={"1": "7", "3": "i", "4": "I"}, prefix="/Z")
|
---|
510 | cl("RuntimeTypeInfo", map={"true": "/GR", "false": "/GR-"})
|
---|
511 | cl("EnableFunctionLevelLinking", map={"true": "/Gy", "false": "/Gy-"})
|
---|
512 | cl("MinimalRebuild", map={"true": "/Gm"})
|
---|
513 | cl("BufferSecurityCheck", map={"true": "/GS", "false": "/GS-"})
|
---|
514 | cl("BasicRuntimeChecks", map={"1": "s", "2": "u", "3": "1"}, prefix="/RTC")
|
---|
515 | cl(
|
---|
516 | "RuntimeLibrary",
|
---|
517 | map={"0": "T", "1": "Td", "2": "D", "3": "Dd"},
|
---|
518 | prefix="/M",
|
---|
519 | )
|
---|
520 | cl("ExceptionHandling", map={"1": "sc", "2": "a"}, prefix="/EH")
|
---|
521 | cl("DefaultCharIsUnsigned", map={"true": "/J"})
|
---|
522 | cl(
|
---|
523 | "TreatWChar_tAsBuiltInType",
|
---|
524 | map={"false": "-", "true": ""},
|
---|
525 | prefix="/Zc:wchar_t",
|
---|
526 | )
|
---|
527 | cl("EnablePREfast", map={"true": "/analyze"})
|
---|
528 | cl("AdditionalOptions", prefix="")
|
---|
529 | cl(
|
---|
530 | "EnableEnhancedInstructionSet",
|
---|
531 | map={"1": "SSE", "2": "SSE2", "3": "AVX", "4": "IA32", "5": "AVX2"},
|
---|
532 | prefix="/arch:",
|
---|
533 | )
|
---|
534 | cflags.extend(
|
---|
535 | [
|
---|
536 | "/FI" + f
|
---|
537 | for f in self._Setting(
|
---|
538 | ("VCCLCompilerTool", "ForcedIncludeFiles"), config, default=[]
|
---|
539 | )
|
---|
540 | ]
|
---|
541 | )
|
---|
542 | if self.vs_version.project_version >= 12.0:
|
---|
543 | # New flag introduced in VS2013 (project version 12.0) Forces writes to
|
---|
544 | # the program database (PDB) to be serialized through MSPDBSRV.EXE.
|
---|
545 | # https://msdn.microsoft.com/en-us/library/dn502518.aspx
|
---|
546 | cflags.append("/FS")
|
---|
547 | # ninja handles parallelism by itself, don't have the compiler do it too.
|
---|
548 | cflags = [x for x in cflags if not x.startswith("/MP")]
|
---|
549 | return cflags
|
---|
550 |
|
---|
551 | def _GetPchFlags(self, config, extension):
|
---|
552 | """Get the flags to be added to the cflags for precompiled header support.
|
---|
553 | """
|
---|
554 | config = self._TargetConfig(config)
|
---|
555 | # The PCH is only built once by a particular source file. Usage of PCH must
|
---|
556 | # only be for the same language (i.e. C vs. C++), so only include the pch
|
---|
557 | # flags when the language matches.
|
---|
558 | if self.msvs_precompiled_header[config]:
|
---|
559 | source_ext = os.path.splitext(self.msvs_precompiled_source[config])[1]
|
---|
560 | if _LanguageMatchesForPch(source_ext, extension):
|
---|
561 | pch = self.msvs_precompiled_header[config]
|
---|
562 | pchbase = os.path.split(pch)[1]
|
---|
563 | return ["/Yu" + pch, "/FI" + pch, "/Fp${pchprefix}." + pchbase + ".pch"]
|
---|
564 | return []
|
---|
565 |
|
---|
566 | def GetCflagsC(self, config):
|
---|
567 | """Returns the flags that need to be added to .c compilations."""
|
---|
568 | config = self._TargetConfig(config)
|
---|
569 | return self._GetPchFlags(config, ".c")
|
---|
570 |
|
---|
571 | def GetCflagsCC(self, config):
|
---|
572 | """Returns the flags that need to be added to .cc compilations."""
|
---|
573 | config = self._TargetConfig(config)
|
---|
574 | return ["/TP"] + self._GetPchFlags(config, ".cc")
|
---|
575 |
|
---|
576 | def _GetAdditionalLibraryDirectories(self, root, config, gyp_to_build_path):
|
---|
577 | """Get and normalize the list of paths in AdditionalLibraryDirectories
|
---|
578 | setting."""
|
---|
579 | config = self._TargetConfig(config)
|
---|
580 | libpaths = self._Setting(
|
---|
581 | (root, "AdditionalLibraryDirectories"), config, default=[]
|
---|
582 | )
|
---|
583 | libpaths = [
|
---|
584 | os.path.normpath(gyp_to_build_path(self.ConvertVSMacros(p, config=config)))
|
---|
585 | for p in libpaths
|
---|
586 | ]
|
---|
587 | return ['/LIBPATH:"' + p + '"' for p in libpaths]
|
---|
588 |
|
---|
589 | def GetLibFlags(self, config, gyp_to_build_path):
|
---|
590 | """Returns the flags that need to be added to lib commands."""
|
---|
591 | config = self._TargetConfig(config)
|
---|
592 | libflags = []
|
---|
593 | lib = self._GetWrapper(
|
---|
594 | self, self.msvs_settings[config], "VCLibrarianTool", append=libflags
|
---|
595 | )
|
---|
596 | libflags.extend(
|
---|
597 | self._GetAdditionalLibraryDirectories(
|
---|
598 | "VCLibrarianTool", config, gyp_to_build_path
|
---|
599 | )
|
---|
600 | )
|
---|
601 | lib("LinkTimeCodeGeneration", map={"true": "/LTCG"})
|
---|
602 | lib(
|
---|
603 | "TargetMachine",
|
---|
604 | map={"1": "X86", "17": "X64", "3": "ARM"},
|
---|
605 | prefix="/MACHINE:",
|
---|
606 | )
|
---|
607 | lib("AdditionalOptions")
|
---|
608 | return libflags
|
---|
609 |
|
---|
610 | def GetDefFile(self, gyp_to_build_path):
|
---|
611 | """Returns the .def file from sources, if any. Otherwise returns None."""
|
---|
612 | spec = self.spec
|
---|
613 | if spec["type"] in ("shared_library", "loadable_module", "executable"):
|
---|
614 | def_files = [
|
---|
615 | s for s in spec.get("sources", []) if s.lower().endswith(".def")
|
---|
616 | ]
|
---|
617 | if len(def_files) == 1:
|
---|
618 | return gyp_to_build_path(def_files[0])
|
---|
619 | elif len(def_files) > 1:
|
---|
620 | raise Exception("Multiple .def files")
|
---|
621 | return None
|
---|
622 |
|
---|
623 | def _GetDefFileAsLdflags(self, ldflags, gyp_to_build_path):
|
---|
624 | """.def files get implicitly converted to a ModuleDefinitionFile for the
|
---|
625 | linker in the VS generator. Emulate that behaviour here."""
|
---|
626 | def_file = self.GetDefFile(gyp_to_build_path)
|
---|
627 | if def_file:
|
---|
628 | ldflags.append('/DEF:"%s"' % def_file)
|
---|
629 |
|
---|
630 | def GetPGDName(self, config, expand_special):
|
---|
631 | """Gets the explicitly overridden pgd name for a target or returns None
|
---|
632 | if it's not overridden."""
|
---|
633 | config = self._TargetConfig(config)
|
---|
634 | output_file = self._Setting(("VCLinkerTool", "ProfileGuidedDatabase"), config)
|
---|
635 | if output_file:
|
---|
636 | output_file = expand_special(
|
---|
637 | self.ConvertVSMacros(output_file, config=config)
|
---|
638 | )
|
---|
639 | return output_file
|
---|
640 |
|
---|
641 | def GetLdflags(
|
---|
642 | self,
|
---|
643 | config,
|
---|
644 | gyp_to_build_path,
|
---|
645 | expand_special,
|
---|
646 | manifest_base_name,
|
---|
647 | output_name,
|
---|
648 | is_executable,
|
---|
649 | build_dir,
|
---|
650 | ):
|
---|
651 | """Returns the flags that need to be added to link commands, and the
|
---|
652 | manifest files."""
|
---|
653 | config = self._TargetConfig(config)
|
---|
654 | ldflags = []
|
---|
655 | ld = self._GetWrapper(
|
---|
656 | self, self.msvs_settings[config], "VCLinkerTool", append=ldflags
|
---|
657 | )
|
---|
658 | self._GetDefFileAsLdflags(ldflags, gyp_to_build_path)
|
---|
659 | ld("GenerateDebugInformation", map={"true": "/DEBUG"})
|
---|
660 | # TODO: These 'map' values come from machineTypeOption enum,
|
---|
661 | # and does not have an official value for ARM64 in VS2017 (yet).
|
---|
662 | # It needs to verify the ARM64 value when machineTypeOption is updated.
|
---|
663 | ld(
|
---|
664 | "TargetMachine",
|
---|
665 | map={"1": "X86", "17": "X64", "3": "ARM", "18": "ARM64"},
|
---|
666 | prefix="/MACHINE:",
|
---|
667 | )
|
---|
668 | ldflags.extend(
|
---|
669 | self._GetAdditionalLibraryDirectories(
|
---|
670 | "VCLinkerTool", config, gyp_to_build_path
|
---|
671 | )
|
---|
672 | )
|
---|
673 | ld("DelayLoadDLLs", prefix="/DELAYLOAD:")
|
---|
674 | ld("TreatLinkerWarningAsErrors", prefix="/WX", map={"true": "", "false": ":NO"})
|
---|
675 | out = self.GetOutputName(config, expand_special)
|
---|
676 | if out:
|
---|
677 | ldflags.append("/OUT:" + out)
|
---|
678 | pdb = self.GetPDBName(config, expand_special, output_name + ".pdb")
|
---|
679 | if pdb:
|
---|
680 | ldflags.append("/PDB:" + pdb)
|
---|
681 | pgd = self.GetPGDName(config, expand_special)
|
---|
682 | if pgd:
|
---|
683 | ldflags.append("/PGD:" + pgd)
|
---|
684 | map_file = self.GetMapFileName(config, expand_special)
|
---|
685 | ld("GenerateMapFile", map={"true": "/MAP:" + map_file if map_file else "/MAP"})
|
---|
686 | ld("MapExports", map={"true": "/MAPINFO:EXPORTS"})
|
---|
687 | ld("AdditionalOptions", prefix="")
|
---|
688 |
|
---|
689 | minimum_required_version = self._Setting(
|
---|
690 | ("VCLinkerTool", "MinimumRequiredVersion"), config, default=""
|
---|
691 | )
|
---|
692 | if minimum_required_version:
|
---|
693 | minimum_required_version = "," + minimum_required_version
|
---|
694 | ld(
|
---|
695 | "SubSystem",
|
---|
696 | map={
|
---|
697 | "1": "CONSOLE%s" % minimum_required_version,
|
---|
698 | "2": "WINDOWS%s" % minimum_required_version,
|
---|
699 | },
|
---|
700 | prefix="/SUBSYSTEM:",
|
---|
701 | )
|
---|
702 |
|
---|
703 | stack_reserve_size = self._Setting(
|
---|
704 | ("VCLinkerTool", "StackReserveSize"), config, default=""
|
---|
705 | )
|
---|
706 | if stack_reserve_size:
|
---|
707 | stack_commit_size = self._Setting(
|
---|
708 | ("VCLinkerTool", "StackCommitSize"), config, default=""
|
---|
709 | )
|
---|
710 | if stack_commit_size:
|
---|
711 | stack_commit_size = "," + stack_commit_size
|
---|
712 | ldflags.append("/STACK:%s%s" % (stack_reserve_size, stack_commit_size))
|
---|
713 |
|
---|
714 | ld("TerminalServerAware", map={"1": ":NO", "2": ""}, prefix="/TSAWARE")
|
---|
715 | ld("LinkIncremental", map={"1": ":NO", "2": ""}, prefix="/INCREMENTAL")
|
---|
716 | ld("BaseAddress", prefix="/BASE:")
|
---|
717 | ld("FixedBaseAddress", map={"1": ":NO", "2": ""}, prefix="/FIXED")
|
---|
718 | ld("RandomizedBaseAddress", map={"1": ":NO", "2": ""}, prefix="/DYNAMICBASE")
|
---|
719 | ld("DataExecutionPrevention", map={"1": ":NO", "2": ""}, prefix="/NXCOMPAT")
|
---|
720 | ld("OptimizeReferences", map={"1": "NOREF", "2": "REF"}, prefix="/OPT:")
|
---|
721 | ld("ForceSymbolReferences", prefix="/INCLUDE:")
|
---|
722 | ld("EnableCOMDATFolding", map={"1": "NOICF", "2": "ICF"}, prefix="/OPT:")
|
---|
723 | ld(
|
---|
724 | "LinkTimeCodeGeneration",
|
---|
725 | map={"1": "", "2": ":PGINSTRUMENT", "3": ":PGOPTIMIZE", "4": ":PGUPDATE"},
|
---|
726 | prefix="/LTCG",
|
---|
727 | )
|
---|
728 | ld("IgnoreDefaultLibraryNames", prefix="/NODEFAULTLIB:")
|
---|
729 | ld("ResourceOnlyDLL", map={"true": "/NOENTRY"})
|
---|
730 | ld("EntryPointSymbol", prefix="/ENTRY:")
|
---|
731 | ld("Profile", map={"true": "/PROFILE"})
|
---|
732 | ld("LargeAddressAware", map={"1": ":NO", "2": ""}, prefix="/LARGEADDRESSAWARE")
|
---|
733 | # TODO(scottmg): This should sort of be somewhere else (not really a flag).
|
---|
734 | ld("AdditionalDependencies", prefix="")
|
---|
735 |
|
---|
736 | if self.GetArch(config) == "x86":
|
---|
737 | safeseh_default = "true"
|
---|
738 | else:
|
---|
739 | safeseh_default = None
|
---|
740 | ld(
|
---|
741 | "ImageHasSafeExceptionHandlers",
|
---|
742 | map={"false": ":NO", "true": ""},
|
---|
743 | prefix="/SAFESEH",
|
---|
744 | default=safeseh_default,
|
---|
745 | )
|
---|
746 |
|
---|
747 | # If the base address is not specifically controlled, DYNAMICBASE should
|
---|
748 | # be on by default.
|
---|
749 | if not any("DYNAMICBASE" in flag or flag == "/FIXED" for flag in ldflags):
|
---|
750 | ldflags.append("/DYNAMICBASE")
|
---|
751 |
|
---|
752 | # If the NXCOMPAT flag has not been specified, default to on. Despite the
|
---|
753 | # documentation that says this only defaults to on when the subsystem is
|
---|
754 | # Vista or greater (which applies to the linker), the IDE defaults it on
|
---|
755 | # unless it's explicitly off.
|
---|
756 | if not any("NXCOMPAT" in flag for flag in ldflags):
|
---|
757 | ldflags.append("/NXCOMPAT")
|
---|
758 |
|
---|
759 | have_def_file = any(flag.startswith("/DEF:") for flag in ldflags)
|
---|
760 | (
|
---|
761 | manifest_flags,
|
---|
762 | intermediate_manifest,
|
---|
763 | manifest_files,
|
---|
764 | ) = self._GetLdManifestFlags(
|
---|
765 | config,
|
---|
766 | manifest_base_name,
|
---|
767 | gyp_to_build_path,
|
---|
768 | is_executable and not have_def_file,
|
---|
769 | build_dir,
|
---|
770 | )
|
---|
771 | ldflags.extend(manifest_flags)
|
---|
772 | return ldflags, intermediate_manifest, manifest_files
|
---|
773 |
|
---|
774 | def _GetLdManifestFlags(
|
---|
775 | self, config, name, gyp_to_build_path, allow_isolation, build_dir
|
---|
776 | ):
|
---|
777 | """Returns a 3-tuple:
|
---|
778 | - the set of flags that need to be added to the link to generate
|
---|
779 | a default manifest
|
---|
780 | - the intermediate manifest that the linker will generate that should be
|
---|
781 | used to assert it doesn't add anything to the merged one.
|
---|
782 | - the list of all the manifest files to be merged by the manifest tool and
|
---|
783 | included into the link."""
|
---|
784 | generate_manifest = self._Setting(
|
---|
785 | ("VCLinkerTool", "GenerateManifest"), config, default="true"
|
---|
786 | )
|
---|
787 | if generate_manifest != "true":
|
---|
788 | # This means not only that the linker should not generate the intermediate
|
---|
789 | # manifest but also that the manifest tool should do nothing even when
|
---|
790 | # additional manifests are specified.
|
---|
791 | return ["/MANIFEST:NO"], [], []
|
---|
792 |
|
---|
793 | output_name = name + ".intermediate.manifest"
|
---|
794 | flags = [
|
---|
795 | "/MANIFEST",
|
---|
796 | "/ManifestFile:" + output_name,
|
---|
797 | ]
|
---|
798 |
|
---|
799 | # Instead of using the MANIFESTUAC flags, we generate a .manifest to
|
---|
800 | # include into the list of manifests. This allows us to avoid the need to
|
---|
801 | # do two passes during linking. The /MANIFEST flag and /ManifestFile are
|
---|
802 | # still used, and the intermediate manifest is used to assert that the
|
---|
803 | # final manifest we get from merging all the additional manifest files
|
---|
804 | # (plus the one we generate here) isn't modified by merging the
|
---|
805 | # intermediate into it.
|
---|
806 |
|
---|
807 | # Always NO, because we generate a manifest file that has what we want.
|
---|
808 | flags.append("/MANIFESTUAC:NO")
|
---|
809 |
|
---|
810 | config = self._TargetConfig(config)
|
---|
811 | enable_uac = self._Setting(
|
---|
812 | ("VCLinkerTool", "EnableUAC"), config, default="true"
|
---|
813 | )
|
---|
814 | manifest_files = []
|
---|
815 | generated_manifest_outer = (
|
---|
816 | "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
|
---|
817 | "<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>"
|
---|
818 | "%s</assembly>"
|
---|
819 | )
|
---|
820 | if enable_uac == "true":
|
---|
821 | execution_level = self._Setting(
|
---|
822 | ("VCLinkerTool", "UACExecutionLevel"), config, default="0"
|
---|
823 | )
|
---|
824 | execution_level_map = {
|
---|
825 | "0": "asInvoker",
|
---|
826 | "1": "highestAvailable",
|
---|
827 | "2": "requireAdministrator",
|
---|
828 | }
|
---|
829 |
|
---|
830 | ui_access = self._Setting(
|
---|
831 | ("VCLinkerTool", "UACUIAccess"), config, default="false"
|
---|
832 | )
|
---|
833 |
|
---|
834 | inner = """
|
---|
835 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
---|
836 | <security>
|
---|
837 | <requestedPrivileges>
|
---|
838 | <requestedExecutionLevel level='%s' uiAccess='%s' />
|
---|
839 | </requestedPrivileges>
|
---|
840 | </security>
|
---|
841 | </trustInfo>""" % (
|
---|
842 | execution_level_map[execution_level],
|
---|
843 | ui_access,
|
---|
844 | )
|
---|
845 | else:
|
---|
846 | inner = ""
|
---|
847 |
|
---|
848 | generated_manifest_contents = generated_manifest_outer % inner
|
---|
849 | generated_name = name + ".generated.manifest"
|
---|
850 | # Need to join with the build_dir here as we're writing it during
|
---|
851 | # generation time, but we return the un-joined version because the build
|
---|
852 | # will occur in that directory. We only write the file if the contents
|
---|
853 | # have changed so that simply regenerating the project files doesn't
|
---|
854 | # cause a relink.
|
---|
855 | build_dir_generated_name = os.path.join(build_dir, generated_name)
|
---|
856 | gyp.common.EnsureDirExists(build_dir_generated_name)
|
---|
857 | f = gyp.common.WriteOnDiff(build_dir_generated_name)
|
---|
858 | f.write(generated_manifest_contents)
|
---|
859 | f.close()
|
---|
860 | manifest_files = [generated_name]
|
---|
861 |
|
---|
862 | if allow_isolation:
|
---|
863 | flags.append("/ALLOWISOLATION")
|
---|
864 |
|
---|
865 | manifest_files += self._GetAdditionalManifestFiles(config, gyp_to_build_path)
|
---|
866 | return flags, output_name, manifest_files
|
---|
867 |
|
---|
868 | def _GetAdditionalManifestFiles(self, config, gyp_to_build_path):
|
---|
869 | """Gets additional manifest files that are added to the default one
|
---|
870 | generated by the linker."""
|
---|
871 | files = self._Setting(
|
---|
872 | ("VCManifestTool", "AdditionalManifestFiles"), config, default=[]
|
---|
873 | )
|
---|
874 | if isinstance(files, str):
|
---|
875 | files = files.split(";")
|
---|
876 | return [
|
---|
877 | os.path.normpath(gyp_to_build_path(self.ConvertVSMacros(f, config=config)))
|
---|
878 | for f in files
|
---|
879 | ]
|
---|
880 |
|
---|
881 | def IsUseLibraryDependencyInputs(self, config):
|
---|
882 | """Returns whether the target should be linked via Use Library Dependency
|
---|
883 | Inputs (using component .objs of a given .lib)."""
|
---|
884 | config = self._TargetConfig(config)
|
---|
885 | uldi = self._Setting(("VCLinkerTool", "UseLibraryDependencyInputs"), config)
|
---|
886 | return uldi == "true"
|
---|
887 |
|
---|
888 | def IsEmbedManifest(self, config):
|
---|
889 | """Returns whether manifest should be linked into binary."""
|
---|
890 | config = self._TargetConfig(config)
|
---|
891 | embed = self._Setting(
|
---|
892 | ("VCManifestTool", "EmbedManifest"), config, default="true"
|
---|
893 | )
|
---|
894 | return embed == "true"
|
---|
895 |
|
---|
896 | def IsLinkIncremental(self, config):
|
---|
897 | """Returns whether the target should be linked incrementally."""
|
---|
898 | config = self._TargetConfig(config)
|
---|
899 | link_inc = self._Setting(("VCLinkerTool", "LinkIncremental"), config)
|
---|
900 | return link_inc != "1"
|
---|
901 |
|
---|
902 | def GetRcflags(self, config, gyp_to_ninja_path):
|
---|
903 | """Returns the flags that need to be added to invocations of the resource
|
---|
904 | compiler."""
|
---|
905 | config = self._TargetConfig(config)
|
---|
906 | rcflags = []
|
---|
907 | rc = self._GetWrapper(
|
---|
908 | self, self.msvs_settings[config], "VCResourceCompilerTool", append=rcflags
|
---|
909 | )
|
---|
910 | rc("AdditionalIncludeDirectories", map=gyp_to_ninja_path, prefix="/I")
|
---|
911 | rcflags.append("/I" + gyp_to_ninja_path("."))
|
---|
912 | rc("PreprocessorDefinitions", prefix="/d")
|
---|
913 | # /l arg must be in hex without leading '0x'
|
---|
914 | rc("Culture", prefix="/l", map=lambda x: hex(int(x))[2:])
|
---|
915 | return rcflags
|
---|
916 |
|
---|
917 | def BuildCygwinBashCommandLine(self, args, path_to_base):
|
---|
918 | """Build a command line that runs args via cygwin bash. We assume that all
|
---|
919 | incoming paths are in Windows normpath'd form, so they need to be
|
---|
920 | converted to posix style for the part of the command line that's passed to
|
---|
921 | bash. We also have to do some Visual Studio macro emulation here because
|
---|
922 | various rules use magic VS names for things. Also note that rules that
|
---|
923 | contain ninja variables cannot be fixed here (for example ${source}), so
|
---|
924 | the outer generator needs to make sure that the paths that are written out
|
---|
925 | are in posix style, if the command line will be used here."""
|
---|
926 | cygwin_dir = os.path.normpath(
|
---|
927 | os.path.join(path_to_base, self.msvs_cygwin_dirs[0])
|
---|
928 | )
|
---|
929 | cd = ("cd %s" % path_to_base).replace("\\", "/")
|
---|
930 | args = [a.replace("\\", "/").replace('"', '\\"') for a in args]
|
---|
931 | args = ["'%s'" % a.replace("'", "'\\''") for a in args]
|
---|
932 | bash_cmd = " ".join(args)
|
---|
933 | cmd = (
|
---|
934 | 'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir
|
---|
935 | + 'bash -c "%s ; %s"' % (cd, bash_cmd)
|
---|
936 | )
|
---|
937 | return cmd
|
---|
938 |
|
---|
939 | def IsRuleRunUnderCygwin(self, rule):
|
---|
940 | """Determine if an action should be run under cygwin. If the variable is
|
---|
941 | unset, or set to 1 we use cygwin."""
|
---|
942 | return (
|
---|
943 | int(rule.get("msvs_cygwin_shell", self.spec.get("msvs_cygwin_shell", 1)))
|
---|
944 | != 0
|
---|
945 | )
|
---|
946 |
|
---|
947 | def _HasExplicitRuleForExtension(self, spec, extension):
|
---|
948 | """Determine if there's an explicit rule for a particular extension."""
|
---|
949 | for rule in spec.get("rules", []):
|
---|
950 | if rule["extension"] == extension:
|
---|
951 | return True
|
---|
952 | return False
|
---|
953 |
|
---|
954 | def _HasExplicitIdlActions(self, spec):
|
---|
955 | """Determine if an action should not run midl for .idl files."""
|
---|
956 | return any(
|
---|
957 | [action.get("explicit_idl_action", 0) for action in spec.get("actions", [])]
|
---|
958 | )
|
---|
959 |
|
---|
960 | def HasExplicitIdlRulesOrActions(self, spec):
|
---|
961 | """Determine if there's an explicit rule or action for idl files. When
|
---|
962 | there isn't we need to generate implicit rules to build MIDL .idl files."""
|
---|
963 | return self._HasExplicitRuleForExtension(
|
---|
964 | spec, "idl"
|
---|
965 | ) or self._HasExplicitIdlActions(spec)
|
---|
966 |
|
---|
967 | def HasExplicitAsmRules(self, spec):
|
---|
968 | """Determine if there's an explicit rule for asm files. When there isn't we
|
---|
969 | need to generate implicit rules to assemble .asm files."""
|
---|
970 | return self._HasExplicitRuleForExtension(spec, "asm")
|
---|
971 |
|
---|
972 | def GetIdlBuildData(self, source, config):
|
---|
973 | """Determine the implicit outputs for an idl file. Returns output
|
---|
974 | directory, outputs, and variables and flags that are required."""
|
---|
975 | config = self._TargetConfig(config)
|
---|
976 | midl_get = self._GetWrapper(self, self.msvs_settings[config], "VCMIDLTool")
|
---|
977 |
|
---|
978 | def midl(name, default=None):
|
---|
979 | return self.ConvertVSMacros(midl_get(name, default=default), config=config)
|
---|
980 |
|
---|
981 | tlb = midl("TypeLibraryName", default="${root}.tlb")
|
---|
982 | header = midl("HeaderFileName", default="${root}.h")
|
---|
983 | dlldata = midl("DLLDataFileName", default="dlldata.c")
|
---|
984 | iid = midl("InterfaceIdentifierFileName", default="${root}_i.c")
|
---|
985 | proxy = midl("ProxyFileName", default="${root}_p.c")
|
---|
986 | # Note that .tlb is not included in the outputs as it is not always
|
---|
987 | # generated depending on the content of the input idl file.
|
---|
988 | outdir = midl("OutputDirectory", default="")
|
---|
989 | output = [header, dlldata, iid, proxy]
|
---|
990 | variables = [
|
---|
991 | ("tlb", tlb),
|
---|
992 | ("h", header),
|
---|
993 | ("dlldata", dlldata),
|
---|
994 | ("iid", iid),
|
---|
995 | ("proxy", proxy),
|
---|
996 | ]
|
---|
997 | # TODO(scottmg): Are there configuration settings to set these flags?
|
---|
998 | target_platform = self.GetArch(config)
|
---|
999 | if target_platform == "x86":
|
---|
1000 | target_platform = "win32"
|
---|
1001 | flags = ["/char", "signed", "/env", target_platform, "/Oicf"]
|
---|
1002 | return outdir, output, variables, flags
|
---|
1003 |
|
---|
1004 |
|
---|
1005 | def _LanguageMatchesForPch(source_ext, pch_source_ext):
|
---|
1006 | c_exts = (".c",)
|
---|
1007 | cc_exts = (".cc", ".cxx", ".cpp")
|
---|
1008 | return (source_ext in c_exts and pch_source_ext in c_exts) or (
|
---|
1009 | source_ext in cc_exts and pch_source_ext in cc_exts
|
---|
1010 | )
|
---|
1011 |
|
---|
1012 |
|
---|
1013 | class PrecompiledHeader(object):
|
---|
1014 | """Helper to generate dependencies and build rules to handle generation of
|
---|
1015 | precompiled headers. Interface matches the GCH handler in xcode_emulation.py.
|
---|
1016 | """
|
---|
1017 |
|
---|
1018 | def __init__(
|
---|
1019 | self, settings, config, gyp_to_build_path, gyp_to_unique_output, obj_ext
|
---|
1020 | ):
|
---|
1021 | self.settings = settings
|
---|
1022 | self.config = config
|
---|
1023 | pch_source = self.settings.msvs_precompiled_source[self.config]
|
---|
1024 | self.pch_source = gyp_to_build_path(pch_source)
|
---|
1025 | filename, _ = os.path.splitext(pch_source)
|
---|
1026 | self.output_obj = gyp_to_unique_output(filename + obj_ext).lower()
|
---|
1027 |
|
---|
1028 | def _PchHeader(self):
|
---|
1029 | """Get the header that will appear in an #include line for all source
|
---|
1030 | files."""
|
---|
1031 | return self.settings.msvs_precompiled_header[self.config]
|
---|
1032 |
|
---|
1033 | def GetObjDependencies(self, sources, objs, arch):
|
---|
1034 | """Given a list of sources files and the corresponding object files,
|
---|
1035 | returns a list of the pch files that should be depended upon. The
|
---|
1036 | additional wrapping in the return value is for interface compatibility
|
---|
1037 | with make.py on Mac, and xcode_emulation.py."""
|
---|
1038 | assert arch is None
|
---|
1039 | if not self._PchHeader():
|
---|
1040 | return []
|
---|
1041 | pch_ext = os.path.splitext(self.pch_source)[1]
|
---|
1042 | for source in sources:
|
---|
1043 | if _LanguageMatchesForPch(os.path.splitext(source)[1], pch_ext):
|
---|
1044 | return [(None, None, self.output_obj)]
|
---|
1045 | return []
|
---|
1046 |
|
---|
1047 | def GetPchBuildCommands(self, arch):
|
---|
1048 | """Not used on Windows as there are no additional build steps required
|
---|
1049 | (instead, existing steps are modified in GetFlagsModifications below)."""
|
---|
1050 | return []
|
---|
1051 |
|
---|
1052 | def GetFlagsModifications(
|
---|
1053 | self, input, output, implicit, command, cflags_c, cflags_cc, expand_special
|
---|
1054 | ):
|
---|
1055 | """Get the modified cflags and implicit dependencies that should be used
|
---|
1056 | for the pch compilation step."""
|
---|
1057 | if input == self.pch_source:
|
---|
1058 | pch_output = ["/Yc" + self._PchHeader()]
|
---|
1059 | if command == "cxx":
|
---|
1060 | return (
|
---|
1061 | [("cflags_cc", map(expand_special, cflags_cc + pch_output))],
|
---|
1062 | self.output_obj,
|
---|
1063 | [],
|
---|
1064 | )
|
---|
1065 | elif command == "cc":
|
---|
1066 | return (
|
---|
1067 | [("cflags_c", map(expand_special, cflags_c + pch_output))],
|
---|
1068 | self.output_obj,
|
---|
1069 | [],
|
---|
1070 | )
|
---|
1071 | return [], output, implicit
|
---|
1072 |
|
---|
1073 |
|
---|
1074 | vs_version = None
|
---|
1075 |
|
---|
1076 |
|
---|
1077 | def GetVSVersion(generator_flags):
|
---|
1078 | global vs_version
|
---|
1079 | if not vs_version:
|
---|
1080 | vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
|
---|
1081 | generator_flags.get("msvs_version", "auto"), allow_fallback=False
|
---|
1082 | )
|
---|
1083 | return vs_version
|
---|
1084 |
|
---|
1085 |
|
---|
1086 | def _GetVsvarsSetupArgs(generator_flags, arch):
|
---|
1087 | vs = GetVSVersion(generator_flags)
|
---|
1088 | return vs.SetupScript()
|
---|
1089 |
|
---|
1090 |
|
---|
1091 | def ExpandMacros(string, expansions):
|
---|
1092 | """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv
|
---|
1093 | for the canonical way to retrieve a suitable dict."""
|
---|
1094 | if "$" in string:
|
---|
1095 | for old, new in expansions.items():
|
---|
1096 | assert "$(" not in new, new
|
---|
1097 | string = string.replace(old, new)
|
---|
1098 | return string
|
---|
1099 |
|
---|
1100 |
|
---|
1101 | def _ExtractImportantEnvironment(output_of_set):
|
---|
1102 | """Extracts environment variables required for the toolchain to run from
|
---|
1103 | a textual dump output by the cmd.exe 'set' command."""
|
---|
1104 | envvars_to_save = (
|
---|
1105 | "goma_.*", # TODO(scottmg): This is ugly, but needed for goma.
|
---|
1106 | "include",
|
---|
1107 | "lib",
|
---|
1108 | "libpath",
|
---|
1109 | "path",
|
---|
1110 | "pathext",
|
---|
1111 | "systemroot",
|
---|
1112 | "temp",
|
---|
1113 | "tmp",
|
---|
1114 | )
|
---|
1115 | env = {}
|
---|
1116 | # This occasionally happens and leads to misleading SYSTEMROOT error messages
|
---|
1117 | # if not caught here.
|
---|
1118 | if output_of_set.count("=") == 0:
|
---|
1119 | raise Exception("Invalid output_of_set. Value is:\n%s" % output_of_set)
|
---|
1120 | for line in output_of_set.splitlines():
|
---|
1121 | for envvar in envvars_to_save:
|
---|
1122 | if re.match(envvar + "=", line.lower()):
|
---|
1123 | var, setting = line.split("=", 1)
|
---|
1124 | if envvar == "path":
|
---|
1125 | # Our own rules (for running gyp-win-tool) and other actions in
|
---|
1126 | # Chromium rely on python being in the path. Add the path to this
|
---|
1127 | # python here so that if it's not in the path when ninja is run
|
---|
1128 | # later, python will still be found.
|
---|
1129 | setting = os.path.dirname(sys.executable) + os.pathsep + setting
|
---|
1130 | env[var.upper()] = setting
|
---|
1131 | break
|
---|
1132 | for required in ("SYSTEMROOT", "TEMP", "TMP"):
|
---|
1133 | if required not in env:
|
---|
1134 | raise Exception(
|
---|
1135 | 'Environment variable "%s" '
|
---|
1136 | "required to be set to valid path" % required
|
---|
1137 | )
|
---|
1138 | return env
|
---|
1139 |
|
---|
1140 |
|
---|
1141 | def _FormatAsEnvironmentBlock(envvar_dict):
|
---|
1142 | """Format as an 'environment block' directly suitable for CreateProcess.
|
---|
1143 | Briefly this is a list of key=value\0, terminated by an additional \0. See
|
---|
1144 | CreateProcess documentation for more details."""
|
---|
1145 | block = ""
|
---|
1146 | nul = "\0"
|
---|
1147 | for key, value in envvar_dict.items():
|
---|
1148 | block += key + "=" + value + nul
|
---|
1149 | block += nul
|
---|
1150 | return block
|
---|
1151 |
|
---|
1152 |
|
---|
1153 | def _ExtractCLPath(output_of_where):
|
---|
1154 | """Gets the path to cl.exe based on the output of calling the environment
|
---|
1155 | setup batch file, followed by the equivalent of `where`."""
|
---|
1156 | # Take the first line, as that's the first found in the PATH.
|
---|
1157 | for line in output_of_where.strip().splitlines():
|
---|
1158 | if line.startswith("LOC:"):
|
---|
1159 | return line[len("LOC:") :].strip()
|
---|
1160 |
|
---|
1161 |
|
---|
1162 | def GenerateEnvironmentFiles(
|
---|
1163 | toplevel_build_dir, generator_flags, system_includes, open_out
|
---|
1164 | ):
|
---|
1165 | """It's not sufficient to have the absolute path to the compiler, linker,
|
---|
1166 | etc. on Windows, as those tools rely on .dlls being in the PATH. We also
|
---|
1167 | need to support both x86 and x64 compilers within the same build (to support
|
---|
1168 | msvs_target_platform hackery). Different architectures require a different
|
---|
1169 | compiler binary, and different supporting environment variables (INCLUDE,
|
---|
1170 | LIB, LIBPATH). So, we extract the environment here, wrap all invocations
|
---|
1171 | of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which
|
---|
1172 | sets up the environment, and then we do not prefix the compiler with
|
---|
1173 | an absolute path, instead preferring something like "cl.exe" in the rule
|
---|
1174 | which will then run whichever the environment setup has put in the path.
|
---|
1175 | When the following procedure to generate environment files does not
|
---|
1176 | meet your requirement (e.g. for custom toolchains), you can pass
|
---|
1177 | "-G ninja_use_custom_environment_files" to the gyp to suppress file
|
---|
1178 | generation and use custom environment files prepared by yourself."""
|
---|
1179 | archs = ("x86", "x64")
|
---|
1180 | if generator_flags.get("ninja_use_custom_environment_files", 0):
|
---|
1181 | cl_paths = {}
|
---|
1182 | for arch in archs:
|
---|
1183 | cl_paths[arch] = "cl.exe"
|
---|
1184 | return cl_paths
|
---|
1185 | vs = GetVSVersion(generator_flags)
|
---|
1186 | cl_paths = {}
|
---|
1187 | for arch in archs:
|
---|
1188 | # Extract environment variables for subprocesses.
|
---|
1189 | args = vs.SetupScript(arch)
|
---|
1190 | args.extend(("&&", "set"))
|
---|
1191 | popen = subprocess.Popen(
|
---|
1192 | args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
---|
1193 | )
|
---|
1194 | variables, _ = popen.communicate()
|
---|
1195 | if PY3:
|
---|
1196 | variables = variables.decode("utf-8")
|
---|
1197 | if popen.returncode != 0:
|
---|
1198 | raise Exception('"%s" failed with error %d' % (args, popen.returncode))
|
---|
1199 | env = _ExtractImportantEnvironment(variables)
|
---|
1200 |
|
---|
1201 | # Inject system includes from gyp files into INCLUDE.
|
---|
1202 | if system_includes:
|
---|
1203 | system_includes = system_includes | OrderedSet(
|
---|
1204 | env.get("INCLUDE", "").split(";")
|
---|
1205 | )
|
---|
1206 | env["INCLUDE"] = ";".join(system_includes)
|
---|
1207 |
|
---|
1208 | env_block = _FormatAsEnvironmentBlock(env)
|
---|
1209 | f = open_out(os.path.join(toplevel_build_dir, "environment." + arch), "w")
|
---|
1210 | f.write(env_block)
|
---|
1211 | f.close()
|
---|
1212 |
|
---|
1213 | # Find cl.exe location for this architecture.
|
---|
1214 | args = vs.SetupScript(arch)
|
---|
1215 | args.extend(
|
---|
1216 | ("&&", "for", "%i", "in", "(cl.exe)", "do", "@echo", "LOC:%~$PATH:i")
|
---|
1217 | )
|
---|
1218 | popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
|
---|
1219 | output, _ = popen.communicate()
|
---|
1220 | if PY3:
|
---|
1221 | output = output.decode("utf-8")
|
---|
1222 | cl_paths[arch] = _ExtractCLPath(output)
|
---|
1223 | return cl_paths
|
---|
1224 |
|
---|
1225 |
|
---|
1226 | def VerifyMissingSources(sources, build_dir, generator_flags, gyp_to_ninja):
|
---|
1227 | """Emulate behavior of msvs_error_on_missing_sources present in the msvs
|
---|
1228 | generator: Check that all regular source files, i.e. not created at run time,
|
---|
1229 | exist on disk. Missing files cause needless recompilation when building via
|
---|
1230 | VS, and we want this check to match for people/bots that build using ninja,
|
---|
1231 | so they're not surprised when the VS build fails."""
|
---|
1232 | if int(generator_flags.get("msvs_error_on_missing_sources", 0)):
|
---|
1233 | no_specials = filter(lambda x: "$" not in x, sources)
|
---|
1234 | relative = [os.path.join(build_dir, gyp_to_ninja(s)) for s in no_specials]
|
---|
1235 | missing = [x for x in relative if not os.path.exists(x)]
|
---|
1236 | if missing:
|
---|
1237 | # They'll look like out\Release\..\..\stuff\things.cc, so normalize the
|
---|
1238 | # path for a slightly less crazy looking output.
|
---|
1239 | cleaned_up = [os.path.normpath(x) for x in missing]
|
---|
1240 | raise Exception("Missing input files:\n%s" % "\n".join(cleaned_up))
|
---|
1241 |
|
---|
1242 |
|
---|
1243 | # Sets some values in default_variables, which are required for many
|
---|
1244 | # generators, run on Windows.
|
---|
1245 | def CalculateCommonVariables(default_variables, params):
|
---|
1246 | generator_flags = params.get("generator_flags", {})
|
---|
1247 |
|
---|
1248 | # Set a variable so conditions can be based on msvs_version.
|
---|
1249 | msvs_version = gyp.msvs_emulation.GetVSVersion(generator_flags)
|
---|
1250 | default_variables["MSVS_VERSION"] = msvs_version.ShortName()
|
---|
1251 |
|
---|
1252 | # To determine processor word size on Windows, in addition to checking
|
---|
1253 | # PROCESSOR_ARCHITECTURE (which reflects the word size of the current
|
---|
1254 | # process), it is also necessary to check PROCESSOR_ARCHITEW6432 (which
|
---|
1255 | # contains the actual word size of the system when running thru WOW64).
|
---|
1256 | if "64" in os.environ.get("PROCESSOR_ARCHITECTURE", "") or "64" in os.environ.get(
|
---|
1257 | "PROCESSOR_ARCHITEW6432", ""
|
---|
1258 | ):
|
---|
1259 | default_variables["MSVS_OS_BITS"] = 64
|
---|
1260 | else:
|
---|
1261 | default_variables["MSVS_OS_BITS"] = 32
|
---|