diff --git a/.github/workflows/net48-compatibility.yml b/.github/workflows/net48-compatibility.yml index fc1edadbae..74a057013e 100644 --- a/.github/workflows/net48-compatibility.yml +++ b/.github/workflows/net48-compatibility.yml @@ -44,9 +44,6 @@ jobs: - name: Restore .NET tools run: dotnet tool restore - - name: Generate code - run: dotnet run --project CodeGen - - name: Run the complete net48 test suite with coverage shell: pwsh run: | diff --git a/AGENTS.md b/AGENTS.md index 28ff7390e7..1b1ca04d71 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,13 +15,14 @@ UnitsNet is a .NET library that provides strongly-typed physical units and quant - **Clean artifacts**: `clean.bat` ### Code Generation -- **Generate code from JSON definitions**: `generate-code.bat` or `dotnet run --project CodeGen` - - Always run this after modifying any JSON files in `Common/UnitDefinitions/` +- **Generate code from JSON definitions**: runs automatically as part of `dotnet build` + - Building runs CodeGen when the JSON files in `Common/UnitDefinitions/` or CodeGen itself changed + - To only regenerate without building everything: `dotnet run --project CodeGen` - The generator reads 131 JSON definition files and creates C# code ### Development Workflow 1. Modify unit definitions in `Common/UnitDefinitions/*.json` -2. Run `generate-code.bat` to regenerate C# code +2. Run `dotnet build UnitsNet.slnx` to regenerate C# code and compile 3. Run `build.bat` to compile and test 4. Use `test.bat` for isolated test runs @@ -61,7 +62,7 @@ The project uses a sophisticated code generation system: - Use division for `FromBaseToUnitFunc` - Prefer scientific notation (1e3, 1e-5) - Use exact constituent constants instead of pre-computed decimals -3. Run `generate-code.bat` +3. Run `dotnet build UnitsNet.slnx` to regenerate code 4. Add tests if needed ## Important Conventions diff --git a/Build/build-functions.psm1 b/Build/build-functions.psm1 index eaa437e7b0..234525d267 100644 --- a/Build/build-functions.psm1 +++ b/Build/build-functions.psm1 @@ -24,11 +24,19 @@ function Remove-ArtifactsDir { Set-Content -LiteralPath (Join-Path $localNuGetFeedDir ".gitkeep") -Value "" } -function Update-GeneratedCode { - write-host -foreground blue "Generate code...`n---" - dotnet run --project "$root/CodeGen" +# The build regenerates code from the unit definitions (see Directory.Build.targets). On CI, fail if that changed any +# files, since the committed generated code must match the unit definitions. +function Assert-GeneratedCodeUpToDate { + if (-not $env:CI) { return } + + write-host -foreground blue "Check generated code is up to date...`n---" + $changes = git -C $root status --porcelain -- '*/GeneratedCode/*' 'UnitsNet.Tests/CustomCode/*' 'Common/UnitEnumValues.g.json' 'Common/UnitRelations.json' if ($lastexitcode -ne 0) { exit 1 } - write-host -foreground blue "Generate code...END`n" + if ($changes) { + write-host -foreground red "Generated code is out of date. Build locally and commit the changes:`n$($changes -join "`n")" + exit 1 + } + write-host -foreground blue "Check generated code is up to date...END`n" } function Start-Build { @@ -140,4 +148,4 @@ function Compress-ArtifactsAsZip { write-host -foreground blue "Zip artifacts...END`n" } -export-modulemember -function Remove-ArtifactsDir, Update-GeneratedCode, Start-Build, Start-Tests, Start-PackNugets, Compress-ArtifactsAsZip +export-modulemember -function Remove-ArtifactsDir, Assert-GeneratedCodeUpToDate, Start-Build, Start-Tests, Start-PackNugets, Compress-ArtifactsAsZip diff --git a/Build/build.ps1 b/Build/build.ps1 index dc83fd453b..b34b5a4ad6 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -29,10 +29,10 @@ try { & "$PSScriptRoot/init.ps1" -SkipCoverageTools:($SkipTests -or $SkipCoverage) Remove-ArtifactsDir - Update-GeneratedCode - # Build main projects with dotnet CLI (cross-platform) + # Build main projects with dotnet CLI (cross-platform). Building also regenerates code from the unit definitions. Start-Build + Assert-GeneratedCodeUpToDate if (-not $SkipTests) { Start-Tests -SkipCoverage:$SkipCoverage } diff --git a/CodeGen/CodeGen.csproj b/CodeGen/CodeGen.csproj index c0b882ce31..fdf92aa8a6 100644 --- a/CodeGen/CodeGen.csproj +++ b/CodeGen/CodeGen.csproj @@ -28,4 +28,31 @@ + + + + + + + + + <_UnitsNetCodeGenDotnet>$(DOTNET_HOST_PATH) + <_UnitsNetCodeGenDotnet Condition="'$(_UnitsNetCodeGenDotnet)' == ''">dotnet + <_UnitsNetRepositoryRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..')) + + + + + + diff --git a/CodeGen/Generators/UnitsNetGenerator.cs b/CodeGen/Generators/UnitsNetGenerator.cs index a2d5ed9ce6..269bf534a7 100644 --- a/CodeGen/Generators/UnitsNetGenerator.cs +++ b/CodeGen/Generators/UnitsNetGenerator.cs @@ -172,7 +172,7 @@ private static void GenerateResourceFiles(Quantity[] quantities, string resource $"{resourcesDirectory}/{quantity.Name}.restext" : $"{resourcesDirectory}/{quantity.Name}.{culture}.restext"; - using var writer = CodeGenFile.CreateText(fileName); + using var writer = new StringWriter(); foreach(Unit unit in quantity.Units) { @@ -197,6 +197,8 @@ private static void GenerateResourceFiles(Quantity[] quantities, string resource } } } + + CodeGenFile.WriteAllText(fileName, writer.ToString()); } } } diff --git a/CodeGen/Helpers/CodeGenFile.cs b/CodeGen/Helpers/CodeGenFile.cs index 54554d979b..179c4728cd 100644 --- a/CodeGen/Helpers/CodeGenFile.cs +++ b/CodeGen/Helpers/CodeGenFile.cs @@ -28,10 +28,17 @@ public static string ReadAllText(string path) } /// - /// Writes all text to a generated or codegen-normalized file as UTF-8 without byte order mark. + /// Writes all text to a generated or codegen-normalized file as UTF-8 without byte order mark. The file is + /// only written if its contents changed, so unchanged generated files keep their timestamps and don't trigger + /// rebuilds of the projects that compile them. /// public static void WriteAllText(string path, string contents) { + if (File.Exists(path) && File.ReadAllText(path, Utf8NoBom) == contents) + { + return; + } + File.WriteAllText(path, contents, Utf8NoBom); } diff --git a/CodeGen/README.md b/CodeGen/README.md index 2e25af4925..3f9f51566e 100644 --- a/CodeGen/README.md +++ b/CodeGen/README.md @@ -5,7 +5,11 @@ It is faster, easier to debug and more familiar to C# developers. ## Generating code -Compile and run: +The generator runs after it builds, when the unit definitions or the generator changed, see `CodeGen.csproj`. UnitsNet +references it, so building the solution or any project with generated code runs it. Set `SkipUnitsNetCodeGen=true` to +build without it. + +To run it on its own: ```cmd cd /dev/UnitsNet/CodeGen dotnet run diff --git a/Directory.Build.targets b/Directory.Build.targets index 9ded9b351a..21e49e433d 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -4,4 +4,20 @@ + + + + + + + + + + diff --git a/Docs/adding-a-new-unit.md b/Docs/adding-a-new-unit.md index 92decc0005..381dd1ad12 100644 --- a/Docs/adding-a-new-unit.md +++ b/Docs/adding-a-new-unit.md @@ -7,7 +7,7 @@ So you want to add a quantity or unit that is not yet part of Units.NET? - [Quick Summary of Steps](#quick-summary-of-steps) - [Detailed steps](#detailed-steps) - [1. Add or modify JSON file for a quantity class](#1-add-or-modify-json-file-for-a-quantity-class) - - [2. Run generate-code.bat](#2-run-generate-codebat) + - [2. Build to generate code](#2-build-to-generate-code) - [3. Reopen solution to load all new files](#3-reopen-solution-to-load-all-new-files) - [4. Fix generated test stubs to resolve compile errors](#4-fix-generated-test-stubs-to-resolve-compile-errors) - [5. Run tests](#5-run-tests) @@ -58,7 +58,7 @@ See the [Quantity and Unit Definition Schema](quantity-and-unit-definition-schem To add a quantity or a unit: - Add or change a quantity JSON file. -- Run `generate-code.bat` file. +- Build the solution, which generates the code. - Specify test values for the new units in the generated test code. Not too difficult. Below are the detailed steps. @@ -139,9 +139,11 @@ The only consequence of not specifying `BaseUnits` is that you cannot construct - `VolumeUnit.ImperialGallon` has no `BaseUnits`, because `Volume = Length^3` and there is no length unit that when multiplied three times would result in imperial gallon. - `RatioUnit.DecimalFraction` has no `BaseUnits`, because dimensionless units are not made up by any SI base units. -### 2. Run [generate-code.bat](https://github.com/angularsen/UnitsNet/blob/master/generate-code.bat) +### 2. Build to generate code -To generate unit classes, unit enumerations and base class for tests. +Build the solution with `dotnet build UnitsNet.slnx`, or from your IDE. Building runs CodeGen whenever the JSON files +changed, which generates the quantities, unit enums and test base classes. To regenerate without building, run +`dotnet run --project CodeGen`. ### 3. Reopen solution to load all new files diff --git a/Docs/quantity-and-unit-definition-schema.md b/Docs/quantity-and-unit-definition-schema.md index 9b3752583f..6a490a4cfa 100644 --- a/Docs/quantity-and-unit-definition-schema.md +++ b/Docs/quantity-and-unit-definition-schema.md @@ -356,10 +356,9 @@ be copied into new definitions. After changing a definition: -1. Run `generate-code.bat` or `dotnet run --project CodeGen`. +1. Run `dotnet build UnitsNet.slnx`, which also regenerates the code. 2. Inspect the generated changes. 3. Add or update independently sourced conversion test values. -4. Run `build.bat` or `dotnet build UnitsNet.slnx`. -5. Run the relevant tests. +4. Run the relevant tests. Generated files under `GeneratedCode` must not be edited manually. diff --git a/UnitsNet.slnx b/UnitsNet.slnx index 2a28e31d84..4504fb43cb 100644 --- a/UnitsNet.slnx +++ b/UnitsNet.slnx @@ -16,7 +16,6 @@ - diff --git a/UnitsNet/UnitsNet.csproj b/UnitsNet/UnitsNet.csproj index 00bfb47f1e..35f9a4b5c9 100644 --- a/UnitsNet/UnitsNet.csproj +++ b/UnitsNet/UnitsNet.csproj @@ -58,6 +58,11 @@ + + + + + diff --git a/generate-code.bat b/generate-code.bat deleted file mode 100644 index 5cfd45c76e..0000000000 --- a/generate-code.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -SET scriptdir=%~dp0 -dotnet run --project "%scriptdir%/CodeGen" -pause diff --git a/generate-code.sh b/generate-code.sh deleted file mode 100755 index 4bc038dd4f..0000000000 --- a/generate-code.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -declare -r SCRIPT_DIR=$(realpath "$(dirname "$0")") - -dotnet run --project "$SCRIPT_DIR/CodeGen"