For some time now, the Fedora Go Special Interest Group (hereinafter “Go SIG”)
has been building Go packages based on individually Go modules installed into
$GOPATH/src and packaged into RPMs instead of using Go modules.
This post explains why I think the current system is broken and explores another
option: vendoring.
Note
This data was generated by rough fedrq queries. There may be a couple
packages missing that depend on golang in irregular ways.
Currently, the Fedora Go SIG maintains 1858 library packages from 1716
source packages. We maintain 556 application packages (i.e., packages that
provide binaries / applications that end users can run) from 459 source
packages. Considering that some source packages provide library
applications and library packages, that amounts to a total 1851 source
packages.
Evidently, there is a large imbalance between the amount of library
packages that we maintain versus the amount of applications we maintain.
With quite few active members of the Go SIG, it proves quite difficult to keep such a
large number of package in proper shape.
Maintaining these library packages is not exactly straightforward.
The Go packaging ecosystem is nonideal for distributions that wish to maintain
each library as a separate package—it was never designed that way.
A fair number of libraries do not publish tagged releases,
so each project depends on an arbitrary commit of those libraries.
Other widely used libraries, such as the golang-x-*
projects, only have 0.Y.Z beta
releases, despite being depended on by a large number (494) of source packages.
(This is a recent development; for a while, these packages did not have tagged
releases either.)
Furthermore, the Go module ecosystem lacks a way for packages to set compatible version
ranges like, for example, the Python and Rust crate ecosystems.
Each project just sets a single version in the go.mod file.
We cannot rely on upstream metadata or stable versions to help us determine
whether an update is incompatible with other packages in the distribution like
other Fedora language ecosystems do.
These inherent issues with the Go package ecosystem design makes it quite easy
to break other packages when updating libraries.
See the Fedora Release Engineering tracker for how many times we have had to
revert library updates that ended up breaking many other packages.
As of late, we have struggled to keep packages functional and up to date.
However, I’d be remiss if I did not acknowledge the heroic work of Go SIG
members who have worked on cutting the amount of the packages that are out of
date or that fail to build from source.
One solution is to run impact checks in Copr and rebuild reverse dependencies before
updating any Go library, but this is quite time consuming with such a large
number of packages maintained by so few people.
Impact checks are not foolproof either, as when libraries completely remove
import paths, Copr will just pull old library versions.
Currently, our packaging tooling—namely, go-rpm-macros—does not support Go modules,
as the module system is not exactly amendable to using a local registry of
bundled dependencies.
The effort to modify the tooling in https://pagure.io/GoSIG/go-sig/issue/35 is
stalled.
We still rely on the deprecated GO111MODULE=off
mode which does not work with
some newer packages that use sub-Go modules without a lot of finagling.
One solution is to vendor libraries within each package.
This involves running go vendor
, creating an archive containing the vendor
directory, and uploading that to the lookaside cache as a secondary source, in
addition to the project’s primary source archive.
Note that we cannot download Go modules inside the main build process, as
Fedora’s buildsystem builds packages with network access disabled.
Vendoring removes the need to maintain so many library packages and ensure that
they are compatible with each other.
If we retired all the library packages, we would be able to significantly
reduce our maintenance burden.
Vendoring also allows us to enable Go modules and stop using the deprecated
GO111MODULE=off
mode.
However, vendoring is not without its issues.
For one thing, following Fedora’s Bundled Software Policy and Licensing
Guidelines is more difficult with vendored packages.
Packagers need to account for the license identifiers of all the vendored
code and construct a cumulative SPDX identifier to add to the License:
tag.
Packagers also must include all license files from each vendored library
contained within the package.
Re-performing a license audit after each update is tedious.
The Bundled Software Policy also requires including
bundled(golang(PATH)) = VERSION
Provides for each vendoring library.
Handling library vulnerabilities can also be difficult: each package needs to
be manually updated and patched to use the new library version.
I have worked on tooling to mitigate these issues.
We have had an RPM generator to automatically generate the aforementioned
bundled()
Provides for some time now.
I started developing the go-vendor-tools project. See my report from this
week for more information about the tool.
It aims to make creating vendor archives and handling licensing a relatively
frictionless process.
The tooling will also allow each packager to regenerate vendor tarballs with
new library versions to mitigate security vulnerabilities.
Using the builtin tooling (i.e., go mod vendor
) with some helper scripts to
vendor Go modules is more practical than attempting to work around the
intractable issues with unbundling a software ecosystem that is not at all
designed for that purpose.
- Updated 2024-03-25: Add section about Go modules