Completed
Branch rebuild (e7a2d2)
by Glenn
08:26
created

WinTool   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 63
dl 0
loc 279
c 0
b 0
f 0
rs 3.6585

How to fix   Complexity   

Complex Class

Complex classes like WinTool often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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:]))
315