June 5, 2018·4 min read·engineeringgomodulescompatibility

Go 1.10.3 prepares GOPATH builds for versioned imports

A small compatibility change lets the existing go command consume some repositories adopting semantic import versioning before full module integration.

A dependency-management transition has to accommodate people who have not adopted the new tool. A library cannot assume that every consumer upgrades at the same time its author changes the repository. Go 1.10.3, released today, includes a narrowly targeted response to that problem. Alongside library fixes, it teaches the existing go command enough about versioned import paths to consume certain module-oriented source trees.

The release record calls this minimal support for the vgo transition. That qualification is essential. It does not turn Go 1.10 into the proposed module dependency manager. Instead, it adds a compatibility rule for resolving imports in a GOPATH tree. The change lets some authors adopt module declarations without requiring a matching version directory in their repository.

I like compatibility work of this kind because it makes adoption less dependent on synchronized changes across unrelated projects. The practical question is whether a library can improve its packaging while remaining consumable by an existing toolchain. This patch addresses a specific instance of that question.

The import path and repository directory can differ

The change description by Russ Cox explains the mismatch. Under semantic import versioning, a package in a second major version can have a path such as my/thing/v2/sub/pkg. Its author may want the repository to keep that package in sub/pkg, with a module declaration identifying my/thing/v2.

One possible layout puts the code in a literal v2 directory. The change deliberately supports another arrangement as well. Requiring the directory move for the transition would impose work on authors who prefer to develop the new major version at the repository root. A compatibility rule allows the old command to interpret that layout.

The module declaration supplies the evidence for the rule. A repository's go.mod says which module path the source intends to implement. In the example, its declaration is straightforward.

module my/thing/v2

When qualifying source imports my/thing/v2/sub/pkg, the old command can resolve it to the existing GOPATH package my/thing/sub/pkg. The major-version element remains meaningful in the source's intended module identity, while the legacy loader uses the directory where it can find the code.

The loader applies the rewrite conditionally

The Go 1.10.3 package loader shows why this is not a general rule to delete version-looking path elements. ModuleImportPath first requires an importing package with a known root. It also leaves paths without a version element unchanged. The remaining checks look for a go.mod in the importing source tree.

If that context is absent, the loader keeps the original path. Even in the qualifying context, an existing package at the full versioned path takes precedence. The compatibility logic therefore does not needlessly redirect an import that the normal layout already satisfies.

When the full path does not resolve, the loader looks for a module declaration at a candidate prefix. The declared module must match the versioned prefix before the loader removes the version component for legacy lookup. A path component that merely resembles a version is insufficient evidence. The source comments explicitly discuss paths with other version-like components inside them.

These conditions are the mechanism's practical contract. A missing directory alone does not authorize arbitrary reinterpretation. The source tree has to declare the relationship between the versioned module name and the unversioned filesystem location. That makes a small compatibility exception inspectable without requiring the old tool to implement the complete proposed dependency model.

Source retrieval needs compatibility too

The same change makes legacy go get ignore go-import metadata entries whose version-control type is mod. A server can then advertise module-oriented access and ordinary Git access together. The old tool can use the entry it understands without mistaking the new entry for a competing repository definition.

This is a separate part of the transition. Import resolution answers where a package belongs once the source is present. Discovery metadata helps the tool obtain that source. Supporting the new path convention only at the loader would leave the download step as another obstacle. The patch covers both places where the older command meets the new conventions.

I regard the restricted scope as an advantage here. A maintenance release can make existing consumers tolerate a declared layout without claiming to provide version selection. That distinction keeps the upgrade's expected behavior understandable.

The release also includes fixes in crypto/tls, crypto/x509, and strings, as its release record states. Those package names describe additional maintenance work, not evidence that module support has become complete. For a library author using the root-directory layout, the relevant new capability is the conditional mapping of a versioned import to its GOPATH location. Consumers still use the legacy dependency model, with this additional interpretation available when the required declarations are present.