Skip to content

Commit 8a02f8a

Browse files
authored
fix(internal/rust): replace BumpPackageVersion with semver.DeriveNext (#3172)
1 parent 10d73ea commit 8a02f8a

File tree

5 files changed

+191
-108
lines changed

5 files changed

+191
-108
lines changed

internal/librarian/internal/rust/release.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"path/filepath"
2424

2525
"github.com/googleapis/librarian/internal/config"
26+
"github.com/googleapis/librarian/internal/semver"
2627
rustrelease "github.com/googleapis/librarian/internal/sidekick/rust_release"
2728
"github.com/pelletier/go-toml/v2"
2829
)
@@ -82,7 +83,9 @@ func release(cfg *config.Config, name string) (*config.Config, error) {
8283
}
8384

8485
found = true
85-
newVersion, err := rustrelease.BumpPackageVersion(manifest.Package.Version)
86+
// Only ever take a minor version bump.
87+
// TODO(https://github.com/googleapis/librarian/issues/3182): Implement desired pre-1.0.0 semantics.
88+
newVersion, err := semver.DeriveNextOptions{BumpVersionCore: true}.DeriveNext(semver.Minor, manifest.Package.Version)
8689
if err != nil {
8790
return err
8891
}

internal/semver/semver.go

Lines changed: 77 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
// Package semver provides functionality for parsing, comparing, and manipulating
16-
// semantic version strings according to the SemVer 2.0.0 spec.
16+
// semantic version strings according to the SemVer 1.0.0 and 2.0.0 spec.
1717
package semver
1818

