Skip to content

Commit ed767ed

Browse files
authored
Replace gyp win_tool.py with tool_wrapper.py (flutter#492)
win_tool.py is incompatible with Python 3. This ports Chromium's tool_wrapper.py to our buildroot, but doesn't port their full, updated resource compiler (rc) toolchain. Chromium moved to a model where they wrap rc in an rc.py script and support resource compilation on Win, Mac, and Linux. This adds an additional CIPD dependency that we can avoid. Instead, this ports the ExecRcWrapper implementation from gyp's win_tool.py, with fixes for Python 3. Upstream source: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/build/toolchain/win Replaces gyp win_tool.py: https://chromium.googlesource.com/external/gyp.git/+/refs/heads/master/pylib/gyp/win_tool.py Issue: flutter/flutter#83043
1 parent 81b4912 commit ed767ed

File tree

3 files changed

+211
-20
lines changed

3 files changed

+211
-20
lines changed

build/toolchain/win/BUILD.gn

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ assert(is_win)
2727
# Its arguments are the VS path and the compiler wrapper tool. It will write
2828
# "environment.x86" and "environment.x64" to the build directory and return a
2929
# list to us.
30-
gyp_win_tool_path =
31-
rebase_path("//third_party/gyp/pylib/gyp/win_tool.py", root_build_dir)
30+
31+
# This tool will is used as a wrapper for various commands below.
32+
tool_wrapper_path = rebase_path("tool_wrapper.py", root_build_dir)
3233

3334
toolchain_data = exec_script("setup_toolchain.py",
3435
[
3536
visual_studio_path,
36-
gyp_win_tool_path,
3737
windows_sdk_path,
3838
visual_studio_runtime_dirs,
3939
current_cpu,
@@ -125,7 +125,8 @@ template("msvc_toolchain") {
125125
}
126126

127127
tool("rc") {
128-
command = "$python_path gyp-win-tool rc-wrapper $env rc.exe {{defines}} {{include_dirs}} /fo{{output}} {{source}}"
128+
command = "$python_path $tool_wrapper_path rc-wrapper $env rc.exe /nologo {{defines}} {{include_dirs}} /fo{{output}} {{source}}"
129+
depsformat = "msvc"
129130
outputs = [
130131
"{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.res",
131132
]
@@ -146,7 +147,7 @@ template("msvc_toolchain") {
146147
ml = "ml.exe"
147148
x64 = ""
148149
}
149-
command = "$python_path gyp-win-tool asm-wrapper $env $ml $x64 {{defines}} {{include_dirs}} {{asmflags}} "
150+
command = "$python_path $tool_wrapper_path asm-wrapper $env $ml $x64 {{defines}} {{include_dirs}} {{asmflags}} "
150151
if (is_msvc_assembler) {
151152
command += "-c -o{{output}} {{source}}"
152153
} else {
@@ -159,7 +160,7 @@ template("msvc_toolchain") {
159160

160161
tool("alink") {
161162
rspfile = "{{output}}.rsp"
162-
command = "$python_path gyp-win-tool link-wrapper $env False lib.exe /nologo /ignore:4221 /OUT:{{output}} @$rspfile"
163+
command = "$python_path $tool_wrapper_path link-wrapper $env False lib.exe /nologo /ignore:4221 /OUT:{{output}} @$rspfile"
163164
description = "LIB {{output}}"
164165
outputs = [
165166
# Ignore {{output_extension}} and always use .lib, there's no reason to
@@ -179,10 +180,10 @@ template("msvc_toolchain") {
179180
"{{root_out_dir}}/{{target_output_name}}{{output_extension}}.lib" # e.g. foo.dll.lib
180181
rspfile = "${dllname}.rsp"
181182

182-
link_command = "$python_path gyp-win-tool link-wrapper $env False link.exe /nologo /IMPLIB:$libname /DLL /OUT:$dllname /PDB:${dllname}.pdb @$rspfile"
183+
link_command = "$python_path $tool_wrapper_path link-wrapper $env False link.exe /nologo /IMPLIB:$libname /DLL /OUT:$dllname /PDB:${dllname}.pdb @$rspfile"
183184

184185
# TODO(brettw) support manifests
185-
#manifest_command = "$python_path gyp-win-tool manifest-wrapper $env mt.exe -nologo -manifest $manifests -out:${dllname}.manifest"
186+
#manifest_command = "$python_path $tool_wrapper_path manifest-wrapper $env mt.exe -nologo -manifest $manifests -out:${dllname}.manifest"
186187
#command = "cmd /c $link_command && $manifest_command"
187188
command = link_command
188189

@@ -208,10 +209,10 @@ template("msvc_toolchain") {
208209
rspfile = "$binary_output.rsp"
209210
pdbfile = "$binary_output.pdb"
210211

211-
link_command = "$python_path gyp-win-tool link-wrapper $env False link.exe /nologo /OUT:$binary_output /PDB:$pdbfile @$rspfile"
212+
link_command = "$python_path $tool_wrapper_path link-wrapper $env False link.exe /nologo /OUT:$binary_output /PDB:$pdbfile @$rspfile"
212213

213214
# TODO(brettw) support manifests
214-
#manifest_command = "$python_path gyp-win-tool manifest-wrapper $env mt.exe -nologo -manifest $manifests -out:{{output}}.manifest"
215+
#manifest_command = "$python_path $tool_wrapper_path manifest-wrapper $env mt.exe -nologo -manifest $manifests -out:{{output}}.manifest"
215216
#command = "cmd /c $link_command && $manifest_command"
216217
command = link_command
217218

@@ -230,13 +231,13 @@ template("msvc_toolchain") {
230231
}
231232

232233
tool("stamp") {
233-
command = "$python_path gyp-win-tool stamp {{output}}"
234+
command = "cmd /c type nul > \"{{output}}\""
234235
description = "STAMP {{output}}"
235236
}
236237

237238
tool("copy") {
238239
command =
239-
"$python_path gyp-win-tool recursive-mirror {{source}} {{output}}"
240+
"$python_path $tool_wrapper_path recursive-mirror {{source}} {{output}}"
240241
description = "COPY {{source}} {{output}}"
241242
}
242243
}

build/toolchain/win/setup_toolchain.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,14 @@ def _CopyTool(source_path):
9696

9797

9898
def main():
99-
if len(sys.argv) != 6:
99+
if len(sys.argv) != 5:
100100
print('Usage setup_toolchain.py '
101-
'<visual studio path> <win tool path> <win sdk path> '
101+
'<visual studio path> <win sdk path> '
102102
'<runtime dirs> <target_cpu>')
103103
sys.exit(2)
104-
tool_source = sys.argv[2]
105-
win_sdk_path = sys.argv[3]
106-
runtime_dirs = sys.argv[4]
107-
target_cpu = sys.argv[5]
108-
109-
_CopyTool(tool_source)
104+
win_sdk_path = sys.argv[2]
105+
runtime_dirs = sys.argv[3]
106+
target_cpu = sys.argv[4]
110107

111108
cpus = ('x86', 'x64', 'arm64')
112109
assert target_cpu in cpus
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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+
5+
"""Utility functions for Windows builds.
6+
This file is copied to the build directory as part of toolchain setup and
7+
is used to set up calls to tools used by the build that need wrappers.
8+
"""
9+
10+
from __future__ import print_function
11+
12+
import os
13+
import re
14+
import shutil
15+
import subprocess
16+
import stat
17+
import sys
18+
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+
exit_code = WinTool().Dispatch(args)
28+
if exit_code is not None:
29+
sys.exit(exit_code)
30+
31+
32+
class 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 ExecDeleteFile(self, path):
85+
"""Simple file delete command."""
86+
if os.path.exists(path):
87+
os.unlink(path)
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, dummy_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+
# Try to diagnose crbug.com/741603
111+
if not os.path.exists(dest):
112+
raise Exception("Copying of %s to %s failed" % (source, dest))
113+
114+
def ExecLinkWrapper(self, arch, use_separate_mspdbsrv, *args):
115+
"""Filter diagnostic output from link that looks like:
116+
' Creating library ui.dll.lib and object ui.dll.exp'
117+
This happens when there are exports from the dll or exe.
118+
"""
119+
env = self._GetEnv(arch)
120+
if use_separate_mspdbsrv == 'True':
121+
self._UseSeparateMspdbsrv(env, args)
122+
if sys.platform == 'win32':
123+
args = list(args) # *args is a tuple by default, which is read-only.
124+
args[0] = args[0].replace('/', '\\')
125+
# https://docs.python.org/2/library/subprocess.html:
126+
# "On Unix with shell=True [...] if args is a sequence, the first item
127+
# specifies the command string, and any additional items will be treated as
128+
# additional arguments to the shell itself. That is to say, Popen does the
129+
# equivalent of:
130+
# Popen(['/bin/sh', '-c', args[0], args[1], ...])"
131+
# For that reason, since going through the shell doesn't seem necessary on
132+
# non-Windows don't do that there.
133+
pe_name = None
134+
for arg in args:
135+
m = _LINK_EXE_OUT_ARG.match(arg)
136+
if m:
137+
pe_name = m.group('out')
138+
link = subprocess.Popen(args, shell=sys.platform == 'win32', env=env,
139+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
140+
# Read output one line at a time as it shows up to avoid OOM failures when
141+
# GBs of output is produced.
142+
for line in link.stdout:
143+
if (not line.startswith(b' Creating library ')
144+
and not line.startswith(b'Generating code')
145+
and not line.startswith(b'Finished generating code')):
146+
print(line)
147+
return link.wait()
148+
149+
def ExecAsmWrapper(self, arch, *args):
150+
"""Filter logo banner from invocations of asm.exe."""
151+
env = self._GetEnv(arch)
152+
if sys.platform == 'win32':
153+
# Windows ARM64 uses clang-cl as assembler which has '/' as path
154+
# separator, convert it to '\\' when running on Windows.
155+
args = list(args) # *args is a tuple by default, which is read-only
156+
args[0] = args[0].replace('/', '\\')
157+
popen = subprocess.Popen(args, shell=True, env=env,
158+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
159+
out, _ = popen.communicate()
160+
for line in out.decode('utf8').splitlines():
161+
if not line.startswith(' Assembling: '):
162+
print(line)
163+
return popen.returncode
164+
165+
def ExecRcWrapper(self, arch, *args):
166+
"""Filter logo banner from invocations of rc.exe. Older versions of RC
167+
don't support the /nologo flag."""
168+
env = self._GetEnv(arch)
169+
popen = subprocess.Popen(args, shell=True, env=env,
170+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
171+
out, _ = popen.communicate()
172+
for line in out.splitlines():
173+
if (not line.startswith(b'Microsoft (R) Windows (R) Resource Compiler') and
174+
not line.startswith(b'Copyright (C) Microsoft Corporation') and line):
175+
print(line)
176+
return popen.returncode
177+
178+
def ExecActionWrapper(self, arch, rspfile, *dirname):
179+
"""Runs an action command line from a response file using the environment
180+
for |arch|. If |dirname| is supplied, use that as the working directory."""
181+
env = self._GetEnv(arch)
182+
# TODO(scottmg): This is a temporary hack to get some specific variables
183+
# through to actions that are set after GN-time. http://crbug.com/333738.
184+
for k, v in os.environ.items():
185+
if k not in env:
186+
env[k] = v
187+
args = open(rspfile).read()
188+
dirname = dirname[0] if dirname else None
189+
return subprocess.call(args, shell=True, env=env, cwd=dirname)
190+
191+
192+
if __name__ == '__main__':
193+
sys.exit(main(sys.argv[1:]))

0 commit comments

Comments
 (0)