Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
tool_wrapper.py
Go to the documentation of this file.
1# Copyright (c) 2012 The Chromium Authors. 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"""Utility functions for Windows builds.
5
6This file is copied to the build directory as part of toolchain setup and
7is used to set up calls to tools used by the build that need wrappers.
8"""
9
10import os
11import pathlib
12import re
13import shutil
14import subprocess
15import stat
16import sys
17
18BASE_DIR = os.path.dirname(os.path.abspath(__file__))
19
20# A regex matching an argument corresponding to the output filename passed to
21# link.exe.
22_LINK_EXE_OUT_ARG = re.compile('/OUT:(?P<out>.+)$', re.IGNORECASE)
23
24
25def main(args):
26 executor = WinTool()
27 exit_code = executor.Dispatch(args)
28 if exit_code is not None:
29 sys.exit(exit_code)
30
31
32class WinTool(object):
33 """This class performs all the Windows tooling steps. The methods can either
34 be executed directly, or dispatched from an argument list."""
35
36 def _UseSeparateMspdbsrv(self, env, args):
37 """Allows to use a unique instance of mspdbsrv.exe per linker instead of a
38 shared one."""
39 if len(args) < 1:
40 raise Exception("Not enough arguments")
41
42 if args[0] != 'link.exe':
43 return
44
45 # Use the output filename passed to the linker to generate an endpoint name
46 # for mspdbsrv.exe.
47 endpoint_name = None
48 for arg in args:
49 m = _LINK_EXE_OUT_ARG.match(arg)
50 if m:
51 endpoint_name = re.sub(r'\W+', '',
52 '%s_%d' % (m.group('out'), os.getpid()))
53 break
54
55 if endpoint_name is None:
56 return
57
58 # Adds the appropriate environment variable. This will be read by link.exe
59 # to know which instance of mspdbsrv.exe it should connect to (if it's
60 # not set then the default endpoint is used).
61 env['_MSPDBSRV_ENDPOINT_'] = endpoint_name
62
63 def Dispatch(self, args):
64 """Dispatches a string command to a method."""
65 if len(args) < 1:
66 raise Exception("Not enough arguments")
67
68 method = "Exec%s" % self._CommandifyName(args[0])
69 return getattr(self, method)(*args[1:])
70
71 def _CommandifyName(self, name_string):
72 """Transforms a tool name like recursive-mirror to RecursiveMirror."""
73 return name_string.title().replace('-', '')
74
75 def _GetEnv(self, arch):
76 """Gets the saved environment from a file for a given architecture."""
77 # The environment is saved as an "environment block" (see CreateProcess
78 # and msvs_emulation for details). We convert to a dict here.
79 # Drop last 2 NULs, one for list terminator, one for trailing vs. separator.
80 pairs = open(arch).read()[:-2].split('\0')
81 kvs = [item.split('=', 1) for item in pairs]
82 return dict(kvs)
83
84 def ExecStamp(self, path):
85 """Simple stamp command."""
86 open(path, 'w').close()
87
88 def ExecDeleteFile(self, path):
89 """Simple file delete command."""
90 if os.path.exists(path):
91 os.unlink(path)
92
93 def ExecRecursiveMirror(self, source, dest):
94 """Emulation of rm -rf out && cp -af in out."""
95 if os.path.exists(dest):
96 if os.path.isdir(dest):
97
98 def _on_error(fn, path, dummy_excinfo):
99 # The operation failed, possibly because the file is set to
100 # read-only. If that's why, make it writable and try the op again.
101 if not os.access(path, os.W_OK):
102 os.chmod(path, stat.S_IWRITE)
103 fn(path)
104
105 shutil.rmtree(dest, onerror=_on_error)
106 else:
107 if not os.access(dest, os.W_OK):
108 # Attempt to make the file writable before deleting it.
109 os.chmod(dest, stat.S_IWRITE)
110 os.unlink(dest)
111
112 if os.path.isdir(source):
113 shutil.copytree(source, dest)
114 else:
115 shutil.copy2(source, dest)
116 # Try to diagnose crbug.com/741603
117 if not os.path.exists(dest):
118 raise Exception("Copying of %s to %s failed" % (source, dest))
119
120 def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args):
121 """Filter diagnostic output from link that looks like:
122 ' Creating library ui.dll.lib and object ui.dll.exp'
123 This happens when there are exports from the dll or exe.
124 """
125 env = self._GetEnv(arch)
126 if use_separate_mspdbsrv == 'True':
127 self._UseSeparateMspdbsrv(env, args)
128 if sys.platform == 'win32':
129 args = list(
130 args) # *args is a tuple by default, which is read-only.
131 args[0] = args[0].replace('/', '\\')
132 # https://docs.python.org/2/library/subprocess.html:
133 # "On Unix with shell=True [...] if args is a sequence, the first item
134 # specifies the command string, and any additional items will be treated as
135 # additional arguments to the shell itself. That is to say, Popen does the
136 # equivalent of:
137 # Popen(['/bin/sh', '-c', args[0], args[1], ...])"
138 # For that reason, since going through the shell doesn't seem necessary on
139 # non-Windows don't do that there.
140 link = subprocess.Popen(args,
141 shell=sys.platform == 'win32',
142 env=env,
143 stdout=subprocess.PIPE,
144 stderr=subprocess.STDOUT,
145 universal_newlines=True)
146 # Read output one line at a time as it shows up to avoid OOM failures when
147 # GBs of output is produced.
148 for line in link.stdout:
149 if (not line.startswith(' Creating library ') and
150 not line.startswith('Generating code') and
151 not line.startswith('Finished generating code')):
152 print(line)
153 link_result = link.wait()
154
155 if link_result != 0:
156 return link_result
157
158 # The toolchain configuration in gn always expects a .lib file to be
159 # included in the output of the link step. However, this only happens
160 # when the output has exports, and that is not always the case. In
161 # order to satisfy the expected outputs, we create a dummy .lib file
162 # in cases where the link step didn't actually create one.
163 for arg in args:
164 m = _LINK_EXE_OUT_ARG.match(arg)
165 if m:
166 output_filename = m.group('out')
167 (basename, extension) = os.path.splitext(output_filename)
168 if extension == '.exe':
169 lib_path = pathlib.Path(basename + ".lib")
170 if not os.path.exists(lib_path):
171 lib_path.touch()
172 break
173
174 return link_result
175
176 def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl,
177 *flags):
178 """Filter noisy filenames output from MIDL compile step that isn't
179 quietable via command line flags.
180 """
181 args = ['midl', '/nologo'] + list(flags) + [
182 '/out', outdir, '/tlb', tlb, '/h', h, '/dlldata', dlldata, '/iid',
183 iid, '/proxy', proxy, idl
184 ]
185 env = self._GetEnv(arch)
186 popen = subprocess.Popen(args,
187 shell=True,
188 env=env,
189 stdout=subprocess.PIPE,
190 stderr=subprocess.STDOUT,
191 universal_newlines=True)
192 out, _ = popen.communicate()
193 # Filter junk out of stdout, and write filtered versions. Output we want
194 # to filter is pairs of lines that look like this:
195 # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl
196 # objidl.idl
197 lines = out.splitlines()
198 prefixes = ('Processing ', '64 bit Processing ')
199 processing = set(
200 os.path.basename(x) for x in lines if x.startswith(prefixes))
201 for line in lines:
202 if not line.startswith(prefixes) and line not in processing:
203 print(line)
204 return popen.returncode
205
206 def ExecAsmWrapper(self, arch, *args):
207 """Filter logo banner from invocations of asm.exe."""
208 env = self._GetEnv(arch)
209 popen = subprocess.Popen(args,
210 shell=True,
211 env=env,
212 stdout=subprocess.PIPE,
213 stderr=subprocess.STDOUT,
214 universal_newlines=True)
215 out, _ = popen.communicate()
216 for line in out.splitlines():
217 # Split to avoid triggering license checks:
218 if (not line.startswith('Copy' + 'right (C' +
219 ') Microsoft Corporation') and
220 not line.startswith('Microsoft (R) Macro Assembler') and
221 not line.startswith(' Assembling: ') and line):
222 print(line)
223 return popen.returncode
224
225 def ExecRcWrapper(self, arch, *args):
226 """Filter logo banner from invocations of rc.exe. Older versions of RC
227 don't support the /nologo flag."""
228 env = self._GetEnv(arch)
229 popen = subprocess.Popen(args,
230 shell=True,
231 env=env,
232 stdout=subprocess.PIPE,
233 stderr=subprocess.STDOUT,
234 universal_newlines=True)
235 out, _ = popen.communicate()
236 for line in out.splitlines():
237 if (not line.startswith(
238 'Microsoft (R) Windows (R) Resource Compiler') and
239 not line.startswith('Copy' + 'right (C' +
240 ') Microsoft Corporation') and line):
241 print(line)
242 return popen.returncode
243
244 def ExecActionWrapper(self, arch, rspfile, *dirname):
245 """Runs an action command line from a response file using the environment
246 for |arch|. If |dirname| is supplied, use that as the working directory."""
247 env = self._GetEnv(arch)
248 # TODO(scottmg): This is a temporary hack to get some specific variables
249 # through to actions that are set after GN-time. http://crbug.com/333738.
250 for k, v in os.environ.items():
251 if k not in env:
252 env[k] = v
253 args = open(rspfile).read()
254 dirname = dirname[0] if dirname else None
255 return subprocess.call(args, shell=True, env=env, cwd=dirname)
256
257
258if __name__ == '__main__':
259 sys.exit(main(sys.argv[1:]))
static bool read(SkStream *stream, void *buffer, size_t amount)
void print(void *str)
Definition bridge.cpp:126
ExecAsmWrapper(self, arch, *args)
ExecDeleteFile(self, path)
ExecStamp(self, path)
ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args)
_CommandifyName(self, name_string)
_UseSeparateMspdbsrv(self, env, args)
ExecRcWrapper(self, arch, *args)
ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl, *flags)
ExecActionWrapper(self, arch, rspfile, *dirname)
ExecRecursiveMirror(self, source, dest)
Definition main.py:1