Using Go build directives to optionally use new APIs in the standard library

July 18, 2020

I mentioned recently that new APIs in the Go standard library were relatively easy to optionally support, because such new APIs only appear in new Go releases and you can conditionally build files based on the Go release that's building your program. But that's a pretty abstract description, so let's make it concrete.

One of the long time limitations of the crypto/tls package was that it only gave you numbers for TLS cipher suites, not any sort of names, and for logging and reporting things to users you often wanted a name. Go's lack of names for its cipher suite numbers left you to roll your own conversion, generally with a big table of mappings, as I do (or did) in my call program. In Go 1.14, the Go authors fixed this by adding tls.CipherSuiteName(). If I was content to have the latest version of call only build on Go 1.14 or later, I could simply convert it to directly using tls.CipherSuiteName() in place of my current hand done solution. However, for various reasons I would like to keep call building on previous Go versions as well, which means that I need to use my old solution if the new API isn't available.

The first step is to pull out the small function that needs to access the API into a separate file, or create a function to do this if you don't have one already. In my case I might as well call my (new) function cipherSuiteName(). Then we create two versions of this function in two files, let's call them ciphername.go and ciphername_old.go. The second file has the current implementation, for older versions of Go, while the first version has the new implementation that calls tls.CipherSuiteName().

The first file can very simple if all you want to do is directly call the new API and accept its result. It needs a Go build directive to only build on Go 1.14 and later, and can look like this:

// There must be a blank line between the
// build directive and 'package main'.
//
// +build go1.14

package main

import "crypto/tls"

func cipherSuiteName(id uint16) string {
    return tls.CipherSuiteName(id)
}

The second file will have a more complicated implementation, which I'm leaving out here, and a Go build directive to not build on Go 1.14 (and later):

// For pre 1.14 Go versions without
// tls.CipherSuiteName().
//
// +build !go1.14

package main

import "fmt"

func cipherSuiteName(id uint16) string {
   ....
}

My view is that you should use the longer file name for the old implementation, because in the long run you're probably going to delete it (when you stop supporting old Go versions). Depending on what your function in ciphername.go does, you might want to keep it or simply switch over to a direct call to tls.CipherSuiteName().

Useful references for Go build directives are the go/build package documentation and Dave Cheney's How to use conditional compilation with the go build tool.

(This is the kind of entry that I write partly for my later use, because I'm sure I'm going to want to do this for other APIs in the future.)

Written on 18 July 2020.
« Not all sysadmin tools should be silent by default
In praise of ZFS On Linux's ZED 'ZFS Event Daemon' »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Sat Jul 18 23:40:13 2020
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.