1
#! /usr/bin/env python3
2
assert __name__ == '__main__'
5
To update ANGLE in Gecko, use Windows with git-bash, and setup depot_tools, python2, and
6
python3. Because depot_tools expects `python` to be `python2` (shame!), python2 must come
7
before python3 in your path.
9
Upstream: https://chromium.googlesource.com/angle/angle
11
Our repo: https://github.com/mozilla/angle
12
It has branches like 'firefox-60' which is the branch we use for pulling into
13
Gecko with this script.
15
This script leaves a record of the merge-base and cherry-picks that we pull into
16
Gecko. (gfx/angle/cherries.log)
18
ANGLE<->Chrome version mappings are here: https://omahaproxy.appspot.com/
19
An easy choice is to grab Chrome's Beta's ANGLE branch.
26
export PATH="$PATH:/path/to/depot_tools"
29
If this is a new repo, don't forget:
33
./scripts/bootstrap.py
37
Update: (in the angle repo)
41
/path/to/gecko/gfx/angle/update-angle.py origin/chromium/XXXX
42
git push moz # Push the firefox-XX branch to github.com/mozilla/angle
54
from typing import * # mypy annotations
56
REPO_DIR = pathlib.Path.cwd()
58
GN_ENV = dict(os.environ)
59
# We need to set DEPOT_TOOLS_WIN_TOOLCHAIN to 0 for non-Googlers, but otherwise
60
# leave it unset since vs_toolchain.py assumes that the user is a Googler with
61
# the Visual Studio files in depot_tools if DEPOT_TOOLS_WIN_TOOLCHAIN is not
62
# explicitly set to 0.
64
for directory in os.environ['PATH'].split(os.pathsep):
65
vs_dir = os.path.join(directory, 'win_toolchain', 'vs_files')
66
if os.path.exists(vs_dir):
70
GN_ENV['DEPOT_TOOLS_WIN_TOOLCHAIN'] = '0'
73
sys.exit('Usage: export_targets.py OUT_DIR ROOTS...')
75
(OUT_DIR, *ROOTS) = sys.argv[1:]
77
assert x.startswith('//:')
79
# ------------------------------------------------------------------------------
81
def run_checked(*args, **kwargs):
82
print(' ', args, file=sys.stderr)
84
return subprocess.run(args, check=True, **kwargs)
88
return sorted(x, key=str.lower)
91
def dag_traverse(root_keys: Sequence[str], pre_recurse_func: Callable[[str], list]):
92
visited_keys: Set[str] = set()
95
if key in visited_keys:
99
t = pre_recurse_func(key)
101
(next_keys, post_recurse_func) = t
104
post_recurse_func = None
109
if post_recurse_func:
110
post_recurse_func(key)
117
# ------------------------------------------------------------------------------
119
print('Importing graph', file=sys.stderr)
122
p = run_checked('gn', 'desc', '--format=json', str(OUT_DIR), '*', stdout=subprocess.PIPE,
123
env=GN_ENV, shell=(True if sys.platform == 'win32' else False))
124
except subprocess.CalledProcessError:
125
sys.stderr.buffer.write(b'"gn desc" failed. Is depot_tools in your PATH?\n')
130
print('\nProcessing graph', file=sys.stderr)
131
descs = json.loads(p.stdout.decode())
134
# ------------------------------------------------------------------------------
136
LIBRARY_TYPES = ('shared_library', 'static_library')
138
def flattened_target(target_name: str, descs: dict, stop_at_lib: bool =True) -> dict:
139
flattened = dict(descs[target_name])
141
EXPECTED_TYPES = LIBRARY_TYPES + ('source_set', 'group', 'action')
146
dep_type = dep['type']
148
if stop_at_lib and dep_type in LIBRARY_TYPES:
151
if dep_type == 'copy':
152
assert not deps, (target_name, dep['deps'])
154
assert dep_type in EXPECTED_TYPES, (k, dep_type)
155
for (k,v) in dep.items():
156
if type(v) in (list, tuple, set):
157
flattened[k] = sortedi(set(flattened.get(k, []) + v))
159
#flattened.setdefault(k, v)
163
dag_traverse(descs[target_name]['deps'], pre)
166
# ------------------------------------------------------------------------------
167
# Check that includes are valid. (gn's version of this check doesn't seem to work!)
169
INCLUDE_REGEX = re.compile(b'(?:^|\\n) *# *include +([<"])([^>"]+)[>"]')
170
assert INCLUDE_REGEX.match(b'#include "foo"')
171
assert INCLUDE_REGEX.match(b'\n#include "foo"')
173
# Most of these are ignored because this script does not currently handle
174
# #includes in #ifdefs properly, so they will erroneously be marked as being
175
# included, but not part of the source list.
177
b'compiler/translator/TranslatorESSL.h',
178
b'compiler/translator/TranslatorGLSL.h',
179
b'compiler/translator/TranslatorHLSL.h',
180
b'compiler/translator/TranslatorMetal.h',
181
b'compiler/translator/TranslatorVulkan.h',
182
b'libANGLE/renderer/d3d/DeviceD3D.h',
183
b'libANGLE/renderer/d3d/DisplayD3D.h',
184
b'libANGLE/renderer/d3d/RenderTargetD3D.h',
185
b'libANGLE/renderer/d3d/d3d11/winrt/NativeWindow11WinRT.h',
186
b'libANGLE/renderer/gl/glx/DisplayGLX.h',
187
b'libANGLE/renderer/gl/cgl/DisplayCGL.h',
188
b'libANGLE/renderer/gl/eagl/DisplayEAGL.h',
189
b'libANGLE/renderer/gl/egl/ozone/DisplayOzone.h',
190
b'libANGLE/renderer/gl/egl/android/DisplayAndroid.h',
191
b'libANGLE/renderer/gl/wgl/DisplayWGL.h',
192
b'libANGLE/renderer/metal/DisplayMtl_api.h',
193
b'libANGLE/renderer/null/DisplayNULL.h',
194
b'libANGLE/renderer/vulkan/android/DisplayVkAndroid.h',
195
b'libANGLE/renderer/vulkan/fuchsia/DisplayVkFuchsia.h',
196
b'libANGLE/renderer/vulkan/ggp/DisplayVkGGP.h',
197
b'libANGLE/renderer/vulkan/mac/DisplayVkMac.h',
198
b'libANGLE/renderer/vulkan/win32/DisplayVkWin32.h',
199
b'libANGLE/renderer/vulkan/xcb/DisplayVkXcb.h',
203
IGNORED_INCLUDE_PREFIXES = {
218
IGNORED_DIRECTORIES = {
219
'//third_party/SwiftShader',
220
'//third_party/vulkan-headers',
221
'//third_party/vulkan-loader',
222
'//third_party/vulkan-tools',
223
'//third_party/vulkan-validation-layers',
226
def has_all_includes(target_name: str, descs: dict) -> bool:
227
for ignored_directory in IGNORED_DIRECTORIES:
228
if target_name.startswith(ignored_directory):
231
flat = flattened_target(target_name, descs, stop_at_lib=False)
232
acceptable_sources = flat.get('sources', []) + flat.get('outputs', [])
233
acceptable_sources = {x.rsplit('/', 1)[-1].encode() for x in acceptable_sources}
236
desc = descs[target_name]
237
for cur_file in desc.get('sources', []):
238
assert cur_file.startswith('/'), cur_file
239
if not cur_file.startswith('//'):
241
cur_file = pathlib.Path(cur_file[2:])
242
text = cur_file.read_bytes()
243
for m in INCLUDE_REGEX.finditer(text):
244
if m.group(1) == b'<':
247
if include in IGNORED_INCLUDES:
250
(prefix, _) = include.split(b'/', 1)
251
if prefix in IGNORED_INCLUDE_PREFIXES:
256
include_file = include.rsplit(b'/', 1)[-1]
257
if include_file not in acceptable_sources:
258
#print(' acceptable_sources:')
259
#for x in sorted(acceptable_sources):
261
print('Warning in {}: {}: Invalid include: {}'.format(target_name, cur_file, include), file=sys.stderr)
263
#print('Looks valid:', m.group())
269
# Gather real targets:
271
def gather_libraries(roots: Sequence[str], descs: dict) -> Set[str]:
274
cur = descs[target_name]
275
print(' ' + cur['type'], target_name, file=sys.stderr)
276
assert has_all_includes(target_name, descs), target_name
278
if cur['type'] in ('shared_library', 'static_library'):
279
libraries.add(target_name)
280
return (cur['deps'], )
282
dag_traverse(roots, fn)
287
libraries = gather_libraries(ROOTS, descs)
288
print(f'\n{len(libraries)} libraries:', file=sys.stderr)
290
print(f' {k}', file=sys.stderr)
291
print('\nstdout begins:', file=sys.stderr)
294
# ------------------------------------------------------------------------------
297
out = {k: flattened_target(k, descs) for k in libraries}
299
for (k,desc) in out.items():
300
dep_libs: Set[str] = set()
301
for dep_name in set(desc['deps']):
302
dep = descs[dep_name]
303
if dep['type'] in LIBRARY_TYPES:
304
dep_libs.add(dep_name[3:])
305
desc['deps'] = sortedi(dep_libs)
307
json.dump(out, sys.stdout, indent=' ')