diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f16b259..c56a5a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,8 @@ jobs: sh -n tests/manager.sh sh -n tests/nuget-isolation.sh sh -n tests/nuget-isolation.test.sh + sh -n tests/update-dir.test.sh + sh -n tests/update-self.sh - name: Pack the kit shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index e45cb0a..b74be97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,14 @@ All notable changes to this project are documented here. The format follows [Kee ### Breaking changes +- The kit's default folder is `.mskit/` instead of `.toolkit/`: a fresh install writes `.mskit/`, and `Directory.Build.props` / `.targets` import `.mskit/msbuild/init.props` / `init.targets`. `update.sh --kit-dir NAME` (`update.ps1 -KitDir NAME`) picks another folder name, and `kit.json` records it as `kitDir`, so later runs find it. A run that finds an older `.toolkit/` install moves it to the kit folder (`git mv` when it is tracked) and rewrites `.toolkit` in `Directory.Build.props`, `Directory.Build.targets`, `.gitignore` and `.github/dependabot.yml`; it stops when both folders exist, and `--dry-run` shows the move and changes nothing. **From 0.2.1 the upgrade takes two runs:** 0.2.1's `update.sh` installs the new version into `.toolkit/` and copies the new script, which moves the folder when you run it again (`sh .toolkit/update.sh`). Keep `.toolkit/` with `--kit-dir .toolkit`. The release zip keeps its `.toolkit/` layout so that 0.2.1's script can install it. See [Moving from `.toolkit/`](./docs/install-and-update.md#moving-from-toolkit). - Warning and error codes drop the underscore after the prefix: `MSKITVER006`, `MSKITPKG015`, and so on, with the same family and number. `NoWarn`, `WarningsAsErrors`, `WarningsNotAsErrors` and `MSKit_SkipPackageChecks` entries that name a code the old way no longer match: rename them. Each section of the [code reference](./docs/reference/codes.md) names the code's former spelling. ### Added - Every warning and error links to its section of the [code reference](./docs/reference/codes.md) (`HelpLink`, shown by the terminal logger and IDEs). `MSKit_CodesHelpBaseUrl` points the links at another copy of the page. - A machine-readable catalog of the codes: every part declares each code it reports as a `BuildDiagnosticDescriptor` item (`Title`, `MessageFormat`, `Description`, `Category`, `DefaultSeverity`, `HelpLink`) in its own `diagnostic.descriptors.props`, so a tool can read them with `dotnet msbuild -getItem:BuildDiagnosticDescriptor` and tell the owning part from the item's `DefiningProjectFullPath`. Each section of the [code reference](./docs/reference/codes.md) now opens with the code's title. See the [diagnostic catalog](./docs/reference/diagnostic-catalog.md). +- `MSKit_KitDirName`: the kit folder's name. The `MSKITROSLYN001`..`003` messages name the update scripts with it, so they show the folder the kit is installed in. - `MSKit_DefaultPackageIconUrl`: when the kit packs its own icon, this URL is also written as `PackageIconUrl`, so clients that predate embedded icons show it; nuget.org keeps showing the embedded one. Empty by default; an owner sets it in its owner layer. `MSKITPKG008` now reports only a `PackageIconUrl` the project sets itself. See [docs/packaging.md](./docs/packaging.md#package-metadata). - `manager/`: the first build of `mskit-manager`, the `DragoAnt.MSBuildKit.Manager` .NET tool (`net8.0`, `net10.0`, `RollForward=Major`) that will install, update and migrate the kit. This build has one command, `status [--json]`, which prints the tool version; the logo goes to stderr, only on a terminal and never with `--no-logo`, so `--json` output always parses. Each run writes a log under `/mskit-manager/logs/`, named after the command, newest 20 kept. Not published yet. - `manager/`: the package model behind the coming `install` — readers for `mskit.package.json`, the trunk's `catalog.json` and a company or team `filter.json`; the part selection (core defaults, then the company filter, the team filter and the repository's `kit.json`, a later layer winning per part, with required and company-locked parts refused and `requires` closed); `kit.json` schema 2, which also reads the schema 1 that `update.sh` writes and keeps a `parts` array spread over several lines; and the install manifest. No command uses them yet. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23edf4e..6257984 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,9 +4,9 @@ Issues and pull requests are welcome. ## Layout -- `kit/.toolkit/` is exactly what a release zip contains and what `update.sh` / `update.ps1` install: `msbuild/` (one folder per part plus the `init.props` / `init.targets` entry points), `res/`, `kit.parts` and the update scripts. -- `samples/MinimalLibrary` is a consumer with a library and a test project; its `.toolkit/` is installed by the self-test and not committed. -- `tests/run.sh` is the self-test; `tests/fixtures/PackageChecks` breaks every package rule on purpose. `tests/manager.sh` builds, tests, packs and installs the tool. +- `kit/.toolkit/` is exactly what a release zip contains (under `.toolkit/`, the layout the 0.2.1 updater reads) and what `update.sh` / `update.ps1` install into a consumer's kit folder, `.mskit/` by default: `msbuild/` (one folder per part plus the `init.props` / `init.targets` entry points), `res/`, `kit.parts` and the update scripts. +- `samples/MinimalLibrary` is a consumer with a library and a test project; its `.mskit/` is installed by the self-test, starting from a v0.2.1 `.toolkit/` install that the new `update.sh` moves, and not committed. `tests/fixtures/TfmConstants` keeps its kit in `.toolkit/` through `--kit-dir`, so a non-default folder builds too. +- `tests/run.sh` is the self-test; `tests/fixtures/PackageChecks` breaks every package rule on purpose. `tests/update-dir.test.sh` checks the kit folder of both update scripts (fresh install, `--kit-dir`, the move from `.toolkit/`, the two-run upgrade from the real v0.2.1 scripts) without `dotnet`. `tests/manager.sh` builds, tests, packs and installs the tool. - `tools/pack-kit.sh` builds the release zip and its SHA-256. - `manager/` is the `mskit-manager` .NET tool: its own solution, central package versions and `global.json`, built against `manager/.toolkit/` — the working-tree kit installed with `sh kit/.toolkit/update.sh --source kit --root manager` and committed. - `docs/` holds the user documentation, read in the order of [docs/README.md](./docs/README.md); `docs/reference/` lists every property and code. `tools/docs-check.sh` keeps them honest against the kit. @@ -55,7 +55,7 @@ Both scripts restore into folders of their own and share third-party packages be - `sh tools/docs-check.sh` fails on a property, item or code without its reference entry, on a name the docs mention that the kit lacks, and on a broken relative link; `--list properties|items|codes` prints the kit's inventory with the file and line of each. - The README stays short: key features, install, links. Detail goes to the topic page in `docs/`. - A new part needs a line in `kit/.toolkit/kit.parts` and its `init.props` / `init.targets` imports in the entry points. -- Keep scripts POSIX `sh` and PowerShell 7 equivalent; `update.sh` and `update.ps1` must produce the same `.toolkit/`. +- Keep scripts POSIX `sh` and PowerShell 7 equivalent; `update.sh` and `update.ps1` must produce the same kit folder. - A test that restores a package built in this repository runs from `tests/run.sh` or `tests/manager.sh`, which keep it out of the machine's global-packages folder and out of the shared folder. - Add a line to `CHANGELOG.md` under `Unreleased`. diff --git a/README.md b/README.md index 15fc5a1..0455bde 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Shared MSBuild settings for .NET repositories that publish NuGet packages: nuget - **Versions from release tags.** Tag `v1.4.0` and the packages are `1.4.0`; branch builds are `1.4.0-ci.`, pull requests `1.4.0-pr..`, local builds `9999.0.0`. [Versioning](./docs/versioning.md) - **Tests on Microsoft.Testing.Platform v2.** Projects named `*.Tests` become [xUnit v3](https://xunit.net/docs/getting-started/v3/microsoft-testing-platform) test projects with coverage, TRX and JUnit reports, an assertion library and [NSubstitute](https://nsubstitute.github.io/). [Testing](./docs/testing.md) - **Build checks that keep a repository consistent.** Target frameworks declared once, banned packages, prerelease dependencies on a stable branch, `TreatWarningsAsErrors` drift. [Build defaults and checks](./docs/build.md) -- **Plain files, one update script.** The kit lives in your repository's `.toolkit/` folder, so every change shows up in a pull request; `update.sh` / `update.ps1` install a release after checking its SHA-256. It is not a NuGet package or an MSBuild SDK: nothing is restored at build time. [Install and update](./docs/install-and-update.md) +- **Plain files, one update script.** The kit lives in your repository's `.mskit/` folder, so every change shows up in a pull request; `update.sh` / `update.ps1` install a release after checking its SHA-256. It is not a NuGet package or an MSBuild SDK: nothing is restored at build time. [Install and update](./docs/install-and-update.md) ## Install @@ -29,7 +29,7 @@ Invoke-WebRequest https://github.com/DragoAnt/MSBuildKit/releases/latest/downloa pwsh ./update.ps1; Remove-Item update.ps1 ``` -Then declare the next version in `Directory.Version.props` and commit `.toolkit/`. [Getting started](./docs/getting-started.md) walks through the first build, test and pack; [`samples/MinimalLibrary`](./samples/MinimalLibrary) is a complete repository with every check passing. +Then declare the next version in `Directory.Version.props` and commit `.mskit/`. [Getting started](./docs/getting-started.md) walks through the first build, test and pack; [`samples/MinimalLibrary`](./samples/MinimalLibrary) is a complete repository with every check passing. ## Documentation diff --git a/docs/build.md b/docs/build.md index b5ffa0a..c9a26eb 100644 --- a/docs/build.md +++ b/docs/build.md @@ -35,10 +35,11 @@ Set `MSKit_IsDevEnv` yourself to build "as CI" locally (`-p:MSKit_IsDevEnv=False | Property | Value | | --- | --- | -| `MSKit_SlnFileDirectory` | the folder of the `.slnx` being built (`SlnxFilePath`), else the folder that holds `.toolkit/`; ends with `/` | +| `MSKit_SlnFileDirectory` | the folder of the `.slnx` being built (`SlnxFilePath`), else the folder that holds `.mskit/`; ends with `/` | | `MSKit_SlnFileName` | the solution name without extension, when building a `.slnx` | | `MSKit_GitRoot` | the nearest folder above the project with a `.git` (worktrees included) | -| `MSKit_ToolkitDir`, `MSKit_ToolkitMSBuildDir` | `.toolkit/` and `.toolkit/msbuild/`, absolute, ending with `/` | +| `MSKit_ToolkitDir`, `MSKit_ToolkitMSBuildDir` | the kit folder (`.mskit/` by default) and its `msbuild/`, absolute, ending with `/` | +| `MSKit_KitDirName` | the kit folder's name, such as `.mskit` | | `MSKit_ProjectObjDir` | the project's `obj` folder, absolute | | `MSKit_Branch` | on GitHub Actions `GITHUB_HEAD_REF` (pull requests) or `GITHUB_REF_NAME`; else read from `.git/HEAD`; else `unknown-branch` | | `MSKit_IsStableBranch` | `true` when `MSKit_Branch` matches `MSKit_IsStableBranchRegex` (case-insensitive): the owner layer's `^(main\|release/.+)$`, the kit's own default `^(main\|master\|release/.+)$` | diff --git a/docs/customizing.md b/docs/customizing.md index 839e619..47223d6 100644 --- a/docs/customizing.md +++ b/docs/customizing.md @@ -1,6 +1,6 @@ # Customizing -Three ways to change what the kit does without editing `.toolkit/msbuild/` (an update overwrites it): set a property, add one of the extension files the kit looks for, or hook your own file in around the kit. To use the kit for another owner, change the owner layer. +Three ways to change what the kit does without editing `.mskit/msbuild/` (an update overwrites it): set a property, add one of the extension files the kit looks for, or hook your own file in around the kit. To use the kit for another owner, change the owner layer. ## Overriding a default @@ -15,13 +15,13 @@ Almost every default is written as ``, so the first va ## The owner layer -`.toolkit/msbuild/init.company.props` is loaded before every part and holds the owner's defaults; `init.company.targets` is its targets-phase twin (empty for DragoAnt). +`.mskit/msbuild/init.company.props` is loaded before every part and holds the owner's defaults; `init.company.targets` is its targets-phase twin (empty for DragoAnt). | Setting | DragoAnt value | Note | | --- | --- | --- | | `ManufacturerName`, `FullManufacturerName` | `DragoAnt` | **unconditional**: set them below the kit import to change them; they feed `Authors`, `Company`, `Copyright` | | `PackageLicenseExpression` | `MIT`, unless `PackageLicenseFile` is set | | -| `PackageIconPath` | `.toolkit/res/package.icon.png` | | +| `PackageIconPath` | `.mskit/res/package.icon.png` | | | `MSKit_DefaultPackageIconUrl` | not set | another owner sets it here to a hosted copy of its icon, so older clients show it too ([Packaging](./packaging.md#package-metadata)) | | `MSKit_PrereleasePackagePrefix` | `DragoAnt.` | [prerelease check](./build.md#reference-checks) | | `MSKit_IsStableBranchRegex` | `^(main\|release/.+)$` | | @@ -61,7 +61,7 @@ Name your own files in `Directory.Build.props` above the kit import; each is imp $(MSBuildThisFileDirectory)build/after-kit.targets - + ``` `MSKit_BeforeInitTargets` and `MSKit_AfterInitTargets` are read in the targets phase, so they can also be set in the csproj. diff --git a/docs/getting-started.md b/docs/getting-started.md index b3feba8..c70c4dc 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -20,22 +20,22 @@ The script downloads the latest release, checks its SHA-256, and writes: | Path | What | | --- | --- | -| `.toolkit/msbuild/` | the kit: one folder per [part](./parts.md) and the `init.props` / `init.targets` entry points | -| `.toolkit/res/package.icon.png` | the package icon | -| `.toolkit/kit.json` | the pinned version, its SHA-256 and your optional parts | -| `.toolkit/kit.parts`, `.toolkit/update.sh`, `.toolkit/update.ps1` | the part list and the update scripts | +| `.mskit/msbuild/` | the kit: one folder per [part](./parts.md) and the `init.props` / `init.targets` entry points | +| `.mskit/res/package.icon.png` | the package icon | +| `.mskit/kit.json` | the pinned version, its SHA-256 and your optional parts | +| `.mskit/kit.parts`, `.mskit/update.sh`, `.mskit/update.ps1` | the part list and the update scripts | | `Directory.Build.props`, `Directory.Build.targets` | only when missing: one import each | If the two `Directory.Build.*` files already exist, the script prints the line each needs instead of editing them: ```xml - + - + ``` -Commit `.toolkit/` with the rest of the repository: the kit is reviewed like any other change. [Install and update](./install-and-update.md) has every option. +Commit `.mskit/` with the rest of the repository: the kit is reviewed like any other change. To use another folder name, pass `--kit-dir ` (`-KitDir`); an older `.toolkit/` install moves on its own. [Install and update](./install-and-update.md) has every option. ## 2 Declare the next version diff --git a/docs/install-and-update.md b/docs/install-and-update.md index 024f354..8e6aa0f 100644 --- a/docs/install-and-update.md +++ b/docs/install-and-update.md @@ -1,23 +1,24 @@ # Install and update -The kit is installed and updated by one script, `update.sh` (POSIX `sh`) or its twin `update.ps1` ([PowerShell 7](https://learn.microsoft.com/powershell/scripting/install/installing-powershell)); both produce the same `.toolkit/`. A copy of each lives in `.toolkit/` after the first install. +The kit is installed and updated by one script, `update.sh` (POSIX `sh`) or its twin `update.ps1` ([PowerShell 7](https://learn.microsoft.com/powershell/scripting/install/installing-powershell)); both produce the same kit folder, `.mskit/` unless you choose another name. A copy of each lives in the kit folder after the first install. ```sh -sh .toolkit/update.sh # reinstall the version pinned in .toolkit/kit.json -sh .toolkit/update.sh --version 0.2.1 # move to another version -sh .toolkit/update.sh --add EF --dry-run # preview adding an optional part -sh .toolkit/update.sh --remove EF # uninstall it again +sh .mskit/update.sh # reinstall the version pinned in .mskit/kit.json +sh .mskit/update.sh --version 0.2.1 # move to another version +sh .mskit/update.sh --add EF --dry-run # preview adding an optional part +sh .mskit/update.sh --remove EF # uninstall it again ``` ## Options | `update.sh` | `update.ps1` | Meaning | | --- | --- | --- | -| `--version X.Y.Z` | `-Version` | The kit version to install; a leading `v` is ignored. Default: the version in `.toolkit/kit.json`, else the latest release | +| `--version X.Y.Z` | `-Version` | The kit version to install; a leading `v` is ignored. Default: the version in `.mskit/kit.json`, else the latest release | | `--add PART` | `-Add` | Install an optional part; repeat for several. Parts it requires come with it | | `--remove PART` | `-Remove` | Uninstall an optional part; a default part cannot be removed | -| `--dry-run` | `-DryRun` | List the files under `.toolkit/msbuild/` that would be added, removed or changed, and change nothing | -| `--source DIR\|ZIP` | `-Source` | Install a local build of the kit: a folder holding `.toolkit/msbuild/init.props`, or a release zip | +| `--dry-run` | `-DryRun` | List the files under `.mskit/msbuild/` that would be added, removed or changed, and the folder move and rewrites of a [migration](#moving-from-toolkit), and change nothing | +| `--kit-dir NAME` | `-KitDir` | The kit folder, a folder name in the repository root. Default: the folder whose `kit.json` records its own name as `kitDir`, else `.mskit`. Two folders claiming the kit stop the run | +| `--source DIR\|ZIP` | `-Source` | Install a local build of the kit: a folder holding `.toolkit/msbuild/init.props` (the release zip's layout), or a release zip | | `--sha256 HEX` | `-Sha256` | The SHA-256 the release zip (or a `--source` zip) must have | | `--repo OWNER/NAME` | `-Repo` | The GitHub repository to download from. Default: the one in `kit.json`, else `DragoAnt/MSBuildKit` | | `--root DIR` | `-Root` | The repository root. Default: the current folder | @@ -28,10 +29,11 @@ sh .toolkit/update.sh --remove EF # uninstall it again 1. It downloads `msbuildkit-.zip` and its `.sha256` from the release, and stops unless the zip matches the published hash. When the version is the one already pinned, the zip must also match the `sha256` in `kit.json`, so a release replaced after you installed it is refused. 2. It selects the parts: every default part, the optional parts recorded in `kit.json`, plus `--add`, minus `--remove`, plus everything those require ([Parts](./parts.md)). -3. It replaces `.toolkit/msbuild/` with the selected parts, copies `.toolkit/res/`, `.toolkit/kit.parts` and the two update scripts, and rewrites `kit.json`. -4. It creates `Directory.Build.props` and `Directory.Build.targets` when they are missing; when they exist without the kit import, it prints the line to add. +3. When the repository still has an older `.toolkit/` install, it [moves it](#moving-from-toolkit) to the kit folder first. +4. It replaces `.mskit/msbuild/` with the selected parts, copies `.mskit/res/`, `.mskit/kit.parts` and the two update scripts, and rewrites `kit.json`. +5. It creates `Directory.Build.props` and `Directory.Build.targets` when they are missing; when they exist without the kit import, it prints the line to add. -`.toolkit/.local/` and every other file of yours are left alone. `--dry-run` reports step 3 for `.toolkit/msbuild/` only and writes nothing. +`.mskit/.local/` and every other file of yours are left alone. `--dry-run` reports steps 3 and 4 and writes nothing. ## `kit.json` @@ -40,11 +42,12 @@ sh .toolkit/update.sh --remove EF # uninstall it again "repository": "DragoAnt/MSBuildKit", "version": "0.2.1", "sha256": "", - "parts": ["PackageAsProj", "EF"] + "parts": ["PackageAsProj", "EF"], + "kitDir": ".mskit" } ``` -Commit it with `.toolkit/`. `parts` lists only the optional parts you chose; default parts are always installed. A `--source` install records the local build's version (or `0.0.0-local`) and the zip's hash, empty for a folder. +Commit it with `.mskit/`. `parts` lists only the optional parts you chose; default parts are always installed. `kitDir` is the folder's own name, which is how a later run without `--kit-dir` finds a folder you named yourself. A `--source` install records the local build's version (or `0.0.0-local`) and the zip's hash, empty for a folder. ## Installing a local build @@ -56,6 +59,25 @@ sh /kit/.toolkit/update.sh --source /kit --root ` in the kit repository builds the same zip a release publishes ([CONTRIBUTING.md](../CONTRIBUTING.md)). +## Moving from `.toolkit/` + +Releases up to 0.2.1 installed the kit into `.toolkit/`. When a run finds `.toolkit/` holding a kit whose `kit.json` has no `kitDir`, and the kit folder does not exist yet, it: + +1. moves `.toolkit/` to the kit folder, `.local/` templates included: with `git mv` when `.toolkit/` is tracked, so the rename is staged, else with a plain move; +2. rewrites `.toolkit` to the new name wherever it is a whole path segment in `Directory.Build.props`, `Directory.Build.targets`, `.gitignore` and `.github/dependabot.yml` (`/` and `\` spellings alike; a name such as `my.toolkit` is left alone); +3. names every move and rewrite in its output, then installs as usual. + +When both `.toolkit/` and the kit folder exist, the run stops and changes nothing: remove the one you do not use. To keep the kit in `.toolkit/`, pass `--kit-dir .toolkit` once; `kit.json` remembers it. Preview the move with `--dry-run`. + +**From 0.2.1 it takes two runs.** The installed 0.2.1 `update.sh` knows only `.toolkit/`: it installs the new version there and copies in the new `update.sh` without running it. Run the new script once more and it moves the kit: + +```sh +sh .toolkit/update.sh --version # 0.2.1's script: installs into .toolkit/, copies the new update.sh +sh .toolkit/update.sh # the new script: moves .toolkit/ to .mskit/ +``` + +`update.ps1` works the same way. Commit after the second run. + ## Updating from 0.2.0 or earlier An update started from 0.2.0 or earlier still runs the old script, which can stop with a `syntax error` after copying the files and before writing `kit.json` when the new `update.sh` differs in length (a CRLF checkout does). Run the same command once more; updates started from 0.2.1 on finish in one run. diff --git a/docs/local-files.md b/docs/local-files.md index 22cc82b..1e2358a 100644 --- a/docs/local-files.md +++ b/docs/local-files.md @@ -6,15 +6,15 @@ Three default parts keep values that must not be committed — connection string | --- | --- | | `%APPDATA%\Microsoft\UserSecrets\` (Windows), `~/.microsoft/usersecrets/` (Linux, macOS) | `MSKit_LocalSecretsBaseDirectory` | | `//` for a project | `LocalSecretsDir` | -| the templates: `.toolkit/.local/` | `MSKit_Templates` | +| the templates: `.mskit/.local/` | `MSKit_Templates` | -`update.sh` never touches `.toolkit/.local/`. Commit the templates with placeholders only — they reach every clone; the real values live only in the per-user folder. +`update.sh` never touches `.mskit/.local/`. Commit the templates with placeholders only — they reach every clone; the real values live only in the per-user folder. ## Locals.Secrets — a project's `secrets.json` A project with a [`UserSecretsId`](https://learn.microsoft.com/aspnet/core/security/app-secrets#enable-secret-storage) gets, on a developer machine: -1. the template `.toolkit/.local/.secrets.json`, created as `{}` when missing; +1. the template `.mskit/.local/.secrets.json`, created as `{}` when missing; 2. `secrets.json` in its `LocalSecretsDir`, copied from the template when missing (your edits there are kept); 3. that file linked into the project as `appsettings.UserSecrets.json` (`UserSecretsLinkNamePrefix` changes the `appsettings` part), so it is one click away in the IDE. @@ -35,7 +35,7 @@ For values MSBuild itself needs (a private feed's key, a signing password), turn ``` -On a developer machine every project then imports `//Directory.Secrets.props` (`MSKit_UseSlnSecretsTargets` does the same for `Directory.Secrets.targets`, in the targets phase), created from `.toolkit/.local/Directory.Secrets.props` when missing; the template is created as an empty `` when it is missing too. A file created during a build is imported from the next one on. +On a developer machine every project then imports `//Directory.Secrets.props` (`MSKit_UseSlnSecretsTargets` does the same for `Directory.Secrets.targets`, in the targets phase), created from `.mskit/.local/Directory.Secrets.props` when missing; the template is created as an empty `` when it is missing too. A file created during a build is imported from the next one on. | Property | Default | Effect | | --- | --- | --- | @@ -53,4 +53,4 @@ A `LocalCompile` item compiles a file that lives in the project's `LocalSecretsD ``` -The project needs a `UserSecretsId`. The file is copied from the template `.toolkit/.local/LocalCompile/LocalSettings.cs` when it is missing; when the template is missing too, the kit writes a stub (`namespace ;`) to it. This runs on every build, CI included, so the template must compile on its own. `LocalCompileTemplateDir` moves the template folder. +The project needs a `UserSecretsId`. The file is copied from the template `.mskit/.local/LocalCompile/LocalSettings.cs` when it is missing; when the template is missing too, the kit writes a stub (`namespace ;`) to it. This runs on every build, CI included, so the template must compile on its own. `LocalCompileTemplateDir` moves the template folder. diff --git a/docs/migrating-from-msbuild-routine.md b/docs/migrating-from-msbuild-routine.md index be3fa6a..c726c4c 100644 --- a/docs/migrating-from-msbuild-routine.md +++ b/docs/migrating-from-msbuild-routine.md @@ -3,7 +3,7 @@ [MSBuild.Routine](https://github.com/DragoAnt/MSBuild.Routine) is the older, submodule-based predecessor of the kit. Moving a repository over: 1. **Remove the submodule:** `git rm .msbuild`, delete `.gitmodules`, and drop `submodules:` from your workflows. -2. **Install the kit** ([Getting started](./getting-started.md)), with `--add PackageAsProj` if you use `Directory.PackageAsProj.targets`, and replace the `.msbuild\shared\init.props` / `init.targets` imports with the `.toolkit/msbuild/` ones. +2. **Install the kit** ([Getting started](./getting-started.md)), with `--add PackageAsProj` if you use `Directory.PackageAsProj.targets`, and replace the `.msbuild\shared\init.props` / `init.targets` imports with the `.mskit/msbuild/` ones. 3. **Drop what is now a default:** `Copyright`, `PackageLicenseExpression`, `RepositoryUrl`, `PackageReleaseNotes`, `TargetFrameworkStrategy`, and the `.msbuild\tfm.constants.props` import. 4. **Remove the versions the kit provides** from `Directory.Packages.props` (`xunit.v3*`, `xunit.runner.visualstudio`, `Microsoft.NET.Test.Sdk`, `Microsoft.Testing.Extensions.CodeCoverage`, `NSubstitute*`, your assertion library, `coverlet.collector`) and the explicit `Microsoft.Testing.Extensions.CodeCoverage` references from test projects; [`MSKITDUP001`](./reference/codes.md#mskitdup001) lists any you missed. To keep FluentAssertions 7, set `MSKit_TestsAssertions=FluentAssertions`. 5. **Add `Directory.Version.props`** with the next `VersionPrefix`, and publish releases with tags such as `v2.0.1` ([Versioning](./versioning.md)). diff --git a/docs/optional-parts.md b/docs/optional-parts.md index 84fa5be..b944d76 100644 --- a/docs/optional-parts.md +++ b/docs/optional-parts.md @@ -1,6 +1,6 @@ # Optional parts -Three parts that are not installed by default. Add one with `sh .toolkit/update.sh --add ` ([Install and update](./install-and-update.md)); the Roslyn parts have [their own page](./roslyn.md). +Three parts that are not installed by default. Add one with `sh .mskit/update.sh --add ` ([Install and update](./install-and-update.md)); the Roslyn parts have [their own page](./roslyn.md). ## PackageAsProj @@ -62,9 +62,9 @@ $dbContextProj = "$PSScriptRoot/src/Acme.Orders.Data/Acme.Orders.Data.csproj" ``` ```sh -pwsh .toolkit/msbuild/DragoAnt.MSBuildKit.EF/scripts/add-migration.ps1 -migrationName AddShippingAddress -pwsh .toolkit/msbuild/DragoAnt.MSBuildKit.EF/scripts/apply-migrations.ps1 -pwsh .toolkit/msbuild/DragoAnt.MSBuildKit.EF/scripts/remove-migration.ps1 +pwsh .mskit/msbuild/DragoAnt.MSBuildKit.EF/scripts/add-migration.ps1 -migrationName AddShippingAddress +pwsh .mskit/msbuild/DragoAnt.MSBuildKit.EF/scripts/apply-migrations.ps1 +pwsh .mskit/msbuild/DragoAnt.MSBuildKit.EF/scripts/remove-migration.ps1 ``` | Script | Runs | diff --git a/docs/package-readme.md b/docs/package-readme.md index 2e4753c..f6e1675 100644 --- a/docs/package-readme.md +++ b/docs/package-readme.md @@ -79,7 +79,7 @@ These stay warnings on CI; skip one with `MSKit_SkipPackageChecks` or `NoWarn` l ## Allowed image hosts -nuget.org shows readme images only from [a fixed list of hosts](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges). The kit ships that list as `.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/nuget.allowed-image-hosts.txt` (copied from the page on 2026-10-05) and refreshes it with kit releases; nothing is downloaded during a build. `raw.githubusercontent.com` and `gitlab.com` are on it, so images from a GitHub or gitlab.com repository render; a self-hosted GitLab is not, and its images raise `MSKITPKG021`. +nuget.org shows readme images only from [a fixed list of hosts](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges). The kit ships that list as `.mskit/msbuild/DragoAnt.MSBuildKit.Packaging/nuget.allowed-image-hosts.txt` (copied from the page on 2026-10-05) and refreshes it with kit releases; nothing is downloaded during a build. `raw.githubusercontent.com` and `gitlab.com` are on it, so images from a GitHub or gitlab.com repository render; a self-hosted GitLab is not, and its images raise `MSKITPKG021`. ## Build cost diff --git a/docs/packaging.md b/docs/packaging.md index 50e1735..42531aa 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -23,7 +23,7 @@ For projects with `IsPackable=True` (the owner layer makes that the default; tes | `PackageLicenseExpression` | `MIT` (owner layer), unless `PackageLicenseFile` is set | [licensing](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#licensing) | | `PackageRequireLicenseAcceptance` | `false` | | | `Authors`, `Copyright` | the owner; `Copyright (c) ` ([Build](./build.md#language-and-product-defaults)) | [copyright](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#copyright) | -| `PackageIcon` | `PackageIconPath` (owner layer: `.toolkit/res/package.icon.png`, 128×128; PNG or JPEG) packed as `icon` (`icon.png`, `icon.jpg`); a `PackageIcon` the project sets is kept. With the kit's icon, `MSKit_DefaultPackageIconUrl` also writes `PackageIconUrl` (nuget.org shows the embedded icon, older clients the URL) | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) | +| `PackageIcon` | `PackageIconPath` (owner layer: `.mskit/res/package.icon.png`, 128×128; PNG or JPEG) packed as `icon` (`icon.png`, `icon.jpg`); a `PackageIcon` the project sets is kept. With the kit's icon, `MSKit_DefaultPackageIconUrl` also writes `PackageIconUrl` (nuget.org shows the embedded icon, older clients the URL) | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) | | `PackageReadmeFile` | generated from `MSKit_PackageReadmeFrom` ([Package readme](./package-readme.md)), else `package.readme.md` next to the csproj, else `README.md` next to it; packed as `readme.md`. `MSKit_PackageReadmeSourcePath` names another file | [README](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#readme) | | `RepositoryType`, `RepositoryUrl`, `PackageProjectUrl` | `git`; `GITHUB_SERVER_URL/GITHUB_REPOSITORY` on GitHub Actions, else the git remote [Source Link](https://learn.microsoft.com/dotnet/standard/library-guidance/sourcelink) reads, without `.git` | [repository](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#repository-type-and-url) | | `PackageReleaseNotes` | on a `https://github.com/` repository the tag's release page on a tag build, else its releases page; on other hosts the releases page the generated readme links (needs `MSKit_PackageReadmeFrom`). `MSKit_DefaultReleaseNotes=False` turns the default off | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) | diff --git a/docs/parts.md b/docs/parts.md index 04fba74..49e37d4 100644 --- a/docs/parts.md +++ b/docs/parts.md @@ -1,6 +1,6 @@ # Parts -The kit is split into parts, one folder each under `.toolkit/msbuild/` (`DragoAnt.MSBuildKit.`; the trunk is `DragoAnt.MSBuildKit`). Default parts are always installed; optional ones are added with `update.sh --add ` and recorded in `kit.json` ([Install and update](./install-and-update.md)). The list lives in `.toolkit/kit.parts`. +The kit is split into parts, one folder each under `.mskit/msbuild/` (`DragoAnt.MSBuildKit.`; the trunk is `DragoAnt.MSBuildKit`). Default parts are always installed; optional ones are added with `update.sh --add ` and recorded in `kit.json` ([Install and update](./install-and-update.md)). The list lives in `.mskit/kit.parts`. | Part | Installed | Requires | What it does | Page | | --- | --- | --- | --- | --- | @@ -23,7 +23,7 @@ The kit is split into parts, one folder each under `.toolkit/msbuild/` (`DragoAn ## Load order -`Directory.Build.props` imports `.toolkit/msbuild/init.props`, which imports each installed part's `init.props` in a fixed order; `Directory.Build.targets` imports `init.targets` the same way. A part that is not installed is skipped. +`Directory.Build.props` imports `.mskit/msbuild/init.props`, which imports each installed part's `init.props` in a fixed order; `Directory.Build.targets` imports `init.targets` the same way. A part that is not installed is skipped. | Phase | Order | | --- | --- | diff --git a/docs/reference/codes.md b/docs/reference/codes.md index a30b3df..fb8a677 100644 --- a/docs/reference/codes.md +++ b/docs/reference/codes.md @@ -278,7 +278,7 @@ Background: [Roslyn components](../roslyn.md). **`Project.CodeAnalyzer` part is not installed** -`MSKITROSLYN001` (formerly `MSKIT_ROSLYN001`) (error) — the name matches the analyzer regex, but the `Project.CodeAnalyzer` part is not installed. `sh .toolkit/update.sh --add Project.CodeAnalyzer`, or rename the project, or set `MSKit_DisableCodeAnalyzerAutoDetect=true` in `Directory.Build.props` above the kit import. +`MSKITROSLYN001` (formerly `MSKIT_ROSLYN001`) (error) — the name matches the analyzer regex, but the `Project.CodeAnalyzer` part is not installed. `sh .mskit/update.sh --add Project.CodeAnalyzer`, or rename the project, or set `MSKit_DisableCodeAnalyzerAutoDetect=true` in `Directory.Build.props` above the kit import. ### MSKITROSLYN002 diff --git a/docs/reference/diagnostic-catalog.md b/docs/reference/diagnostic-catalog.md index c91d65d..8a82e54 100644 --- a/docs/reference/diagnostic-catalog.md +++ b/docs/reference/diagnostic-catalog.md @@ -28,7 +28,7 @@ Every code the kit reports is also declared as an MSBuild item, `BuildDiagnostic ## Where the items live -Each part declares the codes it reports in its own folder, `.toolkit/msbuild/DragoAnt.MSBuildKit./diagnostic.descriptors.props`, which the part's `init.props` imports. A project therefore sees the descriptors of the parts it has installed and no others, and the `DefiningProjectFullPath` of an item names the part that owns the code; `.toolkit/kit.json` holds the kit version. The [code reference](./codes.md) lists which part reports which family. +Each part declares the codes it reports in its own folder, `.mskit/msbuild/DragoAnt.MSBuildKit./diagnostic.descriptors.props`, which the part's `init.props` imports. A project therefore sees the descriptors of the parts it has installed and no others, and the `DefiningProjectFullPath` of an item names the part that owns the code; `.mskit/kit.json` holds the kit version. The [code reference](./codes.md) lists which part reports which family. ```sh dotnet msbuild src/MyLibrary/MyLibrary.csproj -getItem:BuildDiagnosticDescriptor diff --git a/docs/reference/properties.md b/docs/reference/properties.md index e5a3ef6..f47dc35 100644 --- a/docs/reference/properties.md +++ b/docs/reference/properties.md @@ -23,11 +23,12 @@ Every property and item the kit sets or reads, grouped by topic. **Set** = a val | `MSKit_IsStableBranchRegex` | set | owner layer `^(main\|release/.+)$`; kit `^(main\|master\|release/.+)$` | Branches that use `MSKit_StableVersionTemplate` and the prerelease check | | `MSKit_IsStableBranch` | out | | `true` when `MSKit_Branch` matches | | `MSKit_GitRoot` | set | the nearest folder with `.git` | | -| `MSKit_SlnFileDirectory` | set | the `.slnx` folder, else the folder holding `.toolkit/` | Where the extension files are looked up | +| `MSKit_SlnFileDirectory` | set | the `.slnx` folder, else the folder holding `.mskit/` | Where the extension files are looked up | | `MSKit_SlnFileName` | set | the `.slnx` name | | -| `MSKit_ToolkitDir`, `MSKit_ToolkitMSBuildDir` | out | | `.toolkit/` and `.toolkit/msbuild/`, absolute | +| `MSKit_ToolkitDir`, `MSKit_ToolkitMSBuildDir` | out | | The kit folder (`.mskit/` by default) and its `msbuild/`, absolute | +| `MSKit_KitDirName` | out | | The kit folder's name, `.mskit` unless installed with `--kit-dir`; the kit's messages name the update scripts with it | | `MSKit_ProjectObjDir` | out | | The project's `obj` folder, absolute | -| `MSKit_Templates` | set | `.toolkit/.local/` | Templates for local files ([Local files](../local-files.md)) | +| `MSKit_Templates` | set | `.mskit/.local/` | Templates for local files ([Local files](../local-files.md)) | | `MSKit_Diagnostic` | set | `false` | Reserved; nothing reads it in this version | | `BuildDiagnosticDescriptor` | item | one per code of each installed part | A code the kit reports, with its `Title`, `MessageFormat`, `Description`, `Category`, `DefaultSeverity` and `HelpLink`, for tools that read a build's diagnostics ([diagnostic catalog](./diagnostic-catalog.md)) | | `MSKit_CodesHelpBaseUrl` | set | `https://github.com/DragoAnt/MSBuildKit/blob/main/docs/reference/codes.md` | The page every warning and error links to (`HelpLink`); the link adds `#` and the code in lower case ([code reference](./codes.md)). Point it at your own copy of the page | @@ -95,7 +96,7 @@ Every property and item the kit sets or reads, grouped by topic. **Set** = a val | Name | Kind | Default | Meaning | | --- | --- | --- | --- | | `IsPackable` | set | `True` (owner layer); `False` for test projects and code fixes | | -| `PackageIconPath` | set | `.toolkit/res/package.icon.png` (owner layer) | The icon (PNG or JPEG), packed as `icon` | +| `PackageIconPath` | set | `.mskit/res/package.icon.png` (owner layer) | The icon (PNG or JPEG), packed as `icon` | | `MSKit_DefaultPackageIconUrl` | set | empty (off) | A URL of the same icon, written as `PackageIconUrl` next to the kit's embedded icon for older clients; ignored when the project sets `PackageIcon` or `PackageIconUrl` ([Packaging](../packaging.md#package-metadata)) | | `MSKit_PackageIconSourcePath` | out | | The icon file the checks read | | `MSKit_PackageReadmeSourcePath` | set | `package.readme.md`, else `README.md` next to the csproj | The README packed as `readme.md` | diff --git a/docs/roslyn.md b/docs/roslyn.md index 080d4ab..b2aed8d 100644 --- a/docs/roslyn.md +++ b/docs/roslyn.md @@ -3,7 +3,7 @@ Four optional parts build [Roslyn](https://github.com/dotnet/roslyn) analyzers, code fixes and source generators and pack them the way the compiler loads them. Install the ones you need; each brings the parts it requires: ```sh -sh .toolkit/update.sh --add Project.CodeAnalyzer --add Project.CodeFixer --add Project.SourceGenerator +sh .mskit/update.sh --add Project.CodeAnalyzer --add Project.CodeFixer --add Project.SourceGenerator ``` ## Wiring a project diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/audit/audit.roslyn-satellites.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/audit/audit.roslyn-satellites.targets index dd97fe2..3ac1616 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/audit/audit.roslyn-satellites.targets +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/audit/audit.roslyn-satellites.targets @@ -9,7 +9,7 @@ AND '$(MSKit_CodeAnalyzerSatelliteAttached)'!='true'"> + Text="Project '$(MSBuildProjectName)' name matches the CodeAnalyzer auto-detect regex '$(_MSKit_CodeAnalyzerProjectNameRegex_Shared)' but the kit part 'DragoAnt.MSBuildKit.Project.CodeAnalyzer' is not installed.%0AFix: pwsh $(MSKit_KitDirName)/update.ps1 -Add Project.CodeAnalyzer (or: sh $(MSKit_KitDirName)/update.sh --add Project.CodeAnalyzer)%0AOr, if this project is not a Roslyn analyzer, rename it or set MSKit_DisableCodeAnalyzerAutoDetect=true in the csproj." /> + Text="Project '$(MSBuildProjectName)' name matches the CodeFixer auto-detect regex '$(_MSKit_CodeFixerProjectNameRegex_Shared)' but the kit part 'DragoAnt.MSBuildKit.Project.CodeFixer' is not installed.%0AFix: pwsh $(MSKit_KitDirName)/update.ps1 -Add Project.CodeFixer (or: sh $(MSKit_KitDirName)/update.sh --add Project.CodeFixer)%0AOr, if this project is not a Roslyn code-fix provider, rename it or set MSKit_DisableCodeFixerAutoDetect=true in the csproj." /> + Text="Project '$(MSBuildProjectName)' name matches the SourceGenerator auto-detect regex '$(_MSKit_SourceGeneratorProjectNameRegex_Shared)' but the kit part 'DragoAnt.MSBuildKit.Project.SourceGenerator' is not installed.%0AFix: pwsh $(MSKit_KitDirName)/update.ps1 -Add Project.SourceGenerator (or: sh $(MSKit_KitDirName)/update.sh --add Project.SourceGenerator)%0AOr, if this project is not a Roslyn source generator, rename it or set MSKit_DisableSourceGeneratorAutoDetect=true in the csproj." /> diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/core.common.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/core.common.props index f114686..fcad54b 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/core.common.props +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/core.common.props @@ -22,6 +22,7 @@ $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../').Replace('\', '/').TrimEnd('/'))/ $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../../').Replace('\', '/').TrimEnd('/'))/ + $([System.IO.Path]::GetFileName($([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)../..')))) <_ProjectObjDir>$(BaseIntermediateOutputPath) <_ProjectObjDir Condition="'$(_ProjectObjDir)'==''">obj diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props index 4ef8828..add967b 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Core/diagnostic.descriptors.props @@ -11,21 +11,21 @@ HelpLink="$(MSKit_CodesHelpBaseUrl)#mskitcore001" /> - diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets index 2c9767e..f745a7d 100644 --- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets +++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets @@ -50,7 +50,7 @@ $(MSBuildProjectDirectory)/$(PackageReadmeFile) - icon$([System.IO.Path]::GetExtension('$(PackageIconPath)').ToLowerInvariant()) diff --git a/kit/.toolkit/update.ps1 b/kit/.toolkit/update.ps1 index d5fdfea..6cdbcb3 100644 --- a/kit/.toolkit/update.ps1 +++ b/kit/.toolkit/update.ps1 @@ -1,21 +1,22 @@ #!/usr/bin/env pwsh <# .SYNOPSIS -Installs or updates DragoAnt.MSBuildKit in a repository's .toolkit/ folder. +Installs or updates DragoAnt.MSBuildKit in a repository's kit folder (default .mskit/). .DESCRIPTION Downloads a release zip of the kit from GitHub, checks its SHA-256 against the published -checksum (and against .toolkit/kit.json when the version is unchanged), and rewrites -.toolkit/msbuild/ with the default parts plus the optional parts you selected. +checksum (and against kit.json when the version is unchanged), and rewrites /msbuild/ +with the default parts plus the optional parts you selected. An older .toolkit/ install moves +to the kit folder, and the references to it are rewritten. .EXAMPLE -pwsh .toolkit/update.ps1 # update to the version in kit.json, else the latest -pwsh .toolkit/update.ps1 -Version 0.2.0 -DryRun # show what 0.2.0 would change -pwsh .toolkit/update.ps1 -Add PackageAsProj # install an optional part +pwsh .mskit/update.ps1 # update to the version in kit.json, else the latest +pwsh .mskit/update.ps1 -Version 0.2.0 -DryRun # show what 0.2.0 would change +pwsh .mskit/update.ps1 -Add PackageAsProj # install an optional part #> [CmdletBinding()] param( - # Kit version to install; default: the version in .toolkit/kit.json, else the latest release. + # Kit version to install; default: the version in kit.json, else the latest release. [string] $Version, # Optional parts to install, e.g. PackageAsProj, EF, Project.SourceGenerator. [string[]] $Add = @(), @@ -23,6 +24,8 @@ param( [string[]] $Remove = @(), # Show what would change and change nothing. [switch] $DryRun, + # Kit folder in the repository root; default: the folder whose kit.json names it, else .mskit. + [string] $KitDir, # Install from a local kit build (a folder containing .toolkit/ or a release zip). [string] $Source, # Expected SHA-256 of the release zip (or of a -Source zip). @@ -41,8 +44,31 @@ function Fail([string] $text) { throw "update: error: $text" } if (-not $Root) { $Root = (Get-Location).Path } $Root = (Resolve-Path $Root).Path -$toolkit = Join-Path $Root '.toolkit' +$legacyName = '.toolkit' +function KitDirIn([string] $path) { + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { return $null } + $m = [regex]::Match((Get-Content -Raw -LiteralPath $path), '"kitDir"\s*:\s*"([^"]*)"') + if ($m.Success) { return $m.Groups[1].Value } + return $null +} +if ($KitDir) { + if ($KitDir -in @('.', '..') -or $KitDir -match '[/\\:]') { Fail "-KitDir '$KitDir' must be the name of a folder in the repository root" } +} +else { + $claimed = @(Get-ChildItem -LiteralPath $Root -Directory -Force | Where-Object { (KitDirIn (Join-Path $_.FullName 'kit.json')) -ceq $_.Name } | ForEach-Object Name) + if ($claimed.Count -gt 1) { Fail "more than one kit folder: $(($claimed | ForEach-Object { "$_/" }) -join ' '); pass -KitDir NAME" } + $KitDir = if ($claimed.Count -eq 1) { $claimed[0] } else { '.mskit' } +} +$toolkit = Join-Path $Root $KitDir $kitJsonPath = Join-Path $toolkit 'kit.json' +$legacy = Join-Path $Root $legacyName +$migrate = $false +if ($KitDir -cne $legacyName -and ((Test-Path (Join-Path $legacy 'kit.json')) -or (Test-Path (Join-Path $legacy 'msbuild/init.props'))) -and -not (KitDirIn (Join-Path $legacy 'kit.json'))) { + if (Test-Path -LiteralPath $toolkit) { Fail "both $legacyName/ (an older install) and $KitDir/ exist; remove the one you do not use, or pass -KitDir $legacyName to keep the older folder" } + $migrate = $true + $kitJsonPath = Join-Path $legacy 'kit.json' +} +$current = if ($migrate) { $legacy } else { $toolkit } $kitJson = if (Test-Path $kitJsonPath) { Get-Content -Raw $kitJsonPath | ConvertFrom-Json } else { $null } function KitJsonValue([string] $name) { if ($null -ne $kitJson -and $kitJson.PSObject.Properties.Name -contains $name) { return $kitJson.$name } @@ -58,21 +84,21 @@ $work = Join-Path ([System.IO.Path]::GetTempPath()) ("msbuildkit-" + [guid]::New New-Item -ItemType Directory -Path $work | Out-Null try { $actualSha = '' - $kitDir = $null + $kitBuild = $null if ($Source) { if (Test-Path $Source -PathType Container) { if (-not (Test-Path (Join-Path $Source '.toolkit/msbuild/init.props'))) { Fail "'$Source' is not a kit build: no .toolkit/msbuild/init.props" } - $kitDir = (Resolve-Path $Source).Path + $kitBuild = (Resolve-Path $Source).Path } elseif (Test-Path $Source -PathType Leaf) { $actualSha = (Get-FileHash -Algorithm SHA256 $Source).Hash.ToLowerInvariant() if ($Sha256 -and $actualSha -ne $Sha256) { Fail "SHA-256 mismatch for '$Source': expected $Sha256, got $actualSha" } - $kitDir = Join-Path $work 'kit' - Expand-Archive -Path $Source -DestinationPath $kitDir + $kitBuild = Join-Path $work 'kit' + Expand-Archive -Path $Source -DestinationPath $kitBuild } else { Fail "-Source '$Source' does not exist" } if (-not $Version) { - $versionFile = Join-Path $kitDir '.toolkit/kit.version' + $versionFile = Join-Path $kitBuild '.toolkit/kit.version' $Version = if (Test-Path $versionFile) { (Get-Content $versionFile -TotalCount 1).Trim() } else { '0.0.0-local' } } } @@ -95,16 +121,16 @@ try { $pinned = KitJsonValue 'sha256' if (-not $Sha256 -and (KitJsonValue 'version') -eq $Version -and $pinned) { $Sha256 = $pinned.ToLowerInvariant() } if ($Sha256 -and $actualSha -ne $Sha256) { Fail "SHA-256 mismatch for ${zipName}: expected $Sha256 (kit.json or -Sha256), got $actualSha" } - $kitDir = Join-Path $work 'kit' - Expand-Archive -Path $zipPath -DestinationPath $kitDir + $kitBuild = Join-Path $work 'kit' + Expand-Archive -Path $zipPath -DestinationPath $kitBuild } - $newToolkit = Join-Path $kitDir '.toolkit' + $newToolkit = Join-Path $kitBuild '.toolkit' if (-not (Test-Path (Join-Path $newToolkit 'msbuild/init.props'))) { Fail 'the kit build has no .toolkit/msbuild/init.props' } $partsPath = Join-Path $newToolkit 'kit.parts' if (-not (Test-Path $partsPath)) { Fail 'the kit build has no .toolkit/kit.parts' } - if ((Test-Path $toolkit) -and (Resolve-Path $newToolkit).Path -eq (Resolve-Path $toolkit).Path) { - Fail 'the target .toolkit is the kit source itself; run from the repository you want to update, or pass -Root' + if ((Test-Path $current) -and (Resolve-Path $newToolkit).Path -eq (Resolve-Path $current).Path) { + Fail "the target $(Split-Path -Leaf $current)/ is the kit source itself; run from the repository you want to update, or pass -Root" } $parts = [ordered]@{} foreach ($line in Get-Content $partsPath) { @@ -152,7 +178,7 @@ try { Say "kit $Repo $(if ($oldVersion) { $oldVersion } else { 'none' }) -> $Version$(if ($actualSha) { " (sha256 $actualSha)" })" Say "parts: $($selected -join ' ')" - $currentMsbuild = Join-Path $toolkit 'msbuild' + $currentMsbuild = Join-Path $current 'msbuild' function RelativeFiles([string] $dir) { if (-not (Test-Path $dir)) { return @() } $full = (Resolve-Path $dir).Path @@ -162,21 +188,59 @@ try { $new = RelativeFiles $stage $changes = 0 foreach ($f in $new) { - if ($old -notcontains $f) { Say " add .toolkit/msbuild/$f"; $changes++ } - elseif ((Get-FileHash (Join-Path $currentMsbuild $f)).Hash -ne (Get-FileHash (Join-Path $stage $f)).Hash) { Say " change .toolkit/msbuild/$f"; $changes++ } + if ($old -notcontains $f) { Say " add $KitDir/msbuild/$f"; $changes++ } + elseif ((Get-FileHash (Join-Path $currentMsbuild $f)).Hash -ne (Get-FileHash (Join-Path $stage $f)).Hash) { Say " change $KitDir/msbuild/$f"; $changes++ } + } + foreach ($f in $old) { if ($new -notcontains $f) { Say " remove $KitDir/msbuild/$f"; $changes++ } } + + # Renames .toolkit only as a whole path segment, never inside a longer name. + function RewriteRefs([switch] $Dry) { + $pattern = '(?<=^|[/\\ \t"''()=,;])' + [regex]::Escape($legacyName) + '(?=$|[/\\ \t"''\r*])' + foreach ($f in 'Directory.Build.props', 'Directory.Build.targets', '.gitignore', '.github/dependabot.yml') { + $path = Join-Path $Root $f + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { continue } + $bytes = [System.IO.File]::ReadAllBytes($path) + $bom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF + $encoding = [System.Text.UTF8Encoding]::new($bom) + $text = [System.IO.File]::ReadAllText($path) + $rewritten = [regex]::Replace($text, $pattern, $KitDir.Replace('$', '$$'), 'Multiline') + if ($rewritten -ceq $text) { continue } + if ($Dry) { Say " would rewrite $legacyName -> $KitDir in $f" } + else { [System.IO.File]::WriteAllText($path, $rewritten, $encoding); Say "rewrote $legacyName -> $KitDir in $f" } + } } - foreach ($f in $old) { if ($new -notcontains $f) { Say " remove .toolkit/msbuild/$f"; $changes++ } } $dbp = Join-Path $Root 'Directory.Build.props' $dbt = Join-Path $Root 'Directory.Build.targets' if ($DryRun) { - Say "$changes file(s) under .toolkit/msbuild would change" + if ($migrate) { Say "would move $legacyName/ to $KitDir/"; RewriteRefs -Dry } + Say "$changes file(s) under $KitDir/msbuild would change" if (-not (Test-Path $dbp)) { Say ' would create Directory.Build.props' } if (-not (Test-Path $dbt)) { Say ' would create Directory.Build.targets' } Say 'dry run: nothing changed' return } + if ($migrate) { + $tracked = $false + if (Get-Command git -ErrorAction SilentlyContinue) { + $inside = & git -C $Root rev-parse --is-inside-work-tree 2>$null + if ($LASTEXITCODE -eq 0 -and $inside -eq 'true') { $tracked = [bool](& git -C $Root ls-files -- $legacyName 2>$null) } + } + if ($tracked) { + & git -C $Root mv $legacyName $KitDir + if ($LASTEXITCODE -ne 0) { Fail "git mv $legacyName $KitDir failed" } + Say "moved $legacyName/ to $KitDir/ (git mv)" + } + else { + Move-Item -LiteralPath $legacy -Destination $toolkit + Say "moved $legacyName/ to $KitDir/" + } + $kitJsonPath = Join-Path $toolkit 'kit.json' + $currentMsbuild = Join-Path $toolkit 'msbuild' + RewriteRefs + } + New-Item -ItemType Directory -Path $toolkit -Force | Out-Null if (Test-Path $currentMsbuild) { Remove-Item -Recurse -Force $currentMsbuild } Copy-Item -Recurse $stage $currentMsbuild @@ -189,23 +253,23 @@ try { Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $toolkit 'kit.version') $partsJson = ($recorded | ForEach-Object { "`"$_`"" }) -join ', ' - $json = "{`n `"repository`": `"$Repo`",`n `"version`": `"$Version`",`n `"sha256`": `"$actualSha`",`n `"parts`": [$partsJson]`n}`n" + $json = "{`n `"repository`": `"$Repo`",`n `"version`": `"$Version`",`n `"sha256`": `"$actualSha`",`n `"parts`": [$partsJson],`n `"kitDir`": `"$KitDir`"`n}`n" [System.IO.File]::WriteAllText($kitJsonPath, $json) - Say "$changes file(s) under .toolkit/msbuild changed" + Say "$changes file(s) under $KitDir/msbuild changed" - if (-not (Test-Path $dbp)) { - [System.IO.File]::WriteAllText($dbp, "`n`n `n`n`n") - Say 'created Directory.Build.props' - } - elseif ((Get-Content -Raw $dbp) -notmatch '\.toolkit[/\\]msbuild[/\\]init\.props') { - Say 'add to Directory.Build.props: ' - } - if (-not (Test-Path $dbt)) { - [System.IO.File]::WriteAllText($dbt, "`n`n `n`n`n") - Say 'created Directory.Build.targets' - } - elseif ((Get-Content -Raw $dbt) -notmatch '\.toolkit[/\\]msbuild[/\\]init\.targets') { - Say 'add to Directory.Build.targets: ' + foreach ($ext in 'props', 'targets') { + $file = Join-Path $Root "Directory.Build.$ext" + $import = "`$(MSBuildThisFileDirectory)$KitDir/msbuild/init.$ext" + if (-not (Test-Path $file)) { + [System.IO.File]::WriteAllText($file, "`n`n `n`n`n") + Say "created Directory.Build.$ext" + } + else { + $raw = Get-Content -Raw $file + if (-not ($raw.Contains("$KitDir/msbuild/init.$ext") -or $raw.Contains("$KitDir\msbuild\init.$ext"))) { + Say "add to Directory.Build.${ext}: " + } + } } Say "done: DragoAnt.MSBuildKit $Version installed in $toolkit" } diff --git a/kit/.toolkit/update.sh b/kit/.toolkit/update.sh index e1cc11c..19b536b 100644 --- a/kit/.toolkit/update.sh +++ b/kit/.toolkit/update.sh @@ -1,12 +1,25 @@ #!/bin/sh -# Installs or updates DragoAnt.MSBuildKit in a repository's .toolkit/ folder. -# Usage: sh .toolkit/update.sh [--version X.Y.Z] [--add PART] [--remove PART] [--dry-run] +# Installs or updates DragoAnt.MSBuildKit in a repository's kit folder (default .mskit/). +# Usage: sh .mskit/update.sh [--version X.Y.Z] [--add PART] [--remove PART] [--dry-run] [--kit-dir NAME] # [--source DIR|ZIP] [--sha256 HEX] [--repo OWNER/NAME] [--root DIR] # One brace group, parsed in full before it runs: the update may replace this file while it runs. { set -eu +# Started from a .toolkit/ folder, run from a copy: Windows refuses to move a folder holding an open file. +self_copy="" +if [ "${1:-}" = "--from-copy" ]; then + self_copy=$(dirname "$0"); shift +else + case "$(dirname "$0")" in + .toolkit|*/.toolkit|*\\.toolkit) + self_copy=$(mktemp -d) + cp "$0" "$self_copy/update.sh" + exec sh "$self_copy/update.sh" --from-copy "$@" ;; + esac +fi + repo="DragoAnt/MSBuildKit" version="" source="" @@ -15,15 +28,19 @@ root="" dry_run=0 add_parts="" remove_parts="" +kit_name="" +legacy_name=".toolkit" usage() { sed -n '2,4p' "$0" | sed 's/^# \{0,1\}//' cat <<'EOF' - --version X.Y.Z kit version to install (default: the version in .toolkit/kit.json, else the latest release) + --version X.Y.Z kit version to install (default: the version in kit.json, else the latest release) --add PART install an optional part (repeatable), e.g. --add PackageAsProj --remove PART uninstall an optional part (repeatable) --dry-run show what would change and change nothing + --kit-dir NAME kit folder in the repository root (default: the folder whose kit.json names it, else .mskit); + an older .toolkit/ install moves there --source DIR|ZIP install from a local kit build instead of a GitHub release --sha256 HEX expected SHA-256 of the release zip (or of a --source zip) --repo OWNER/NAME GitHub repository to download releases from (default: DragoAnt/MSBuildKit) @@ -44,6 +61,7 @@ while [ $# -gt 0 ]; do --add) [ $# -ge 2 ] || fail "--add needs a value"; add_parts="$add_parts $2"; shift 2 ;; --remove) [ $# -ge 2 ] || fail "--remove needs a value"; remove_parts="$remove_parts $2"; shift 2 ;; --dry-run) dry_run=1; shift ;; + --kit-dir) [ $# -ge 2 ] || fail "--kit-dir needs a value"; kit_name="$2"; shift 2 ;; -h|--help) usage; exit 0 ;; *) fail "unknown argument '$1' (see --help)" ;; esac @@ -51,8 +69,33 @@ done [ -n "$root" ] || root=$(pwd) [ -d "$root" ] || fail "root '$root' is not a directory" -toolkit="$root/.toolkit" + +kitdir_in() { [ -f "$1" ] && sed -n 's/.*"kitDir"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$1" | head -n 1 || true; } +if [ -n "$kit_name" ]; then + case "$kit_name" in + .|..|*/*|*\\*|*:*) fail "--kit-dir '$kit_name' must be the name of a folder in the repository root" ;; + esac +else + count=0; claimed="" + for j in "$root"/*/kit.json "$root"/.[!.]*/kit.json; do + [ -f "$j" ] || continue + d=$(basename "$(dirname "$j")") + if [ "$(kitdir_in "$j")" = "$d" ]; then count=$((count+1)); kit_name="$d"; claimed="$claimed $d/"; fi + done + [ "$count" -le 1 ] || fail "more than one kit folder:$claimed; pass --kit-dir NAME" + [ -n "$kit_name" ] || kit_name=".mskit" +fi +toolkit="$root/$kit_name" kit_json="$toolkit/kit.json" +legacy="$root/$legacy_name" +migrate=0 +if [ "$kit_name" != "$legacy_name" ] && { [ -f "$legacy/kit.json" ] || [ -f "$legacy/msbuild/init.props" ]; } && [ -z "$(kitdir_in "$legacy/kit.json")" ]; then + [ ! -e "$toolkit" ] || fail "both $legacy_name/ (an older install) and $kit_name/ exist; remove the one you do not use, or pass --kit-dir $legacy_name to keep the older folder" + migrate=1 + kit_json="$legacy/kit.json" +fi +current="$toolkit" +[ "$migrate" -eq 0 ] || current="$legacy" json_value() { [ -f "$kit_json" ] && sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$kit_json" | head -n 1 || true; } json_parts() { [ -f "$kit_json" ] && sed -n 's/.*"parts"[[:space:]]*:[[:space:]]*\[\(.*\)\].*/\1/p' "$kit_json" | tr -d '" ' | tr ',' ' ' || true; } @@ -75,7 +118,7 @@ extract_zip() { } work=$(mktemp -d) -trap 'rm -rf "$work"' EXIT INT TERM +trap 'rm -rf "$work"; if [ -n "$self_copy" ]; then rm -f "$self_copy/update.sh"; rmdir "$self_copy" 2>/dev/null || true; fi' EXIT INT TERM kit_dir="" actual_sha="" @@ -125,7 +168,7 @@ fi new_toolkit="$kit_dir/.toolkit" [ -f "$new_toolkit/msbuild/init.props" ] || fail "the kit build has no .toolkit/msbuild/init.props" [ -f "$new_toolkit/kit.parts" ] || fail "the kit build has no .toolkit/kit.parts" -if [ -d "$toolkit" ] && [ "$(cd "$new_toolkit" && pwd -P)" = "$(cd "$toolkit" && pwd -P)" ]; then fail "the target .toolkit is the kit source itself; run from the repository you want to update, or pass --root"; fi +if [ -d "$current" ] && [ "$(cd "$new_toolkit" && pwd -P)" = "$(cd "$current" && pwd -P)" ]; then fail "the target $(basename "$current")/ is the kit source itself; run from the repository you want to update, or pass --root"; fi parts_file="$work/kit.parts" tr -d '\r' < "$new_toolkit/kit.parts" | grep -v '^[[:space:]]*#' | grep -v '^[[:space:]]*$' > "$parts_file" @@ -174,54 +217,88 @@ old_version=$(json_value version) say "kit $repo ${old_version:-none} -> $version${actual_sha:+ (sha256 $actual_sha)}" say "parts: $(echo $selected)" changes=0 -if [ -d "$toolkit/msbuild" ]; then - (cd "$toolkit/msbuild" && find . -type f | sort) > "$work/old.lst" +if [ -d "$current/msbuild" ]; then + (cd "$current/msbuild" && find . -type f | sort) > "$work/old.lst" else : > "$work/old.lst" fi (cd "$stage/msbuild" && find . -type f | sort) > "$work/new.lst" -for f in $(comm -13 "$work/old.lst" "$work/new.lst"); do say " add .toolkit/msbuild/${f#./}"; changes=$((changes+1)); done -for f in $(comm -23 "$work/old.lst" "$work/new.lst"); do say " remove .toolkit/msbuild/${f#./}"; changes=$((changes+1)); done +for f in $(comm -13 "$work/old.lst" "$work/new.lst"); do say " add $kit_name/msbuild/${f#./}"; changes=$((changes+1)); done +for f in $(comm -23 "$work/old.lst" "$work/new.lst"); do say " remove $kit_name/msbuild/${f#./}"; changes=$((changes+1)); done for f in $(comm -12 "$work/old.lst" "$work/new.lst"); do - cmp -s "$toolkit/msbuild/$f" "$stage/msbuild/$f" || { say " change .toolkit/msbuild/${f#./}"; changes=$((changes+1)); } + cmp -s "$current/msbuild/$f" "$stage/msbuild/$f" || { say " change $kit_name/msbuild/${f#./}"; changes=$((changes+1)); } done +# Renames .toolkit only as a whole path segment, never inside a longer name; "dry" only reports. +rewrite_refs() { + for f in Directory.Build.props Directory.Build.targets .gitignore .github/dependabot.yml; do + [ -f "$root/$f" ] || continue + awk -v old="$legacy_name" -v new="$kit_name" ' + function edge(c, set) { return c == "" || index(set, c) > 0 } + { + line = $0; out = "" + while ((i = index(line, old)) > 0) { + prev = i > 1 ? substr(line, i - 1, 1) : substr(out, length(out), 1) + after = substr(line, i + length(old), 1) + if (edge(prev, "/\\ \t\"\047()=,;") && edge(after, "/\\ \t\"\047\r*")) out = out substr(line, 1, i - 1) new + else out = out substr(line, 1, i - 1 + length(old)) + line = substr(line, i + length(old)) + } + print out line + }' "$root/$f" > "$work/rewrite" + cmp -s "$root/$f" "$work/rewrite" && continue + if [ "${1:-}" = dry ]; then say " would rewrite $legacy_name -> $kit_name in $f" + else cat "$work/rewrite" > "$root/$f"; say "rewrote $legacy_name -> $kit_name in $f"; fi + done +} + dbp="$root/Directory.Build.props" dbt="$root/Directory.Build.targets" if [ "$dry_run" -eq 1 ]; then - say "$changes file(s) under .toolkit/msbuild would change" + if [ "$migrate" -eq 1 ]; then say "would move $legacy_name/ to $kit_name/"; rewrite_refs dry; fi + say "$changes file(s) under $kit_name/msbuild would change" [ -f "$dbp" ] || say " would create Directory.Build.props" [ -f "$dbt" ] || say " would create Directory.Build.targets" say "dry run: nothing changed" exit 0 fi +if [ "$migrate" -eq 1 ]; then + if git -C "$root" rev-parse --is-inside-work-tree > /dev/null 2>&1 && [ -n "$(git -C "$root" ls-files -- "$legacy_name" 2>/dev/null)" ]; then + git -C "$root" mv "$legacy_name" "$kit_name" || fail "git mv $legacy_name $kit_name failed" + say "moved $legacy_name/ to $kit_name/ (git mv)" + else + mv "$legacy" "$toolkit" || fail "cannot move $legacy_name/ to $kit_name/" + say "moved $legacy_name/ to $kit_name/" + fi + kit_json="$toolkit/kit.json" + rewrite_refs +fi + mkdir -p "$toolkit" rm -rf "$toolkit/msbuild" cp -R "$stage/msbuild" "$toolkit/msbuild" if [ -d "$new_toolkit/res" ]; then mkdir -p "$toolkit/res"; cp -R "$new_toolkit/res/." "$toolkit/res/"; fi cp "$new_toolkit/kit.parts" "$toolkit/kit.parts" rm -f "$toolkit/kit.version" -say "$changes file(s) under .toolkit/msbuild changed" +say "$changes file(s) under $kit_name/msbuild changed" parts_json="" for p in $recorded; do parts_json="$parts_json${parts_json:+, }\"$p\""; done -printf '{\n "repository": "%s",\n "version": "%s",\n "sha256": "%s",\n "parts": [%s]\n}\n' \ - "$repo" "$version" "$actual_sha" "$parts_json" > "$kit_json" - -if [ ! -f "$dbp" ]; then - printf '\n\n \n\n\n' > "$dbp" - say "created Directory.Build.props" -elif ! grep -q '\.toolkit[/\\]msbuild[/\\]init\.props' "$dbp"; then - say "add to Directory.Build.props: " -fi -if [ ! -f "$dbt" ]; then - printf '\n\n \n\n\n' > "$dbt" - say "created Directory.Build.targets" -elif ! grep -q '\.toolkit[/\\]msbuild[/\\]init\.targets' "$dbt"; then - say "add to Directory.Build.targets: " -fi +printf '{\n "repository": "%s",\n "version": "%s",\n "sha256": "%s",\n "parts": [%s],\n "kitDir": "%s"\n}\n' \ + "$repo" "$version" "$actual_sha" "$parts_json" "$kit_name" > "$kit_json" + +for ext in props targets; do + file="$root/Directory.Build.$ext" + import="\$(MSBuildThisFileDirectory)$kit_name/msbuild/init.$ext" + if [ ! -f "$file" ]; then + printf '\n\n \n\n\n' "$import" > "$file" + say "created Directory.Build.$ext" + elif ! grep -qF -e "$kit_name/msbuild/init.$ext" -e "$kit_name\\msbuild\\init.$ext" "$file"; then + say "add to Directory.Build.$ext: " + fi +done for f in update.ps1 update.sh; do if [ -f "$new_toolkit/$f" ]; then cp "$new_toolkit/$f" "$toolkit/$f"; fi; done say "done: DragoAnt.MSBuildKit $version installed in $toolkit" exit 0 diff --git a/samples/MinimalLibrary/.gitignore b/samples/MinimalLibrary/.gitignore index 428d735..88883e4 100644 --- a/samples/MinimalLibrary/.gitignore +++ b/samples/MinimalLibrary/.gitignore @@ -1 +1 @@ -.toolkit/ +.mskit/ diff --git a/samples/MinimalLibrary/Directory.Build.props b/samples/MinimalLibrary/Directory.Build.props index 6927d4e..119f255 100644 --- a/samples/MinimalLibrary/Directory.Build.props +++ b/samples/MinimalLibrary/Directory.Build.props @@ -4,6 +4,6 @@ net8.0;net10.0 - + diff --git a/samples/MinimalLibrary/Directory.Build.targets b/samples/MinimalLibrary/Directory.Build.targets index 07200a8..19f89e4 100644 --- a/samples/MinimalLibrary/Directory.Build.targets +++ b/samples/MinimalLibrary/Directory.Build.targets @@ -1,5 +1,5 @@ - + diff --git a/tests/codes.sh b/tests/codes.sh index b644274..05b6a31 100644 --- a/tests/codes.sh +++ b/tests/codes.sh @@ -33,7 +33,7 @@ catalog_items() { /^[[:space:]]*"Title":/ { title = val($0) } /^[[:space:]]*"DefaultSeverity":/ { severity = val($0) } /^[[:space:]]*"HelpLink":/ { link = val($0) } - /^[[:space:]]*"DefiningProjectFullPath":/ { part = val($0); if (!sub(/^.*\/\.toolkit\/msbuild\//, "", part) || !sub(/\/diagnostic\.descriptors\.props$/, "", part)) part = "?" } + /^[[:space:]]*"DefiningProjectFullPath":/ { part = val($0); if (!sub(/^.*\/\.(toolkit|mskit)\/msbuild\//, "", part) || !sub(/\/diagnostic\.descriptors\.props$/, "", part)) part = "?" } /^[[:space:]]*}/ { if (id != "") print id "\t" part "\t" severity "\t" link "\t" (title == "" ? "-" : "titled"); id = part = severity = link = title = "" } ' "$out/$name.json" | sort > "$out/$name.tsv" } @@ -60,7 +60,7 @@ grep -q "^MSKITVER006 DragoAnt.MSBuildKit Error $codes_url#mskitver006 " "$out/c catalog_items "$lib" catalog-sample while IFS=' ' read -r code part where; do - [ -d "$sample/.toolkit/msbuild/$part" ] && printf '%s\t%s\n' "$code" "$part" + [ -d "$sample/.mskit/msbuild/$part" ] && printf '%s\t%s\n' "$code" "$part" done < "$out/catalog-declared.tsv" > "$out/catalog-sample.expected" cut -f1,2 "$out/catalog-sample.tsv" > "$out/catalog-sample.actual" [ -s "$out/catalog-sample.actual" ] && cmp -s "$out/catalog-sample.expected" "$out/catalog-sample.actual" && ! grep -q "PackageAsProj" "$out/catalog-sample.actual" \ diff --git a/tests/fixtures/PackageChecks/.gitignore b/tests/fixtures/PackageChecks/.gitignore index 428d735..88883e4 100644 --- a/tests/fixtures/PackageChecks/.gitignore +++ b/tests/fixtures/PackageChecks/.gitignore @@ -1 +1 @@ -.toolkit/ +.mskit/ diff --git a/tests/fixtures/PackageChecks/Directory.Build.props b/tests/fixtures/PackageChecks/Directory.Build.props index 6851b7e..a97879c 100644 --- a/tests/fixtures/PackageChecks/Directory.Build.props +++ b/tests/fixtures/PackageChecks/Directory.Build.props @@ -4,6 +4,6 @@ net8.0 - + diff --git a/tests/fixtures/PackageChecks/Directory.Build.targets b/tests/fixtures/PackageChecks/Directory.Build.targets index 07200a8..19f89e4 100644 --- a/tests/fixtures/PackageChecks/Directory.Build.targets +++ b/tests/fixtures/PackageChecks/Directory.Build.targets @@ -1,5 +1,5 @@ - + diff --git a/tests/fixtures/PackageIcon/.gitignore b/tests/fixtures/PackageIcon/.gitignore index 428d735..88883e4 100644 --- a/tests/fixtures/PackageIcon/.gitignore +++ b/tests/fixtures/PackageIcon/.gitignore @@ -1 +1 @@ -.toolkit/ +.mskit/ diff --git a/tests/fixtures/PackageIcon/Directory.Build.props b/tests/fixtures/PackageIcon/Directory.Build.props index ec7baa2..f2c6e20 100644 --- a/tests/fixtures/PackageIcon/Directory.Build.props +++ b/tests/fixtures/PackageIcon/Directory.Build.props @@ -7,6 +7,6 @@ $(MSBuildThisFileDirectory)Icons/$(FixtureIcon) - + diff --git a/tests/fixtures/PackageIcon/Directory.Build.targets b/tests/fixtures/PackageIcon/Directory.Build.targets index 07200a8..19f89e4 100644 --- a/tests/fixtures/PackageIcon/Directory.Build.targets +++ b/tests/fixtures/PackageIcon/Directory.Build.targets @@ -1,5 +1,5 @@ - + diff --git a/tests/fixtures/PackageReadme/.gitignore b/tests/fixtures/PackageReadme/.gitignore index 428d735..88883e4 100644 --- a/tests/fixtures/PackageReadme/.gitignore +++ b/tests/fixtures/PackageReadme/.gitignore @@ -1 +1 @@ -.toolkit/ +.mskit/ diff --git a/tests/fixtures/PackageReadme/Directory.Build.props b/tests/fixtures/PackageReadme/Directory.Build.props index 72d4aa3..5085419 100644 --- a/tests/fixtures/PackageReadme/Directory.Build.props +++ b/tests/fixtures/PackageReadme/Directory.Build.props @@ -11,6 +11,6 @@ - + diff --git a/tests/fixtures/PackageReadme/Directory.Build.targets b/tests/fixtures/PackageReadme/Directory.Build.targets index 07200a8..19f89e4 100644 --- a/tests/fixtures/PackageReadme/Directory.Build.targets +++ b/tests/fixtures/PackageReadme/Directory.Build.targets @@ -1,5 +1,5 @@ - + diff --git a/tests/nuget-isolation.sh b/tests/nuget-isolation.sh index 5609f65..9462083 100644 --- a/tests/nuget-isolation.sh +++ b/tests/nuget-isolation.sh @@ -231,7 +231,7 @@ ni_guard() { ni_hardcoded() { ni_m=$1; shift ni_mb=$(printf '%s' "$ni_m" | tr '/' '\\') - grep -rnIiE --exclude-dir=bin --exclude-dir=obj --exclude-dir=dist --exclude-dir=.toolkit --exclude-dir=TestResults \ + grep -rnIiE --exclude-dir=bin --exclude-dir=obj --exclude-dir=dist --exclude-dir=.toolkit --exclude-dir=.mskit --exclude-dir=TestResults \ --exclude=nuget-isolation.sh --exclude=nuget-isolation.test.sh \ -e '\.nuget[/\\]+packages' -e '\$\{?NUGET_PACKAGES' -e '"NUGET_PACKAGES"' -e 'NuGetPackageRoot' \ -e "$(printf '%s' "$ni_m" | sed 's/[][\\.*^$+?(){}|]/\\&/g')" -e "$(printf '%s' "$ni_mb" | sed 's/[][\\.*^$+?(){}|]/\\&/g')" \ diff --git a/tests/run.sh b/tests/run.sh index 234c85b..5225a6b 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -31,10 +31,25 @@ bad() { echo "FAIL $*"; failures=$((failures+1)); } . "$here/tests/nuget-isolation.sh" ni_begin selftest "$nuget_fallback" -for root in "$sample" "$fixtures" "$readme_fixture" "$tfm_fixture" "$icon_fixture"; do +# The sample starts as a v0.2.1 install in .toolkit/, so every check below builds a migrated tree; +# TfmConstants keeps the kit in .toolkit/ through --kit-dir, so a non-default folder builds too. +rm -rf "$sample/.toolkit" "$sample/.mskit" +if git -C "$here" rev-parse -q --verify 'refs/tags/v0.2.1^{commit}' > /dev/null 2>&1; then + git -C "$here" archive v0.2.1 kit/.toolkit/update.sh | tar -x -C "$out" + sh "$out/kit/.toolkit/update.sh" --source "$kit" --root "$sample" > "$out/install-v0.2.1.log" || { cat "$out/install-v0.2.1.log"; exit 1; } + [ -d "$sample/.toolkit/msbuild" ] && pass "the v0.2.1 update.sh installed the kit into the sample's .toolkit/" || bad "the v0.2.1 update.sh left no .toolkit/ in the sample" +else + echo "SKIP the sample's v0.2.1 start (no v0.2.1 tag; fetch the tags)" +fi +for root in "$sample" "$fixtures" "$readme_fixture" "$icon_fixture"; do sh "$kit/.toolkit/update.sh" --source "$kit" --root "$root" > "$out/install.log" || { cat "$out/install.log"; exit 1; } done +sh "$kit/.toolkit/update.sh" --source "$kit" --root "$tfm_fixture" --kit-dir .toolkit > "$out/install.log" || { cat "$out/install.log"; exit 1; } pass "update.sh installed the kit into the sample and the fixtures" +[ -f "$sample/.mskit/kit.json" ] && [ ! -e "$sample/.toolkit" ] && pass "update.sh moved the sample's kit to .mskit/" || bad "the sample's kit is not in .mskit/ alone" +[ -f "$tfm_fixture/.toolkit/kit.json" ] && [ ! -e "$tfm_fixture/.mskit" ] && pass "update.sh kept TfmConstants on --kit-dir .toolkit" || bad "TfmConstants did not keep .toolkit/" +if sh "$here/tests/update-dir.test.sh" > "$out/update-dir.log" 2>&1; then pass "update dir: the kit-folder checks pass" +else bad "update dir: the kit-folder checks (see $out/update-dir.log)"; grep "^FAIL" "$out/update-dir.log" || tail -n 5 "$out/update-dir.log"; fi . "$here/tests/update-self.sh" lib="$sample/src/DragoAnt.Samples.MinimalLibrary/DragoAnt.Samples.MinimalLibrary.csproj" diff --git a/tests/update-dir.test.sh b/tests/update-dir.test.sh new file mode 100644 index 0000000..779d026 --- /dev/null +++ b/tests/update-dir.test.sh @@ -0,0 +1,189 @@ +#!/bin/sh +# The kit folder of update.sh and update.ps1: a fresh install writes .mskit/, --kit-dir picks another +# name and kit.json remembers it, an older .toolkit/ install moves, and 0.2.1 reaches .mskit/ in two runs. +# No dotnet, no network: every run installs from the working-tree kit. update.ps1 runs when pwsh exists. +# Usage: sh tests/update-dir.test.sh +set -eu + +here=$(cd "$(dirname "$0")/.." && pwd) +kit="$here/kit" +failures=0 +pass() { echo "PASS update dir: $*"; } +bad() { echo "FAIL update dir: $*"; failures=$((failures+1)); } + +t=$(mktemp -d) +trap 'rm -rf "$t"' EXIT + +impls="sh" +if command -v pwsh > /dev/null 2>&1; then impls="sh ps1"; else echo "SKIP update dir: update.ps1 cases (no pwsh)"; fi + +# upd