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

cmd/go: should offer a way to abort compilation if a build tag constraint is not met #21673

Closed
theckman opened this issue Aug 28, 2017 · 5 comments

Comments

@theckman
Copy link
Contributor

With the release of Go 1.9, the monotonic time source within time.Time means we can now use time.Time.Sub() to keep track of how long things took (even across leap seconds). For me, this means the code that I had using go:linkname to expose the monotonic time source can be removed from my project and be replaced with time.Now().

However, when removing my usage of this time source I realized that I had no easy way to enforce that consumers of my code build it against a Go runtime version that is safe for them. I can absolutely use build tags to to guard the functionality, but then the build fails because functions in that file are now undefined. The error the developer sees at build time indicates that some of my source code is missing, not that the Go runtime is too old.

If I do create a second file that has copies of the missing functions, that panic when invoked, the incompatibility isn't potentially discovered until runtime.

While I do talk about the runtime version in this example, I think this could be applied to every build tag offered by go build. This means that projects where the author has intentionally not targeted an architecture can have the build fail with a helpful error message. I may be making a wild assumption here, but it's possible this type of functionality may be helpful during the Go 1 => Go 2 transition period too (assuming one will happen).


To solve this problem in a concise way, I ended up writing a library that is released to the public domain that can help with this.[1] The downside is that it then requires you to add a small dependency to your project or copy the code in, which may not be deal depending on your needs.

I've also written a blog post[2] to document this problem, look at some of the solutions to the problem, and then provide an overview of the package I wrote. I'd be happy to add the blog post to the ExperienceReports wiki page but I'm not sure what the best title for the section would be. Should I use a section already there, or do you think I should create a new one with a name like Runtime Compatibility Constraints?

[1] https://github.com/theckman/goconstraint
[2] https://medium.com/@theckman/version-constraints-and-go-c9309be15773

@ianlancetaylor
Copy link
Contributor

It sounds like you already have a workable solution, albeit one that requires a small extra dependency. Since this bug report is against cmd/go, do you have a suggestion for how this could be done in the go tool?

@theckman
Copy link
Contributor Author

@ianlancetaylor Thank you for the quick reply. I noodled on that question a bit while working on the post, and I think there are three ideas that come to mind.

I have two initial ideas that are very closely related, but only vary because I'm unsure which is more clear on what the behavior will be. I touched on briefly on the first idea in the blog post, which would be to add an abort build tag to the utility. You could use it something like this:

// +build go1.9,abort

In this case, if the go1.9 constraint fails it would tell the build utility to abort the build and indicate why the build was aborted.

This way you could also easily do this:

// +build linux,amd64,abort darwin,amd64,abort

Which would build successfully if this was linux-amd64 or darwin-amd64.

That said I've baked my noodle on this approach a bit, and since writing the post last evening I've started to wonder if the ,abort method is a bit in violation of Clear is better than clever proverb.

The second thought I had, which I haven't confirmed is easy from a current code perspective, would be to have a marker at the end of a build tag to indicate that if it's not met we should fail the build:

// +build go1.9:abort

I'm not sure how I feel about this approach because I can't figure out how it'd work for build tags that we combine. // +build linux,amd64:abort?

A third approach, borrowing from Perl, would be to add a new keyword (e.g., use) to define the minimum Go version required. This would probably require the most work, which may not make it worth it.

@akavel
Copy link
Contributor

akavel commented Aug 29, 2017

Some notes:

  • instead of the panics in the +build !go1.9 case, I believe you could use something like below to get a compile time error:

    func stringForThing() string {
    	REQUIRES_GO_1_9
    }
    
  • as to the abort tag, I think the currently proposed "if constraint fails, abort" logic could be troublesome here if there were multiple files with different tags, e.g. foo_linux.go + foo_windows.go — how would abort work then?

@theckman
Copy link
Contributor Author

theckman commented Aug 30, 2017

@akavel Thank you for taking the time to reply. I think this problem is definitely complex, so I enjoy thinking about it more to see if there are other solutions.

For your first point, you're absolutely right. I could reference the constant in those functions to make it fail to build. It's a great suggestion. 👍 I think there is one tradeoff still, and it's only that I still need to duplicate all of the functions that are referenced outside of that file. I was hoping to avoid that.

Hmm. I think maybe the abort usage could be complex than I first thought when it comes to OS restrictions. Although, I'm sort-of having trouble coming up with a concrete example of how the abort tag would be used in a way that would cause problems.

Would an example be that you want to abort if the build if it's not Linux or Windows? If you tried to put the abort flag in either of the files, would it fail the build? On Linux, I don't believe the content of the _windows.go wouldn't be read so the abort tag for // +build windows,abort wouldn't be evaluated.

If you were using this mechanism I don't think you would be able to abort the build in either of those files, possibly because the OS is in the name. This would require a third file without an OS on the name. If we step back and assume the OS isn't on the file (i.e., we have a third file to control this abort logic), can we use abort with how I suggested and abort the build if it's not Linux or Windows?

I think the answer is no, we wouldn't. My current suggestion would fail to provide that because the comma separated tags (e.g., linux,windows) are AND, while space separated are OR. If we inverted how abort worked would that solve it?

In other words, what if we treat the abort like a terminator? If the earlier constraints (tags) were met, and we reach an abort, it aborts the build and specifies which constraints weren't matched. For example:

// if this isn't Go1.9+
// abort the build
// +build !go1.9,abort

You could then also have:

// if this isn't windows
// and this isn't linux
// abort the build
// +build !windows,!linux,abort

You would absolutely have to put this in a third file, if you're going to have OS-specific files, but we could make that something expected and documented.

@ianlancetaylor
Copy link
Contributor

As far as I can see @akavel 's suggestion addresses the core problem.

@golang golang locked and limited conversation to collaborators Mar 29, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants