Building a gadget snap

Gadget snaps are an essential part of Ubuntu Core. They’re responsible for defining and manipulating device-specific system properties and configuration and, as such, they’re part of both the installation image and operating environment.

See Gadget snaps for information on what they can contain and how they’re configured, and see Types of snap for details on the other types of snap that make up an Ubuntu Core image.

Building a gadget snap enables specific device and customisation options to be set and integrated into an Ubuntu Core image.

The following gadget repositories contain the gadget snap definitions for amd64 (64 bit PC Gadget Snap) and the Raspberry Pi family of devices supported by Ubuntu Core:

There are typically two methods for building a gadget snap, cross building and native building. Cross building is likely the most convenient and performant as the gadget is built within a container on the host machine.

See below for examples of both to build the Raspberry Pi “Universal” Gadget Snap.

Cross building

Cross building allows a gadget to be built on an architecture different from the target. In the following example, we build for armhf on an amd64 host.

Prerequisites

  • An Ubuntu host (20.04 or newer is recommended)
  • Snapcraft

First, clone the appropriate gadget repository, switch to the appropriate branch, make whatever modifications are necessary, and simply run the snapcraft command:

$ git clone https://github.com/snapcore/pi-gadget
$ cd pi-gadget
$ git checkout 20-armhf
[edit the gadget]
$ snapcraft
[...]
Snapped pi_20-1_armhf.snap

By default, snapcraft attempts to build the gadget snap in either an LXD (default) or Multipass container, isolating the host system from the build system. Both Multipass and LXD allow for the build architecture to differ from the run-on architecture, as defined by the architecture stanza in the snapcraft.yaml file for the gadget snap:

architecture
  - build-on: [amd64, armhf]
    run-on: armhf

See Architectures for more details on defining architectures and Image building for instructions on how to build a bootable image that includes the gadget snap.

Native building

This method allows for the gadget snap to be built on the same hardware the gadget is intended for.

Prerequisites

To build the gadget snap:

  1. Install and set up LXD
  2. Launch a fresh instance of Ubuntu 20.04
  3. Within the instance:
    • Install snapcraft
    • Clone the repo, switch to the appropriate build and arch branch
    • Build the gadget with snapcraft
  4. Exit the instance and obtain the snap from within the container

Running the following commands on the Raspberry Pi will perform the above process:

$ sudo snap install lxd
$ sudo lxd init --auto
$ sudo lxc launch ubuntu:20.04 focal
$ sudo lxc shell focal
# snap install snapcraft --classic
# git clone https://github.com/snapcore/pi-gadget/
# cd pi-gadget
# snapcraft --destructive-mode
[...]
Snapped pi_20-1_arm64.snap
# exit
$ lxc file pull focal/root/pi-gadget/pi_20-1_arm64.snap .

See Image building for instructions on how to build a bootable image that includes the gadget snap.

2 Likes

you probably want to attach something like:

# exit
$ lxc file pull focal/root/pi-gadget/pi_*.snap .

to show how to obtain the built snap outside of the container …

2 Likes

Thanks - great suggestion. I’ve added this (and learnt that lxc file pull unfortunately doesn’t support wildcards).

3 Likes

Since the Pi gadget snap specifies building on either amd64 or arm64, is there a reason to check out the branch 20-armhf rather than 20-arm64?

No. I think at the time I wrote this, and tested it, there was a problem with the pi 64bit image. I’ll test it to make sure it now works and update this doc - thanks for flagging this.

Unsurprisingly, there’s a lot of “armhf” references throughout the docs, I’m guessing much of which can be upgraded to arm64.

“By default, snapcraft attempts to build the gadget snap in a Multipass container, isolating the host system from the build system.”

With Snapcraft 7 (at least for core 22), the default is now LXD, not Multipass.

Two more points.

First, with core22, “run-on” is now “build-for”.

Also, when building with LXD, you get the built snap outside the container automatically, no? So there is no need for “lxc file pull”.

“By default, snapcraft attempts to build the gadget snap in a Multipass container …”

These days, I think LXD is the default build provider. You should confirm that.

1 Like

If updating docs to UC22, all references to “run-on” should say “build-for”.

Thanks for the reminder. As I want to test this (UC22 and arm64), I’ve created a separate task to update this doc.

Lots of thoughts on this document; I think it needs a lot of love.

Firstly, I think breaking it up into “cross vs native building” is a bit contrived; it isn’t really the crux of the issue. The problem is more-so “how do I do the thing” in the first place; what does specifying grub as my bootloader mean, for instance. I didn’t know until about two weeks ago…

My proposal is a bit big, but:

Break up into page 1 & 2:
    - trivial:  stage the (reference) snap
         - it's easier, so here we can showcase 
           - including some new interface (pwm,gpio,custom-device), 
           - gadget.yaml should be large focus in this case
     - hard: build u-boot from source, construct boot.scr
           - annoying because we don't produce generic u-boot for ARM
           - upshot: we distribute u-boot payloads for RISC-V :)
           - really get into boot.scr, maybe a generic one (Field has several)

snapd’s grub implementation requires UKI (universal kernel image) so I think page 1 mentions exclusively GRUB, page 2 discusses exclusively u-boot.
No secureboot, no FDE. To complicated, bla.

Good news:

  1. We (Core) have reference gadgets which use GRUB, so the page can act as documentation for those reference gadgets
  2. We (Field) have reference gadgets which use u-boot, so the page can act as documentation for those (non-reference) gadgets – I plan to make Field’s gadget repository public Soon™.

Bad news:

  1. ??? I have to do some work, I guess?