feat(dotnet): opt-in per-target-framework target variants for multi-targeted projects - #36680
feat(dotnet): opt-in per-target-framework target variants for multi-targeted projects#36680Redth wants to merge 3 commits into
Conversation
…argeted projects Multi-targeted .NET projects (those declaring `<TargetFrameworks>`) expose a correctness gap that project-level targets can't express: an unqualified `dotnet build` has no single host that can build every framework (an iOS + Windows project is the canonical case), and the outputs and cache identity of each framework's build are collapsed together. This adds an opt-in `frameworkVariants` plugin option. When enabled, the MSBuild analyzer enumerates the evaluated ProjectGraph inner builds (one per target framework) and emits per-framework target variants alongside the existing unqualified targets: - `build-<tfm>` / `build-<tfm>-release` - `test-<tfm>` (test projects) - `publish-<tfm>` (executable projects) Each variant passes `--framework` to the CLI, scopes its outputs and cache identity to the framework's evaluated output/intermediate directories, wires the correct dependency edges, and records the framework in target metadata. Variant names join the framework to the configured target name with a hyphen (never a colon), so they can't be mistaken for a configuration in Nx's `project:target:configuration` syntax. Names derive from the configured target names, normalize deterministically, and collisions are reported and skipped rather than silently overwriting a target. The option is off by default and leaves the generated targets byte-for-byte unchanged; single-targeted projects are never expanded. Refs discussion nrwl#36676, discussion nrwl#35837. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
👷 Deploy request for nx-docs pending review.Visit the deploys page to approve it
|
👷 Deploy request for nx-dev pending review.Visit the deploys page to approve it
|
…f-contained Addresses review feedback on the initial framework-variants commit: - Reduce scope to build variants only (build-<tfm> / build-<tfm>-release). The test/publish variants keyed off project-level isTest/isExe, which do not necessarily apply per framework; test variants are deferred to a separate design and RID publish is handled in a follow-up. - Make build variants self-contained: drop the `^build` dependency and the `--no-dependencies` flag so a single-framework build lets MSBuild build each referenced project's compatible framework directly, instead of triggering an all-framework dependency build that reintroduces the host-compatibility failure these variants exist to avoid. `^production` remains an input so a dependency source change still invalidates the variant. - Honor a disabled build target and stop ignoring user config: variants are not generated when `build` is disabled, are removed if `build` is later disabled, and the user's `build` configuration is merged into each variant. Variants carry a `frameworkVariantOf` target-metadata marker so the plugin can match them to their base target. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ariants Adds an integration test that runs the real analyzer executable against a temporary multi-targeted project and asserts on the public JSON the Nx plugin consumes. This closes the gap left by the unit tests, which cover only the pure TargetBuilder logic: it proves the MSBuild ProjectGraph inner-build enumeration in Analyzer actually surfaces per-framework build variants (with framework args, self-contained dependencies, and framework-scoped outputs) when enabled, and none when disabled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
This is the one I'm most excited about, and the problem is real. We've settled the open design questions on our side rather than leaving you to guess. The reasoning is in #36676, and the change list is below. Headline: your original Blocked on #36527This needs Design changes
Bugs, independent of the above
Worth keepingPlenty here should survive the rework. The self-contained reasoning in the header comment is right, and the tradeoff is stated honestly. Deriving per-framework paths from the evaluated inner-build |
Current Behavior
Multi-targeted .NET projects (those declaring
<TargetFrameworks>) only get unqualified inferred targets. A singlenx buildinvokesdotnet buildacross every target framework at once, which:nx affected/ distributed execution no way to select a single framework.Expected Behavior
An opt-in
frameworkVariantsplugin option. When enabled, the MSBuild analyzer enumerates the evaluatedProjectGraphinner builds (one per target framework — real evaluated MSBuild nodes, not XML parsing) and emits per-framework build variants alongside the existing unqualified targets:build-<tfm>— build a single framework (Debug by default, withdebug/releaseconfigurations)build-<tfm>-release— build a single framework in ReleaseFor
net10.0;net10.0-iosyou getbuild-net10.0,build-net10.0-ios,build-net10.0-release,build-net10.0-ios-release.Each variant passes
--framework <tfm>, scopes its outputs and cache identity to that framework's evaluated output/intermediate directories (both Debug and Release forms), and records the framework in target metadata.Design notes
^buildand does not pass--no-dependencies. Depending on the aggregate build would rebuild every framework of every dependency and reintroduce the host-compatibility problem the feature solves; instead MSBuild builds each referenced project's framework-compatible inner build directly. Tradeoff: coarser task-level caching of dependencies —^productionstays an input so a dependency source change still invalidates the variant.buildconfig and disabling. Variants aren't generated whenbuildis disabled, are removed ifbuildis disabled, and the user'sbuildconfiguration is merged into each variant. Variants carry aframeworkVariantOfmetadata marker so the plugin matches them to their base target.build:releasetriggers Nx's "Ambiguous target specifier" warning because:collides withproject:target:configuration. Variant names join the framework to the configured (possibly renamed) target name with a hyphen, normalize deterministically, and any collision is reported and skipped rather than overwriting a target.ProjectInstance.No
nxcore API changes are required.Out of scope (follow-ups)
--runtime, RID-scoped folders) — the stacked follow-up feat(dotnet): opt-in per-runtime-identifier target variants for multi-targeted executables #36681; addresses the #33474 / #33662 class of failure by construction.test/publishvariants — deferred; project-level test/executable capability doesn't necessarily hold per framework, so those need their own design.projectType, tags, and host routing from #36676 are intentionally excluded.Testing
TargetBuilderFrameworkVariantsTests) — opt-in guard, build-only generation, self-contained (no aggregate dep, no--no-dependencies), framework-scoped outputs, metadata (targetFramework+frameworkVariantOf), colon-safe naming, configured-name derivation, collision handling.create-nodesspec — option pass-through, disable gating, variant config merge, variant removal on disable.dotnet-framework-variants.test.ts) — variants through the real plugin + analyzer, self-contained check, and building a single framework in isolation.astro-docsdotnet introduction.Related Issue(s)
Refs discussion #36676, discussion #35837. Related: #33474, #33662.