In the past I’ve generally tried to use the model where I’d put a Go package library and a corresponding single purpose tool inside the same git repository. For example the tool serve-dir exports loghttp, the tool panicparse exports stack.
I like this layout because I can do refactoring of the package’s API and experiment with the tool, ensuring optimal minimal library API. It’s much easier to manage a single git repository than keeping many git repositories in sync, especially before Go modules.
In some cases, like for serve-dir and panicparse, the project was initially a tool, library wasn’t exported initially. The library was exported after the tool was released, only once I found value in a subset of the tool’s code for others to reuse it.
Go module and explicit versioning enables a fail safe way to roll out breaking changes, and it’s great on that front.
I’ve worked in open source for a long time. One observation I had is that it is much harder for community supported (or one person) projects to maintain strict backward compatibility than for commercial software. The unpaid author(s) likely has less time to do forward-looking thoughtful APIs, let alone do code or API reviews. This leads to an interesting situation, where there’s some incentives for unpaid authors to either stick to v0 forever and never provide any guarantee, or bump major version more frequently. The former leads to a much less attractive project, as depending on such project is a liability. The later causes a challenge I hadn’t foreseen with the cmd+library layout I was using.
When I released panicparse v2, I realized that it’s becoming increasingly important to not mix executables and exported libraries in the same git repository, more precisely in the same Go module. I’ve documented my adventure in panicparse-1.6.0 post.
The challenge is that changing APIs in a backward incompatible way requires to
update the major version according to semver, which changes its import path by
inserting a /v2/
in the path.
The problem is that updating a library doesn’t mean that the CLI has user facing changes. It’s particularly problematic for tools where a library was carved out after the fact. Making incremental changes to a tool often means breaking changes in the implementation, and this carving out results in counter productive forced major version bump.
This results in having to tell users to fetch the /v2/
version of the tool,
while in fact this could have been considered a non breaking feature
improvement.
I wish there were an easy answer. For now I fear the only one is to split the library into a separate package.