Skip to content

Commit 896b358

Browse files
fmeumcopybara-github
authored andcommitted
Ensure that extension unique names followed by ~ are prefix-free
Fixes errors such as the following when a module provides two extensions that share a name: ``` ERROR: /my/workspace/library_path/BUILD:4:13: no such package '@_main~my_ext~2~xyz//': The repository '@_main~my_ext~2~xyz' could not be resolved: Repository '@_main~my_ext~2~xyz' is not defined and referenced by '//library_path:library_name' ERROR: Analysis of target '//my_path:my_target' failed; build aborted: ``` This was a consequence of the extension unique names followed by `~` being ``` _main~my_ext~2~ _main~my_ext~ ``` since 19a9710. Before, they were of the following form, which was prefix-free: ``` _main~my_ext2~ _main~my_ext~ ``` Fixes #19155 Closes #19156. PiperOrigin-RevId: 553567725 Change-Id: I98650663fea3bfee77752a06a99132e507d91aef
1 parent fcd1ff7 commit 896b358

File tree

3 files changed

+84
-28
lines changed

3 files changed

+84
-28
lines changed

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphFunction.java

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.google.common.collect.ImmutableMap.toImmutableMap;
2121

2222
import com.google.common.annotations.VisibleForTesting;
23+
import com.google.common.base.Preconditions;
2324
import com.google.common.collect.BiMap;
2425
import com.google.common.collect.HashBiMap;
2526
import com.google.common.collect.ImmutableBiMap;
@@ -247,39 +248,45 @@ static BzlmodFlagsAndEnvVars getFlagsAndEnvVars(Environment env) throws Interrup
247248

248249
private ImmutableBiMap<String, ModuleExtensionId> calculateUniqueNameForUsedExtensionId(
249250
ImmutableTable<ModuleExtensionId, ModuleKey, ModuleExtensionUsage> extensionUsagesById) {
250-
// Calculate a unique name for each used extension id.
251+
// Calculate a unique name for each used extension id with the following property that is
252+
// required for BzlmodRepoRuleFunction to unambiguously identify the extension that generates a
253+
// given repo:
254+
// After appending a single `~` to each such name, none of the resulting strings is a prefix of
255+
// any other such string.
251256
BiMap<String, ModuleExtensionId> extensionUniqueNames = HashBiMap.create();
252257
for (ModuleExtensionId id : extensionUsagesById.rowKeySet()) {
253-
// Ensure that the resulting extension name (and thus the repository names derived from it) do
254-
// not start with a tilde.
255-
RepositoryName repository = id.getBzlFileLabel().getRepository();
256-
String nonEmptyRepoPart = repository.isMain() ? "_main" : repository.getName();
257-
// When using a namespace, prefix the extension name with "_" to distinguish the prefix from
258-
// those generated by non-namespaced extension usages. Extension names are identified by their
259-
// Starlark identifier, which in the case of an exported symbol cannot start with "_".
260-
String bestName =
261-
id.getIsolationKey()
262-
.map(
263-
namespace ->
264-
String.format(
265-
"%s~_%s~%s~%s~%s",
266-
nonEmptyRepoPart,
267-
id.getExtensionName(),
268-
namespace.getModule().getName(),
269-
namespace.getModule().getVersion(),
270-
namespace.getUsageExportedName()))
271-
.orElse(nonEmptyRepoPart + "~" + id.getExtensionName());
272-
if (extensionUniqueNames.putIfAbsent(bestName, id) == null) {
273-
continue;
274-
}
275-
int suffix = 2;
276-
while (extensionUniqueNames.putIfAbsent(bestName + "~" + suffix, id) != null) {
277-
suffix++;
258+
int attempt = 1;
259+
while (extensionUniqueNames.putIfAbsent(makeUniqueNameCandidate(id, attempt), id) != null) {
260+
attempt++;
278261
}
279262
}
280263
return ImmutableBiMap.copyOf(extensionUniqueNames);
281264
}
282265

266+
private static String makeUniqueNameCandidate(ModuleExtensionId id, int attempt) {
267+
// Ensure that the resulting extension name (and thus the repository names derived from it) do
268+
// not start with a tilde.
269+
RepositoryName repository = id.getBzlFileLabel().getRepository();
270+
String nonEmptyRepoPart = repository.isMain() ? "_main" : repository.getName();
271+
// When using a namespace, prefix the extension name with "_" to distinguish the prefix from
272+
// those generated by non-namespaced extension usages. Extension names are identified by their
273+
// Starlark identifier, which in the case of an exported symbol cannot start with "_".
274+
Preconditions.checkArgument(attempt >= 1);
275+
String extensionNameDisambiguator = attempt == 1 ? "" : String.valueOf(attempt);
276+
return id.getIsolationKey()
277+
.map(
278+
namespace ->
279+
String.format(
280+
"%s~_%s%s~%s~%s~%s",
281+
nonEmptyRepoPart,
282+
id.getExtensionName(),
283+
extensionNameDisambiguator,
284+
namespace.getModule().getName(),
285+
namespace.getModule().getVersion(),
286+
namespace.getUsageExportedName()))
287+
.orElse(nonEmptyRepoPart + "~" + id.getExtensionName() + extensionNameDisambiguator);
288+
}
289+
283290
static class BazelDepGraphFunctionException extends SkyFunctionException {
284291
BazelDepGraphFunctionException(ExternalDepsException e, Transience transience) {
285292
super(e, transience);

src/test/java/com/google/devtools/build/lib/bazel/bzlmod/BazelDepGraphFunctionTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ public void createValue_moduleExtensions() throws Exception {
292292
maven, "rules_jvm_external~1.0~maven",
293293
pip, "rules_python~2.0~pip",
294294
myext, "dep~2.0~myext",
295-
myext2, "dep~2.0~myext~2");
295+
myext2, "dep~2.0~myext2");
296296

297297
assertThat(value.getFullRepoMapping(ModuleKey.ROOT))
298298
.isEqualTo(
@@ -323,7 +323,7 @@ public void createValue_moduleExtensions() throws Exception {
323323
"oneext",
324324
"dep~2.0~myext~myext",
325325
"twoext",
326-
"dep~2.0~myext~2~myext"));
326+
"dep~2.0~myext2~myext"));
327327
}
328328

