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
9These functions are executed via gyp-win-tool when using the ninja generator.
10"""
11
12import os
13import shutil
14import subprocess
15import sys
16
17BASE_DIR = os.path.dirname(os.path.abspath(__file__))
18
19
20def main(args):
21  executor = WinTool()
22  exit_code = executor.Dispatch(args)
23  if exit_code is not None:
24    sys.exit(exit_code)
25
26
27class WinTool(object):
28  """This class performs all the Windows tooling steps. The methods can either
29  be executed directly, or dispatched from an argument list."""
30
31  def Dispatch(self, args):
32    """Dispatches a string command to a method."""
33    if len(args) < 1:
34      raise Exception("Not enough arguments")
35
36    method = "Exec%s" % self._CommandifyName(args[0])
37    return getattr(self, method)(*args[1:])
38
39  def _CommandifyName(self, name_string):
40    """Transforms a tool name like recursive-mirror to RecursiveMirror."""
41    return name_string.title().replace('-', '')
42
43  def _GetEnv(self, arch):
44    """Gets the saved environment from a file for a given architecture."""
45    # The environment is saved as an "environment block" (see CreateProcess
46    # and msvs_emulation for details). We convert to a dict here.
47    # Drop last 2 NULs, one for list terminator, one for trailing vs. separator.
48    pairs = open(arch).read()[:-2].split('\0')
49    kvs = [item.split('=', 1) for item in pairs]
50    return dict(kvs)
51
52  def ExecStamp(self, path):
53    """Simple stamp command."""
54    open(path, 'w').close()
55
56  def ExecRecursiveMirror(self, source, dest):
57    """Emulation of rm -rf out && cp -af in out."""
58    if os.path.exists(dest):
59      if os.path.isdir(dest):
60        shutil.rmtree(dest)
61      else:
62        os.unlink(dest)
63    if os.path.isdir(source):
64      shutil.copytree(source, dest)
65    else:
66      shutil.copy2(source, dest)
67
68  def ExecLinkWrapper(self, arch, *args):
69    """Filter diagnostic output from link that looks like:
70    '   Creating library ui.dll.lib and object ui.dll.exp'
71    This happens when there are exports from the dll or exe.
72    """
73    env = self._GetEnv(arch)
74    popen = subprocess.Popen(args, shell=True, env=env,
75                              stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
76    out, _ = popen.communicate()
77    for line in out.splitlines():
78      if not line.startswith('   Creating library '):
79        print line
80    return popen.returncode
81
82  def ExecManifestWrapper(self, arch, *args):
83    """Run manifest tool with environment set. Strip out undesirable warning
84    (some XML blocks are recognized by the OS loader, but not the manifest
85    tool)."""
86    env = self._GetEnv(arch)
87    popen = subprocess.Popen(args, shell=True, env=env,
88                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
89    out, _ = popen.communicate()
90    for line in out.splitlines():
91      if line and 'manifest authoring warning 81010002' not in line:
92        print line
93    return popen.returncode
94
95  def ExecManifestToRc(self, arch, *args):
96    """Creates a resource file pointing a SxS assembly manifest.
97    |args| is tuple containing path to resource file, path to manifest file
98    and resource name which can be "1" (for executables) or "2" (for DLLs)."""
99    manifest_path, resource_path, resource_name = args
100    with open(resource_path, 'wb') as output:
101      output.write('#include <windows.h>\n%s RT_MANIFEST "%s"' % (
102        resource_name,
103        os.path.abspath(manifest_path).replace('\\', '/')))
104
105  def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl,
106                      *flags):
107    """Filter noisy filenames output from MIDL compile step that isn't
108    quietable via command line flags.
109    """
110    args = ['midl', '/nologo'] + list(flags) + [
111        '/out', outdir,
112        '/tlb', tlb,
113        '/h', h,
114        '/dlldata', dlldata,
115        '/iid', iid,
116        '/proxy', proxy,
117        idl]
118    env = self._GetEnv(arch)
119    popen = subprocess.Popen(args, shell=True, env=env,
120                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
121    out, _ = popen.communicate()
122    # Filter junk out of stdout, and write filtered versions. Output we want
123    # to filter is pairs of lines that look like this:
124    # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl
125    # objidl.idl
126    lines = out.splitlines()
127    prefix = 'Processing '
128    processing = set(os.path.basename(x) for x in lines if x.startswith(prefix))
129    for line in lines:
130      if not line.startswith(prefix) and line not in processing:
131        print line
132    return popen.returncode
133
134  def ExecAsmWrapper(self, arch, *args):
135    """Filter logo banner from invocations of asm.exe."""
136    env = self._GetEnv(arch)
137    # MSVS doesn't assemble x64 asm files.
138    if arch == 'environment.x64':
139      return 0
140    popen = subprocess.Popen(args, shell=True, env=env,
141                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
142    out, _ = popen.communicate()
143    for line in out.splitlines():
144      if (not line.startswith('Copyright (C) Microsoft Corporation') and
145          not line.startswith('Microsoft (R) Macro Assembler') and
146          not line.startswith(' Assembling: ') and
147          line):
148        print line
149    return popen.returncode
150
151  def ExecRcWrapper(self, arch, *args):
152    """Filter logo banner from invocations of rc.exe. Older versions of RC
153    don't support the /nologo flag."""
154    env = self._GetEnv(arch)
155    popen = subprocess.Popen(args, shell=True, env=env,
156                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
157    out, _ = popen.communicate()
158    for line in out.splitlines():
159      if (not line.startswith('Microsoft (R) Windows (R) Resource Compiler') and
160          not line.startswith('Copyright (C) Microsoft Corporation') and
161          line):
162        print line
163    return popen.returncode
164
165  def ExecActionWrapper(self, arch, rspfile, *dir):
166    """Runs an action command line from a response file using the environment
167    for |arch|. If |dir| is supplied, use that as the working directory."""
168    env = self._GetEnv(arch)
169    args = open(rspfile).read()
170    dir = dir[0] if dir else None
171    popen = subprocess.Popen(args, shell=True, env=env, cwd=dir)
172    popen.wait()
173    return popen.returncode
174
175if __name__ == '__main__':
176  sys.exit(main(sys.argv[1:]))
177