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
24 changes: 16 additions & 8 deletions FirstClassErrors.Cli/FirstClassErrors.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
</PropertyGroup>

<!-- NuGet packaging: this project ships as the `fce` .NET tool.
PackAsTool bundles the app and its dependency closure under tools/<tfm>/any, so a
`dotnet tool install FirstClassErrors.Cli` yields a runnable `fce`. The GenDoc worker the tool
spawns as a separate process is copied next to the app by the targets below and travels inside the
same folder, which is where the tool resolves it at runtime (AppContext.BaseDirectory). Packed by
tools/packaging/pack.sh alongside the two library packages. -->
PackAsTool bundles the app and its managed dependency closure under tools/<tfm>/any, so a
`dotnet tool install FirstClassErrors.Cli` yields a runnable `fce`. The GenDoc worker the tool spawns as a
separate process is NOT a managed dependency (ReferenceOutputAssembly=false, below), so PackAsTool does not
embed it on its own. It reaches the package because PackAsTool packs the CLI's *publish* output and the
_PublishDocumentationWorker target (below) drops the worker's closure into that publish output — so it lands
under tools/<tfm>/any next to the executable, where the tool resolves it at runtime
(AppContext.BaseDirectory). tools/packaging/pack.sh packs this alongside the two library packages and asserts
the worker is actually present in the .nupkg, so its bundling is enforced by a test, not hoped for. -->
<PropertyGroup>
<PackAsTool>true</PackAsTool>
<ToolCommandName>fce</ToolCommandName>
Expand Down Expand Up @@ -90,9 +93,14 @@
</ItemGroup>

<!-- Bundle the documentation worker alongside the CLI. The worker targets the same framework as the CLI, so its
build output sits under the sibling bin folder for the current Configuration/TargetFramework. The worker is
already built (via the ProjectReference above) by the time these run. Validate locally with `dotnet build`
and `dotnet publish`. -->
build output sits under the sibling bin folder for the current Configuration/TargetFramework, already built via
the ProjectReference above. _CopyDocumentationWorker (AfterTargets="Build") feeds $(OutDir), so running from
source (dev, integration tests) finds the worker; _PublishDocumentationWorker (AfterTargets="Publish") feeds
$(PublishDir), which is what PackAsTool zips into the tool package under tools/<tfm>/any.
Do NOT rely on `dotnet build` or `dotnet publish` to validate that the tool ships its worker: neither exercises
the .nupkg content, so both stay green even if the worker never reaches the package. The real gates are the
worker-present assertion in tools/packaging/pack.sh and the manual tool-install smoke test documented in
maintainers/ReleaseDryRun (install the packed tool, run `fce generate`, expect a non-empty catalog). -->
<Target Name="_CopyDocumentationWorker" AfterTargets="Build">
<ItemGroup>
<_DocumentationWorkerFiles Include="$(MSBuildThisFileDirectory)..\FirstClassErrors.GenDoc.Worker\bin\$(Configuration)\$(TargetFramework)\**\*.*" />
Expand Down
51 changes: 51 additions & 0 deletions maintainers/ReleaseDryRun.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,57 @@ The actual **push to nuget.org** and the **repository-signed bytes** nuget.org
serves cannot be exercised without publishing — nuget.org has no "dry-run push".
That final link is only ever validated by a real release.

Neither dry run proves that the **installed `fce` tool actually runs**, either.
`fce generate` does not do all the work in-process: it spawns
`FirstClassErrors.GenDoc.Worker` in a child process and resolves it next to the
installed executable (`AppContext.BaseDirectory`). The worker travels inside the
tool package only because `PackAsTool` packs the CLI's *publish* output and the
`_PublishDocumentationWorker` target drops the worker's closure there — a
mechanism `dotnet build` and `dotnet publish` do not exercise (they lay files out
next to a build binary, never inside the `.nupkg`). See the tool-install smoke
test below.

## The tool-install smoke test (the `fce` worker)

