Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(592)

Side by Side Diff: pylib/gyp/msvs_emulation.py

Issue 9460049: Next level of changes to get more of Chrome building (Closed) Base URL: https://gyp.googlecode.com/svn/trunk
Patch Set: wip Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pylib/gyp/generator/ninja.py ('k') | pylib/gyp/ninja_syntax.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 Google Inc. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """ 5 """
6 This module helps emulate Visual Studio 2008 behavior on top of other 6 This module helps emulate Visual Studio 2008 behavior on top of other
7 build systems, primarily ninja. 7 build systems, primarily ninja.
8 """ 8 """
9 9
10 import gyp.MSVSVersion
11 import os
10 import re 12 import re
13 import sys
11 14
12 windows_quoter_regex = re.compile(r'(\\*)"') 15 windows_quoter_regex = re.compile(r'(\\*)"')
13 16
14 def QuoteCmdExeArgument(arg): 17 def QuoteCmdExeArgument(arg):
15 """Quote a command line argument so that it appears as one argument when 18 """Quote a command line argument so that it appears as one argument when
16 processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for 19 processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for
17 Windows programs).""" 20 Windows programs)."""
18 # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment 21 # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment
19 # threads. This is actually the quoting rules for CommandLineToArgvW, not 22 # threads. This is actually the quoting rules for CommandLineToArgvW, not
20 # for the shell, because the shell doesn't do anything in Windows. This 23 # for the shell, because the shell doesn't do anything in Windows. This
21 # works more or less because most programs (including the compiler, etc.) 24 # works more or less because most programs (including the compiler, etc.)
22 # use that function to handle command line arguments. 25 # use that function to handle command line arguments.
23 # 26 tmp = arg
27
28 # If the string ends in a \, then it will be interpreted as an escaper for
29 # the trailing ", so we need to pre-escape that.
30 if tmp[-1] == '\\':
31 tmp = tmp + '\\'
32
24 # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes 33 # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes
25 # preceding it, and results in n backslashes + the quote. So we substitute 34 # preceding it, and results in n backslashes + the quote. So we substitute
26 # in 2* what we match, +1 more, plus the quote. 35 # in 2* what we match, +1 more, plus the quote.
27 tmp = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg) 36 tmp = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', tmp)
28 37
29 # Now, we need to escape some things that are actually for the shell. 38 # Now, we need to escape some things that are actually for the shell.
30 # ^-escape various characters that are otherwise interpreted by the shell. 39 # ^-escape various characters that are otherwise interpreted by the shell.
31 tmp = re.sub(r'([&|^])', r'^\1', tmp) 40 # Note, removed ^ escape, these args go in .rsp files now. Blech. :(
41 #tmp = re.sub(r'([&|^])', r'^\1', tmp)
32 42
33 # %'s also need to be doubled otherwise they're interpreted as batch 43 # %'s also need to be doubled otherwise they're interpreted as batch
34 # positional arguments. Also make sure to escape the % so that they're 44 # positional arguments. Also make sure to escape the % so that they're
35 # passed literally through escaping so they can be singled to just the 45 # passed literally through escaping so they can be singled to just the
36 # original %. Otherwise, trying to pass the literal representation that 46 # original %. Otherwise, trying to pass the literal representation that
37 # looks like an environment variable to the shell (e.g. %PATH%) would fail. 47 # looks like an environment variable to the shell (e.g. %PATH%) would fail.
38 tmp = tmp.replace('%', '^%^%') 48 # Note, removed ^ escape, these args go in .rsp files now. Blech :(
49 tmp = tmp.replace('%', '%%')
39 50
40 # Finally, wrap the whole thing in quotes so that the above quote rule 51 # Finally, wrap the whole thing in quotes so that the above quote rule
41 # applies and whitespace isn't a word break. 52 # applies and whitespace isn't a word break.
42 return '"' + tmp + '"' 53 return '"' + tmp + '"'
43 54
44 def EncodeCmdExeList(args): 55
45 """Process a list of arguments using QuoteCmdExeArgument.""" 56 def _GenericRetrieve(root, default, path):
46 return ' '.join(QuoteCmdExeArgument(arg) for arg in args) 57 """Given a list of dictionary keys |path| and a tree of dicts |root|, find
58 value at path, or return |default| if any of the path doesn't exist."""
59 if not root:
60 return default
61 if not path:
62 return root
63 return _GenericRetrieve(root.get(path[0]), default, path[1:])
64
65
66 def _AddPrefix(element, prefix):
67 """Add |prefix| to |element| or each subelement if element is iterable."""
68 if element is None:
69 return element
70 # Note, not Iterable because we don't want to handle strings like that.
71 if isinstance(element, list) or isinstance(element, tuple):
72 return [prefix + e for e in element]
73 else:
74 return prefix + element
75
76
77 def _DoRemapping(element, map):
78 """If |element| then remap it through |map|. If |element| is iterable then
79 each item will be remapped. Any elements not found will be removed."""
80 if map is not None and element is not None:
81 if isinstance(element, list) or isinstance(element, tuple):
82 element = filter(None, [map.get(elem) for elem in element])
83 else:
84 element = map.get(element)
85 return element
86
87
88 def _AppendOrReturn(append, element):
89 """If |append| is None, simply return |element|. If |append| is not None,
90 then add |element| to it, adding each item in |element| if it's a list or
91 tuple."""
92 if append is not None and element is not None:
93 if isinstance(element, list) or isinstance(element, tuple):
94 append.extend(element)
95 else:
96 append.append(element)
97 else:
98 return element
99
100
101 class MsvsSettings(object):
102 """A class that understands the gyp 'msvs_...' values (especially the
103 msvs_settings field). They largely correpond to the VS2008 IDE DOM. This
104 class helps map those settings to command line options."""
105
106 def __init__(self, spec):
107 self.spec = spec
108
109 supported_fields = [
110 ('msvs_configuration_attributes', dict),
111 ('msvs_settings', dict),
112 ('msvs_system_include_dirs', list),
113 ('msvs_disabled_warnings', list),
114 ('msvs_cygwin_dirs', list),
115 ]
116 configs = spec['configurations']
117 for field, default in supported_fields:
118 setattr(self, field, {})
119 for configname, config in configs.iteritems():
120 getattr(self, field)[configname] = config.get(field, default())
121
122 def ConvertVSMacros(self, s):
123 """Convert from VS macro names to something equivalent."""
124 if '$' in s:
125 replacements = {
126 '$(VSInstallDir)': os.environ.get('VSInstallDir') + '\\',
127 '$(VCInstallDir)': os.environ.get('VCInstallDir') + '\\',
128 '$(DXSDK_DIR)': os.environ.get('DXSDK_DIR') + '\\',
129 '$(OutDir)\\': '',
130 }
131 for old, new in replacements.iteritems():
132 s = s.replace(old, new)
133 return s
134
135 def EncodeCmdExeList(self, args):
136 """Process a list of arguments using QuoteCmdExeArgument."""
137 expanded = [self.ConvertVSMacros(arg) for arg in args]
138 return ' '.join(QuoteCmdExeArgument(arg) for arg in expanded)
139
140 def AdjustLibraries(self, libraries):
141 """Strip -l from library if it's specified with that."""
142 return [lib[2:] if lib.startswith('-l') else lib for lib in libraries]
143
144 def _GetAndMunge(self, field, path, default, prefix, append, map):
145 """Retrieve a value from |field| at |path| or return |default|. If
146 |append| is specified, and the item is found, it will be appended to that
147 object instead of returned. If |map| is specified, results will be
148 remapped through |map| before being returned or appended."""
149 result = _GenericRetrieve(field, default, path)
150 result = _DoRemapping(result, map)
151 result = _AddPrefix(result, prefix)
152 return _AppendOrReturn(append, result)
153
154 class _GetWrapper(object):
155 def __init__(self, parent, field, base_path, append=None):
156 self.parent = parent
157 self.field = field
158 self.base_path = [base_path]
159 self.append = append
160
161 def __call__(self, name, map=None, prefix='', default=None):
162 return self.parent._GetAndMunge(self.field, self.base_path + [name],
163 default=default, prefix=prefix, append=self.append, map=map)
164
165 def Setting(self, path, config,
166 default=None, prefix='', append=None, map=None):
167 """_GetAndMunge for msvs_settings."""
168 return self._GetAndMunge(
169 self.msvs_settings[config], path, default, prefix, append, map)
170
171 def ConfigAttrib(self, path, config,
172 default=None, prefix='', append=None, map=None):
173 """_GetAndMunge for msvs_configuration_attributes."""
174 return self._GetAndMunge(
175 self.msvs_configuration_attributes[config],
176 path, default, prefix, append, map)
177
178 def AdjustIncludeDirs(self, include_dirs, config):
179 """Updates include_dirs to expand VS specific paths, and adds the system
180 include dirs used for platform SDK and similar."""
181 includes = include_dirs + self.msvs_system_include_dirs[config]
182 return [self.ConvertVSMacros(p) for p in includes]
183
184 def GetComputedDefines(self, config):
185 """Returns the set of defines that are injected to the defines list based
186 on other VS settings."""
187 defines = []
188 if self.ConfigAttrib(['CharacterSet'], config) == '1':
189 defines.extend(('_UNICODE', 'UNICODE'))
190 if self.ConfigAttrib(['CharacterSet'], config) == '2':
191 defines.append('_MBCS')
192 defines.extend(self.Setting(('VCCLCompilerTool', 'PreprocessorDefinitions'),
193 config, default=[]))
194 return defines
195
196 def GetCflags(self, config):
197 """Returns the flags that need to be added to .c and .cc compilations."""
198 cflags = []
199 cflags.extend(['$!/wd' + w for w in self.msvs_disabled_warnings[config]])
200 cl = self._GetWrapper(self, self.msvs_settings[config],
201 'VCCLCompilerTool', append=cflags)
202 cl('Optimization', map={'0':'d', '2':'s'}, prefix='$!/O')
203 cl('InlineFunctionExpansion', prefix='$!/Ob')
204 cl('OmitFramePointers', map={'false':'-', 'true':''}, prefix='$!/Oy')
205 cl('FavorSizeOrSpeed', map={'1':'s', '2':'t'}, prefix='$!/O')
206 cl('WholeProgramOptimization', map={'true':'$!/GL'})
207 cl('WarningLevel', prefix='$!/W')
208 cl('WarnAsError', map={'true':'$!/WX'})
209 cl('DebugInformationFormat', map={'1':'7', '3':'i', '4':'I'}, prefix='$!/Z')
210 cl('RuntimeTypeInfo', map={'true':'$!/GR', 'false':'$!/GR-'})
211 cl('EnableFunctionLevelLinking', map={'true':'$!/Gy', 'false':'$!/Gy-'})
212 cl('MinimalRebuild', map={'true':'$!/Gm'})
213 cl('BufferSecurityCheck', map={'true':'$!/GS', 'false':'$!/GS-'})
214 cl('BasicRuntimeChecks', map={'1':'s', '2':'u', '3':'1'}, prefix='$!/RTC')
215 cl('RuntimeLibrary',
216 map={'0':'T', '1':'Td', '2':'D', '3':'Dd'}, prefix='$!/M')
217 cl('ExceptionHandling', map={'1':'sc','2':'a'}, prefix='$!/EH')
218 cl('AdditionalOptions', prefix='$!')
219 return cflags
220
221 def GetCflagsC(self, config):
222 """Returns the flags that need to be added to .c compilations."""
223 return []
224
225 def GetCflagsCC(self, config):
226 """Returns the flags that need to be added to .cc compilations."""
227 return ['$!/TP']
228
229 def _GetDefFileAsLdflags(self, spec, ldflags, gyp_to_build_path):
230 """.def files get implicitly converted to a ModuleDefinitionFile for the
231 linker in the VS generator. Emulate that behaviour here."""
232 def_file = ''
233 if spec['type'] in ('shared_library', 'loadable_module'):
234 def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
235 if len(def_files) == 1:
236 ldflags.append('$!/DEF:"%s"' % gyp_to_build_path(def_files[0]))
237 elif len(def_files) > 1:
238 raise Exception("Multiple .def files")
239
240 def GetLibFlags(self, config, spec):
241 libflags = []
242 lib = self._GetWrapper(self, self.msvs_settings[config],
243 'VCLibrarianTool', append=libflags)
244 libpaths = self.Setting(('VCLibrarianTool', 'AdditionalLibraryDirectories'),
245 config, default=[])
246 libpaths = [os.path.normpath(self.ConvertVSMacros(p)) for p in libpaths]
247 libflags.extend(['$!/LIBPATH:"' + p + '"' for p in libpaths])
248 lib('AdditionalOptions', prefix='$!')
249 return libflags
250
251 def GetLdflags(self, config, spec, product_dir, gyp_to_build_path):
252 """Returns the flags that need to be added to link and lib commands."""
253 ldflags = []
254 ld = self._GetWrapper(self, self.msvs_settings[config],
255 'VCLinkerTool', append=ldflags)
256 self._GetDefFileAsLdflags(spec, ldflags, gyp_to_build_path)
257 ld('GenerateDebugInformation', map={'true':'$!/DEBUG'})
258 ld('TargetMachine', map={'1':'X86', '17':'X64'}, prefix='$!/MACHINE:')
259 libpaths = self.Setting(('VCLinkerTool', 'AdditionalLibraryDirectories'),
260 config, default=[])
261 libpaths = [os.path.normpath(self.ConvertVSMacros(p)) for p in libpaths]
262 ldflags.extend(['$!/LIBPATH:"' + p + '"' for p in libpaths])
263 ld('DelayLoadDLLs', prefix='$!/DELAYLOAD:')
264 ld('AdditionalOptions', prefix='$!')
265 ld('SubSystem', map={'1':'CONSOLE', '2':'WINDOWS'}, prefix='$!/SUBSYSTEM:')
266 ld('LinkIncremental', map={'1':':NO', '2':''}, prefix='$!/INCREMENTAL')
267 ld('FixedBaseAddress', map={'1':':NO', '2':''}, prefix='$!/FIXED')
268 ld('RandomizedBaseAddress',
269 map={'1':':NO', '2':''}, prefix='$!/DYNAMICBASE')
270 ld('DataExecutionPrevention',
271 map={'1':':NO', '2':''}, prefix='$!/NXCOMPAT')
272 ld('OptimizeReferences', map={'1':'NOREF', '2':'REF'}, prefix='$!/OPT:')
273 ld('EnableCOMDATFolding', map={'1':'NOICF', '2':'ICF'}, prefix='$!/OPT:')
274 ld('LinkTimeCodeGeneration', map={'1':'$!/LTCG'})
275 ld('IgnoreDefaultLibraryNames', prefix='$!/NODEFAULTLIB:')
276 # TODO(scottmg): This should sort of be somewhere else (not really a flag).
277 ld('AdditionalDependencies', prefix='$!')
278 # TODO(scottmg): These too.
279 ldflags.extend(('kernel32.lib', 'user32.lib', 'gdi32.lib', 'winspool.lib',
280 'comdlg32.lib', 'advapi32.lib', 'shell32.lib', 'ole32.lib',
281 'oleaut32.lib', 'uuid.lib', 'odbc32.lib', 'odbccp32.lib',
282 'DelayImp.lib'))
283 return ldflags
284
285 def GetOutputName(self, config, product_dir):
286 ld = self._GetWrapper(self, self.msvs_settings[config], 'VCLinkerTool')
287 return ld('OutputFile')
288
289 def GetIdlRule(self, source, config,
290 gyp_to_build_path, gyp_to_unique_output, expand_rule_variables):
291 """Determine the implicit outputs for an idl file. Returns both the inputs
292 and the outputs for the build rule."""
293 midl = self._GetWrapper(self, self.msvs_settings[config], 'VCMIDLTool')
294 tlb = midl('TypeLibraryName')
295 header = midl('HeaderFileName')
296 dlldata = midl('DLLDataFileName')
297 iid = midl('InterfaceIdentifierFileName')
298 proxy = midl('ProxyFileName')
299 # Note that .tlb is not included in the outputs as it is not always
300 # generated depending on the content of the input idl file.
301 output = [header, dlldata, iid, proxy]
302 input = gyp_to_build_path(source)
303 outdir = gyp_to_build_path(midl('OutputDirectory'))
304 filename = os.path.splitext(os.path.split(source)[1])[0]
305 def fix_path(path, rel=None):
306 path = os.path.join(outdir, path)
307 path = expand_rule_variables(path, '?', '?', '?', '?', filename)
308 if rel:
309 path = os.path.relpath(path, rel)
310 return path
311 output = [fix_path(o) for o in output]
312 # TODO(scottmg): flags: /char signed /env win32, /Oicf
313 variables = [('name', filename),
314 ('tlb', fix_path(tlb, outdir)),
315 ('h', fix_path(header, outdir)),
316 ('dlldata', fix_path(dlldata, outdir)),
317 ('iid', fix_path(iid, outdir)),
318 ('proxy', fix_path(proxy, outdir)),
319 ('outdir', outdir)]
320 return input, output, variables
321
322 def HandleIdlFiles(self, ninja, spec, sources, config_name,
323 gyp_to_build_path, gyp_to_unique_output, expand_rule_variables):
324 """Visual Studio has implicit .idl build rules for MIDL files, but other
325 projects (e.g., WebKit) use .idl for a different import format with custom
326 build rules. So, only do the implicit rules if they're not overriden by
327 explicit rules."""
328 native_idl = True
329 for rule in spec.get('rules', []):
330 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
331 native_idl = False
332 if native_idl:
333 for source in sources:
334 filename, ext = os.path.splitext(source)
335 if ext == '.idl':
336 input, output, vars = self.GetIdlRule(
337 source,
338 config_name,
339 gyp_to_build_path,
340 gyp_to_unique_output,
341 expand_rule_variables)
342 ninja.build(output, 'idl', input, variables=vars)
343
344 vs_version = None
345 def GetVSVersion(generator_flags):
346 global vs_version
347 if not vs_version:
348 vs_version = gyp.MSVSVersion.SelectVisualStudioVersion(
349 generator_flags.get('msvs_version', 'auto'))
350 return vs_version
351
352 def _GetBinaryPath(generator_flags, tool):
353 vs = GetVSVersion(generator_flags)
354 return ('"' +
355 os.path.normpath(os.path.join(vs.Path(), "VC/bin", tool)) +
356 '"')
357
358 def GetCLPath(generator_flags):
359 return _GetBinaryPath(generator_flags, 'cl.exe')
360
361 def GetLinkPath(generator_flags):
362 return _GetBinaryPath(generator_flags, 'link.exe')
363
364 def GetMidlPath(generator_flags):
365 return _GetBinaryPath(generator_flags, 'midl.exe')
366
367 def IsRuleRunUnderCygwin(spec):
368 """Determine if an action should be run under cygwin. If the variable is
369 unset, or set to 1 we use cygwin."""
370 if not spec:
371 return False
372 return int(spec.get('msvs_cygwin_shell', 1)) != 0
373
374 def BuildCygwinBashCommandLine(settings, rule, args, path_to_base, config):
375 """Build a command line that runs args via cygwin bash. We assume that all
376 incoming paths are in Windows normpath'd form, so they need to be converted
377 to posix style for the part of the command line that's passed to bash. We
378 also have to do some Visual Studio macro emulation here because various rules
379 use magic VS names for things. Also note that rules that contain ninja
380 variables cannot be fixed here (for example ${source}), so the outer
381 generator needs to make sure that the paths that are written out are in posix
382 style, if the command line will be used here."""
383 assert IsRuleRunUnderCygwin(rule)
384 cygwin_dir = os.path.normpath(
385 os.path.join(path_to_base, settings.msvs_cygwin_dirs[config][0]))
386 cd = ('cd %s' % path_to_base).replace('\\', '/')
387 args = [settings.ConvertVSMacros(a).replace('\\', '/') for a in args]
388 args = ["'%s'" % a.replace("'", "\\'") for a in args]
389 bash_cmd = ' '.join(args)
390 cmd = (
391 'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir +
392 'bash -c "%s ; %s"' % (cd, bash_cmd))
393 return cmd
OLDNEW
« no previous file with comments | « pylib/gyp/generator/ninja.py ('k') | pylib/gyp/ninja_syntax.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698