SDK internals

Source directory

All files that go into an SDK should be placed in a source directory where you’ll run SDKcraft to initialize, define, pack and publish the SDK.

SDK platform

A platform describes where an SDK can be built and installed.

The components describing a platform are:

  • The base image: used to build SDKs and initialize workshops.

  • The CPU architecture: amd64, arm64, armhf, i386, ppc64el, riscv64, or s390x.

The easiest way to define a platform is to name it after the CPU architecture:

sdkcraft.yaml
# ...
base: ubuntu@24.04
platforms:
  amd64:
  arm64:

The above SDK can be built on amd64 or arm64 machines and installed in ubuntu@24.04 workshops with the same architecture.

The base can also be moved into the platform names:

sdkcraft.yaml
# ...
platforms:
  ubuntu@24.04:amd64:
  ubuntu@24.04:arm64:

More complex scenarios can be described using the following attributes:

Key

Value

Description

build-on

array

List of supported CPU architectures that can build the SDK for the platform.

If the SDK has no base or build-base, each entry must be prefixed by a valid base and a colon, e.g., ubuntu@22.04:amd64. This has no effect on the supported build machines, because SDKcraft performs builds in containers. The prefix must match build-for.

build-for

string

CPU architecture the SDK is expected to run on, or all if the SDK can run on all supported architectures. SDK authors are responsible for ensuring compatibility.

If the SDK has no base or build-base, each entry must be prefixed by a valid base and a colon, e.g., ubuntu@24.04:riscv64. The prefix must match every entry in build-on.

Architecture-independent SDKs require the complex format:

sdkcraft.yaml
# ...
platforms:
  all:
    build-on: [amd64, arm64, riscv64]
    build-for: all

SDK parts

Parts can be thought of as the building blocks of Workshop and SDKcraft. Each part in the sdkcraft.yaml definition describes a specific component or piece of the SDK being packaged, providing a way to modularize the package and manage its dependencies.

SDKcraft is built as a craft-application, which affects how parts are implemented. However, note that stage-packages and stage-snaps aren’t enabled yet; instead, rely on the hooks to implement custom logic of package and snap installation.

For a complete reference of parts and their properties, refer to the corresponding Craft Parts documentation section.

SDK plugs and slots

Currently, Workshop and SDKcraft support the following interface plugs:

Slots can only be defined for the mount interface.

Camera interface

A camera plug in the definition must specify the plug name and the interface:

sdk.yaml
 # ...
 plugs:
   <NAME>:
     interface: camera

This makes the host’s cameras directly available inside the workshop as video capture devices.

Note

See the explanation for more details.

Desktop interface

A desktop plug in the definition must specify the plug name and the interface:

sdk.yaml
 # ...
 plugs:
   <NAME>:
     interface: desktop

This makes the host’s Wayland socket directly available inside the workshop.

Note

See the explanation for more details.

GPU interface

A GPU plug in the definition must specify the plug name and the interface:

sdk.yaml
 # ...
 plugs:
   gpu:
     interface: gpu

This makes the host’s GPUs directly available inside the workshop via the GPU pass-through mechanism.

Note

See the explanation for more details.

Mount interface

A mount plug in the definition must specify the plug name, the interface, and the target directory. The plug can specify permissions and ownership for the target, and whether it is read-only:

sdk.yaml
 # ...
 plugs:
   <NAME>:
     interface: mount
     workshop-target: <WORKSHOP DIRECTORY>
     mode: <OCTAL FILE MODE> # optional
     uid: <USER ID> # optional
     gid: <GROUP ID> # optional
     read-only: <true | false> # optional

This mounts a directory automatically created by Workshop on the host to the workshop-target directory. The $SDK variable can be used to refer to the SDK installation path inside the workshop. The host directory will be created under the path designated by the $XDG_DATA_HOME variable. The workshop directory will be created using the given mode, uid, and gid.

A mount slot in the definition must specify the slot name, the interface, and the source directory:

sdk.yaml
 # ...
 slots:
   <NAME>:
     interface: mount
     workshop-source: <WORKSHOP DIRECTORY>

This exposes the workshop-source directory inside the workshop to be mounted to another directory within the workshop. The $SDK variable can be used to refer to the SDK installation path inside the workshop.

Note

See the explanation for more details.

SSH interface

An SSH plug in the definition must specify the plug name and the interface:

sdk.yaml
 # ...
 plugs:
   ssh-agent:
     interface: ssh-agent

This proxies the host’s SSH keys and configuration inside the workshop via a Unix domain socket.

Note

See the explanation for more details.

Tunnel interface

A tunnel plug in the definition must specify the plug name, the interface and optionally an endpoint:

sdk.yaml
# ...
plugs:
  <NAME>:
    interface: tunnel
    endpoint: <ENDPOINT>

Similarly, a tunnel slot in the definition must specify the slot name, the interface and optionally an endpoint:

sdk.yaml
# ...
slots:
  <NAME>:
    interface: tunnel
    endpoint: <ENDPOINT>

