Skip to content

Commit 2d34167

Browse files
authored
Cleanup create_updated_flutter_deps.py a bit (flutter#177162)
Make the code for updating Dart dependencies easier to follow by splitting the logic into helper functions, adding comments and tweaking logic slightly. Make it handle all *_git variables uniformly instead of hardcoding specific ones. Make it handle the case where *_git variable does not exist without emitting incorrect result. Add a unit test to make it simpler to check that script does reasonable things for all different inputs.
1 parent 47d35a2 commit 2d34167

File tree

2 files changed

+231
-39
lines changed

2 files changed

+231
-39
lines changed

engine/src/tools/dart/create_updated_flutter_deps.py

Lines changed: 114 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
DART_DEPS = os.path.realpath(os.path.join(DART_SCRIPT_DIR, '../../flutter/third_party/dart/DEPS'))
2020
FLUTTER_DEPS = os.path.realpath(os.path.join(DART_SCRIPT_DIR, '../../../../DEPS'))
2121

22+
# Path to Dart SDK checkout within Flutter repo.
23+
DART_SDK_ROOT = 'engine/src/flutter/third_party/dart'
24+
2225
class VarImpl(object):
2326
def __init__(self, local_scope):
2427
self._local_scope = local_scope
@@ -64,22 +67,122 @@ def ParseArgs(args):
6467
default=FLUTTER_DEPS)
6568
return parser.parse_args(args)
6669

70+
def PrettifySourcePathForDEPS(flutter_vars, dep_path, source):
71+
"""Prepare source for writing into Flutter DEPS file.
72+
73+
If source is not a string then it is expected to be a dictionary defining
74+
a CIPD dependency. In this case it is written as is - but sorted to
75+
guarantee stability of the output.
76+
77+
Otherwise source is a path to a repo plus version (hash or tag):
78+
79+
{repo_host}/{repo_path}@{version}
80+
81+
We want to convert this into one of the following:
82+
83+
Var(repo_host_var) + 'repo_path' + '@' + Var(version_var)
84+
Var(repo_host_var) + 'repo_path@version'
85+
'source'
86+
87+
Where repo_host_var is one of '*_git' variables and version_var is one
88+
of 'dart_{dep_name}_tag' or 'dart_{dep_name}_rev' variables.
89+
"""
90+
91+
# If this a CIPD dependency then keep it as-is but sort its contents
92+
# to ensure stable ordering.
93+
if not isinstance(source, str):
94+
return dict(sorted(source.items()))
95+
96+
# Decompose source into {repo_host}/{repo_path}@{version}
97+
repo_host_var = None
98+
version_var = None
99+
repo_path_with_version = source
100+
for var_name, var_value in flutter_vars.items():
101+
if var_name.endswith("_git") and source.startswith(var_value):
102+
repo_host_var = var_name
103+
repo_path_with_version = source[len(var_value):]
104+
break
105+
106+
if repo_path_with_version.find('@') == -1:
107+
raise ValueError(f'{dep_path} source is unversioned')
108+
109+
repo_path, version = repo_path_with_version.split('@', 1)
110+
111+
# Figure out the name of the dependency from its path to compute
112+
# corresponding version_var.
113+
#
114+
# Normally, the last component of the dep_path is the name of the dependency.
115+
# However some dependencies are placed in a subdirectory named "src"
116+
# within a directory named after the dependency.
117+
dep_name = os.path.basename(dep_path)
118+
if dep_name == 'src':
119+
dep_name = os.path.basename(os.path.dirname(dep_path))
120+
for var_name in [f'dart_{dep_name}_tag', f'dart_{dep_name}_rev']:
121+
if var_name in flutter_vars:
122+
version_var = var_name
123+
break
124+
125+
# Format result from available individual pieces.
126+
result = []
127+
if repo_host_var is not None:
128+
result += [f"Var('{repo_host_var}')"]
129+
if version_var is not None:
130+
result += [f"'{repo_path}'", "'@'", f"Var('{version_var}')"]
131+
else:
132+
result += [f"'{repo_path_with_version}'"]
133+
return " + ".join(result)
134+
135+
def ComputeDartDeps(flutter_vars, flutter_deps, dart_deps):
136+
"""Compute sources for deps nested under DART_SDK_ROOT in Flutter DEPS.
137+
138+
These dependencies originate from Dart SDK, so their version are
139+
computed by looking them up in Dart DEPS using appropriately
140+
relocated paths, e.g. '{DART_SDK_ROOT}/third_party/foo' is located at
141+
'sdk/third_party/foo' in Dart DEPS.
142+
143+
Source paths are expressed in terms of 'xyz_git' and 'dart_xyz_tag' or
144+
'dart_xyz_rev' variables if possible.
145+
146+
If corresponding path is not found in Dart DEPS the dependency is considered
147+
no longer needed and is removed from Flutter DEPS.
148+
149+
Returns: dictionary of dependencies
150+
"""
151+
new_dart_deps = {}
152+
153+
# Trailing / to avoid matching Dart SDK dependency itself.
154+
dart_sdk_root_dir = DART_SDK_ROOT + '/'
155+
156+
# Find all dependencies which are nested inside Dart SDK and check
157+
# if Dart SDK still needs them. If Dart DEPS still mentions them
158+
# take updated version from Dart DEPS.
159+
for (dep_path, dep_source) in sorted(flutter_deps.items()):
160+
if dep_path.startswith(dart_sdk_root_dir):
161+
# Dart dependencies are given relative to root directory called `sdk/`.
162+
dart_dep_path = f'sdk/{dep_path[len(dart_sdk_root_dir):]}'
163+
if dart_dep_path in dart_deps:
164+
# Still used, add it to the result.
165+
new_dart_deps[dep_path] = PrettifySourcePathForDEPS(flutter_vars, dep_path, dart_deps[dart_dep_path])
166+
167+
return new_dart_deps
168+
67169
def Main(argv):
68170
args = ParseArgs(argv)
69171
if args.dart_deps == DART_DEPS and not os.path.isfile(DART_DEPS):
70172
args.dart_deps = OLD_DART_DEPS
71-
(new_vars, new_deps) = ParseDepsFile(args.dart_deps)
72-
(old_vars, old_deps) = ParseDepsFile(args.flutter_deps)
173+
(dart_vars, dart_deps) = ParseDepsFile(args.dart_deps)
174+
(flutter_vars, flutter_deps) = ParseDepsFile(args.flutter_deps)
73175

