add specialized parsing for utsname.release to std.SemanticVersion

This commit is contained in:
Thibault Leclercq 2025-08-26 14:29:07 +02:00
parent b409cdf63f
commit 7f76c170e7
2 changed files with 37 additions and 5 deletions

View file

@ -140,6 +140,21 @@ pub fn parse(text: []const u8) !Version {
return ver;
}
/// Parse versions coming from `uname` releases that are not fully semver compliant: a '+' sign can be appended without a build version.
/// See https://github.com/torvalds/linux/blob/8f5ae30d69d7543eee0d70083daf4de8fe15d585/scripts/setlocalversion#L197-L210
pub fn parseUtsnameRelease(release: []const u8) !Version {
// Directly parse release if no '+'.
const build_index = std.mem.indexOfScalar(u8, release, '+') orelse return parse(release);
if (build_index == release.len - 1) {
// If '+' is the last character, strip it.
return parse(release[0..build_index]);
}
// There is a '+', but it is not the last character. Keep everything.
return parse(release);
}
fn parseNum(text: []const u8) error{ InvalidVersion, Overflow }!usize {
// Leading zeroes are not allowed.
if (text.len > 1 and text[0] == '0') return error.InvalidVersion;
@ -195,7 +210,10 @@ test format {
"1.0.0+0.build.1-rc.10000aaa-kk-0.1",
"5.4.0-1018-raspi",
"5.7.123",
}) |valid| try std.testing.expectFmt(valid, "{f}", .{try parse(valid)});
}) |valid| {
try std.testing.expectFmt(valid, "{f}", .{try parse(valid)});
try std.testing.expectFmt(valid, "{f}", .{try parseUtsnameRelease(valid)});
}
// Invalid version strings should be rejected.
for ([_][]const u8{
@ -257,17 +275,24 @@ test format {
"+4",
".",
"....3",
}) |invalid| try expectError(error.InvalidVersion, parse(invalid));
}) |invalid| {
try expectError(error.InvalidVersion, parse(invalid));
try expectError(error.InvalidVersion, parseUtsnameRelease(invalid));
}
// Valid version string that may overflow.
const big_valid = "99999999999999999999999.999999999999999999.99999999999999999";
if (parse(big_valid)) |ver| {
try std.testing.expectFmt(big_valid, "{f}", .{ver});
} else |err| try expect(err == error.Overflow);
if (parseUtsnameRelease(big_valid)) |ver| {
try std.testing.expectFmt(big_valid, "{f}", .{ver});
} else |err| try expect(err == error.Overflow);
// Invalid version string that may overflow.
const big_invalid = "99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12";
if (parse(big_invalid)) |ver| std.debug.panic("expected error, found {f}", .{ver}) else |_| {}
if (parseUtsnameRelease(big_invalid)) |ver| std.debug.panic("expected error, found {f}", .{ver}) else |_| {}
}
test "precedence" {
@ -298,3 +323,12 @@ test "zig_version" {
const compatible = comptime @import("builtin").zig_version.order(older_version) == .gt;
if (!compatible) @compileError("zig_version test failed");
}
test "utsname release version" {
// Non compliant utsname release version strings should be correctly parsed.
for ([_][2][]const u8{
.{ "0.0.4", "0.0.4" },
.{ "0.0.4+", "0.0.4" },
.{ "0.0.4+build-info", "0.0.4+build-info" },
}) |valid| try std.testing.expectFmt(valid[1], "{f}", .{try parseUtsnameRelease(valid[0])});
}

View file

@ -218,9 +218,7 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
.linux, .illumos => {
const uts = posix.uname();
const release = mem.sliceTo(&uts.release, 0);
// The release field sometimes has a weird format,
// `Version.parse` will attempt to find some meaningful interpretation.
if (std.SemanticVersion.parse(release)) |ver| {
if (std.SemanticVersion.parseUtsnameRelease(release)) |ver| {
var stripped = ver;
stripped.pre = null;
stripped.build = null;