Tool Acquisition (.NET 8/9 Auto-Acquire)
On .NET 10+, RunEfcpt can run the efcpt CLI on demand via dnx <package> --yes -- ... -
no install step required. On .NET 8 and .NET 9, dnx is not usable, so the tool has to already
be installed somewhere the task can find it: a restored local tool manifest, a global tool on
PATH, or an explicit EfcptToolPath. Historically, if none of those were present, the task
fell back to dotnet tool update --global <package> - which frequently fails in practice because
~/.dotnet/tools isn't on PATH in many CI images and fresh dev machines, and a global install
also isn't hermetic (it's shared, mutable state outside the project).
EfcptAutoAcquireTool (default true) fixes this by bootstrapping a project-local, obj-scoped
tool manifest instead - hermetic, self-cleaning (it lives under obj/, so it's wiped by
dotnet clean), and independent of PATH.
What it does
When all of the following are true, RunEfcpt bootstraps an obj-local tool manifest and installs
the efcpt tool into it, before tool resolution runs:
- The project targets .NET 8 or .NET 9 (or, more precisely, dnx is not usable - see What "dnx not usable" means below).
EfcptAutoAcquireToolistrue(the default).EfcptOfflineModeis not enabled (see Interaction with offline mode).- No explicit
EfcptToolPathis set. - No already-usable tool manifest was discovered (walking up from
WorkingDirectory) that actually lists the efcpt tool. - No global tool is already resolvable on
PATH. EfcptToolPackageIdhas a value (it does by default:ErikEJ.EFCorePowerTools.Cli).
The bootstrap itself is equivalent to running, in $(EfcptOutput) (typically obj\efcpt\):
dotnet new tool-manifest
dotnet tool install ErikEJ.EFCorePowerTools.Cli --version 10.*
dotnet new tool-manifest is only run if .config/dotnet-tools.json doesn't already exist at
that location - so a second build reuses the manifest from the first (as long as obj/ wasn't
cleaned). Once the manifest and tool are in place, tool resolution proceeds exactly as it would
for a project that came with its own committed manifest: dotnet tool run efcpt -- ....
What "dnx not usable" means
The same condition RunEfcpt uses for the dnx resolution branch: the project does not
simultaneously satisfy target framework net10.0+, a .NET 10+ SDK being installed, and dnx
being available. If any of those three is false - including simply targeting net8.0 or
net9.0 - dnx is not usable and auto-acquisition (if otherwise enabled) can kick in.
If dnx is usable, auto-acquisition never runs - dnx already handles execution without any install step, so there is nothing for it to do. Auto-acquisition is unaffected on .NET 10+.
Opting out
Set EfcptAutoAcquireTool=false to disable auto-acquisition and restore the legacy behavior
(fall back to the global tool path / dotnet tool update --global):
<PropertyGroup>
<EfcptAutoAcquireTool>false</EfcptAutoAcquireTool>
</PropertyGroup>
This is a reasonable choice if you already commit a tool manifest to source control, or if you intentionally manage the efcpt tool as a global install across your team/CI images.
Interaction with offline mode
EfcptOfflineMode always wins: when offline mode is enabled, auto-acquisition never runs,
regardless of EfcptAutoAcquireTool. Installing a tool is itself a network operation, and
offline mode's whole contract is "never spawn a network-dependent step." This precedence is
enforced inside the RunEfcpt task itself (not merely via an MSBuild condition), so it can't be
bypassed by property ordering or overrides.
In practice, this means offline builds still need one of the network-free tool sources
documented in offline.md (a pre-restored manifest, a pre-installed global tool, or
an explicit EfcptToolPath) - auto-acquisition cannot substitute for that pre-provisioning step,
and offline builds on .NET 8/9 with none of those available still fail fast with JD0026.
When acquisition fails
If auto-acquisition runs but dotnet new tool-manifest or dotnet tool install fails (network
issue, bad EfcptToolVersion constraint, misconfigured NuGet feed, etc.), the build fails with
error JD0027 - see
error-codes.md for the full message shape
and remediation options.
EfcptDoctor
EfcptDoctor is an opt-in diagnostic target that reports exactly which execution path RunEfcpt
would take for the current project - without running (or installing) anything:
dotnet build -t:EfcptDoctor
It reports:
- The target framework and its parsed major version
- Whether the .NET 10+ SDK and
dnxare available - Whether a tool manifest was discovered (and whether it lists the efcpt tool)
- Whether a global tool is resolvable on
PATH - The explicit
EfcptToolPathstate - The effective
EfcptOfflineMode/EfcptAutoAcquireToolsettings - A verdict: either the execution path that will be used, or the exact remediation needed if none is viable
EfcptDoctor never fails the build by default - it's purely informational. Pass
-p:EfcptDoctorStrict=true to make it fail (non-zero exit) when no viable execution path was
found, which is useful as a CI gate to catch tool-resolution misconfiguration before it surfaces
as a confusing failure mid-build:
dotnet build -t:EfcptDoctor -p:EfcptDoctorStrict=true
The same report is also available without a project or a build via the jd-efcpt CLI's own
doctor command - see jd-efcpt CLI. Both share the exact same diagnosis
engine, so they can never report a different verdict for the same inputs.
Summary
| Scenario | Behavior |
|---|---|
| .NET 10+, dnx usable | dnx execution; auto-acquisition never runs |
| .NET 8/9, manifest already present and lists the tool | Manifest resolution; auto-acquisition never runs |
.NET 8/9, global tool already on PATH |
Global tool resolution; auto-acquisition never runs |
.NET 8/9, nothing usable, EfcptAutoAcquireTool=true (default), online |
Obj-local manifest bootstrapped and installed, then dotnet tool run |
.NET 8/9, nothing usable, EfcptAutoAcquireTool=false |
Legacy global-tool fallback (dotnet tool update --global) |
EfcptOfflineMode=true, nothing pre-provisioned |
Fails fast with JD0026; auto-acquisition never attempted |
Auto-acquisition attempted but dotnet tool install fails |
Fails with JD0027 |