Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/user-guide/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,37 @@ error JD0025: Failed to add SQL file warnings: Access to the path is denied

---

### JD0026: Efcpt Tool Not Available Offline
**Severity**: Error
**Task**: RunEfcpt

`EfcptOfflineMode` (or the `EFCPT_OFFLINE` environment variable) is enabled, but the efcpt tool
is not guaranteed to run without a network call - no explicit, existing `EfcptToolPath` was
provided, no tool manifest was discovered, and no global tool was found on `PATH`. Offline mode
never spawns `dnx`, restores a tool manifest, or updates a global tool, since all three require
network access.

**Example**:
```
error JD0026: EfcptOfflineMode is enabled, but the efcpt tool is not guaranteed to run without a
network call. Offline mode will not spawn dnx, restore a tool manifest, or update a global tool,
since all three require network access. Pre-provision the tool before building offline using one
of the following: (1) a local tool manifest - run: dotnet new tool-manifest && dotnet tool
install ErikEJ.EFCorePowerTools.Cli --version 10.*; (2) a global tool - run: dotnet tool install
--global ErikEJ.EFCorePowerTools.Cli --version 10.*; or (3) set EfcptToolPath to an explicit,
pre-installed efcpt executable. ...
```

**Resolution**:
- Pre-provision a local tool manifest and restore it before the offline build runs: `dotnet new
tool-manifest && dotnet tool install ErikEJ.EFCorePowerTools.Cli --version 10.*`
- Or install the tool as a global tool ahead of time: `dotnet tool install --global
ErikEJ.EFCorePowerTools.Cli --version 10.*`
- Or set `EfcptToolPath` to an explicit, pre-installed efcpt executable
- See [offline.md](offline.md) for the full offline/air-gapped build workflow

---

## Troubleshooting Tips

### General Troubleshooting Steps
Expand Down
93 changes: 93 additions & 0 deletions docs/user-guide/offline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Offline / Air-Gapped Builds

`EfcptOfflineMode` lets you build in environments that have no outbound network access -
air-gapped CI agents, locked-down secure build environments, or any pipeline where a build
should never silently reach out to the internet.

## What it does