When a tunnel interface plug is connected to a slot, clients can connect to the address of the plug. The connection will be forwarded to the address of the slot. Regular SDKs define the workshop side of the connection, leaving the host system to the system SDK.

The supported protocols are TCP, UDP and Unix domain sockets. Unix domain sockets are compatible with TCP, but UDP plugs can only connect to UDP slots.

TCP and UDP endpoints look like <IPv4>:<PORT>/<PROTOCOL> or '[<IPv6>]:<PORT>/<PROTOCOL>'. Workshop doesn’t resolve hostnames, but supports the aliases localhost, ip6-localhost and ip6-loopback.

Unix domain socket endpoints are either paths to a socket file or abstract sockets of the form '@<STRING>'. The $HOME and $XDG_RUNTIME_DIR variables can be used in paths.

Attributes can be abbreviated by omitting tcp and localhost:

Address

Alternatives

127.0.0.1:1234/tcp

localhost:1234/tcp, 1234/tcp, 127.0.0.1:1234, 1234

0.0.0.0:1234/tcp

0.0.0.0:1234

'[::1]:1234/tcp'

ip6-localhost:1234/tcp, ip6-loopback:1234, '[::1]:1234'

127.0.0.1:1234/udp

localhost:1234/udp, 1234/udp

'[::]:1234/udp'

/run/service.sock

'@abstract'

Port numbers may also be omitted, but only on one side of a connection. For such connections, both sides use the same port.

Note

See the explanation for more details.

SDK hooks

Workshop supports the following lifecycle hooks, which can be defined when the SDK is built using SDKcraft:

Name

When Workshop runs it

What it does

setup-base

At workshop launch, workshop refresh: after unpacking the base image and starting the workshop, but before mounting the project directory and connecting plugs and slots.

Configures system packages and services required by the SDK.

setup-project

At workshop launch, workshop refresh, workshop restore: after mounting the project directory and auto-connecting plugs and slots but before the workshop is set to Ready.

Configures the user environment for the SDK to become operational.

save-state

At workshop refresh, workshop restore: before destroying the old workshop.

Saves SDK-specific data to the state directory. The hook itself comes from the old SDK revision.

restore-state

At workshop refresh, workshop restore: after running setup-project hooks for all SDKs.

Restores SDK-specific data from the state directory. The hook itself comes from the new SDK revision.

check-health

At workshop launch: after running setup-project hooks for all SDKs.

At workshop refresh, workshop restore: after running restore-state hooks for all SDKs.

Sets the state of the SDK (okay, waiting or error) using workshopctl, which affects the status of the workshop.

Each hook is defined as a bash script of the same name under hooks/ in the source directory. Inside the workshop, the SDK is mounted at /var/lib/workshop/sdk/<SDK>/ and hooks are stored in the sdk/hooks/ subdirectory. Most hooks run as root and use that subdirectory as the working directory. The exception is setup-project, which runs as the workshop user in the /project/ directory.

A hook can signal an error by returning a nonzero exit code; a zero code indicates success. The options errexit and pipefail are set by default, so most commands which return a nonzero exit code cause the hook to exit with the same code. If --verbose is passed to workshop launch or workshop refresh, the option xtrace is also set.

Note

The hooks aren’t mentioned in the SDK definition; SDKcraft automatically enumerates them when packing the SDK.

An SDK’s position in the workshop definition determines when its hooks execute. SDKs are always processed in the following order: system, user-listed SDKs, sketch. Each hook waits for the previous one to complete before executing.

SDK state

An SDK can store any data specific to it within the workshop. For this purpose, an environment variable named $SDK_STATE_DIR is exposed by Workshop at runtime; it resolves to an internal directory in the workshop, which save-state and restore-state can use to preserve and recover the data respectively.

Note

The $SDK_STATE_DIR variable is only available to the save-state and restore-state SDK hooks. It is not accessible to the workshop user, the SDK itself, or in the workshop definition.

The state directory is a dedicated volume created by Workshop at runtime for each SDK in every workshop, and is removed when the workshop stops. The *-state hooks can use it to store or retrieve any arbitrary data required by the SDK.

SDK channels

When SDKs are published by their creators and consumed by workshops, different versions and releases are tracked through the use of channels. A channel is a combination of a track, a risk, and an optional branch, e.g., latest/beta.

Tracks allow multiple published versions of an SDK to exist in parallel; while no specific scheme is enforced, it is desirable to use a semantic version, e.g., 1.2.3, or the latest keyword, which maps to the latest published version and serves as the default.

Risks represent a choice of maturity levels for a particular track:

  • stable: indicates that the software can be used in production

  • candidate: for software that’s being tested prior to stable deployment

  • beta: for software that can be used outside of production

  • edge: for unstable software that’s still in active development; nothing is guaranteed

Branches are short-lived subdivisions of a channel intended for experimentation, e.g. 1.2.3/edge/issue-56789. After 30 days of no activity, a branch will be closed automatically.

Attention

SDK channels should not be confused with SDK revisions.

See also

Explanation: