> Is “noop” the special case you are referring to?
"Well, yes, but actually no". It's _not_ a noop - to publish a new version of a library in GoLang requires tagging a commit in the source code repo. This, in turn, is tricky because there's no way to review a version bump during PR - unlike, say, JavaScript where a PR makes a change to `package.json`'s `version` field (which will be used to determine the version of the resultant built library), there is no in-code representation of what the version "will be when merged" of a change being reviewed. We've resorted to hacking-in a `version.txt` file which is updated and read by our version bump automation.
But that's not all! When making a _major_ version bump in a library, you also need to change the `module` line in your own `go.mod` to have a trailing `/v<n>` (e.g. https://github.com/Masterminds/semver/blob/master/go.mod#L1). Another special case - every other language's generic version bump automation logic is "update <file location> to hold the version", Go's is "IF bump is major, THEN update go.mod to hold the major version".
This is separate from the awkwardness that comes from "publishing" via source code vs. publishing to a binary repository, where the difficulty arises from `go mod download` needing to authenticate to GitHub, whereas every other language's build tools authenticate to the location where built libraries are stored (Artifactory etc.)
So, yeah, in fairness there are actually two separate annoyances here that I mistakenly represented as the same - "recording version in tags makes it impossible to review the version-bump _of_ a change-in-review" and "publishing source code directly, rather than built libraries, means pulling dependencies from private repositories requires messing with authentication". Forgive me - there are just so many friction points in GoLang's development process, it's hard to keep them straight.
> why are you comparing publishing a library in Go to publishing executables in other languages?
Eh - I guess my terminology was off there. I considered and avoided using the word "binary" instead of "executable" because idk if every language uses a binary format to represent the result of building their libraries. The distinction I was trying to draw was between languages that have a transformation process (building/compiling/assembling/whatever) which turns "source code" into "a <thing> that other code can depend on", and GoLang which...doesn't do that. To be explicit - yes, in all cases I'm talking about building and publishing the consumable representation _of_ a library repository.
> Are you talking about publishing breaking changes without bumping the major version?
No, I'm not. I agree that breaking changes need to be accompanied by a Major Version increment. I'm talking about how cumbersome it is in GoLang _to_ bump the major version that you depend on of a dependency library. You don't just have to update the dependency in your go.mod to `github.com/foo/bar v2.0.0`. Because the import path _includes_ the version, you need to update all the `import` statements throughout your code to use the new `github.com/foo/bar/v2` path. Pointless busywork - even worse than the endless `if err != nil` statements which can be defended on the tenuous grounds that they prompt an author to think about how to handle their errors - this import path change is _truly_ useless as a method to catch errors because, if there is a syntax-breaking change in the depended-upon code, that will be caught at build-time _anyway_.
An update to the major version of a library that you depend on is a Big Change, and should be treated with caution. I'm not advocating for doing it blindly or casually. But I don't believe that forcing a basically-cosmetic change to all the import statements _of_ the consumed module does anything to ensure caution.
"Well, yes, but actually no". It's _not_ a noop - to publish a new version of a library in GoLang requires tagging a commit in the source code repo. This, in turn, is tricky because there's no way to review a version bump during PR - unlike, say, JavaScript where a PR makes a change to `package.json`'s `version` field (which will be used to determine the version of the resultant built library), there is no in-code representation of what the version "will be when merged" of a change being reviewed. We've resorted to hacking-in a `version.txt` file which is updated and read by our version bump automation.
But that's not all! When making a _major_ version bump in a library, you also need to change the `module` line in your own `go.mod` to have a trailing `/v<n>` (e.g. https://github.com/Masterminds/semver/blob/master/go.mod#L1). Another special case - every other language's generic version bump automation logic is "update <file location> to hold the version", Go's is "IF bump is major, THEN update go.mod to hold the major version".
This is separate from the awkwardness that comes from "publishing" via source code vs. publishing to a binary repository, where the difficulty arises from `go mod download` needing to authenticate to GitHub, whereas every other language's build tools authenticate to the location where built libraries are stored (Artifactory etc.)
So, yeah, in fairness there are actually two separate annoyances here that I mistakenly represented as the same - "recording version in tags makes it impossible to review the version-bump _of_ a change-in-review" and "publishing source code directly, rather than built libraries, means pulling dependencies from private repositories requires messing with authentication". Forgive me - there are just so many friction points in GoLang's development process, it's hard to keep them straight.
> why are you comparing publishing a library in Go to publishing executables in other languages?
Eh - I guess my terminology was off there. I considered and avoided using the word "binary" instead of "executable" because idk if every language uses a binary format to represent the result of building their libraries. The distinction I was trying to draw was between languages that have a transformation process (building/compiling/assembling/whatever) which turns "source code" into "a <thing> that other code can depend on", and GoLang which...doesn't do that. To be explicit - yes, in all cases I'm talking about building and publishing the consumable representation _of_ a library repository.
> Are you talking about publishing breaking changes without bumping the major version?
No, I'm not. I agree that breaking changes need to be accompanied by a Major Version increment. I'm talking about how cumbersome it is in GoLang _to_ bump the major version that you depend on of a dependency library. You don't just have to update the dependency in your go.mod to `github.com/foo/bar v2.0.0`. Because the import path _includes_ the version, you need to update all the `import` statements throughout your code to use the new `github.com/foo/bar/v2` path. Pointless busywork - even worse than the endless `if err != nil` statements which can be defended on the tenuous grounds that they prompt an author to think about how to handle their errors - this import path change is _truly_ useless as a method to catch errors because, if there is a syntax-breaking change in the depended-upon code, that will be caught at build-time _anyway_.
An update to the major version of a library that you depend on is a Big Change, and should be treated with caution. I'm not advocating for doing it blindly or casually. But I don't believe that forcing a basically-cosmetic change to all the import statements _of_ the consumed module does anything to ensure caution.