When `EfcptOfflineMode` is `true` (or the `EFCPT_OFFLINE` environment variable is set to a
truthy value - see [Enabling offline mode](#enabling-offline-mode) below for the exact accepted
values), the `RunEfcpt` task refuses to spawn any of the three network-dependent code
paths it would otherwise use to resolve or restore the efcpt CLI:

1. **dnx execution** - on .NET 10+ projects, the task would normally run `dotnet dnx <package>
--yes -- ...` to fetch and execute the tool on demand. This is skipped entirely offline.
2. **Tool-manifest restore** - `dotnet tool restore` against a discovered
`.config/dotnet-tools.json` manifest. Skipped offline.
3. **Global tool update** - `dotnet tool update --global <package>`. Skipped offline.

It also disables the SDK update-check target (`EfcptCheckForUpdates`), which otherwise makes an
outbound call to check for a newer `JD.Efcpt.Sdk` version.

## What you need to provide

Because none of the network-dependent paths run, the efcpt tool must already be available
through one of three network-free routes:

- **A local tool manifest that has already been restored** before the offline build runs:
```bash
dotnet new tool-manifest
dotnet tool install ErikEJ.EFCorePowerTools.Cli --version 10.*
```
Restore this once, on a machine with network access (or in an earlier CI stage that does have
access), then carry the restored `.config/dotnet-tools.json` (and the NuGet tool cache) into
the offline environment.

- **A global tool install**, performed ahead of time on the build agent image:
```bash
dotnet tool install --global ErikEJ.EFCorePowerTools.Cli --version 10.*
```

- **An explicit, pre-provisioned executable**, via `EfcptToolPath`:
```xml
<PropertyGroup>
<EfcptOfflineMode>true</EfcptOfflineMode>
<EfcptToolPath>C:\tools\efcpt\efcpt.exe</EfcptToolPath>
</PropertyGroup>
```

If none of these are available, the build fails fast with error `JD0026` instead of hanging or
failing obscurely against a missing tool - see [error-codes.md](error-codes.md#jd0026-efcpt-tool-not-available-offline).

## Enabling offline mode

MSBuild property (recommended, per-project or via `Directory.Build.props`):

```xml
<PropertyGroup>
<EfcptOfflineMode>true</EfcptOfflineMode>
</PropertyGroup>
```

Or via the `EFCPT_OFFLINE` environment variable (useful for CI pipelines that shouldn't need to
touch every consuming project's file):

```bash
set EFCPT_OFFLINE=true
dotnet build
```

Either is sufficient; they are OR-ed together at two levels:

- **MSBuild property default.** `EfcptOfflineMode`'s own default (applied only when the property
is not explicitly set by the project/`Directory.Build.props`) checks `$(EFCPT_OFFLINE)` -
MSBuild exposes process environment variables as ordinary properties, so this needs no special
plumbing. If `$(EFCPT_OFFLINE)` is one of the truthy tokens below, `EfcptOfflineMode` defaults
to `true`; this is what makes the update-check target (`_EfcptCheckForUpdates`, which reads
`$(EfcptOfflineMode)` directly) also skip when only the env var is set.
- **Task-level OR.** Independently, the `RunEfcpt` task itself also reads
`Environment.GetEnvironmentVariable("EFCPT_OFFLINE")` at execution time and OR's it with the
`EfcptOfflineMode` task property, so offline behavior holds even if something explicitly set
`EfcptOfflineMode=false` at the MSBuild property level while `EFCPT_OFFLINE` is set in the
process environment.

**Accepted truthy values** for `EFCPT_OFFLINE` (case-insensitive): `true`, `yes`, `on`, `1`,
`enable`, `enabled`, `y`. Any other value - including `false`, `0`, `no`, `off`, or an empty/unset
variable - does **not** enable offline mode via the environment variable.

## What still runs

Offline mode only gates the three network-dependent tool resolution/restore branches and the
update-check target. It does not change DACPAC building, schema fingerprinting, or model
generation itself - those are already local operations that don't touch the network.
26 changes: 26 additions & 0 deletions src/JD.Efcpt.Build.Definitions/BuildTransitivePropsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ public static PackageDefinition Create()
group.Property<EfcptToolCommand>( "efcpt", "'$(EfcptToolCommand)'==''");
group.Property<EfcptToolPath>( "", "'$(EfcptToolPath)'==''");
group.Property<EfcptDotNetExe>( "dotnet", "'$(EfcptDotNetExe)'==''");
// EFCPT_OFFLINE env-var bridge: MSBuild exposes environment variables as
// $(EFCPT_OFFLINE) automatically. When EfcptOfflineMode has not been set
// explicitly, honor a truthy EFCPT_OFFLINE value as the default so the
// documented "either is sufficient" contract holds even for targets/tasks
// that read $(EfcptOfflineMode) directly (e.g. _EfcptCheckForUpdates).
// Truthy tokens intentionally mirror StringExtensions.IsTrue() (true/yes/on/1/
// enable/enabled/y); MSBuild condition equality is case-insensitive, so no
// additional case variants are needed. EFCPT_OFFLINE=false/0/unset does NOT
// enable offline mode.
group.Property<EfcptOfflineMode>( "true", "'$(EfcptOfflineMode)'=='' and ('$(EFCPT_OFFLINE)'=='true' or '$(EFCPT_OFFLINE)'=='yes' or '$(EFCPT_OFFLINE)'=='on' or '$(EFCPT_OFFLINE)'=='1' or '$(EFCPT_OFFLINE)'=='enable' or '$(EFCPT_OFFLINE)'=='enabled' or '$(EFCPT_OFFLINE)'=='y')");
group.Property<EfcptOfflineMode>( "false", "'$(EfcptOfflineMode)'==''");
group.Property<EfcptFingerprintFile>( "$(EfcptOutput)fingerprint.txt", "'$(EfcptFingerprintFile)'==''");
group.Property<EfcptStampFile>( "$(EfcptOutput).efcpt.stamp", "'$(EfcptStampFile)'==''");
group.Property<EfcptDetectGeneratedFileChanges>( "false", "'$(EfcptDetectGeneratedFileChanges)'==''");
Expand Down Expand Up @@ -141,6 +152,17 @@ public static PackageDefinition Create()
group.Property<EfcptToolCommand>( "efcpt", "'$(EfcptToolCommand)'==''");
group.Property<EfcptToolPath>( "", "'$(EfcptToolPath)'==''");
group.Property<EfcptDotNetExe>( "dotnet", "'$(EfcptDotNetExe)'==''");
// EFCPT_OFFLINE env-var bridge: MSBuild exposes environment variables as
// $(EFCPT_OFFLINE) automatically. When EfcptOfflineMode has not been set
// explicitly, honor a truthy EFCPT_OFFLINE value as the default so the
// documented "either is sufficient" contract holds even for targets/tasks
// that read $(EfcptOfflineMode) directly (e.g. _EfcptCheckForUpdates).
// Truthy tokens intentionally mirror StringExtensions.IsTrue() (true/yes/on/1/
// enable/enabled/y); MSBuild condition equality is case-insensitive, so no
// additional case variants are needed. EFCPT_OFFLINE=false/0/unset does NOT
// enable offline mode.
group.Property<EfcptOfflineMode>( "true", "'$(EfcptOfflineMode)'=='' and ('$(EFCPT_OFFLINE)'=='true' or '$(EFCPT_OFFLINE)'=='yes' or '$(EFCPT_OFFLINE)'=='on' or '$(EFCPT_OFFLINE)'=='1' or '$(EFCPT_OFFLINE)'=='enable' or '$(EFCPT_OFFLINE)'=='enabled' or '$(EFCPT_OFFLINE)'=='y')");
group.Property<EfcptOfflineMode>( "false", "'$(EfcptOfflineMode)'==''");
group.Property<EfcptFingerprintFile>( "$(EfcptOutput)fingerprint.txt", "'$(EfcptFingerprintFile)'==''");
group.Property<EfcptStampFile>( "$(EfcptOutput).efcpt.stamp", "'$(EfcptStampFile)'==''");
group.Property<EfcptDetectGeneratedFileChanges>( "false", "'$(EfcptDetectGeneratedFileChanges)'==''");
Expand Down Expand Up @@ -443,6 +465,10 @@ public static PackageDefinition Create()
{
public string Name => "EfcptLogVerbosity";
}
public readonly struct EfcptOfflineMode : IMsBuildPropertyName
{
public string Name => "EfcptOfflineMode";
}
public readonly struct EfcptOutput : IMsBuildPropertyName
{
public string Name => "EfcptOutput";
Expand Down
Loading
Loading