Initial commit
[yaffs-website] / node_modules / node-gyp / gyp / pylib / gyp / win_tool.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2012 Google Inc. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """Utility functions for Windows builds.
8
9 These functions are executed via gyp-win-tool when using the ninja generator.
10 """
11
12 import os
13 import re
14 import shutil
15 import subprocess
16 import stat
17 import string
18 import sys
19
20 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
21
22 # A regex matching an argument corresponding to the output filename passed to
23 # link.exe.
24 _LINK_EXE_OUT_ARG = re.compile('/OUT:(?P<out>.+)$', re.IGNORECASE)
25
26 def main(args):
27   executor = WinTool()
28   exit_code = executor.Dispatch(args)
29   if exit_code is not None:
30     sys.exit(exit_code)
31
32
33 class WinTool(object):
34   """This class performs all the Windows tooling steps. The methods can either
35   be executed directly, or dispatched from an argument list."""
36
37   def _UseSeparateMspdbsrv(self, env, args):
38     """Allows to use a unique instance of mspdbsrv.exe per linker instead of a
39     shared one."""
40     if len(args) < 1:
41       raise Exception("Not enough arguments")
42
43     if args[0] != 'link.exe':
44       return
45
46     # Use the output filename passed to the linker to generate an endpoint name
47     # for mspdbsrv.exe.
48     endpoint_name = None
49     for arg in args:
50       m = _LINK_EXE_OUT_ARG.match(arg)
51       if m:
52         endpoint_name = re.sub(r'\W+', '',
53             '%s_%d' % (m.group('out'), os.getpid()))
54         break
55
56     if endpoint_name is None:
57       return
58
59     # Adds the appropriate environment variable. This will be read by link.exe
60     # to know which instance of mspdbsrv.exe it should connect to (if it's
61     # not set then the default endpoint is used).
62     env['_MSPDBSRV_ENDPOINT_'] = endpoint_name
63
64   def Dispatch(self, args):
65     """Dispatches a string command to a method."""
66     if len(args) < 1:
67       raise Exception("Not enough arguments")
68
69     method = "Exec%s" % self._CommandifyName(args[0])
70     return getattr(self, method)(*args[1:])
71
72   def _CommandifyName(self, name_string):
73     """Transforms a tool name like recursive-mirror to RecursiveMirror."""
74     return name_string.title().replace('-', '')
75
76   def _GetEnv(self, arch):
77     """Gets the saved environment from a file for a given architecture."""
78     # The environment is saved as an "environment block" (see CreateProcess
79     # and msvs_emulation for details). We convert to a dict here.
80     # Drop last 2 NULs, one for list terminator, one for trailing vs. separator.
81     pairs = open(arch).read()[:-2].split('\0')
82     kvs = [item.split('=', 1) for item in pairs]
83     return dict(kvs)
84
85   def ExecStamp(self, path):
86     """Simple stamp command."""
87     open(path, 'w').close()
88
89   def ExecRecursiveMirror(self, source, dest):
90     """Emulation of rm -rf out && cp -af in out."""
91     if os.path.exists(dest):
92       if os.path.isdir(dest):
93         def _on_error(fn, path, excinfo):
94           # The operation failed, possibly because the file is set to
95           # read-only. If that's why, make it writable and try the op again.
96           if not os.access(path, os.W_OK):
97             os.chmod(path, stat.S_IWRITE)
98           fn(path)
99         shutil.rmtree(dest, onerror=_on_error)
100       else:
101         if not os.access(dest, os.W_OK):
102           # Attempt to make the file writable before deleting it.
103           os.chmod(dest, stat.S_IWRITE)
104         os.unlink(dest)
105
106     if os.path.isdir(source):
107       shutil.copytree(source, dest)
108     else:
109       shutil.copy2(source, dest)
110
111   def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args):
112     """Filter diagnostic output from link that looks like:
113     '   Creating library ui.dll.lib and object ui.dll.exp'
114     This happens when there are exports from the dll or exe.
115     """
116     env = self._GetEnv(arch)
117     if use_separate_mspdbsrv == 'True':
118       self._UseSeparateMspdbsrv(env, args)
119     link = subprocess.Popen([args[0].replace('/', '\\')] + list(args[1:]),
120                             shell=True,
121                             env=env,
122                             stdout=subprocess.PIPE,
123                             stderr=subprocess.STDOUT)
124     out, _ = link.communicate()
125     for line in out.splitlines():
126       if (not line.startswith('   Creating library ') and
127           not line.startswith('Generating code') and
128           not line.startswith('Finished generating code')):
129         print line
130     return link.returncode
131
132   def ExecLinkWithManifests(self, arch, embed_manifest, out, ldcmd, resname,
133                             mt, rc, intermediate_manifest, *manifests):
134     """A wrapper for handling creating a manifest resource and then executing
135     a link command."""
136     # The 'normal' way to do manifests is to have link generate a manifest
137     # based on gathering dependencies from the object files, then merge that
138     # manifest with other manifests supplied as sources, convert the merged
139     # manifest to a resource, and then *relink*, including the compiled
140     # version of the manifest resource. This breaks incremental linking, and
141     # is generally overly complicated. Instead, we merge all the manifests
142     # provided (along with one that includes what would normally be in the
143     # linker-generated one, see msvs_emulation.py), and include that into the
144     # first and only link. We still tell link to generate a manifest, but we
145     # only use that to assert that our simpler process did not miss anything.
146     variables = {
147       'python': sys.executable,
148       'arch': arch,
149       'out': out,
150       'ldcmd': ldcmd,
151       'resname': resname,
152       'mt': mt,
153       'rc': rc,
154       'intermediate_manifest': intermediate_manifest,
155       'manifests': ' '.join(manifests),
156     }
157     add_to_ld = ''
158     if manifests:
159       subprocess.check_call(
160           '%(python)s gyp-win-tool manifest-wrapper %(arch)s %(mt)s -nologo '
161           '-manifest %(manifests)s -out:%(out)s.manifest' % variables)
162       if embed_manifest == 'True':
163         subprocess.check_call(
164             '%(python)s gyp-win-tool manifest-to-rc %(arch)s %(out)s.manifest'
165           ' %(out)s.manifest.rc %(resname)s' % variables)
166         subprocess.check_call(
167             '%(python)s gyp-win-tool rc-wrapper %(arch)s %(rc)s '
168             '%(out)s.manifest.rc' % variables)
169         add_to_ld = ' %(out)s.manifest.res' % variables
170     subprocess.check_call(ldcmd + add_to_ld)
171
172     # Run mt.exe on the theoretically complete manifest we generated, merging
173     # it with the one the linker generated to confirm that the linker
174     # generated one does not add anything. This is strictly unnecessary for
175     # correctness, it's only to verify that e.g. /MANIFESTDEPENDENCY was not
176     # used in a #pragma comment.
177     if manifests:
178       # Merge the intermediate one with ours to .assert.manifest, then check
179       # that .assert.manifest is identical to ours.
180       subprocess.check_call(
181           '%(python)s gyp-win-tool manifest-wrapper %(arch)s %(mt)s -nologo '
182           '-manifest %(out)s.manifest %(intermediate_manifest)s '
183           '-out:%(out)s.assert.manifest' % variables)
184       assert_manifest = '%(out)s.assert.manifest' % variables
185       our_manifest = '%(out)s.manifest' % variables
186       # Load and normalize the manifests. mt.exe sometimes removes whitespace,
187       # and sometimes doesn't unfortunately.
188       with open(our_manifest, 'rb') as our_f:
189         with open(assert_manifest, 'rb') as assert_f:
190           our_data = our_f.read().translate(None, string.whitespace)
191           assert_data = assert_f.read().translate(None, string.whitespace)
192       if our_data != assert_data:
193         os.unlink(out)
194         def dump(filename):
195           sys.stderr.write('%s\n-----\n' % filename)
196           with open(filename, 'rb') as f:
197             sys.stderr.write(f.read() + '\n-----\n')
198         dump(intermediate_manifest)
199         dump(our_manifest)
200         dump(assert_manifest)
201         sys.stderr.write(
202             'Linker generated manifest "%s" added to final manifest "%s" '
203             '(result in "%s"). '
204             'Were /MANIFEST switches used in #pragma statements? ' % (
205               intermediate_manifest, our_manifest, assert_manifest))
206         return 1
207
208   def ExecManifestWrapper(self, arch, *args):
209     """Run manifest tool with environment set. Strip out undesirable warning
210     (some XML blocks are recognized by the OS loader, but not the manifest
211     tool)."""
212     env = self._GetEnv(arch)
213     popen = subprocess.Popen(args, shell=True, env=env,
214                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
215     out, _ = popen.communicate()
216     for line in out.splitlines():
217       if line and 'manifest authoring warning 81010002' not in line:
218         print line
219     return popen.returncode
220
221   def ExecManifestToRc(self, arch, *args):
222     """Creates a resource file pointing a SxS assembly manifest.
223     |args| is tuple containing path to resource file, path to manifest file
224     and resource name which can be "1" (for executables) or "2" (for DLLs)."""
225     manifest_path, resource_path, resource_name = args
226     with open(resource_path, 'wb') as output:
227       output.write('#include <windows.h>\n%s RT_MANIFEST "%s"' % (
228         resource_name,
229         os.path.abspath(manifest_path).replace('\\', '/')))
230
231   def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl,
232                       *flags):
233     """Filter noisy filenames output from MIDL compile step that isn't
234     quietable via command line flags.
235     """
236     args = ['midl', '/nologo'] + list(flags) + [
237         '/out', outdir,
238         '/tlb', tlb,
239         '/h', h,
240         '/dlldata', dlldata,
241         '/iid', iid,
242         '/proxy', proxy,
243         idl]
244     env = self._GetEnv(arch)
245     popen = subprocess.Popen(args, shell=True, env=env,
246                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
247     out, _ = popen.communicate()
248     # Filter junk out of stdout, and write filtered versions. Output we want
249     # to filter is pairs of lines that look like this:
250     # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl
251     # objidl.idl
252     lines = out.splitlines()
253     prefixes = ('Processing ', '64 bit Processing ')
254     processing = set(os.path.basename(x)
255                      for x in lines if x.startswith(prefixes))
256     for line in lines:
257       if not line.startswith(prefixes) and line not in processing:
258         print line
259     return popen.returncode
260
261   def ExecAsmWrapper(self, arch, *args):
262     """Filter logo banner from invocations of asm.exe."""
263     env = self._GetEnv(arch)
264     popen = subprocess.Popen(args, shell=True, env=env,
265                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
266     out, _ = popen.communicate()
267     for line in out.splitlines():
268       if (not line.startswith('Copyright (C) Microsoft Corporation') and
269           not line.startswith('Microsoft (R) Macro Assembler') and
270           not line.startswith(' Assembling: ') and
271           line):
272         print line
273     return popen.returncode
274
275   def ExecRcWrapper(self, arch, *args):
276     """Filter logo banner from invocations of rc.exe. Older versions of RC
277     don't support the /nologo flag."""
278     env = self._GetEnv(arch)
279     popen = subprocess.Popen(args, shell=True, env=env,
280                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
281     out, _ = popen.communicate()
282     for line in out.splitlines():
283       if (not line.startswith('Microsoft (R) Windows (R) Resource Compiler') and
284           not line.startswith('Copyright (C) Microsoft Corporation') and
285           line):
286         print line
287     return popen.returncode
288
289   def ExecActionWrapper(self, arch, rspfile, *dir):
290     """Runs an action command line from a response file using the environment
291     for |arch|. If |dir| is supplied, use that as the working directory."""
292     env = self._GetEnv(arch)
293     # TODO(scottmg): This is a temporary hack to get some specific variables
294     # through to actions that are set after gyp-time. http://crbug.com/333738.
295     for k, v in os.environ.iteritems():
296       if k not in env:
297         env[k] = v
298     args = open(rspfile).read()
299     dir = dir[0] if dir else None
300     return subprocess.call(args, shell=True, env=env, cwd=dir)
301
302   def ExecClCompile(self, project_dir, selected_files):
303     """Executed by msvs-ninja projects when the 'ClCompile' target is used to
304     build selected C/C++ files."""
305     project_dir = os.path.relpath(project_dir, BASE_DIR)
306     selected_files = selected_files.split(';')
307     ninja_targets = [os.path.join(project_dir, filename) + '^^'
308         for filename in selected_files]
309     cmd = ['ninja.exe']
310     cmd.extend(ninja_targets)
311     return subprocess.call(cmd, shell=True, cwd=BASE_DIR)
312
313 if __name__ == '__main__':
314   sys.exit(main(sys.argv[1:]))