`tools/packaging/pack.sh` asserts, for the `cli` train, that the worker **file**
(`FirstClassErrors.GenDoc.Worker.dll`) is present under `tools/<tfm>/any/` in the
`.nupkg`. That guard is real and runs on every packed `cli` train — but a present
file is not a working tool: the worker's closure can still be incomplete (a
missing dependency, a wrong `.runtimeconfig.json`), which the file-presence check
cannot see. The only oracle that proves the packaged closure actually runs is a
real install followed by `fce generate`.

Run it **at least once before the first `cli-v…` tag**, and again after any change
to the CLI packaging (the `.csproj` worker targets, `PackAsTool`, or the worker's
dependencies):

```sh
# 1. Build and pack the cli train exactly as the release does.
dotnet build FirstClassErrors.sln -c Release
tools/packaging/pack.sh 0.0.0-workercheck.1 cli # -> artifacts/FirstClassErrors.Cli.0.0.0-workercheck.1.nupkg

# 2. Install the packed tool globally. Install by PACKAGE ID (FirstClassErrors.Cli);
# that is not the command name (fce).
dotnet tool install --global --add-source ./artifacts FirstClassErrors.Cli --version 0.0.0-workercheck.1

# 3. Generate a catalog. Either point at a built, opted-in assembly...
fce generate --assemblies path/to/YourProject.dll --format markdown --output ./out/catalog.md --service-name demo
# ...or at a solution with at least one opted-in project (GenerateErrorDocumentation=true):
fce generate --solution path/to/Your.sln --format markdown --output ./out --service-name demo
# Expect a NON-EMPTY catalog and ZERO "documentation worker could not be located".

# 4. Clean up.
dotnet tool uninstall --global FirstClassErrors.Cli
```

A convenient in-repo target for step 3 is `FirstClassErrors.Usage`: it opts in
(`GenerateErrorDocumentation=true`) and defines documented errors, so after a
Release build its assembly at
`FirstClassErrors.Usage/bin/Release/net10.0/FirstClassErrors.Usage.dll` yields a
non-empty catalog. Two naming traps to avoid: the package file is
`FirstClassErrors.Cli.<version>.nupkg` (the `PackageId`), not `fce.<version>.nupkg`,
and `dotnet tool install` takes that same package id, not the `fce` command name.

## Related

- [`release`](workflows/release.en.md) — the workflow this rehearses, described
Expand Down
53 changes: 53 additions & 0 deletions maintainers/ReleaseDryRun.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,59 @@ nuget.org sert ne peuvent pas être exercés sans publier — nuget.org n'a pas
« push à blanc ». Ce dernier maillon n'est jamais validé que par une vraie
release.

Aucun dry run ne prouve non plus que l'**outil `fce` installé fonctionne
réellement**. `fce generate` ne fait pas tout le travail en process : il lance
`FirstClassErrors.GenDoc.Worker` dans un process enfant et le résout à côté de
l'exécutable installé (`AppContext.BaseDirectory`). Le worker ne voyage dans le
package du tool que parce que `PackAsTool` empaquette la sortie *publish* du CLI
et que la cible `_PublishDocumentationWorker` y dépose la clôture du worker — un
mécanisme que `dotnet build` et `dotnet publish` n'exercent pas (ils déposent des
fichiers à côté d'un binaire de build, jamais dans le `.nupkg`). Voir le test de
tool-install ci-dessous.

## Le test de fumée tool-install (le worker `fce`)

`tools/packaging/pack.sh` vérifie, pour le train `cli`, que le **fichier** du
worker (`FirstClassErrors.GenDoc.Worker.dll`) est présent sous `tools/<tfm>/any/`
dans le `.nupkg`. Ce garde est réel et tourne sur chaque train `cli` packé — mais
un fichier présent n'est pas un tool qui marche : la clôture du worker peut rester
incomplète (une dépendance manquante, un mauvais `.runtimeconfig.json`), ce que le
contrôle de présence ne voit pas. Le seul oracle qui prouve que la clôture
empaquetée s'exécute vraiment est une installation réelle suivie d'un
`fce generate`.

Lance-le **au moins une fois avant le premier tag `cli-v…`**, et de nouveau après
tout changement de l'empaquetage du CLI (les cibles worker du `.csproj`,
`PackAsTool`, ou les dépendances du worker) :

