From 65b0fdb4f34e941d7f855b9954c45c95760b3420 Mon Sep 17 00:00:00 2001 From: Sebastian Weddmark Olsson Date: Thu, 25 Jun 2026 10:52:14 +0200 Subject: [PATCH] eunit: de-dup deps by app_name so test target wins over prod variant When an app A test-depends on B and B depends on A, the eunit rule's dep list ended up containing both A's :test_erlang_app (ctx.attr.target) and A's :erlang_app (pulled in transitively via lib_info.deps). After e70ed5a these two write distinct .beam files at different execroot paths but erl_libs_contents keys destinations on app_name, producing two symlink actions for the same eunit_deps/A/ebin/A.beam output and an analysis-time conflicting actions error. Route the dep list through flat_deps with ctx.attr.target placed first so the first-wins de-dup by app_name keeps the test variant (a superset of the prod variant) in ERL_LIBS, matching the action-coalescing behaviour we had before e70ed5a. --- private/eunit.bzl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/private/eunit.bzl b/private/eunit.bzl index a51ca1a5..6b090100 100644 --- a/private/eunit.bzl +++ b/private/eunit.bzl @@ -1,6 +1,7 @@ load( "//:erlang_app_info.bzl", "ErlangAppInfo", + "flat_deps", ) load( "//:util.bzl", @@ -48,10 +49,15 @@ def _quote(string_list_term): return string_list_term.replace('"', '\\"') def _impl(ctx): - deps = list(ctx.attr.deps) lib_info = ctx.attr.target[ErlangAppInfo] - deps.extend(lib_info.deps) - deps.append(ctx.attr.target) + + # The test target must come first so that flat_deps's first-wins + # de-dup by app_name picks the test variant over any prod variant + # of the same app pulled in transitively via test_deps cycles + # (e.g. app A depends on B in test_deps, and B depends on A). + # Without this, two ErlangAppInfos sharing an app_name would both + # try to symlink their .beam files to the same ERL_LIBS path. + deps = flat_deps([ctx.attr.target] + list(ctx.attr.deps) + lib_info.deps) # Use the eunit_mods attribute if provided, otherwise calculate from beam and test_beam # Note: when eunit_mods is not provided, we need to include both: