Revision as of 16:24, 16 December 2017

This is an alternative proposal to PackagingDrafts/Go. It builds on the hard work of the Go SIG and reuses the rpm automation of PackagingDrafts/Go when it exists, and produces compatible packages.

Instead of relying on an external utility to produce verbose, difficult to understand spec code, it builds the Go integration inside rpm, and factors out common tasks. It tries to limit spec content to items where the packager adds actual value, and makes it easy to adapt to upstream changes. It uses rpm facilities to auto-compute Requires and Provides. Note that Go has not standardized on a common component tool yet, therefore the auto-produced dependencies are highly granular and lacking versionning constrains.

This proposal achieves a drastic reduction of Go spec sizes, up to 90% in some cases, not counting changelog lines. It removes hundreds of high-maintenance lines per spec file. It increases quality by enabling stricter checks in the factored-out code, without relying on packagers to cut and paste the correct snippets in their spec files. It aggressively runs all the unit tests found in upstream projects. It does not try to rely on bundled Go code, to hide upstream renamings, to avoid rebasing packages when their dependencies change.

The proposal has been tested in Rawhide and EL7 over a set of ~ 140 Go packages. This set is a mix of current Fedora packages, bumped to a more recent version, rewrites of Fedora packages, and completely new packages.

no reliance on external utilities to compute code requirements. No more dependencies that do not match the shipped Go code.

no maze of variable indirections. No more packages everyone is afraid of touching.

no maze of cargo-culted and bitrotting shell code. No more packages everyone is afraid of touching.

compatibility with existing packages (though many are so obsolete they need complete replacement)

Limitations

very granular requires/provides, due to the lack of a common packaging format for Go projects.

no automated version constrains on requires, due to the lack of a common packaging format for Go projects.

can not choose the correct commit for the packager, due to the lack of release discipline in many Go projects. Need periodic bumping of all Go packages, followed by a mass rebuild, to avoid getting stuck in the past.

does not eliminate dependency loops, caused by the lack of release discipline in many Go projects. Use bootstraping. Mass rebuilds need two stages.

does not build shared libraries, due to their lack of adoption by most Go projects. Updating a Go component requires the rebuild of all its users. However, this project facilitates the creation of a coherent baseline of Go code, that can be converted to shared libraries later.

gives up on many conventions of current Fedora Go packaging, as they were an obstacle to the target ease of packaging.

Testing the proposal

In rpmbuild and spectool

Drop the files posted here in the following locations

macros-* files: in /usr/lib/rpm/macros.d/

go.attr: in /usr/lib/rpm/fileattrs/

go.prov and go.req: in /usr/lib/rpm/

In EL7

You need to add the following files

Naming

Source packages (src.rpm)

Packages dedicated to the furniture of Go code to other projects, with eventually some ancillary utilities, MUST use a Go-specific name derived from the upstream Go package import path. This name is automatically computed in %{goname} by %gometa.

Packages that provide an application such as etcd MUST be named after the application. End users do not care about the language their applications are written in.

Packages that provide connector code in multiple programming languages SHOULD also be named in some neutral non Go-specific way.

Go code packages: %{goname}-devel

In a dedicated source package

Packages that ship Go code in %gopath should be named %{goname}-devel. If your source package is already named %{goname}, that is easily achieved with:

Do remember that for Go each directory is a package. Never separate the .go files contained in a single directory in different packages (unit tests excepted).

Implementation: %gorpmname

%gometa uses the %gorpmname macro to compute the main %{goname} from %{goipath}.

%gorpmname can produce collisions%gorpmname tries to compute human-friendly and rpm-compatible naming from Go import paths. It simplifies them, removes redundancies and common qualifiers. As a result it is possible for two different import paths to produce the same result. In that case, feel free to adjust this result manually to avoid the collision. And please report the case.

Go utilities: xxx-utils

Ancillary Go utilities (not full applications) should be shipped in xxx-utils packages. The rules are the same as for %{goname}-devel packages. The xxx prefix should be %{goname} or a simplified project identifier.

Go example code: %doc

Example code is usually shipped as %doc in the corresponding %{goname}-devel package. You can also produce a separate-devel package dedicated to the example import path.

