Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go: Use new type for all semantic versions #16460

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Go: Use new type for all semantic versions #16460

wants to merge 10 commits into from

Conversation

mbg
Copy link
Member

@mbg mbg commented May 8, 2024

Context

Throughout the Go autobuilder, we handle semantic versions. The semver package has no dedicated type for them and all functions from the package work on string values. This makes it difficult to understand where in the code we have something that we know is a valid semantic version and where we might have a string value that isn't. This is made worse by semver requiring all semantic versions to have a v prefix.

What this PR does

We introduce a new SemVer interface, backed by an implementation based on a private type, which is intended to guarantee that a SemVer value is a valid semantic version. Since the type is not exported, the NewSemVer function must be used to construct SemVer values. This function expects any version string as input and formats it according to semver's requirements.

All existing code in the autobuilder is modified to use the SemVer type whereever we would previously have used functions from the semver package.

How to review

Best reviewed commit-by-commit.

@mbg mbg self-assigned this May 8, 2024
@mbg mbg requested a review from a team as a code owner May 8, 2024 22:17
@github-actions github-actions bot added the Go label May 8, 2024
Copy link
Contributor

@owen-mc owen-mc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This largely looks good. Only one important comment, and quite a few questions to check that you've thought about the consequences of some changes.

go/extractor/toolchain/toolchain.go Outdated Show resolved Hide resolved
go/extractor/util/semver.go Outdated Show resolved Hide resolved
go/extractor/util/semver.go Outdated Show resolved Hide resolved
go/extractor/util/semver_test.go Outdated Show resolved Hide resolved
go/extractor/util/semver.go Show resolved Hide resolved
if v.goEnvVersionFound {
v.goEnvVersion = toolchain.GetEnvGoVersion()[2:]
}
v.goEnvVersion = toolchain.GetEnvGoSemVer()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've lost a call to toolchain.IsInstalled(). Are you sure that that isn't needed? Do we have any tests that cover that case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Thanks for spotting this.

toolchain.IsInstalled() just looks up the path of the go executable with exec.LookPath and returns true or false depending on whether that succeeded.

Meanwhile, toolchain.GetEnvGoSemVer() calls toolchain.GetEnvGoVersion() which invokes go version. Unlike toolchain.IsInstalled() this fails if something goes wrong.

So, if go is not installed, then previously we would have ended up with no goEnvVersion, but now that would actually terminate the process. So that's definitely a problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With respect to tests, I have some work on testing resolve build-environment specifically. I will try to get that done ASAP as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to expedite the tests - I just meant that you could add it to your list of things that need testing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The work on those tests is almost done (I did 99% of it a few weeks ago) and they would have caught this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually those tests wouldn't have caught it either, because there is no nice way to simulate go not being installed in an integration test. I played around with different approaches, but I am not too keen on any of them. Happy to discuss the options I tried with you.

I instead looked at migrating our old resolve build-environment tests from when we initially worked on the feature to the internal repo, and have a draft PR for that there now.

I am also wondering if GetEnvGoSemVer should really use log.Fatalf or if we could just get away with returning nil or throwing it at util.NewSemVer to see if it can make sense of it. We'd have to consider the downstream implications of doing that, though.

For now, I have addressed this specific issue by restoring the check for toolchain.IsInstalled()

// A value indicating whether a version string was found
Found bool
}
type GoVersionInfo = util.SemVer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're now combining what used to be two different states: "found a go version, but it was invalid" and "didn't find a go version". Is it okay that we can't distinguish those any more?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I checked to see if there were any cases where we cared about that distinction, but I'll check again to make sure.

// The version in the `go.mod` file is above the supported range. There is no Go version
// installed. We install the maximum supported version as a best effort.
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion +
") is above the supported range (" + minGoVersion + "-" + maxGoVersion +
msg = "The version of Go found in the `go.mod` file (" + v.goModVersion.String() +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(musing) It's a shame that we have to have all these .String()s everywhere. If we were printing directly we could use %s and the String() method would automatically be called on the argument (I believe). But that would involve major refactoring.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree. While I was doing this I thought about replacing all of this with format strings. However, that seemed a bit unrelated to the work in this PR and I didn't want to make the diff even worse than it already is. We could look at doing that separately, though.

owen-mc
owen-mc previously approved these changes May 14, 2024
@mbg
Copy link
Member Author

mbg commented May 15, 2024

After the set of most recent changes, some things to check:

  • Do we miss out on Canonical anywhere now that it is no longer in NewSemVer?
  • Is there anywhere else that needs StandardSemVer()?

@mbg mbg requested a review from owen-mc May 15, 2024 11:41

// the version string may already contain a "-";
// if it does, drop the "-" since we add it back later
if version[rcIndex-1] != '-' {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: do we need to worry about the possibility that rcIndex is 0?

// the "go" prefix, and ones with the "v" prefix. Go's non-semver-compliant release candidate
// versions are also automatically corrected from e.g. "go1.20rc1" to "v1.20-rc1". If given
// the empty string, this function return `nil`. Otherwise, for invalid version strings, the function
// prints a message to the log and exits the process.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain why we don't call semver.Canonical on the output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants