Flutter Engine
The Flutter Engine
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)
def ExecAsmWrapper(self, arch, *args)
def ExecDeleteFile(self, path)
Definition: tool_wrapper.py:88
def ExecActionWrapper(self, arch, rspfile, *dirname)
def ExecMidlWrapper(self, arch, outdir, tlb, h, dlldata, iid, proxy, idl, *flags)
def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args)
def Dispatch(self, args)
Definition: tool_wrapper.py:63
def ExecStamp(self, path)
Definition: tool_wrapper.py:84
def ExecRcWrapper(self, arch, *args)
def _CommandifyName(self, name_string)
Definition: tool_wrapper.py:71
def _UseSeparateMspdbsrv(self, env, args)
Definition: tool_wrapper.py:36
def ExecRecursiveMirror(self, source, dest)
Definition: tool_wrapper.py:93
def _GetEnv(self, arch)
Definition: tool_wrapper.py:75
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not set
Definition: switches.h:76
Definition: main.py:1
def print(*args, **kwargs)
Definition: run_tests.py:49
def main(args)
Definition: tool_wrapper.py:25