1919
import (
@@ -31,7 +31,8 @@ type Version struct {
3131
// Prerelease is the non-numeric part of the prerelease string (e.g., "alpha", "beta").
3232
Prerelease string
3333
// PrereleaseSeparator is the separator between the pre-release string and
34-
// its version (e.g., ".").
34+
// its version (e.g., "."). SemVer 1.0.0 versions do not have a prerelease
35+
// separator.
3536
PrereleaseSeparator string
3637
// PrereleaseNumber is the numeric part of the pre-release segment of the
3738
// version string (e.g., the 1 in "alpha.1"). Zero is a valid pre-release
@@ -41,22 +42,25 @@ type Version struct {
4142
}
4243

4344
// semverV1PrereleaseNumberRegexp extracts the prerelease number, if present, in
44-
// the prerelease portion of the SemVer 1.0.0 version string.
45+
// the prerelease portion of the SemVer 1.0.0 version string. For example, a
46+
// version string like "1.2.3-alpha01" is a SemVer 1.0.0. compliant, numbered
47+
// prerelease - https://semver.org/spec/v1.0.0.html#spec-item-4.
4548
var semverV1PrereleaseNumberRegexp = regexp.MustCompile(`^(.*?)(\d+)$`)
4649

47-
// Parse parses a version string into a Version struct.
48-
func Parse(versionString string) (*Version, error) {
50+
// Parse deconstructs the SemVer 1.0.0 or 2.0.0 version string into a Version
51+
// struct.
52+
func Parse(versionString string) (Version, error) {
4953
// Our client versions must not have a "v" prefix.
5054
if strings.HasPrefix(versionString, "v") {
51-
return nil, fmt.Errorf("invalid version format: %s", versionString)
55+
return Version{}, fmt.Errorf("invalid version format: %s", versionString)
5256
}
5357

5458
// Prepend "v" internally so that we can use various [semver] APIs.
5559
// Then canonicalize it to zero-fill any missing version segments.
5660
// Strips build metadata if present - we do not use build metadata suffixes.
5761
vPrefixedVersion := "v" + versionString
5862
if !semver.IsValid(vPrefixedVersion) {
59-
return nil, fmt.Errorf("invalid version format: %s", versionString)
63+
return Version{}, fmt.Errorf("invalid version format: %s", versionString)
6064
}
6165
vPrefixedVersion = semver.Canonical(vPrefixedVersion)
6266

@@ -68,22 +72,22 @@ func Parse(versionString string) (*Version, error) {
6872
versionCore = strings.TrimSuffix(versionCore, prerelease)
6973
vParts := strings.Split(versionCore, ".")
7074

71-
v := &Version{}
75+
var v Version
7276
var err error
7377

7478
v.Major, err = strconv.Atoi(vParts[0])
7579
if err != nil {
76-
return nil, fmt.Errorf("invalid major version: %w", err)
80+
return Version{}, fmt.Errorf("invalid major version: %w", err)
7781
}
7882

7983
v.Minor, err = strconv.Atoi(vParts[1])
8084
if err != nil {
81-
return nil, fmt.Errorf("invalid minor version: %w", err)
85+
return Version{}, fmt.Errorf("invalid minor version: %w", err)
8286
}
8387

8488
v.Patch, err = strconv.Atoi(vParts[2])
8589
if err != nil {
86-
return nil, fmt.Errorf("invalid patch version: %w", err)
90+
return Version{}, fmt.Errorf("invalid patch version: %w", err)
8791
}
8892

8993
if prerelease == "" {
@@ -106,7 +110,7 @@ func Parse(versionString string) (*Version, error) {
106110
if numStr != "" {
107111
num, err := strconv.Atoi(numStr)
108112
if err != nil {
109-
return nil, fmt.Errorf("invalid prerelease number: %w", err)
113+
return Version{}, fmt.Errorf("invalid prerelease number: %w", err)
110114
}
111115
v.PrereleaseNumber = &num
112116
}
@@ -115,7 +119,7 @@ func Parse(versionString string) (*Version, error) {
115119
}
116120

117121
// String formats a Version struct into a string.
118-
func (v *Version) String() string {
122+
func (v Version) String() string {
119123
version := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
120124
if v.Prerelease != "" {
121125
version += "-" + v.Prerelease
@@ -126,44 +130,6 @@ func (v *Version) String() string {
126130
return version
127131
}
128132

129-
// incrementPrerelease increments the pre-release version number, or appends
130-
// one if it doesn't exist.
131-
func (v *Version) incrementPrerelease() {
132-
if v.PrereleaseNumber == nil {
133-
v.PrereleaseSeparator = "."
134-
// Initialize a new int pointer set to 0. Fallthrough to increment to 1.
135-
// We prefer the first prerelease to use 1 instead of 0.
136-
v.PrereleaseNumber = new(int)
137-
}
138-
*v.PrereleaseNumber++
139-
}
140-
141-
func (v *Version) bump(highestChange ChangeLevel) {
142-
if v.Prerelease != "" {
143-
// Only bump the prerelease version number.
144-
v.incrementPrerelease()
145-
return
146-
}
147-
148-
// Bump the version core.
149-
// Breaking changes and feat result in minor bump for pre-1.0.0 versions.
150-
if (v.Major == 0 && highestChange == Major) || highestChange == Minor {
151-
v.Minor++
152-
v.Patch = 0
153-
return
154-
}
155-
if highestChange == Patch {
156-
v.Patch++
157-
return
158-
}
159-
if highestChange == Major {
160-
v.Major++
161-
v.Minor = 0
162-
v.Patch = 0
163-
return
164-
}
165-
}
166-
167133
// MaxVersion returns the largest semantic version string among the provided version strings.
168134
func MaxVersion(versionStrings ...string) string {
169135
if len(versionStrings) == 0 {
@@ -214,18 +180,73 @@ func (c ChangeLevel) String() string {
214180
return [...]string{"none", "patch", "minor", "major"}[c]
215181
}
216182

217-
// DeriveNext calculates the next version based on the highest change type and current version.
218-
func DeriveNext(highestChange ChangeLevel, currentVersion string) (string, error) {
183+
// DeriveNextOptions contains options for controlling SemVer version derivation.
184+
type DeriveNextOptions struct {
185+
// BumpVersionCore forces the version bump to occur in the version core,
186+
// as opposed to the prerelease number, if one was present. If true, and
187+
// the version has a prerelease number, that number will be reset to 1.
188+
//
189+
// Default behavior is to prefer bumping the prerelease number or adding one
190+
// when the version is a prerelease without a number.
191+
BumpVersionCore bool
192+
}
193+
194+
// DeriveNext determines the appropriate SemVer version bump based on the
195+
// provided [ChangeLevel] and the provided [DeriveNextOptions].
196+
func (o DeriveNextOptions) DeriveNext(highestChange ChangeLevel, currentVersion string) (string, error) {
219197
if highestChange == None {
220198
return currentVersion, nil
221199
}
222200

223-
currentSemVer, err := Parse(currentVersion)
201+
version, err := Parse(currentVersion)
224202
if err != nil {
225203
return "", fmt.Errorf("failed to parse current version: %w", err)
226204
}
227205

228-
currentSemVer.bump(highestChange)
206+
// Only bump the prerelease version number.
207+
if version.Prerelease != "" && !o.BumpVersionCore {
208+
// Append prerelease number if there isn't one.
209+
if version.PrereleaseNumber == nil {
210+
version.PrereleaseSeparator = "."
211+
212+
// Initialize a new int pointer set to 0. Fallthrough to increment
213+
// to 1. We prefer the first prerelease to use 1 instead of 0.
214+
version.PrereleaseNumber = new(int)
215+
}
216+
217+
*version.PrereleaseNumber++
218+
return version.String(), nil
219+
}
220+
221+
// Reset prerelease number, if present, then fallthrough to bump version core.
222+
if version.PrereleaseNumber != nil && o.BumpVersionCore {
223+
*version.PrereleaseNumber = 1
224+
}
229225

230-
return currentSemVer.String(), nil
226+
// Breaking changes result in a minor bump for pre-1.0.0 versions.
227+
if highestChange == Major && version.Major == 0 {
228+
highestChange = Minor
229+
}
230+
231+
// Bump the version core.
232+
switch highestChange {
233+
case Major:
234+
version.Major++
235+
version.Minor = 0
236+
version.Patch = 0
237+
case Minor:
238+
version.Minor++
239+
version.Patch = 0
240+
case Patch:
241+
version.Patch++
242+
}
243+
244+
return version.String(), nil
245+
}
246+
247+
// DeriveNext calculates the next version based on the highest change type and
248+
// current version using the default [DeriveNextOptions]. This is a convenience
249+
// method.
250+
func DeriveNext(highestChange ChangeLevel, currentVersion string) (string, error) {
251+
return DeriveNextOptions{}.DeriveNext(highestChange, currentVersion)
231252
}

0 commit comments

Comments
 (0)