Skip to content

Commit 61cd272

Browse files
authored
Handle all known variants used in gradle version catalogues (#1468)
1 parent 568c0ce commit 61cd272

File tree

4 files changed

+317
-25
lines changed

4 files changed

+317
-25
lines changed

private/extensions/maven.bzl

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -337,29 +337,52 @@ def _coordinates_match(artifact, coordinates):
337337
return (artifact.group == coords.group and
338338
artifact.artifact == coords.artifact)
339339

340-
def _process_gradle_versions_file(parsed, bom_modules):
340+
def process_gradle_versions_file(parsed, bom_modules):
341341
artifacts = []
342342
boms = []
343343

344-
for value in parsed.get("libraries", {}).values():
345-
if not "module" in value.keys():
346-
continue
347-
coords = value["module"]
344+
for alias, value in parsed.get("libraries", {}).items():
345+
# Handle different dependency declaration formats
346+
coords = None
347+
348+
# Case 1: Simple string notation: "group:artifact:version"
349+
if type(value) == "string":
350+
coords = value
351+
# Case 2: Map notation
352+
elif type(value) == "dict":
353+
# Case 2a: Map with "module" key
354+
if "module" in value.keys():
355+
coords = value["module"]
356+
# Case 2b: Map with "group" and "name" keys
357+
elif "group" in value.keys() and "name" in value.keys():
358+
coords = "%s:%s" % (value["group"], value["name"])
359+
else:
360+
fail("Library '%s' must have either 'module' or both 'group' and 'name' keys" % alias)
361+
362+
# Handle version (applies to both module and group+name formats)
363+
if "version.ref" in value.keys():
364+
version = parsed.get("versions", {}).get(value["version.ref"])
365+
if not version:
366+
fail("Unable to resolve version.ref %s" % value["version.ref"])
367+
coords += ":%s" % version
368+
elif "version" in value.keys():
369+
coords += ":%s" % value["version"]
370+
371+
# Handle packaging (e.g., "aar" for Android libraries)
372+
# Note: Gradle uses "package" but we'll check common variants
373+
packaging = value.get("package", value.get("packaging", "jar"))
374+
if packaging != "jar":
375+
coords += "@%s" % packaging
376+
else:
377+
fail("Library '%s' has unsupported format: %s" % (alias, type(value)))
348378

349-
if "version.ref" in value.keys():
350-
version = parsed.get("versions", {}).get(value["version.ref"])
351-
if not version:
352-
fail("Unable to resolve version.ref %s" % value["version.ref"])
353-
coords += ":%s" % version
354-
elif "version" in value.keys():
355-
coords += ":%s" % value["version"]
379+
# Determine the module identifier for BOM checking
380+
# Extract just group:artifact for comparison with bom_modules
381+
module_id = coords.split(":")[0] + ":" + coords.split(":")[1] if ":" in coords else coords
356382

357-
if value["module"] in bom_modules:
383+
if module_id in bom_modules:
358384
boms.append(unpack_coordinates(coords))
359385
else:
360-
packaging = value.get("package", "jar")
361-
if packaging != "jar":
362-
coords += "@%s" % packaging
363386
artifacts.append(unpack_coordinates(coords))
364387

365388
return artifacts, boms
@@ -376,7 +399,7 @@ def _process_module_tags(mctx, mod, target_repos, repo_name_2_module_name):
376399
content = mctx.read(mctx.path(from_toml_tag.libs_versions_toml))
377400
parsed = parse_toml(content)
378401

379-
(new_artifacts, new_boms) = _process_gradle_versions_file(parsed, from_toml_tag.bom_modules)
402+
(new_artifacts, new_boms) = process_gradle_versions_file(parsed, from_toml_tag.bom_modules)
380403

381404
repo["artifacts"] = repo.get("artifacts", []) + new_artifacts
382405
repo["boms"] = repo.get("boms", []) + new_boms

private/lib/toml_parser.bzl

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -321,14 +321,7 @@ def _parse_string(value_str, line_num, section_path):
321321
# Basic string - handle escape sequences
322322
# Double-quoted TOML strings have the same spec as JSON strings <3
323323

324-
result = json.decode(value_str, default = JSON_DECODE_ERROR)
325-
if result == JSON_DECODE_ERROR:
326-
print("Invalid string %s: '%s'" % (_parser_location(line_num, section_path), value_str))
327-
328-
# Re-run json.decode() without defaults to get the error message.
329-
json.decode(value_str)
330-
331-
return result
324+
return json.decode(value_str)
332325
else:
333326
fail("Invalid string format %s: %'s'" % (_parser_location(line_num, section_path), value_str))
334327

tests/unit/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ load(":coursier_utilities_test.bzl", "coursier_utilities_test_suite")
55
load(":java_utilities_test.bzl", "java_utilities_test_suite")
66
load(":proxy_test.bzl", "proxy_test_suite")
77
load(":specs_test.bzl", "artifact_specs_test_suite")
8+
load(":version_catalogs_test.bzl", "version_catalogs_test_suite")
89

910
artifact_specs_test_suite()
1011

@@ -19,3 +20,5 @@ coursier_utilities_test_suite()
1920
java_utilities_test_suite()
2021

2122
proxy_test_suite()
23+
24+
version_catalogs_test_suite()
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
2+
load("//private/extensions:maven.bzl", "process_gradle_versions_file")
3+
load("//private/lib:toml_parser.bzl", "parse_toml")
4+
5+
def _simple_string_notation_impl(ctx):
6+
env = unittest.begin(ctx)
7+
8+
toml_content = """\
9+
[libraries]
10+
commons-lang = "org.apache.commons:commons-lang3:3.12.0"
11+
"""
12+
13+
parsed = parse_toml(toml_content)
14+
artifacts, boms = process_gradle_versions_file(parsed, [])
15+
16+
asserts.equals(env, 1, len(artifacts))
17+
asserts.equals(env, 0, len(boms))
18+
asserts.equals(env, "org.apache.commons", artifacts[0].group)
19+
asserts.equals(env, "commons-lang3", artifacts[0].artifact)
20+
asserts.equals(env, "3.12.0", artifacts[0].version)
21+
22+
return unittest.end(env)
23+
24+
simple_string_notation_test = unittest.make(_simple_string_notation_impl)
25+
26+
def _map_with_module_and_inline_version_impl(ctx):
27+
env = unittest.begin(ctx)
28+
29+
toml_content = """\
30+
[libraries]
31+
guava = { module = "com.google.guava:guava", version = "32.1.0-jre" }
32+
"""
33+
34+
parsed = parse_toml(toml_content)
35+
artifacts, boms = process_gradle_versions_file(parsed, [])
36+
37+
asserts.equals(env, 1, len(artifacts))
38+
asserts.equals(env, 0, len(boms))
39+
asserts.equals(env, "com.google.guava", artifacts[0].group)
40+
asserts.equals(env, "guava", artifacts[0].artifact)
41+
asserts.equals(env, "32.1.0-jre", artifacts[0].version)
42+
43+
return unittest.end(env)
44+
45+
map_with_module_and_inline_version_test = unittest.make(_map_with_module_and_inline_version_impl)
46+
47+
def _map_with_module_and_version_ref_impl(ctx):
48+
env = unittest.begin(ctx)
49+
50+
toml_content = """\
51+
[versions]
52+
junit = "5.10.0"
53+
54+
[libraries]
55+
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
56+
"""
57+
58+
parsed = parse_toml(toml_content)
59+
artifacts, boms = process_gradle_versions_file(parsed, [])
60+
61+
asserts.equals(env, 1, len(artifacts))
62+
asserts.equals(env, 0, len(boms))
63+
asserts.equals(env, "org.junit.jupiter", artifacts[0].group)
64+
asserts.equals(env, "junit-jupiter-api", artifacts[0].artifact)
65+
asserts.equals(env, "5.10.0", artifacts[0].version)
66+
67+
return unittest.end(env)
68+
69+
map_with_module_and_version_ref_test = unittest.make(_map_with_module_and_version_ref_impl)
70+
71+
def _map_with_module_no_version_impl(ctx):
72+
env = unittest.begin(ctx)
73+
74+
toml_content = """\
75+
[libraries]
76+
guava-from-bom = { module = "com.google.guava:guava" }
77+
"""
78+
79+
parsed = parse_toml(toml_content)
80+
artifacts, boms = process_gradle_versions_file(parsed, [])
81+
82+
asserts.equals(env, 1, len(artifacts))
83+
asserts.equals(env, 0, len(boms))
84+
asserts.equals(env, "com.google.guava", artifacts[0].group)
85+
asserts.equals(env, "guava", artifacts[0].artifact)
86+
asserts.equals(env, "", artifacts[0].version)
87+
88+
return unittest.end(env)
89+
90+
map_with_module_no_version_test = unittest.make(_map_with_module_no_version_impl)
91+
92+
def _map_with_group_name_and_inline_version_impl(ctx):
93+
env = unittest.begin(ctx)
94+
95+
toml_content = """\
96+
[libraries]
97+
androidx-core = { group = "androidx.core", name = "core-ktx", version = "1.12.0" }
98+
"""
99+
100+
parsed = parse_toml(toml_content)
101+
artifacts, boms = process_gradle_versions_file(parsed, [])
102+
103+
asserts.equals(env, 1, len(artifacts))
104+
asserts.equals(env, 0, len(boms))
105+
asserts.equals(env, "androidx.core", artifacts[0].group)
106+
asserts.equals(env, "core-ktx", artifacts[0].artifact)
107+
asserts.equals(env, "1.12.0", artifacts[0].version)
108+
109+
return unittest.end(env)
110+
111+
map_with_group_name_and_inline_version_test = unittest.make(_map_with_group_name_and_inline_version_impl)
112+
113+
def _map_with_group_name_and_version_ref_impl(ctx):
114+
env = unittest.begin(ctx)
115+
116+
toml_content = """\
117+
[versions]
118+
kotlin = "1.9.0"
119+
120+
[libraries]
121+
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
122+
"""
123+
124+
parsed = parse_toml(toml_content)
125+
artifacts, boms = process_gradle_versions_file(parsed, [])
126+
127+
asserts.equals(env, 1, len(artifacts))
128+
asserts.equals(env, 0, len(boms))
129+
asserts.equals(env, "org.jetbrains.kotlin", artifacts[0].group)
130+
asserts.equals(env, "kotlin-stdlib", artifacts[0].artifact)
131+
asserts.equals(env, "1.9.0", artifacts[0].version)
132+
133+
return unittest.end(env)
134+
135+
map_with_group_name_and_version_ref_test = unittest.make(_map_with_group_name_and_version_ref_impl)
136+
137+
def _map_with_group_name_no_version_impl(ctx):
138+
env = unittest.begin(ctx)
139+
140+
toml_content = """\
141+
[versions]
142+
compose = "1.5.0"
143+
144+
[libraries]
145+
compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
146+
"""
147+
148+
parsed = parse_toml(toml_content)
149+
artifacts, boms = process_gradle_versions_file(parsed, [])
150+
151+
asserts.equals(env, 1, len(artifacts))
152+
asserts.equals(env, 0, len(boms))
153+
asserts.equals(env, "androidx.compose.ui", artifacts[0].group)
154+
asserts.equals(env, "ui", artifacts[0].artifact)
155+
asserts.equals(env, "1.5.0", artifacts[0].version)
156+
157+
return unittest.end(env)
158+
159+
map_with_group_name_no_version_test = unittest.make(_map_with_group_name_no_version_impl)
160+
161+
def _map_with_module_and_packaging_impl(ctx):
162+
env = unittest.begin(ctx)
163+
164+
toml_content = """\
165+
[libraries]
166+
play-services = { module = "com.google.android.gms:play-services-tasks", package = "aar", version = "18.1.0" }
167+
"""
168+
169+
parsed = parse_toml(toml_content)
170+
artifacts, boms = process_gradle_versions_file(parsed, [])
171+
172+
asserts.equals(env, 1, len(artifacts))
173+
asserts.equals(env, 0, len(boms))
174+
asserts.equals(env, "com.google.android.gms", artifacts[0].group)
175+
asserts.equals(env, "play-services-tasks", artifacts[0].artifact)
176+
asserts.equals(env, "18.1.0", artifacts[0].version)
177+
asserts.equals(env, "aar", artifacts[0].packaging)
178+
179+
return unittest.end(env)
180+
181+
map_with_module_and_packaging_test = unittest.make(_map_with_module_and_packaging_impl)
182+
183+
def _map_with_group_name_and_packaging_impl(ctx):
184+
env = unittest.begin(ctx)
185+
186+
toml_content = """\
187+
[libraries]
188+
android-material = { group = "com.google.android.material", name = "material", version = "1.10.0", package = "aar" }
189+
"""
190+
191+
parsed = parse_toml(toml_content)
192+
artifacts, boms = process_gradle_versions_file(parsed, [])
193+
194+
asserts.equals(env, 1, len(artifacts))
195+
asserts.equals(env, 0, len(boms))
196+
asserts.equals(env, "com.google.android.material", artifacts[0].group)
197+
asserts.equals(env, "material", artifacts[0].artifact)
198+
asserts.equals(env, "1.10.0", artifacts[0].version)
199+
asserts.equals(env, "aar", artifacts[0].packaging)
200+
201+
return unittest.end(env)
202+
203+
map_with_group_name_and_packaging_test = unittest.make(_map_with_group_name_and_packaging_impl)
204+
205+
def _bom_handling_impl(ctx):
206+
env = unittest.begin(ctx)
207+
208+
toml_content = """\
209+
[libraries]
210+
guava-bom = { module = "com.google.guava:guava-bom", version = "32.1.0-jre" }
211+
guava = { module = "com.google.guava:guava", version = "32.1.0-jre" }
212+
"""
213+
214+
parsed = parse_toml(toml_content)
215+
artifacts, boms = process_gradle_versions_file(parsed, ["com.google.guava:guava-bom"])
216+
217+
asserts.equals(env, 1, len(artifacts))
218+
asserts.equals(env, 1, len(boms))
219+
220+
# Check the artifact
221+
asserts.equals(env, "com.google.guava", artifacts[0].group)
222+
asserts.equals(env, "guava", artifacts[0].artifact)
223+
asserts.equals(env, "32.1.0-jre", artifacts[0].version)
224+
225+
# Check the BOM
226+
asserts.equals(env, "com.google.guava", boms[0].group)
227+
asserts.equals(env, "guava-bom", boms[0].artifact)
228+
asserts.equals(env, "32.1.0-jre", boms[0].version)
229+
230+
return unittest.end(env)
231+
232+
bom_handling_test = unittest.make(_bom_handling_impl)
233+
234+
def _multiple_libraries_impl(ctx):
235+
env = unittest.begin(ctx)
236+
237+
toml_content = """\
238+
[versions]
239+
kotlin = "1.9.0"
240+
junit = "5.10.0"
241+
242+
[libraries]
243+
commons-lang = "org.apache.commons:commons-lang3:3.12.0"
244+
guava = { module = "com.google.guava:guava", version = "32.1.0-jre" }
245+
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
246+
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
247+
"""
248+
249+
parsed = parse_toml(toml_content)
250+
artifacts, boms = process_gradle_versions_file(parsed, [])
251+
252+
asserts.equals(env, 4, len(artifacts))
253+
asserts.equals(env, 0, len(boms))
254+
255+
return unittest.end(env)
256+
257+
multiple_libraries_test = unittest.make(_multiple_libraries_impl)
258+
259+
def version_catalogs_test_suite():
260+
unittest.suite(
261+
"version_catalogs_tests",
262+
simple_string_notation_test,
263+
map_with_module_and_inline_version_test,
264+
map_with_module_and_version_ref_test,
265+
map_with_module_no_version_test,
266+
map_with_group_name_and_inline_version_test,
267+
map_with_group_name_and_version_ref_test,
268+
map_with_group_name_no_version_test,
269+
map_with_module_and_packaging_test,
270+
map_with_group_name_and_packaging_test,
271+
bom_handling_test,
272+
multiple_libraries_test,
273+
)

0 commit comments

Comments
 (0)