Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/net48-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
9 changes: 5 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
18 changes: 13 additions & 5 deletions Build/build-functions.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions Build/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
27 changes: 27 additions & 0 deletions CodeGen/CodeGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,31 @@
<PackageReference Include="System.CommandLine.DragonFruit" />
</ItemGroup>

<!--
Generates code from the unit definitions after CodeGen builds. UnitsNet references this project, so building the
solution or any project with generated code keeps that code in sync with the JSON. It only runs when the
definitions or the generator changed since the last run. Skipped in IDE design-time builds, or with
SkipUnitsNetCodeGen=true.
-->
<ItemGroup>
<UnitsNetCodeGenInput Include="$(MSBuildThisFileDirectory)../Common/UnitDefinitions/*.json" />
<UnitsNetCodeGenInput Include="$(MSBuildThisFileDirectory)../Common/UnitRelations.json" />
</ItemGroup>

<Target Name="RunUnitsNetCodeGen"
AfterTargets="Build"
Condition="'$(DesignTimeBuild)' != 'true' and '$(SkipUnitsNetCodeGen)' != 'true'"
Inputs="@(UnitsNetCodeGenInput);$(TargetPath)"
Outputs="$(IntermediateOutputPath)UnitsNetCodeGen.stamp">
<PropertyGroup>
<_UnitsNetCodeGenDotnet>$(DOTNET_HOST_PATH)</_UnitsNetCodeGenDotnet>
<_UnitsNetCodeGenDotnet Condition="'$(_UnitsNetCodeGenDotnet)' == ''">dotnet</_UnitsNetCodeGenDotnet>
<_UnitsNetRepositoryRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..'))</_UnitsNetRepositoryRoot>
</PropertyGroup>

<Exec Command="&quot;$(_UnitsNetCodeGenDotnet)&quot; &quot;$(TargetPath)&quot; --repository-root &quot;$(_UnitsNetRepositoryRoot)&quot;"
StandardOutputImportance="low" />
<Touch Files="$(IntermediateOutputPath)UnitsNetCodeGen.stamp" AlwaysCreate="true" />
</Target>

</Project>
4 changes: 3 additions & 1 deletion CodeGen/Generators/UnitsNetGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -197,6 +197,8 @@ private static void GenerateResourceFiles(Quantity[] quantities, string resource
}
}
}

CodeGenFile.WriteAllText(fileName, writer.ToString());
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion CodeGen/Helpers/CodeGenFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@ public static string ReadAllText(string path)
}

/// <summary>
/// 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.
/// </summary>
public static void WriteAllText(string path, string contents)
{
if (File.Exists(path) && File.ReadAllText(path, Utf8NoBom) == contents)
{
return;
}

File.WriteAllText(path, contents, Utf8NoBom);
}

Expand Down
6 changes: 5 additions & 1 deletion CodeGen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,20 @@
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFrameworkIdentifier)' == '.NETFramework'">
<Compile Include="$(MSBuildThisFileDirectory)NullableAttributes.cs" />
</ItemGroup>

<!--
CodeGen regenerates the code in the GeneratedCode folders after it builds (see CodeGen.csproj). These projects build
after CodeGen through their reference to UnitsNet, so pick up any generated files that were added or removed after
this project was evaluated.
-->
<Target Name="RefreshUnitsNetGeneratedItems"
AfterTargets="ResolveProjectReferences"
Condition="Exists('$(MSBuildProjectDirectory)/GeneratedCode') and '$(DesignTimeBuild)' != 'true'">
<ItemGroup>
<Compile Remove="@(Compile)" Condition="!Exists('%(FullPath)')" />
<Compile Include="GeneratedCode/**/*.cs;CustomCode/*.cs" Exclude="@(Compile)" />
<EmbeddedResource Remove="@(EmbeddedResource)" Condition="!Exists('%(FullPath)')" />
<EmbeddedResource Include="GeneratedCode/Resources/*.restext" Exclude="@(EmbeddedResource)" />
</ItemGroup>
</Target>
</Project>
10 changes: 6 additions & 4 deletions Docs/adding-a-new-unit.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
5 changes: 2 additions & 3 deletions Docs/quantity-and-unit-definition-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 0 additions & 1 deletion UnitsNet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
<File Path="Directory.Build.props" />
<File Path="Directory.Build.targets" />
<File Path="Directory.Packages.props" />
<File Path="generate-code.bat" />
<File Path="init.bat" />
<File Path="LICENSE" />
<File Path="nuget.config" />
Expand Down
5 changes: 5 additions & 0 deletions UnitsNet/UnitsNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
<EmbeddedResource Include="GeneratedCode\Resources\*.restext" />
</ItemGroup>

<!-- Build CodeGen first, which regenerates the code from the unit definitions after it builds. -->
<ItemGroup>
<ProjectReference Include="../CodeGen/CodeGen.csproj" ReferenceOutputAssembly="false" SkipGetTargetFrameworkProperties="true" UndefineProperties="TargetFramework" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
</ItemGroup>
Expand Down
4 changes: 0 additions & 4 deletions generate-code.bat

This file was deleted.

5 changes: 0 additions & 5 deletions generate-code.sh

This file was deleted.

Loading