Skip to content

eunit: de-dup deps by app_name so test target wins over prod variant#6

Merged
sebastiw merged 1 commit into
developfrom
fix/eunit-dedup-app-name
Jun 25, 2026
Merged

eunit: de-dup deps by app_name so test target wins over prod variant#6
sebastiw merged 1 commit into
developfrom
fix/eunit-dedup-app-name

Conversation

@sebastiw

Copy link
Copy Markdown
Collaborator

Summary

Fix a Bazel analysis-time conflicting actions error in eunit when a
target's test_deps transitively depend back on the target's own app.

Background

Commit e70ed5a (#5, "extract_app: namespace .beam/.app outputs by target name")
intentionally writes :erlang_app and :test_erlang_app outputs to distinct
paths (<target>/ebin/foo.beam) so both can live in the same package. The
ErlangAppInfo provider was deliberately left unchanged, on the assumption
that downstream rules consume the provider and would be unaffected.

private/eunit.bzl::_impl however builds its dependency list as:

deps = list(ctx.attr.deps)
lib_info = ctx.attr.target[ErlangAppInfo]
deps.extend(lib_info.deps)
deps.append(ctx.attr.target)

If ctx.attr.target is :test_erlang_app for app A, and any entry in
lib_info.deps (i.e. the resolved deps + test_deps of :test_erlang_app)
transitively pulls in A's own :erlang_app via a test_deps cycle
(A test-depends on B, B depends on A), the resulting list contains
two distinct ErlangAppInfos sharing app_name = "A".

erl_libs_contents keys its symlink destinations on app_name:

dep_path = path_join(dir, lib_info.app_name)
...
dest = symlink(ctx, src, path_join(dep_path, "ebin", src.basename))

Before e70ed5a both source .beam files lived at the same execroot path
(ebin/A.beam) and Bazel coalesced the two symlink actions into one.
After the namespacing the source paths differ
(erlang_app/ebin/A.beam vs test_erlang_app/ebin/A.beam) but the
destination is still <name>_deps/A/ebin/A.beam, producing:

ERROR: file '.../eunit_deps/A/ebin/A.beam' is generated by these conflicting
actions:
  PrimaryInput: ... test_erlang_app/ebin/A.beam, ... erlang_app/ebin/A.beam
  PrimaryOutput: ... eunit_deps/A/ebin/A.beam

Fix

De-duplicate the dependency list by app_name with the test target winning,
by routing it through flat_deps (first-wins semantics) with
ctx.attr.target placed first:

deps = flat_deps([ctx.attr.target] + list(ctx.attr.deps) + lib_info.deps)

This mirrors what the pre-e70ed5a action coalescing was effectively doing:
the test variant's beams (a superset of the prod variant's) end up in
ERL_LIBS, and the prod variant pulled in transitively is suppressed.

private/ct.bzl is not affected because it never adds the test target to
its dep list (test suites flow in via compiled_suites, not deps), so
no analogous change is needed there.

Validation

  • cd test && bazelisk test //... -> 13/13 pass.
  • cd examples/umbrella && bazelisk test //... -> 9/9 pass.
  • Reproduced and fixed the original failure against a downstream consumer
    with two affected apps (cyclic test_deps):
    • Before: bazel build //app:eunit fails analysis with the conflicting
      actions error shown above (for two distinct apps).
    • After: both apps build and their eunit targets pass.

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.
@sebastiw sebastiw merged commit 3745315 into develop Jun 25, 2026
17 of 19 checks passed
@sebastiw sebastiw deleted the fix/eunit-dedup-app-name branch June 25, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants