Integrating with Linux package management systems #1214
Description
Hi,
I'm looking at integrating Go software in my Linux OS package management system.
The basic reason is that Go projects can and do use resources (data, apps, libs) not released as Go projects, and non-Go projects may wish to depend on Go software. A language-specific solution only works as long as one stays within the language ecosystem. And experience shows that subsuming everything else in a language-specific ecosystem is too much work.
In other words I want to jump to step 7 of ¹, while the Go community is working here on step 6.
The targeted system is rpm/dnf (used in Fedora / Red Hat / Centos). rpm has a long history of integrating with multiple language-specific dependency tools. And what works for rpm should work in other Linux systems since years of intense competition made those systems converge on the same features.
To integrate cleanly, I need the following properties:
- standard deployment paths or filenames, so rpm detects it needs to run a language-specific dep solver
- a command that, when fed the detected paths, reads the content of those paths and outputs the identity of the software artifact (canonical name, version, and other attributes)
- a command that, when fed the detected paths, reads the content of those paths and outputs the id and constrains of artifacts needed to use those
Since Go projects have a standard structure in GOPATH, 1. is already satisfied.
I can simulate 2 since creating a package in rpm requires collecting most of the same metadata (though it would be nice to check if the metadata declared by the packager agrees with what the software object thinks about itself)
I could probably write my own Gopkg.toml parser to do 3., but I understand the file format is not stuck in stone and anyway it is much preferred to leave langage-specific magic to langage-specific tools.
As additional constrains, neither 2 nor 3 should change files or perform network calls. 2. because we really do not want to ship material altered from upstream in Fedora, and 3 because we want builds to be reproducible and auditable, allowing network state to change the result is a nogo. I understand that means it won't be possible to sort git commits, so for go packages that declare git commit as deps, we'll have to either force this specific commit or allow any commit in deps.
Therefore I'd like go dep to grow a pure dependency analysis mode with no downloads, no network access and no file writing.
If necessary I can run a preprocessor with file writing but not network access. However some projects won't like that we install the preprocessed files in addition to their stuff, so a pure passive command is preferred.
A dependency in rpm is a simple
- artifactid
- artifactid = version
- artifactid < version
- artifactid >= version (and so on)
Assuming version uses the usual sanity rules, ie 2 > 2.10 > 2.1.1 etc
More advanced syntax is available but 99% of software lives happily without it
(http://rpm.org/user_doc/boolean_dependencies.html, http://rpm.org/user_doc/dependencies.html)
Currently the packager declares dependencies manually in the golang namespace, so
github.com/matttproud/golang_protobuf_extensions/pbutil will provide the following
- golang(github.com/matttproud/golang_protobuf_extensions/pbtest) = 1.0.0
and a code that needs it will require
- golang(github.com/matttproud/golang_protobuf_extensions/pbtest) or
- golang(github.com/matttproud/golang_protobuf_extensions/pbtest) > 0.9 (to prohibit old versions) or
- golang(github.com/matttproud/golang_protobuf_extensions/pbtest) = 1.0.0 (to use this specific version)
For the attributes that impact dependency selection but do not participate to canonical version sort I suppose that we'll have to use slightly different tokens.
For branches;
- golang(something)
- golang(something)(branch=mybranch)
with consumers choosing the token they want to match on
For commits
- golang(something)
- golang(something)(commit=mycommit)
For commits on specific branches
- golang(something)
- golang(something)(branch=mybranch)
- golang(something)(commit=mycommit)
For freestyle version modifiers
- golang(something)
- golang(something)(vermod=alphax)
As an additionnal complexity we're moving unit tests (*_test.go) in specific packages so ii'd be nice if there was a way for the dependency generator to only emit deps for tests or only emit deps without tests.
Would it be possible for go dep to emit something like this? It does not need to match this syntax exactly, as long as there is one line for each dep and the syntax is sane I can rewrite it to rpm format in rpm routines
¹
As far as I've seen most languages go through the following phases as they mature:
- I don't need no version or dep management, my latest code is always perfect
- Rewriting the world is too much work, I need something to import other's code, the latest version will do
- @#* I'm sick of others committing broken code or breaking compat, I need something to import other's code and clamp it to a stable baseline that only needs testing once
- @#* My baseline is missing latest fixes/features, my other_project uses about the same with more recent/old versions, I need something to import other's code, figure safe upgrade paths from the latest baseline I tested, and update it to a safe baseline I can share among my projects
- People roll competing systems, and major angst ensues when they try to share code
- The language ecosystem grows an official solution
5.a or 6.a The language grows a shared library system. Why rebuild all the time the same code once you have a system that agrees on what is the "good" code version. Plus shipping the same binary objects multiple times sucks for embedded systems and microservices) - Linux people map the official language solution in their system-level software management, so language apps can appear in the system app store, use other apps in the store and be used by other apps in the store
The whole process can take more or less time depending on the language community values and vested interests battling any official solution so they can sell their own management addons, provide consulting to build/install badly integrated code, or profit from bad integration to reinvent wheels because accessing existing wheels is too hard.