Skip to content

Commit b91dcf6

Browse files
committed
build: preserve semantic version separators
SemVer separates pre-release identifiers and build metadata with periods, but semanticAlphabet omits the period, so normalizeVerString silently collapses beta.rc1 into betarc1. btcwallet is the outlier in its own family: btcd version.go:14 and lnd build/version.go:38 already allow the period, so the same -ldflags "-X ...appBuild=exp.sha.5114f85" produces a different version string in btcwallet than in btcd. This aligns them rather than changing behaviour across the family. appBuild is settable at build time via -ldflags, and SemVer build metadata is dot-separated, so a build stamped with exp.sha.5114f85 currently reports expsha5114f85. Note on the doc comments: semanticAlphabet is a per-character filter, so allowing '.' also lets a malformed arrangement of valid characters through ("beta." now survives where it was previously stripped). btcd and lnd have the same naive filter, so this records the limitation rather than diverging from them.
1 parent eee8e99 commit b91dcf6

2 files changed

Lines changed: 55 additions & 3 deletions

File tree

build/version.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
// semanticAlphabet
14-
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
14+
const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."
1515

1616
// These constants define the application version and follow the semantic
1717
// versioning 2.0.0 spec (http://semver.org/).
@@ -21,13 +21,19 @@ const (
2121
appPatch uint = 1
2222

2323
// appPreRelease MUST only contain characters from semanticAlphabet
24-
// per the semantic versioning spec.
24+
// per the semantic versioning spec. That is necessary but not
25+
// sufficient: semanticAlphabet is a per-character filter and cannot
26+
// reject a malformed arrangement of valid characters, such as a
27+
// trailing or doubled '.', so the value must also be a well-formed
28+
// dot-separated list of non-empty identifiers.
2529
appPreRelease = "alpha"
2630
)
2731

2832
// appBuild is defined as a variable so it can be overridden during the build
2933
// process with '-ldflags "-X main.appBuild foo' if needed. It MUST only
30-
// contain characters from semanticAlphabet per the semantic versioning spec.
34+
// contain characters from semanticAlphabet per the semantic versioning spec,
35+
// and like appPreRelease must also be a well-formed dot-separated list of
36+
// non-empty identifiers, which the per-character filter cannot enforce.
3137
var appBuild string
3238

3339
// Version returns the application version as a properly formed string per the

build/version_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) 2026 The btcsuite developers
2+
// Use of this source code is governed by an ISC
3+
// license that can be found in the LICENSE file.
4+
5+
package build
6+
7+
import "testing"
8+
9+
// TestNormalizeVerString ensures semantic version separators are preserved
10+
// while characters outside the alphabet are still stripped.
11+
func TestNormalizeVerString(t *testing.T) {
12+
t.Parallel()
13+
14+
tests := []struct {
15+
name string
16+
in string
17+
want string
18+
}{
19+
{
20+
name: "dotted pre-release identifier",
21+
in: "beta.rc1",
22+
want: "beta.rc1",
23+
},
24+
{
25+
// appBuild may be set to a dotted value via -ldflags at
26+
// build time.
27+
name: "dotted build metadata",
28+
in: "exp.sha.5114f85",
29+
want: "exp.sha.5114f85",
30+
},
31+
{
32+
// Guards against the filter being dropped entirely: an
33+
// identity function satisfies the cases above.
34+
name: "character outside the alphabet",
35+
in: "beta!rc1",
36+
want: "betarc1",
37+
},
38+
}
39+
40+
for _, test := range tests {
41+
if got := normalizeVerString(test.in); got != test.want {
42+
t.Fatalf("%s: normalizeVerString(%q) = %q, want %q",
43+
test.name, test.in, got, test.want)
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)