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"