329329
@Test

src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,55 @@ public void simpleExtension_nonCanonicalLabel_repoName() throws Exception {
427427
assertThat(result.get(skyKey).getModule().getGlobal("data")).isEqualTo("foo:fu bar:ba quz:qu");
428428
}
429429

430+
@Test
431+
public void multipleExtensions_sameName() throws Exception {
432+
scratch.file(
433+
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
434+
"bazel_dep(name='data_repo', version='1.0')",
435+
"first_ext = use_extension('//first_ext:defs.bzl', 'ext')",
436+
"first_ext.tag(name='foo', data='first_fu')",
437+
"first_ext.tag(name='bar', data='first_ba')",
438+
"use_repo(first_ext, first_foo='foo', first_bar='bar')",
439+
"second_ext = use_extension('//second_ext:defs.bzl', 'ext')",
440+
"second_ext.tag(name='foo', data='second_fu')",
441+
"second_ext.tag(name='bar', data='second_ba')",
442+
"use_repo(second_ext, second_foo='foo', second_bar='bar')");
443+
scratch.file(workspaceRoot.getRelative("first_ext/BUILD").getPathString());
444+
scratch.file(
445+
workspaceRoot.getRelative("first_ext/defs.bzl").getPathString(),
446+
"load('@data_repo//:defs.bzl','data_repo')",
447+
"tag = tag_class(attrs = {'name':attr.string(),'data':attr.string()})",
448+
"def _ext_impl(ctx):",
449+
" for mod in ctx.modules:",
450+
" for tag in mod.tags.tag:",
451+
" data_repo(name=tag.name,data=tag.data)",
452+
"ext = module_extension(implementation=_ext_impl, tag_classes={'tag':tag})");
453+
scratch.file(workspaceRoot.getRelative("second_ext/BUILD").getPathString());
454+
scratch.file(
455+
workspaceRoot.getRelative("second_ext/defs.bzl").getPathString(),
456+
"load('//first_ext:defs.bzl', _ext = 'ext')",
457+
"ext = _ext");
458+
scratch.file(workspaceRoot.getRelative("BUILD").getPathString());
459+
scratch.file(
460+
workspaceRoot.getRelative("data.bzl").getPathString(),
461+
"load('@first_foo//:data.bzl', first_foo_data='data')",
462+
"load('@first_bar//:data.bzl', first_bar_data='data')",
463+
"load('@second_foo//:data.bzl', second_foo_data='data')",
464+
"load('@second_bar//:data.bzl', second_bar_data='data')",
465+
"data = 'first_foo:'+first_foo_data+' first_bar:'+first_bar_data"
466+
+ "+' second_foo:'+second_foo_data+' second_bar:'+second_bar_data");
467+
468+
SkyKey skyKey = BzlLoadValue.keyForBuild(Label.parseCanonical("//:data.bzl"));
469+
EvaluationResult<BzlLoadValue> result =
470+
evaluator.evaluate(ImmutableList.of(skyKey), evaluationContext);
471+
if (result.hasError()) {
472+
throw result.getError().getException();
473+
}
474+
assertThat(result.get(skyKey).getModule().getGlobal("data"))
475+
.isEqualTo(
476+
"first_foo:first_fu first_bar:first_ba second_foo:second_fu " + "second_bar:second_ba");
477+
}
478+
430479
@Test
431480
public void multipleModules() throws Exception {
432481
scratch.file(

0 commit comments

Comments
 (0)