diff --git a/.claude/settings.json b/.claude/settings.json index 8cc878e58..33fe326e2 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,52 +1,14 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", - "statusLine": { - "type": "command", - "command": "pwsh -NoProfile -Command '$input | & (Get-ChildItem $HOME/.claude/plugins/cache/myfoodbag/mfb-statusline/*/statusline-command.ps1 | Select-Object -Last 1)'" - }, - "enabledPlugins": { - "mfb-statusline@myfoodbag": true - }, "permissions": { "allow": [ - "Bash(dotnet build:*)", - "Bash(dotnet test:*)", - "Bash(dotnet restore:*)", - "Bash(dotnet pack:*)", - "Bash(dotnet --info)", - "Bash(dotnet --version)", - "Bash(dotnet list:*)", + "Bash(dotnet:*)", "Bash(./build.ps1:*)", "Bash(./build.sh:*)", "Bash(./build.cmd:*)", - "Bash(pwsh ./build.ps1:*)", - "Bash(pwsh -NoProfile ./build.ps1:*)", - "Bash(git status)", - "Bash(git status:*)", - "Bash(git diff:*)", - "Bash(git log:*)", - "Bash(git show:*)", - "Bash(git branch:*)", - "Bash(git remote -v)", - "Bash(git rev-parse:*)", - "Bash(git ls-files:*)", - "Bash(git config --get:*)", - "Bash(gh pr list:*)", - "Bash(gh pr view:*)", - "Bash(gh pr diff:*)", - "Bash(gh issue list:*)", - "Bash(gh issue view:*)", - "Bash(gh run list:*)", - "Bash(gh run view:*)", - "Bash(gh workflow list:*)", - "Bash(gh api:*)" - ], - "deny": [ - "Bash(git push --force:*)", - "Bash(git push -f:*)", - "Bash(git reset --hard:*)", - "Bash(git clean -f:*)", - "Bash(rm -rf:*)" + "Bash(pwsh:*)", + "Bash(git:*)", + "Bash(gh:*)" ] } } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3b08bb53b..713f0c064 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,5 +8,5 @@ # changelogs, etc.) have no code owner and are gated only by the required CI # status check. -/src/** @ChrisonSimtian -/tests/** @ChrisonSimtian +/src/** @Fallout-build/maintainers +/tests/** @Fallout-build/maintainers diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml index 76faad6d7..f1009949a 100644 --- a/.github/workflows/experimental.yml +++ b/.github/workflows/experimental.yml @@ -28,6 +28,11 @@ on: - '.assets/**' - '**/*.md' +# Cancel a superseded alpha build when a newer commit lands (#322). Never on release.yml. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -44,7 +49,6 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: @@ -52,14 +56,21 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-experimental-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-experimental- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: Pack' - run: dotnet fallout Pack + # build + unit-test before publishing alpha (#324). One invocation: NUKE runs + # this as discrete internal stages (Restore → Compile → Test → Pack) and fails + # at the breaking stage — separate `dotnet fallout` steps would re-run the + # dependency graph (double-compile), so we keep it a single invocation. + # If Test fails, the job stops here and nothing is published. + - name: 'Run: Test + Pack' + run: dotnet fallout Test Pack - name: 'Push: all *.nupkg to GitHub Packages (alpha)' run: | set -euo pipefail @@ -72,7 +83,7 @@ jobs: for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages (alpha)..." dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ + --source "https://nuget.pkg.github.com/Fallout-build/index.json" \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --skip-duplicate done diff --git a/.github/workflows/macos-latest.yml b/.github/workflows/macos-latest.yml index b17aacacd..426d42ffa 100644 --- a/.github/workflows/macos-latest.yml +++ b/.github/workflows/macos-latest.yml @@ -18,11 +18,20 @@ name: macos-latest on: push: + tags: + - 'v*' + pull_request: branches: - - experimental - - main - 'release/*' - 'support/*' + paths-ignore: + - 'docs/**' + - '.assets/**' + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: macos-latest: @@ -31,7 +40,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 5b90bedfa..98f84874f 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -28,6 +28,11 @@ on: - '.assets/**' - '**/*.md' +# Cancel a superseded preview build when a newer commit lands (#322). Never on release.yml. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -44,7 +49,6 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: @@ -52,14 +56,21 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-preview-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-preview- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: Pack' - run: dotnet fallout Pack + # build + unit-test before publishing preview (#324). One invocation: NUKE runs + # this as discrete internal stages (Restore → Compile → Test → Pack) and fails + # at the breaking stage — separate `dotnet fallout` steps would re-run the + # dependency graph (double-compile), so we keep it a single invocation. + # If Test fails, the job stops here and nothing is published. + - name: 'Run: Test + Pack' + run: dotnet fallout Test Pack - name: 'Push: all *.nupkg to GitHub Packages (preview)' run: | set -euo pipefail @@ -72,7 +83,7 @@ jobs: for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages (preview)..." dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ + --source "https://nuget.pkg.github.com/Fallout-build/index.json" \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --skip-duplicate done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7820252e..10e1e1e4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,7 +94,6 @@ jobs: with: ref: ${{ inputs.tag || github.ref }} fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive # vendor/vs-solutionpersistence - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: @@ -102,6 +101,8 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-release-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-release- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: @@ -198,7 +199,7 @@ jobs: for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages..." dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ + --source "https://nuget.pkg.github.com/Fallout-build/index.json" \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --skip-duplicate done diff --git a/.github/workflows/ubuntu-latest.yml b/.github/workflows/ubuntu-latest.yml index e1e942332..45f548d6e 100644 --- a/.github/workflows/ubuntu-latest.yml +++ b/.github/workflows/ubuntu-latest.yml @@ -28,6 +28,10 @@ on: - '.assets/**' - '**/*.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: ubuntu-latest: name: ubuntu-latest @@ -35,7 +39,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.head_ref }} diff --git a/.github/workflows/windows-latest.yml b/.github/workflows/windows-latest.yml index abfd9026f..bc97f4c73 100644 --- a/.github/workflows/windows-latest.yml +++ b/.github/workflows/windows-latest.yml @@ -18,11 +18,20 @@ name: windows-latest on: push: + tags: + - 'v*' + pull_request: branches: - - experimental - - main - 'release/*' - 'support/*' + paths-ignore: + - 'docs/**' + - '.assets/**' + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: windows-latest: @@ -31,7 +40,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac51bd0f..49a482d1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,7 +123,7 @@ The NUKE → Fallout hard fork. Originally NUKE by [@matkoch](https://github.com ### Backwards-compat: Nuke.* transition shims - **`Nuke.Common` MVP shim package** (#70): thin wrapper assembly in the `Nuke.*` namespace whose types inherit from the corresponding `Fallout.*` types — lets pre-rename consumers compile against the new packages without source changes. Lives at `src/Shims/Nuke.Common/`. - **`TransitionShimGenerator` source generator** (#69): emits shim type wrappers per-target, covering the Nuke.Common "Easy tier" surface (plain types, interfaces, enums) and static-class method delegation. Hard-tier types (sealed structs, delegates, etc.) raise `SHIM001` warnings with a pointer to `fallout-migrate`. -- **Shim packages publish to GitHub Packages** (#47): `Nuke.*` package IDs belong to matkoch on nuget.org, so the transition shim feed lives at GH Packages (`https://nuget.pkg.github.com/ChrisonSimtian/index.json`). Production-build feed is nuget.org with `Fallout.*` only. +- **Shim packages publish to GitHub Packages** (#47): `Nuke.*` package IDs belong to matkoch on nuget.org, so the transition shim feed lives at GH Packages (`https://nuget.pkg.github.com/Fallout-build/index.json`). Production-build feed is nuget.org with `Fallout.*` only. ### Vendored fork of `Microsoft.VisualStudio.SolutionPersistence` - **Replaced `matkoch.Microsoft.VisualStudio.SolutionPersistence` with a vendored fork** (#86). The upstream Microsoft package only ships `net472` + `net8.0`; our source generators need `netstandard2.0`. Matt's fork had the netstandard2.0 patches — we forked his repo at [`ChrisonSimtian/vs-solutionpersistence`](https://github.com/ChrisonSimtian/vs-solutionpersistence) (preserves the MIT license + upstream Microsoft history + attribution chain). Sources are a submodule at `vendor/vs-solutionpersistence/`; wrapper csproj at `src/Fallout.VisualStudio.SolutionPersistence/` compiles them with the TFMs we need. Assembly name stays `Microsoft.VisualStudio.SolutionPersistence` so type identity is preserved. (Note: in 10.2 the wrapper packed as `IsPackable=false` and caused the restore bug fixed in 10.3.0 above — known issue, fix-forward.) diff --git a/README.md b/README.md index d8c82e46b..3efa64741 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,9 @@ Generated by [Repobeats](https://repobeats.axiom.co). ### Stars over time -[![Star History Chart](https://api.star-history.com/svg?repos=ChrisonSimtian/Fallout&type=Date)](https://star-history.com/#ChrisonSimtian/Fallout&Date) +[![RepoStars](https://repostars.dev/api/embed?repo=Fallout-build%2FFallout&theme=terminal)](https://repostars.dev/?repos=Fallout-build%2FFallout&theme=terminal) -Generated by [star-history.com](https://star-history.com). Auto-updates as new stargazers arrive. +Generated by [repostars.dev](https://www.repostars.dev/). Auto-updates as new stargazers arrive. ## Sponsorship diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 835e50ea9..6a272c419 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -1,37 +1,51 @@ using Fallout.Common.CI.GitHubActions; using Fallout.Components; -// macOS and Windows runs are reserved for post-merge validation on the -// long-lived branches (experimental, main, release/YYYY, support/*). PRs and -// feature-branch pushes get Linux-only for fast, cheap feedback. Cross-platform -// regressions on those branches surface as a red commit — same fail-fast model. +// Cross-platform (macOS/Windows) full Test+Pack is gated to RELEASE INTENT +// (#318/#326): it runs only on a PR into a production branch (release/YYYY, +// support/*) and on a release tag push (v*) — never on routine pushes to +// main/experimental, never on a per-merge basis. On main/experimental "we've +// got our edge": the ubuntu-latest PR gate + the alpha/preview pipelines. +// (workflow_dispatch as a manual cross-platform trigger isn't emitted here — +// the generator only writes workflow_dispatch when it has inputs; GitHub's +// built-in run re-run covers the on-demand case.) +// +// concurrency cancel-in-progress (#322): superseded runs are cancelled rather +// than stacked. Never applied to release.yml (a publish must not be cancelled). [GitHubActions( "macos-latest", GitHubActionsImage.MacOsLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, - OnPushBranches = new[] { ExperimentalBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, + OnPushTags = new[] { "v*" }, + OnPullRequestBranches = new[] { ReleaseBranchPattern, SupportBranchPattern }, + OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] [GitHubActions( "windows-latest", GitHubActionsImage.WindowsLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, - OnPushBranches = new[] { ExperimentalBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, + OnPushTags = new[] { "v*" }, + OnPullRequestBranches = new[] { ReleaseBranchPattern, SupportBranchPattern }, + OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] -// pull_request only — same-repo branches would otherwise fire both push and -// pull_request events on every push, double-running the validation. -// -// CheckoutRef = github.head_ref pins checkout to the PR source branch instead of the merge SHA, -// keeping HEAD attached so GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads -// .git/HEAD via GitRepository.FromLocalDirectory) resolves a non-null branch. +// The Linux PR gate — the only required status check. pull_request only: +// feature-branch pushes run zero CI until a PR is opened against a long-lived +// branch (#327). CheckoutRef = github.head_ref pins checkout to the PR source +// branch instead of the merge SHA, keeping HEAD attached so +// GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads .git/HEAD +// via GitRepository.FromLocalDirectory) resolves a non-null branch. [GitHubActions( "ubuntu-latest", GitHubActionsImage.UbuntuLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, CheckoutRef = "${{ github.head_ref }}", // Trigger for PRs targeting experimental, main, or any release/YYYY / support/* // branch — all are long-lived and protected; all require the ubuntu-latest check. diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md index b85d0e49f..646e2f979 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -12,6 +12,18 @@ Three groups: conventions to respect, things never to do, and the tool-wrapper r - **No per-file license headers.** The MIT notice lives in [`LICENSE`](../../LICENSE) at the repo root, and NuGet packages declare MIT via `PackageLicenseExpression`. Per-file headers were stripped in v11 (one source of truth + the header URL would have rotted on the repo-org transfer). Vendored third-party code keeps its own copyright headers — don't touch those (e.g. files under `src/Persistence/Fallout.Persistence.Solution/` retain Microsoft's MIT notice). - **`[Experimental]` for opt-in unstable public APIs.** Not-yet-stable public surface is marked with `[Experimental("FALLOUT0xx")]` rather than held back or shipped silently. See [the `[Experimental]` convention](#experimental-for-opt-in-unstable-apis) below and the [diagnostic-ID registry](../experimental-apis.md). +## Writing style for issues, PRs, and commits + +Applies to AI tools and humans. Many readers are non-native English speakers — keep it readable. + +- Be short and precise. Lead with the point. +- Prefer bullet points over paragraphs. +- Use plain, simple English. Short sentences, common words. +- Cut filler: no preamble, no hedging, no AI-flavored padding. +- Say what changed and why. Drop the rest. + +Covers GitHub issues, PR titles/bodies/comments, and commit messages. + ## `[Experimental]` for opt-in unstable APIs Per [ADR-0004 §5](../adr/0004-calendar-versioning-and-dual-pace-channels.md), public APIs that aren't ready to commit to a stability guarantee are marked with [`System.Diagnostics.CodeAnalysis.ExperimentalAttribute`](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute) instead of being held back or shipped silently. The attribute ships in the .NET 8+ BCL — **no package reference needed** (the repo targets .NET 10). @@ -34,9 +46,25 @@ public sealed class NewPluginHost - **Channel discipline differs.** On the `experimental` (alpha) / `main` (preview) test lanes, churn is expected and the attribute is a courtesy. On a `release/YYYY` **production line**, any risky-but-shipped public surface **must** wear `[Experimental]` — that contract is what keeps the stable line trustworthy while still carrying new work. - **Don't apply it speculatively.** Because the diagnostic is error-by-default, marking an API that's already used internally breaks the build everywhere it's referenced. Only add `[Experimental]` to a genuinely not-yet-stable API, and suppress every internal usage in the same change so the build stays green. +## CI pipeline & triggers + +Shaped by [milestone #18](https://github.com/ChrisonSimtian/Fallout/milestone/18) and the [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md) ladder. Invariants: + +- **Feature branches run zero CI until a PR is opened.** Push triggers list **only** long-lived branches; nothing fires on `feature/*`, `bugfix/*`, etc. until they're PR'd against `experimental`/`main`/`release/*`/`support/*`. Do **not** add a working-branch pattern to any `OnPush*`/`branches:` trigger. +- **The Linux PR gate (`ubuntu-latest`) is the only required check** — runs on PRs to the four long-lived branches. +- **`experimental` (push) → `-alpha`, `main` (push) → `-preview`** to GitHub Packages (`experimental.yml` / `preview.yml`). +- **Cross-platform `windows`/`macos` are gated to release intent** — PR-to-`release/*`/`support/*` or a `v*` tag push only. They do **not** run on `main`/`experimental` pushes. ("On `main` we've got our edge.") +- **`concurrency: cancel-in-progress` on every build workflow except `release.yml`** — never cancel a publish mid-flight. +- **Canonical CI-ignore paths:** `docs/**`, `.assets/**`, `**/*.md` — applied to every PR/push trigger. +- The `ubuntu-latest` / `windows-latest` / `macos-latest` workflows are **generated** from `build/Build.CI.GitHubActions.cs` — edit the attributes + constants there and regenerate (`./build.sh`), never hand-edit the `.yml`. `experimental.yml` / `preview.yml` / `release.yml` are hand-written. +- **Every publishing lane runs `Test` before it publishes** (#324). `experimental.yml`, `preview.yml`, and `release.yml` all run a single `dotnet fallout Test Pack` invocation — NUKE executes it as discrete internal stages (Restore → Compile → Test → Pack) and fails at the breaking stage, so a test failure stops the job before the push step. Don't split a lane into separate `dotnet fallout Compile`/`Test`/`Pack` steps — each invocation re-runs the dependency graph (double-compile); the single invocation *is* the staged build. +- **Caching** (#328): every workflow caches `~/.nuget/packages` + `.fallout/temp`, keyed on `global.json` + `**/*.csproj` + `Directory.Packages.props` (the dependency-affecting set), with a `restore-keys:` prefix fallback for partial restores. There is no `packages.lock.json` to add to the key, and build outputs (`bin`/`obj`) are deliberately **not** cached (stale-artifact correctness risk). + ## What not to do - Don't reintroduce `source/` — production code lives under `src/`, tests under `tests/`. Same for `images/` (now `.assets/`). +- Don't add `main`/`experimental` (or any working-branch pattern) to the **push** triggers of the cross-platform workflows — they're release-intent-gated on purpose (milestone #18 / #318 / #326). +- Don't add `submodules: recursive` to checkouts — there are no submodules (no `.gitmodules`); it's a dead init step. - Don't add `secret`/`default` defaults to tool JSON files (see CONTRIBUTING.md). - Don't introduce a new test framework or assertion library — stay on xUnit + FluentAssertions + Verify. - Don't commit `output/` or any `bin/`/`obj/` directory. diff --git a/docs/agents/release-and-versioning.md b/docs/agents/release-and-versioning.md index 64493bf57..b6ab8dec4 100644 --- a/docs/agents/release-and-versioning.md +++ b/docs/agents/release-and-versioning.md @@ -92,7 +92,7 @@ If you only discover the breaking nature mid-review, apply all relevant steps be | Job | Environment | Fires on tag push? | What ships | Gating | |---|---|---|---|---| | `publish-nuget-org` | `nuget-org` | **No — opt-in only** via `workflow_dispatch` flag | `Fallout.*.nupkg` to https://api.nuget.org/v3/index.json | Workflow flag + approval-gated env | -| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/ChrisonSimtian/index.json | None | +| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/Fallout-build/index.json | None | | `publish-github-releases` | `github-releases` | Yes | All `*.nupkg` attached to a GitHub Release on the tag, auto-generated notes | None | ### Test lanes (from `experimental` and `main`) diff --git a/docs/migration/from-nuke.md b/docs/migration/from-nuke.md index 1332f6d86..f1eba5a94 100644 --- a/docs/migration/from-nuke.md +++ b/docs/migration/from-nuke.md @@ -82,7 +82,7 @@ The shim ships on **GitHub Packages** (not nuget.org — that namespace belongs - + diff --git a/src/Fallout.Build.Shared/CompletionUtility.cs b/src/Fallout.Build.Shared/CompletionUtility.cs index 4c8d5c151..0f0d615e5 100644 --- a/src/Fallout.Build.Shared/CompletionUtility.cs +++ b/src/Fallout.Build.Shared/CompletionUtility.cs @@ -64,7 +64,8 @@ string[] GetValues(JsonProperty property) } else if (property.Value.TryGetProperty("$ref", out var refProperty)) { - var definition = definitions.GetValueOrDefault(refProperty.GetString().NotNull().Split('/').LastOrDefault()); + var definitionKey = refProperty.GetString().NotNull().Split('/').LastOrDefault(); + definitions.TryGetValue(definitionKey, out var definition); return GetValues(definition); } @@ -106,7 +107,8 @@ void AddTargetsOrValues(string parameter) .TakeUntil(ArgumentParser.IsArgument) .Select(ArgumentParser.GetArgumentMemberName); - var items = completionItems.GetValueOrDefault(parameter)?.Except(passedItems, StringComparer.OrdinalIgnoreCase) ?? + var items = (completionItems.TryGetValue(parameter, out var parameterItems) ? parameterItems : null) + ?.Except(passedItems, StringComparer.OrdinalIgnoreCase) ?? new string[0]; if (parameter.EqualsOrdinalIgnoreCase(Constants.InvokedTargetsParameterName)) diff --git a/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs b/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs index a9d32edbc..be0cf3520 100644 --- a/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs +++ b/src/Fallout.Common/CI/AppVeyor/AppVeyor.cs @@ -34,7 +34,7 @@ public partial class AppVeyor : Host, IBuildServer internal static bool IsRunningAppVeyor => EnvironmentInfo.HasVariable("APPVEYOR"); - private readonly Lazy _cli = Lazy.Create(() => IsRunningAppVeyor ? ToolResolver.GetEnvironmentOrPathTool("appveyor") : null); + private readonly Lazy _cli = new(() => IsRunningAppVeyor ? ToolResolver.GetEnvironmentOrPathTool("appveyor") : null); private int _messageCount; internal AppVeyor() diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs index 0e76cf618..f926b1efa 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActions.cs @@ -29,12 +29,12 @@ public partial class GitHubActions : Host, IBuildServer internal GitHubActions() { - _eventContext = Lazy.Create(() => + _eventContext = new(() => { var content = File.ReadAllText(EventPath); return JsonNode.Parse(content)?.AsObject() ?? new JsonObject(); }); - _httpClient = Lazy.Create(() => + _httpClient = new(() => { var base64Auth = Convert.ToBase64String(Encoding.ASCII.GetBytes($":{Token.NotNull()}")); @@ -44,7 +44,7 @@ internal GitHubActions() client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64Auth); return client; }); - _jobId = Lazy.Create(GetJobId); + _jobId = new(GetJobId); } string IBuildServer.Branch => Ref; diff --git a/src/Fallout.Common/CI/TeamCity/TeamCity.cs b/src/Fallout.Common/CI/TeamCity/TeamCity.cs index b12931127..80882f606 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCity.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCity.cs @@ -73,10 +73,10 @@ internal TeamCity(Action messageSink) { _messageSink = messageSink ?? Console.WriteLine; - _systemProperties = Lazy.Create(() => ParseDictionary(EnvironmentInfo.GetVariable("TEAMCITY_BUILD_PROPERTIES_FILE"))); - _configurationProperties = Lazy.Create(() => ParseDictionary(SystemProperties?["teamcity.configuration.properties.file"])); - _runnerProperties = Lazy.Create(() => ParseDictionary(SystemProperties?["teamcity.runner.properties.file"])); - _recentlyFailedTests = Lazy.Create(() => + _systemProperties = new(() => ParseDictionary(EnvironmentInfo.GetVariable("TEAMCITY_BUILD_PROPERTIES_FILE"))); + _configurationProperties = new(() => ParseDictionary(SystemProperties?["teamcity.configuration.properties.file"])); + _runnerProperties = new(() => ParseDictionary(SystemProperties?["teamcity.runner.properties.file"])); + _recentlyFailedTests = new(() => { var file = (AbsolutePath) SystemProperties?["teamcity.tests.recentlyFailedTests.file"]; return file.FileExists() @@ -104,16 +104,19 @@ internal TeamCity(Action messageSink) public string AuthPassword => SystemProperties["teamcity.auth.password"]; public string ProjectId => ConfigurationProperties?["teamcity.project.id"]; public long BuildId => long.Parse(ConfigurationProperties?["teamcity.build.id"] ?? 0.ToString()); - public bool IsBuildPersonal => bool.Parse(SystemProperties?.GetValueOrDefault("build.is.personal") ?? bool.FalseString); - public bool IsPullRequest => ConfigurationProperties?.GetValueOrDefault("teamcity.pullRequest.number") != null; + public bool IsBuildPersonal => bool.Parse(GetConfigurationValue(SystemProperties, "build.is.personal") ?? bool.FalseString); + public bool IsPullRequest => GetConfigurationValue(ConfigurationProperties, "teamcity.pullRequest.number") != null; public long? PullRequestNumber => IsPullRequest ? long.Parse(ConfigurationProperties["teamcity.pullRequest.number"]) : null; public string PullRequestSourceBranch => IsPullRequest ? ConfigurationProperties["teamcity.pullRequest.source.branch"] : null; public string PullRequestTargetBranch => IsPullRequest ? ConfigurationProperties["teamcity.pullRequest.target.branch"] : null; public string PullRequestTitle => IsPullRequest ? ConfigurationProperties["teamcity.pullRequest.title"] : null; - [NoConvert] public string BranchName => ConfigurationProperties?.GetValueOrDefault("teamcity.build.branch") + [NoConvert] public string BranchName => GetConfigurationValue(ConfigurationProperties, "teamcity.build.branch") .NotNull("Configuration property 'teamcity.build.branch' is null. See https://youtrack.jetbrains.com/issue/TW-62888."); + private static string GetConfigurationValue(IReadOnlyDictionary properties, string key) + => properties != null && properties.TryGetValue(key, out var value) ? value : null; + public void DisableServiceMessages() { Write("disableServiceMessages"); diff --git a/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs b/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs index 04453628b..e72120274 100644 --- a/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs +++ b/src/Fallout.Common/CI/TeamCity/TeamCityAttribute.cs @@ -254,7 +254,7 @@ protected virtual IEnumerable GetSecretParameters() { Type = TeamCityParameterType.Password, Name = x, - DefaultValue = guids.GetValueOrDefault(x), + DefaultValue = guids.TryGetValue(x, out var guid) ? guid : null, Display = TeamCityParameterDisplay.Hidden }); } diff --git a/src/Fallout.Utilities/Collections/Dictionary.GetOrDefault.cs b/src/Fallout.Utilities/Collections/Dictionary.GetOrDefault.cs deleted file mode 100644 index 82d545718..000000000 --- a/src/Fallout.Utilities/Collections/Dictionary.GetOrDefault.cs +++ /dev/null @@ -1,22 +0,0 @@ -#if NETSTANDARD2_0 - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace Fallout.Common.Utilities.Collections; - -[DebuggerStepThrough] -[DebuggerNonUserCode] -public static partial class DictionaryExtensions -{ - internal static TValue GetValueOrDefault( - this IReadOnlyDictionary dictionary, - TKey key, - TValue defaultValue = default) - { - return dictionary.TryGetValue(key, out var value) ? value : defaultValue; - } -} -#endif diff --git a/src/Fallout.Utilities/Collections/LookupTable.cs b/src/Fallout.Utilities/Collections/LookupTable.cs index 49e0be619..48e3ade6c 100644 --- a/src/Fallout.Utilities/Collections/LookupTable.cs +++ b/src/Fallout.Utilities/Collections/LookupTable.cs @@ -39,13 +39,13 @@ public IEnumerable this[TKey key] public void Add(TKey key, TValue value) { - var list = (_dictionary[key] = _dictionary.GetValueOrDefault(key, new List())).NotNull(); + var list = (_dictionary[key] = _dictionary.TryGetValue(key, out var existing) ? existing : new List()).NotNull(); list.Add(value); } public void AddRange(TKey key, IEnumerable values) { - var list = (_dictionary[key] = _dictionary.GetValueOrDefault(key, new List())).NotNull(); + var list = (_dictionary[key] = _dictionary.TryGetValue(key, out var existing) ? existing : new List()).NotNull(); foreach (var value in values) list.Add(value); } @@ -57,7 +57,8 @@ public void Remove(TKey key) public void Remove(TKey key, TValue value) { - _dictionary.GetValueOrDefault(key)?.Remove(value); + if (_dictionary.TryGetValue(key, out var list)) + list?.Remove(value); } public void Clear() diff --git a/src/Fallout.Utilities/Lazy.cs b/src/Fallout.Utilities/Lazy.cs index f5f69da89..137de2f3e 100644 --- a/src/Fallout.Utilities/Lazy.cs +++ b/src/Fallout.Utilities/Lazy.cs @@ -11,6 +11,7 @@ public static class Lazy /// /// Creates a from a delegate. /// + [Obsolete("Use 'new Lazy(provider)' directly. This forwarder will be removed in a future release.")] public static Lazy Create(Func provider) { return new Lazy(provider); diff --git a/src/Fallout.Utilities/Reflection/ReflectionUtility.cs b/src/Fallout.Utilities/Reflection/ReflectionUtility.cs index 46ddb1b55..d90b50f7e 100644 --- a/src/Fallout.Utilities/Reflection/ReflectionUtility.cs +++ b/src/Fallout.Utilities/Reflection/ReflectionUtility.cs @@ -179,7 +179,7 @@ public static IEnumerable GetAllMembers( { var memberName = interfaceMembers.Key; var memberType = interfaceMembers.First().MemberType; - var classMember = classMembers.GetValueOrDefault(memberName); + var classMember = classMembers.TryGetValue(memberName, out var member) ? member : null; if (filterQuasiOverridden && classMember == null && interfaceMembers.Count() > 1) { diff --git a/src/Shims/Nuke.Build/README.md b/src/Shims/Nuke.Build/README.md index c4d6797d8..69f5a348b 100644 --- a/src/Shims/Nuke.Build/README.md +++ b/src/Shims/Nuke.Build/README.md @@ -11,7 +11,7 @@ Coverage and limitations are the same as the `Nuke.Common` shim — see [`../Nuk Add this fork's GitHub Packages feed to your `nuget.config`: ```xml - + ``` Then bump your `Nuke.Build` package reference to the latest 10.3.x or later. diff --git a/src/Shims/Nuke.Common/README.md b/src/Shims/Nuke.Common/README.md index 3d7a48380..6d515d5c7 100644 --- a/src/Shims/Nuke.Common/README.md +++ b/src/Shims/Nuke.Common/README.md @@ -45,14 +45,14 @@ Until automated dual-publish lands (tracked in [#69](https://github.com/ChrisonS dotnet pack src/Shims/Nuke.Common/Nuke.Common.csproj -c Release dotnet nuget push **/Nuke.Common.*.nupkg ` --api-key $env:GITHUB_TOKEN ` - --source https://nuget.pkg.github.com/ChrisonSimtian/index.json + --source https://nuget.pkg.github.com/Fallout-build/index.json ``` ## Migration path For projects that just want their `class Build : NukeBuild { ... }` to compile against our framework: -1. Add `https://nuget.pkg.github.com/ChrisonSimtian/index.json` as a NuGet source (`nuget.config`). +1. Add `https://nuget.pkg.github.com/Fallout-build/index.json` as a NuGet source (`nuget.config`). 2. Bump the `Nuke.Common` package version to the latest published here. 3. Build. If the shim covers what you use, you're done. Otherwise: 4. Run `fallout-migrate` to rewrite the remaining references. diff --git a/src/Shims/Nuke.Components/README.md b/src/Shims/Nuke.Components/README.md index afe0142e7..65c2f49d2 100644 --- a/src/Shims/Nuke.Components/README.md +++ b/src/Shims/Nuke.Components/README.md @@ -11,7 +11,7 @@ Limitations are the same as the `Nuke.Common` shim — see [`../Nuke.Common/READ Add this fork's GitHub Packages feed to your `nuget.config`: ```xml - + ``` Then bump your `Nuke.Components` package reference to the latest 10.3.x or later.