Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ ioxide hands you raw bytes and stays out of HTTP; when you want a framework on t
`ioxide.Kestrel` swaps the transport under an existing ASP.NET Core app with your endpoints
unchanged.

> Linux 6.1+ · .NET 10 / .NET 11 · `0.14.236` - experimental
> Linux 6.1+ · .NET 10 / .NET 11 · experimental

**[Documentation](https://mda2av.github.io/ioxide/)** - architecture, guides, and every example as
runnable code side by side.
Expand Down
15 changes: 14 additions & 1 deletion src/ioxide/IoxideRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@ namespace ioxide;
/// </summary>
public static class IoxideRuntime
{
public const string Version = "0.14.236";
/// <summary>
/// The version of the NuGet package this assembly shipped in.
/// </summary>
/// <remarks>
/// Generated from the Version property in ioxide.csproj by the GenerateVersionSource target
/// there, not written here. As a hand-kept literal it had to be remembered on every release and
/// was not: it still read "0.0.17" - untouched since the 0.0.x days - against packages on
/// 0.13.233, one of four statements of the same number with all four disagreeing (#224).
///
/// Generated rather than read off the assembly at run time: AssemblyInformationalVersionAttribute
/// would be the obvious source, but reflecting over assembly attributes is what Native AOT
/// trims. A const costs nothing and survives trimming.
/// </remarks>
public const string Version = IoxideVersion.Value;

// Wiring (a builder API will eventually wrap this):
// var reactor = new Reactor(id, config); // implements IRingHost
Expand Down
35 changes: 35 additions & 0 deletions src/ioxide/ioxide.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,39 @@
<None Include="../../README.md" Pack="true" PackagePath="/" />
</ItemGroup>

<!--
IoxideRuntime.Version is generated from the Version property above rather than written next to
it in C#. A second literal has to be remembered on every release and was not: it read "0.0.17"
against packages on 0.13.233, having gone untouched since the 0.0.x days (#224).

Generated rather than read back off the assembly at runtime, because reflection over assembly
attributes is exactly what Native AOT trims. This costs nothing at run time - it is a const, so
it inlines - and cannot disagree with the Version above, which is the only thing a release edits.

%3B is an escaped semicolon: MSBuild would otherwise read one as an item separator.
-->
<Target Name="GenerateVersionSource" BeforeTargets="BeforeCompile;CoreCompile">
<PropertyGroup>
<_IoxideVersionFile>$(IntermediateOutputPath)IoxideVersion.g.cs</_IoxideVersionFile>
</PropertyGroup>

<ItemGroup>
<_IoxideVersionLines Include="// &lt;auto-generated /&gt;" />
<_IoxideVersionLines Include="// Written by the GenerateVersionSource target in ioxide.csproj, from its Version property." />
<_IoxideVersionLines Include="// Do not edit, and do not commit: it lives under obj/." />
<_IoxideVersionLines Include="namespace ioxide%3B" />
<_IoxideVersionLines Include="internal static class IoxideVersion" />
<_IoxideVersionLines Include="{" />
<_IoxideVersionLines Include=" public const string Value = &quot;$(Version)&quot;%3B" />
<_IoxideVersionLines Include="}" />
</ItemGroup>

<WriteLinesToFile File="$(_IoxideVersionFile)" Lines="@(_IoxideVersionLines)"
Overwrite="true" WriteOnlyWhenDifferent="true" />

<ItemGroup>
<Compile Include="$(_IoxideVersionFile)" />
</ItemGroup>
</Target>

</Project>
1 change: 1 addition & 0 deletions tests/Ioxide.Tests.Unit/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private static int Main()
{
var runner = new Runner();

VersionTests.Register(runner);
DemuxParseTests.Register(runner);
MessageTests.Register(runner);
ResponseCapTests.Register(runner);
Expand Down
75 changes: 75 additions & 0 deletions tests/Ioxide.Tests.Unit/VersionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Text.RegularExpressions;
using ioxide;

namespace Ioxide.Tests;

/// <summary>
/// <see cref="IoxideRuntime.Version"/> reports the version the package was built as.
/// </summary>
/// <remarks>
/// It used to be a hand-kept literal, and literals rot: it read "0.0.17" against packages on
/// 0.13.233, untouched since the 0.0.x days while three other statements of the same number moved
/// without it (#224). It is now generated from ioxide.csproj's Version property, so the test worth
/// having compares it against that property - if the generator stops running, or someone puts the
/// literal back, these fail.
///
/// No reflection anywhere here, deliberately: the whole reason the version is generated rather than
/// read off AssemblyInformationalVersionAttribute is that Native AOT trims attribute reflection, and
/// a test that reached for it would be testing something the shipped configuration cannot do.
/// </remarks>
internal static class VersionTests
{
public static void Register(Runner runner)
{
runner.Test("version: IoxideRuntime.Version matches ioxide.csproj", () =>
{
string csproj = FindCsproj();
Match declared = Regex.Match(File.ReadAllText(csproj), @"<Version>([^<]+)</Version>");

Assert.True(declared.Success, $"no <Version> element in {csproj}");
Assert.Equal(declared.Groups[1].Value, IoxideRuntime.Version);
});

runner.Test("version: it is a real version, not a placeholder", () =>
{
Assert.True(IoxideRuntime.Version.Length > 0, "Version is empty");

Assert.True(Version.TryParse(IoxideRuntime.Version, out Version? parsed) && parsed is not null,
$"Version is not parseable: '{IoxideRuntime.Version}'");

Assert.True(parsed!.Major > 0 || parsed.Minor > 0 || parsed.Build > 0,
$"Version is all zeroes: '{IoxideRuntime.Version}'");
});

runner.Test("version: no build metadata leaks into it", () =>
{
// The SDK appends "+<commit sha>" to the informational version from the repository's git
// metadata - this build's assembly attribute reads "0.14.236+75c9bf6...". Generating the
// const from the Version property sidesteps that entirely, and this pins it: callers
// compare the value against a package version, and no package is named with a sha on it.
Assert.True(!IoxideRuntime.Version.Contains('+'),
$"build metadata reached the reported version: '{IoxideRuntime.Version}'");
});
}

/// <summary>
/// Walks up from the test binary to the repo root, identified by the solution file beside it.
/// Plain file system, no reflection - see the note on the class.
/// </summary>
private static string FindCsproj()
{
for (DirectoryInfo? at = new(AppContext.BaseDirectory); at is not null; at = at.Parent)
{
if (File.Exists(Path.Combine(at.FullName, "ioxide.slnx")))
{
string csproj = Path.Combine(at.FullName, "src", "ioxide", "ioxide.csproj");

Assert.True(File.Exists(csproj), $"found the repo root at {at.FullName} but no {csproj}");
return csproj;
}
}

throw new Exception(
$"no ioxide.slnx above {AppContext.BaseDirectory} - this test reads the version out of the repo");
}
}
Loading