```sh
# 1. Builder et packer le train cli exactement comme la release.
dotnet build FirstClassErrors.sln -c Release
tools/packaging/pack.sh 0.0.0-workercheck.1 cli # -> artifacts/FirstClassErrors.Cli.0.0.0-workercheck.1.nupkg

# 2. Installer le tool packé en global. Installer par IDENTIFIANT DE PACKAGE
# (FirstClassErrors.Cli) ; ce n'est pas le nom de commande (fce).
dotnet tool install --global --add-source ./artifacts FirstClassErrors.Cli --version 0.0.0-workercheck.1

# 3. Générer un catalogue. Soit en pointant une assembly buildée et opt-in...
fce generate --assemblies chemin/vers/VotreProjet.dll --format markdown --output ./out/catalog.md --service-name demo
# ...soit une solution avec au moins un projet opt-in (GenerateErrorDocumentation=true) :
fce generate --solution chemin/vers/Votre.sln --format markdown --output ./out --service-name demo
# Attendu : un catalogue NON VIDE et ZÉRO « documentation worker could not be located ».

# 4. Nettoyer.
dotnet tool uninstall --global FirstClassErrors.Cli
```

Une cible pratique du dépôt pour l'étape 3 est `FirstClassErrors.Usage` : elle
opte-in (`GenerateErrorDocumentation=true`) et définit des erreurs documentées,
donc après un build Release son assembly, à
`FirstClassErrors.Usage/bin/Release/net10.0/FirstClassErrors.Usage.dll`, produit
un catalogue non vide. Deux pièges de nommage à éviter : le fichier de package est
`FirstClassErrors.Cli.<version>.nupkg` (le `PackageId`), pas
`fce.<version>.nupkg`, et `dotnet tool install` prend ce même identifiant de
package, pas le nom de commande `fce`.

## En rapport

- [`release`](workflows/release.fr.md) — le workflow que ceci répète, décrit
Expand Down
24 changes: 23 additions & 1 deletion tools/packaging/pack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ case "$scope" in
;;
cli)
# The `fce` .NET tool (PackAsTool; the GenDoc worker it spawns travels bundled inside the tool
# package). Released on its own cadence and version.
# package -- asserted after the pack, below). Released on its own cadence and version.
projects='FirstClassErrors.Cli/FirstClassErrors.Cli.csproj'
;;
*)
Expand All @@ -63,3 +63,25 @@ for package in artifacts/*.nupkg; do
exit 1
fi
done

# Positive proof that the fce tool ships its GenDoc worker. `fce generate` does not do the whole job
# in-process: it spawns FirstClassErrors.GenDoc.Worker in a child process (dotnet exec) and resolves it
# next to the installed executable (ResolveWorkerAssemblyPath -> AppContext.BaseDirectory). PackAsTool packs
# the CLI's *publish* output, and _PublishDocumentationWorker (AfterTargets="Publish" in the .csproj) drops
# the worker's closure into that publish directory, so it travels under tools/<tfm>/any inside the package.
# That mechanism is easy to break silently: neither `dotnet build` nor `dotnet publish` exercises the .nupkg
# content, so dropping the target (or PackAsTool ceasing to pack the publish output) would pass every local
# check and only surface as "documentation worker could not be located" on a user's first `fce generate`.
# A pack that stops bundling the worker must fail here, loudly, not in the field. This asserts presence; the
# closure is completeness-checked out of band by the tool-install smoke test in maintainers/ReleaseDryRun.
if [ "$scope" = "cli" ]; then
# The fce tool package carries the CLI's PackageId (FirstClassErrors.Cli), not the ToolCommandName (fce).
for package in artifacts/FirstClassErrors.Cli.*.nupkg; do
if unzip -l "$package" | grep -q 'tools/.*/any/.*FirstClassErrors\.GenDoc\.Worker\.dll'; then
echo "ok: GenDoc worker bundled in $package"
else
echo "error: GenDoc worker missing from the fce tool package $package" >&2
exit 1
fi
done
fi