74176
updated_vars = {}
75177

76178
# Collect updated dependencies
77-
for (k,v) in sorted(old_vars.items()):
179+
for (k,v) in sorted(flutter_vars.items()):
78180
if k not in ('dart_revision', 'dart_git') and k.startswith('dart_'):
79181
dart_key = k[len('dart_'):]
80-
if dart_key in new_vars:
81-
updated_revision = new_vars[dart_key].lstrip('@') if dart_key in new_vars else v
82-
updated_vars[k] = updated_revision
182+
if dart_key in dart_vars:
183+
updated_vars[k] = dart_vars[dart_key].lstrip('@')
184+
185+
new_dart_deps = ComputeDartDeps(flutter_vars, flutter_deps, dart_deps)
83186

84187
# Write updated DEPS file to a side
85188
updatedfilename = args.flutter_deps + ".new"
@@ -104,40 +207,12 @@ def Main(argv):
104207
elif lines[i].startswith(" # WARNING: Unused Dart dependencies"):
105208
updatedfile.write('\n')
106209
i = i + 1
107-
while i < len(lines) and (lines[i].startswith(" # WARNING: end of dart dependencies") == 0):
210+
while i < len(lines) and not lines[i].startswith(" # WARNING: end of dart dependencies"):
108211
i = i + 1
109-
for (k, v) in sorted(old_deps.items()):
110-
if (k.startswith('engine/src/flutter/third_party/dart/')):
111-
for (dart_k, dart_v) in (list(new_deps.items())):
112-
dart_k_suffix = dart_k[len('sdk/') if dart_k.startswith('sdk/') else 0:]
113-
if (k.endswith(dart_k_suffix)):
114-
if (isinstance(dart_v, str)):
115-
updated_value = dart_v.replace(new_vars["dart_git"], "Var('dart_git') + '/")
116-
updated_value = updated_value.replace(new_vars["chromium_git"], "Var('chromium_git') + '")
117-
updated_value = updated_value.replace(new_vars["android_git"], "Var('android_git') + '")
118-
119-
plain_v = os.path.basename(dart_k)
120-
# Some dependencies are placed in a subdirectory named "src"
121-
# within a directory named for the package.
122-
if plain_v == 'src':
123-
plain_v = os.path.basename(os.path.dirname(dart_k))
124-
# This dependency has to be special-cased here because the
125-
# repository name is not the same as the directory name.
126-
if plain_v == "quiver":
127-
plain_v = "quiver-dart"
128-
if ('dart_' + plain_v + '_tag' in updated_vars):
129-
updated_value = updated_value[:updated_value.rfind('@')] + "' + '@' + Var('dart_" + plain_v + "_tag')"
130-
elif ('dart_' + plain_v + '_rev' in updated_vars):
131-
updated_value = updated_value[:updated_value.rfind('@')] + "' + '@' + Var('dart_" + plain_v + "_rev')"
132-
else:
133-
updated_value = updated_value + "'"
134-
else:
135-
# Non-string values(dicts) copy verbatim, keeping them sorted
136-
# to ensure stable ordering of items.
137-
updated_value = dict(sorted(dart_v.items()))
138-
139-
updatedfile.write(" '%s':\n %s,\n\n" % (k, updated_value))
140-
break
212+
213+
for dep_path, dep_source in new_dart_deps.items():
214+
updatedfile.write(f" '{dep_path}':\n {dep_source},\n\n")
215+
141216
updatedfile.write(lines[i])
142217
i = i + 1
143218

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright 2013 The Flutter Authors. 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+
# Usage: python3 create_updated_flutter_deps_tests.py
8+
#
9+
# Unit tests for create_updated_flutter_deps.py script.
10+
11+
import unittest
12+
13+
from create_updated_flutter_deps import (
14+
DART_SDK_ROOT,
15+
ComputeDartDeps,
16+
PrettifySourcePathForDEPS,
17+
)
18+
19+
20+
class TestPrettifySourcePathForDEPS(unittest.TestCase):
21+
def test_PrettifySourcePathForDEPS_unversioned(self):
22+
with self.assertRaises(ValueError):
23+
PrettifySourcePathForDEPS(flutter_vars={}, dep_path="a", source="b")
24+
25+
def test_PrettifySourcePathForDEPS_all_cases(self):
26+
a_git = "https://a.googlesource.com"
27+
b_git = "https://b.googlesource.com"
28+
flutter_vars = {
29+
"a_git": a_git,
30+
"dart_dep2_tag": "xyz",
31+
"dart_dep3_rev": "def",
32+
}
33+
34+
deps = {
35+
"/no_repo_var/dep1": f"{b_git}/repos/dep1@whatever",
36+
"/no_repo_var/dep2": f"{b_git}/repos/dep2@whatever",
37+
"/no_repo_var/dep2/src": f"{b_git}/repos/dep2@whatever",
38+
"/no_repo_var/dep3": f"{b_git}/repos/dep3@whatever",
39+
"/no_repo_var/dep3/src": f"{b_git}/repos/dep3@whatever",
40+
"/a_git_repo/dep1": f"{a_git}/repos/dep1@whatever",
41+
"/a_git_repo/dep2": f"{a_git}/repos/dep2@whatever",
42+
"/a_git_repo/dep2/src": f"{a_git}/repos/dep2@whatever",
43+
"/a_git_repo/dep3": f"{a_git}/repos/dep3@whatever",
44+
"/a_git_repo/dep3/src": f"{a_git}/repos/dep3@whatever",
45+
}
46+
47+
expected = {
48+
"/no_repo_var/dep1": f"'{b_git}/repos/dep1@whatever'",
49+
"/no_repo_var/dep2": f"'{b_git}/repos/dep2' + '@' + Var('dart_dep2_tag')",
50+
"/no_repo_var/dep2/src": f"'{b_git}/repos/dep2' + '@' + Var('dart_dep2_tag')",
51+
"/no_repo_var/dep3": f"'{b_git}/repos/dep3' + '@' + Var('dart_dep3_rev')",
52+
"/no_repo_var/dep3/src": f"'{b_git}/repos/dep3' + '@' + Var('dart_dep3_rev')",
53+
"/a_git_repo/dep1": "Var('a_git') + '/repos/dep1@whatever'",
54+
"/a_git_repo/dep2": "Var('a_git') + '/repos/dep2' + '@' + Var('dart_dep2_tag')",
55+
"/a_git_repo/dep2/src": "Var('a_git') + '/repos/dep2' + '@' + Var('dart_dep2_tag')",
56+
"/a_git_repo/dep3": "Var('a_git') + '/repos/dep3' + '@' + Var('dart_dep3_rev')",
57+
"/a_git_repo/dep3/src": "Var('a_git') + '/repos/dep3' + '@' + Var('dart_dep3_rev')",
58+
}
59+
60+
for dep_path, source_path in deps.items():
61+
self.assertEqual(
62+
PrettifySourcePathForDEPS(flutter_vars, dep_path, source_path),
63+
expected[dep_path],
64+
)
65+
66+
67+
class TestComputeDartDeps(unittest.TestCase):
68+
def test_ComputeDartDeps_nothing_to_do(self):
69+
# Note: DART_SDK_ROOT dependency itself should be simply ignored.
70+
self.assertEqual(
71+
ComputeDartDeps(
72+
flutter_vars={},
73+
flutter_deps={
74+
DART_SDK_ROOT: "whatever",
75+
},
76+
dart_deps={
77+
"sdk": "xyz",
78+
},
79+
),
80+
{},
81+
)
82+
83+
def test_ComputeDartDeps_unused_dep(self):
84+
a_git = "https://a.googlesource.com"
85+
self.assertEqual(
86+
ComputeDartDeps(
87+
flutter_vars={
88+
"a_git": a_git,
89+
},
90+
flutter_deps={
91+
f"{DART_SDK_ROOT}/third_party/dep": f"{a_git}/repos/dep@version",
92+
},
93+
dart_deps={},
94+
),
95+
{},
96+
)
97+
98+
def test_ComputeDartDeps_used_dep(self):
99+
a_git = "https://a.googlesource.com"
100+
self.assertEqual(
101+
ComputeDartDeps(
102+
flutter_vars={
103+
"a_git": a_git,
104+
},
105+
flutter_deps={
106+
f"{DART_SDK_ROOT}/third_party/dep": "whatever",
107+
},
108+
dart_deps={"sdk/third_party/dep": f"{a_git}/repos/dep@version"},
109+
),
110+
{
111+
f"{DART_SDK_ROOT}/third_party/dep": "Var('a_git') + '/repos/dep@version'",
112+
},
113+
)
114+
115+
116+
if __name__ == "__main__":
117+
unittest.main()

0 commit comments

Comments
 (0)