Walkthrough

This chapter will present a typical Go spec file step by step, with comments and explanations.

Spec preamble: %{goipath}, %{forgeurl} and %gometa

A Go package is identified by its import path. A Go spec file will therefore start with the %{goipath} declaration. Don't get it wrong, it will control the behaviour of the rest of the spec file.

%global goipath google.golang.org/api

If you’re lucky the Internet hosting of the Go package can be automatically deduced from this variable (typically by prefixing it with https://). If that is not the case, you need to declare explicitly the hosting URL:

If rpmbuild complains later your hosting service is unknown of %forgemeta, please extend this macro.

The %{forgeurl} declaration is followed by Version, %{commit} and %{tag}. Use the combination that matches your use-case. The rules are the same as in Forge-hosted packaging.

%global commit 3a1d936b7575b82197a1fea0632218dd07b1e65c

Commits vs releases You SHOULD package releases in priority. Please reward the projects that make an effort to identify stable code states. Only fall back to commits when the project does not release, when the release is more than six months old, or if you absolutely need one of the changes of a later commit. In the later cases please inform the project politely of the reason you needed to give up on their official releases. Promoting releases is a way to limit incompatible commit hell.

The code versioning information is followed by a clear project description that will be reused in the various rpm packages produced from this spec file.

%{goname}-devel package metadata

%package devel
Summary: %{summary}
BuildArch: noarch
Obsoletes: golang-google-golang-api-devel < 0.100
If the corresponding import path was provided by another package in the past. See also the corresponding guidelines.
Replacing Go -devel packages does not require providing the old package name because they are accessed via golang() dependencies.
%description devel
%{common_description}
This package contains the source code needed for building packages that import
the %{goipath} Go namespace.

%prep: %forgesetup

Assuming you followed the preamble instructions, preparation is reduced to:

%prep
%forgesetup

Followed by eventual patching the usual rpm way.

Vendoring You MUST remove the bundled code eventually shipped by upstream in the vendor directories.

rm -fr vendor

%build: %gobuildroot and %gobuild

If you need to build some Go binaries, use the following pattern:

%gobuildroot%gobuild -o _bin/something %{goipath}/cmd/something

%gobuildroot set ups the build environment and creates _bin. Most Go projects ship commands in a cmd subdirectory.

%install: %{gofindfilter} and %goinstall

The installation phase is reduced to:

%install
gofiles=$(find . %{gofindfilter} -print)
%goinstall $gofiles

You can add more flags after %{gofindfilter}, use your own filter, apply the same pattern to a specific subdirectory.

%goinstall will install Go files in the correct place with default permissions and generate the corresponding devel.file-list. If you wish to generate a separate list (for example when producing separate code packages), just pass the -f flag to the macro:

The %gochecks macro will walk through all the project sub-directories containing .go files and try to execute %{gotest} there.

%gochecks

It is an opinionated and aggressive behavior designed to detect problems as soon as possible. Due to the pervasive use of rapid-changing commits in the Go ecosystem with little release engineering running every possible unit test is a MUST.

You can disable testing in a specific sub-directory by passing it as argument to %gocheck

%gochecks subdir

To disable testing in the root directory use .

%gochecks .

You can also use wildcards

%gochecks '_examples/*'

Common reasons to exclude a directory from testing:

the directory does not actually contain any Go code,

this is an example directory we do not care about,

the unit tests contained in the directory want to access the network,

the unit tests depend on a specific server running in the build environment,

the unit tests depend on another Go package which is not packaged yet (please package it!),

the Go compiler is crashing (please report the Fedora bug!)

upstream confirms the tests should not be ran,

there is some other problem you’re currently investigating with upstream

Remember to add the dependencies needed by the unit tests as BuildRequires in source package metadata. They should be of the following form:

BuildRequires: golang(missing_import_path_Go_complains_about)

Be very careful to note down why you are disabling a particular unit test, with the eventual bug report URL. Do try to enable it again later if the reason is not definitive.

Handling dependency loops

Dependency loops are quite frequent in Go at unit test level. The correct way to handle them is to apply bootstrapping guidelines to disable one of the tests involved in the loop, with the corresponding BuildRequires: