You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Blocked by #311 (the well-known hook path lives under .buildvana/).
Reference version
2.1.12-preview
Background and motivation
SelfReferenceUpdater rewrites the three well-known files (global.json, .config/dotnet-tools.json, Directory.Packages.props). But repos can embed the released version in arbitrary other files. Concrete case: Buildvana's own buildvana.jsonc carries a $schema URL pinned to a release tag, which today must be hand-amended into the dogfood commit after every release.
Hard-wiring more targets doesn't generalize (the $schema case only ever fires in Buildvana's own repo — a consumer's release never produces Buildvana packages), and a declarative rewrite list would grow into a bad programming language. The right escape hatch is real code: an optional, repo-owned hook run at a named moment of the release flow.
The hook is named after the moment it fires — the assembly of the post-release commit (see AddPostReleaseCommit) — not after dogfooding, its motivating use case. Hooks are event handlers; naming one after its first use case misdescribes the mechanism and, as an earlier draft of this proposal demonstrated, invites wrongly coupling the trigger to the use case (gating the hook on the dogfood option).
Proposed enhancement
If .buildvana/hooks/release/post-release.cs exists, bv release runs it (via dotnet run) from the home directory, at the moment the post-release commit is assembled: after the well-known self-reference rewrites (when dogfooding is enabled) and before anything is pushed. The hook runs whether or not dogfooding is enabled; the dogfood option gates only the built-in rewrites. When the file is absent, the hook is skipped with an info message.
Hook path convention: .buildvana/hooks/<command>/<moment>.cs — the directory names the command, the file names the moment. This proposal ships exactly one moment.
Context is passed via a JSON context file: bv serializes the release context (home directory, version being released in plain and full-SemVer forms, previously released version, prerelease and public-release flags, artifacts directory, the produced package id→version map, and whether dogfooding ran) to a temporary file and sets a single environment variable, BV_HOOK_CONTEXT, to its absolute path. The file is deleted when the hook completes; its content is logged at Detail verbosity for post-mortems.
Buildvana SDK ships typed loaders: when building a file-based app located under .buildvana/hooks/, the SDK injects Compile items for source files it ships, providing internal types: a BvHookContext record with a static Load() method (reads BV_HOOK_CONTEXT, deserializes), and a Buildvana configuration loader that probes the four configuration-file candidates under the current directory (the hook runs from home; discovery has already happened), applies the existing exactly-one rule, and parses jsonc-tolerantly. The context carries the facts of the run; the configuration loader serves hooks that need any standing repo setting. Hooks in Buildvana-SDK repos therefore start at var context = BvHookContext.Load(); — no scaffolding. The detection is path-based, not gated on a bv-passed property, so hooks stay runnable by hand (BV_HOOK_CONTEXT=... dotnet run post-release.cs) for testing outside a release.
The hook reports nothing back. bv snapshots git status --porcelain before and after; files changed by the hook join the post-release commit alongside the well-known rewrites (or constitute it entirely, when dogfooding is off or rewrote nothing).
A non-zero exit code aborts the release before anything is pushed.
bv clean also runs dotnet clean on each *.cs file under .buildvana/hooks/ (recursively), clearing its file-based-app build cache.
Documentation states the contract, conventions, and sharp edges rather than enforcing anything:
"post-release" names the post-release commit: when the hook runs, nothing has been pushed or published yet, and a non-zero exit aborts the entire release. Announcements and other externally-visible actions don't belong here.
Prefer BCL-only hooks; the BCL (including System.Text.Json) covers version-rewriting jobs.
For third-party dependencies, prefer versionless #:package resolved through the repo's Directory.Packages.props (supported by file-based apps under central package management) — the version then lives where dependency updates already look.
Never reference self-produced packages via versionless #:package: at hook time Directory.Packages.props has already been rewritten to the version being released, which is on no feed until the release completes — restore fails mid-release, deterministically.
#:project is the sanctioned way to use repo-local library code: no version pin, compiles against HEAD.
Pinned #:package Foo@x.y.z is allowed but owned by the repo: pins drift on dependency updates, and a pin on a self-produced package lags its own release by one. If you break your own repo, you own both pieces.
Hooks require Buildvana SDK, which reaches them through the repo's Directory.Build.{props,targets} parent-inclusion chain. A repo may add its own .buildvana/Directory.Build.{props,targets}, but they must follow the well-known parent-inclusion pattern; otherwise hooks break and the repo owns both pieces.
Local file-based-app caching may not notice implicit-build-file changes; CI is always a cold build. bv clean clears it.
Buildvana's own repo adds .buildvana/hooks/release/post-release.cs rewriting the $schema version segment (BCL regex), guarded on the dogfooded context flag, serving as the worked example and ending the hand-amend ritual.
Acceptance criteria:
Hook detected and executed at the documented moment, with the documented context contract, independently of the dogfood option; skipped (with an info message) when absent.
Hook-modified files land in the post-release commit; hook failure aborts the release with the hook's output surfaced.
The post-release commit message remains accurate when the hook contributes changes (see implementation proposals).
bv clean clears hook build caches.
The SDK injects the typed loaders for file-based apps under .buildvana/hooks/, with tests where the SDK test infrastructure allows.
Documentation covering the contract and the sharp edges above.
This repo's own $schema hook in place.
Tests for detection, context-file contract (including the boolean flags and previousVersion's first-release case), diff-detection, execution with dogfooding disabled, failure handling.
Changelog entry under ## Unreleased changes.
Implementation proposals
ReleaseCommand: run the hook through IProcessRunner immediately after the dogfood block (unconditionally), before PushUpdates; merge diff-detected paths with the SelfReferenceUpdater results (empty when dogfooding is off) into a single AddPostReleaseCommit call, made only when the merged list is non-empty.
Porcelain snapshot via the existing Git service; the working tree is clean at that point apart from the well-known rewrites, so attribution is unambiguous.
Commit message: Post-release updates for {version} [skip ci], fixed. A separate historical message (Update self-references to {version} [skip ci]) for commits containing only the built-in rewrites was considered and dropped: the message composition will be revisited anyway (for one, [skip ci] should be provided by the server adapter, which opens the Git-host vs. CI-environment distinction — out of scope here).
Context file shape (camelCase; bikeshed welcome): homeDirectory, releaseVersion (plain form), releaseSemVer (full SemVer, including build metadata when the build embeds it in artifact names), previousVersion (null when no previous release exists), isPrerelease, isPublicRelease (currently invariantly true, since bv release requires a public release; the invariant falls the day bv release learns to run locally), artifactsDirectory (absolute), producedPackages (object mapping package id → version), dogfooded (whether the built-in self-reference rewrites ran in this release — the resolved outcome, which the --dogfood flag may have overridden away from the configured value).
Previous version source: latest release tag reachable from HEAD (GetLatestVersions already exists); covers repos with nothing to dogfood, and reachability keeps it correct when releasing from a maintenance branch alongside a newer line.
usingSystem.Text.Json;usingSystem.Text.RegularExpressions;// Bootstrap: this repo builds with the previous published SDK, which does not// yet ship BvHookContext — read the context file directly. Consumers get// `var context = BvHookContext.Load();` instead.usingvarcontext=JsonDocument.Parse(File.ReadAllText(Environment.GetEnvironmentVariable("BV_HOOK_CONTEXT")!));if(!context.RootElement.GetProperty("dogfooded").GetBoolean()){return;}// The $schema URL pins a release tag; tags use the full SemVer form.varversion=context.RootElement.GetProperty("releaseSemVer").GetString()!;varpath="buildvana.jsonc";vartext=File.ReadAllText(path);text=Regex.Replace(text,@"(Tenacom/Buildvana/)[^/]+(/schemas/)",$"${{1}}{version}$2");File.WriteAllText(path,text);
(The guard mirrors the built-in rewrites: the $schema URL is itself a self-reference, so it moves only when dogfooding moves the rest.)
Risks
Consumer code now runs inside bv release and can fail it; mitigated by running before any push, so failures abort cleanly.
Same trust model as the rest of the build (MSBuild targets, tests already execute repo code with release secrets in the environment); releases don't run on fork PRs. No new attack surface.
Scope creep toward a general-purpose hook matrix; guarded by shipping exactly one named moment (release/post-release) until a second need is demonstrated. The <command>/<moment> convention leaves room (e.g., a hypothetical release/post-publish) without promising anything.
Hooks referencing self-produced packages: versionless-via-CPM fails the release deterministically (documented as "never"); a pin lags its own release by one (documented, owned by the repo).
Additional information
Consumer-side $schema staleness (updating Buildvana in a consumer repo) is intentionally out of scope; it happens at tool-update time, when bv isn't running, and may get a different mechanism or none.
Blocked by #311 (the well-known hook path lives under
.buildvana/).Reference version
2.1.12-preview
Background and motivation
SelfReferenceUpdaterrewrites the three well-known files (global.json,.config/dotnet-tools.json,Directory.Packages.props). But repos can embed the released version in arbitrary other files. Concrete case: Buildvana's ownbuildvana.jsonccarries a$schemaURL pinned to a release tag, which today must be hand-amended into the dogfood commit after every release.Hard-wiring more targets doesn't generalize (the
$schemacase only ever fires in Buildvana's own repo — a consumer's release never produces Buildvana packages), and a declarative rewrite list would grow into a bad programming language. The right escape hatch is real code: an optional, repo-owned hook run at a named moment of the release flow.The hook is named after the moment it fires — the assembly of the post-release commit (see
AddPostReleaseCommit) — not after dogfooding, its motivating use case. Hooks are event handlers; naming one after its first use case misdescribes the mechanism and, as an earlier draft of this proposal demonstrated, invites wrongly coupling the trigger to the use case (gating the hook on thedogfoodoption).Proposed enhancement
.buildvana/hooks/release/post-release.csexists,bv releaseruns it (viadotnet run) from the home directory, at the moment the post-release commit is assembled: after the well-known self-reference rewrites (when dogfooding is enabled) and before anything is pushed. The hook runs whether or not dogfooding is enabled; thedogfoodoption gates only the built-in rewrites. When the file is absent, the hook is skipped with an info message..buildvana/hooks/<command>/<moment>.cs— the directory names the command, the file names the moment. This proposal ships exactly one moment.BV_HOOK_CONTEXT, to its absolute path. The file is deleted when the hook completes; its content is logged atDetailverbosity for post-mortems..buildvana/hooks/, the SDK injectsCompileitems for source files it ships, providinginternaltypes: aBvHookContextrecord with a staticLoad()method (readsBV_HOOK_CONTEXT, deserializes), and a Buildvana configuration loader that probes the four configuration-file candidates under the current directory (the hook runs from home; discovery has already happened), applies the existing exactly-one rule, and parses jsonc-tolerantly. The context carries the facts of the run; the configuration loader serves hooks that need any standing repo setting. Hooks in Buildvana-SDK repos therefore start atvar context = BvHookContext.Load();— no scaffolding. The detection is path-based, not gated on a bv-passed property, so hooks stay runnable by hand (BV_HOOK_CONTEXT=... dotnet run post-release.cs) for testing outside a release.git status --porcelainbefore and after; files changed by the hook join the post-release commit alongside the well-known rewrites (or constitute it entirely, when dogfooding is off or rewrote nothing).bv cleanalso runsdotnet cleanon each*.csfile under.buildvana/hooks/(recursively), clearing its file-based-app build cache.System.Text.Json) covers version-rewriting jobs.#:packageresolved through the repo'sDirectory.Packages.props(supported by file-based apps under central package management) — the version then lives where dependency updates already look.#:package: at hook timeDirectory.Packages.propshas already been rewritten to the version being released, which is on no feed until the release completes — restore fails mid-release, deterministically.#:projectis the sanctioned way to use repo-local library code: no version pin, compiles against HEAD.#:package Foo@x.y.zis allowed but owned by the repo: pins drift on dependency updates, and a pin on a self-produced package lags its own release by one. If you break your own repo, you own both pieces.Directory.Build.{props,targets}parent-inclusion chain. A repo may add its own.buildvana/Directory.Build.{props,targets}, but they must follow the well-known parent-inclusion pattern; otherwise hooks break and the repo owns both pieces.bv cleanclears it..buildvana/hooks/release/post-release.csrewriting the$schemaversion segment (BCL regex), guarded on thedogfoodedcontext flag, serving as the worked example and ending the hand-amend ritual.Acceptance criteria:
dogfoodoption; skipped (with an info message) when absent.bv cleanclears hook build caches..buildvana/hooks/, with tests where the SDK test infrastructure allows.$schemahook in place.previousVersion's first-release case), diff-detection, execution with dogfooding disabled, failure handling.## Unreleased changes.Implementation proposals
ReleaseCommand: run the hook throughIProcessRunnerimmediately after the dogfood block (unconditionally), beforePushUpdates; merge diff-detected paths with theSelfReferenceUpdaterresults (empty when dogfooding is off) into a singleAddPostReleaseCommitcall, made only when the merged list is non-empty.Post-release updates for {version} [skip ci], fixed. A separate historical message (Update self-references to {version} [skip ci]) for commits containing only the built-in rewrites was considered and dropped: the message composition will be revisited anyway (for one,[skip ci]should be provided by the server adapter, which opens the Git-host vs. CI-environment distinction — out of scope here).homeDirectory,releaseVersion(plain form),releaseSemVer(full SemVer, including build metadata when the build embeds it in artifact names),previousVersion(nullwhen no previous release exists),isPrerelease,isPublicRelease(currently invariantlytrue, sincebv releaserequires a public release; the invariant falls the daybv releaselearns to run locally),artifactsDirectory(absolute),producedPackages(object mapping package id → version),dogfooded(whether the built-in self-reference rewrites ran in this release — the resolved outcome, which the--dogfoodflag may have overridden away from the configured value).GetLatestVersionsalready exists); covers repos with nothing to dogfood, and reachability keeps it correct when releasing from a maintenance branch alongside a newer line.bvshould verify the repo's pinned Buildvana SDK version before running commands that involve the SDK #317). New fields may be added; none removed or repurposed.System.Text.Jsonignores unknown fields by default, so old loaders tolerate new bv output.Usage examples
Buildvana's own hook, approximately:
(The guard mirrors the built-in rewrites: the
$schemaURL is itself a self-reference, so it moves only when dogfooding moves the rest.)Risks
bv releaseand can fail it; mitigated by running before any push, so failures abort cleanly.release/post-release) until a second need is demonstrated. The<command>/<moment>convention leaves room (e.g., a hypotheticalrelease/post-publish) without promising anything.Additional information
Consumer-side
$schemastaleness (updating Buildvana in a consumer repo) is intentionally out of scope; it happens at tool-update time, when bv isn't running, and may get a different mechanism or none.