How to add a Golang recipe to Yocto

Yocto supports Golang recipes, but they are slightly different to standard ones.

Simple Recipe

For a basic recipe without any dependancies, it is still quite simple.

SUMMARY = "This is a simple example recipe that cross-compiles a Go program."
SECTION = "examples"
HOMEPAGE = "https://golang.org/"

LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "git://go.googlesource.com/example;branch=master;protocol=https"
SRCREV = "32022caedd6a177a7717aa8680cbe179e1045935"
UPSTREAM_CHECK_COMMITS = "1"

inherit go
GO_IMPORT = "golang.org/x/example"
GO_INSTALL = "${GO_IMPORT}/hello"

# New Go versions has Go modules support enabled by default
export GO111MODULE="off"

# This is just to make clear where this example is
do_install:append() {
    mv ${D}${bindir}/hello ${D}${bindir}/${BPN}
}

Some of the key things that are different with a Golang recipe:

You need to inherit go (to bring in the go class) and tell BitBake that this is a Golang recipe.

You need to set the GO_IMPORT variable. This is because the GOPATH is created at ${WORKDIR}/${BPN}-${PV} and so source code is not downloaded to ${WORKDIR}/${BPN}-${PV}/src/ but to ${WORKDIR}/${PN}-${PV}/src/${GO_IMPORT}.

Note also, the resulting binary gets placed in /usr/bin.

A More Complex Golang Recipe with Module Dependancies

When your Golang project contains dependancies, it becomes a little more complex. As BitBake does not access the network outside of the do_fetch() task, it does not pull down the dependancies within the source code. This means you need to include all of the dependant modules in your SRC_URI.

SUMMARY = "This is a simple example recipe that cross-compiles a Go program."
SECTION = "examples"
HOMEPAGE = "https://golang.org/"

LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

PV = "1.0"
PR = "r0"

inherit go
GO_IMPORT = "github.com/ming4real/ini-demo"

# New Go versions has Go modules support enabled by default
export GO111MODULE="off"

S = "${WORKDIR}/git"

SRC_URI = " \
    git://git@github.com/ming4real/ini-demo.git;branch=main;protocol=ssh;name=demo \
    git://git@github.com/go-ini/ini.git;branch=main;protocol=ssh;name=goini;destsuffix=git/src/gopkg.in/ini.v1 \
"

SRCREV_FORMAT = "demo"

SRCREV_demo = "${AUTOREV}"
SRCREV_goini = "${AUTOREV}"
UPSTREAM_CHECK_COMMITS = "1"

# This is just to make clear where this example is
do_install:append() {
    install -d ${D}/usr/local/bin
    mv ${D}${bindir}/ini-demo ${D}/usr/local/bin/${BPN}
}

FILES:${PN} += " \
    /usr/bin \
    /usr/local/bin/${BPN} \
"

The two main changes you need to make are:

Name your Sources

Because there are multiple SRC_URIs, each one needs a unique name so that we can identify it to specify the SRCREVs individually. this is done with the ;name=XXX parameter in the URI.

This also allows you to have individual licences for each source if that is required.

Specify the Destination

Because each package has its GO_IMPORT value, we need to manually place the module into the working tree. This is done with the destsuffix=XXX parameter, where XXX will be git/src/<Module Path>. The module path is the one found in the go.mod file of your project.

As a side note, I have moved my binary to /usr/local/bin out of personal preference to keep things that I create separate from ‘standard’ Linux binaries.