Customize lifecycle steps and project variables

Snapcraft ships with craftctl, a utility for executing steps in parts. When defining a part, you can use it to customise the part’s files and build environment before and after a step.

Declare an override key

You can customise the lifecycle steps for any part in your snap. Overriding the step is achieved through the part override-* keys, which execute a scriptlet at that step. The scriptlet is executed with /bin/bash.

If an override key is declared at all, it entirely replaces the step’s normal behavior. Instead, whatever code in the key, even no code, is executed.

In the general case, you can override any step with this structure:

parts:
  <part-name>:
    override-<step>: |
      # Scriptlet code

Replace <step> with the name of a step in the lifecycle.

Override a step

Craftctl comes in most handy when when you need to tweak files or settings before or after executing a step.

To specify where the regular step behavior occurs inside the scriptlet, call it with the special craftctl default command. Using that command as an anchor point, alter the build files or environment with other commands before or after it.

Don’t forget to execute the step

If you don’t call craftctl default inside a scriptlet, the step is skipped, and the part doesn’t finish building.

Let’s say you want to modify a file copied from a part’s source. You would add an override for the pull step with the override-pull key, run the default step with craftctl, then change the copied source file.

The following example does just that. The icon path inside the part’s desktop file in the is tweaked after downloading it, but before Snapcraft builds it.

parts:
  gnome-text-editor:
    source: https://gitlab.gnome.org/GNOME/gnome-text-editor
    override-pull: |
      craftctl default
      sed -i.bak -e 's|Icon=@app_id@$|Icon=snap.gnome-text-editor.icon|g' data/org.gnome.TextEditor.desktop

Override project variables

With craftctl, you can dynamically set project variables specific to Snapcraft – distinct from environment variables – within an override scriptlet. Set values with:

craftctl set <key>=<value>

You can also retrieve the current value of a project key, with:

craftctl get <key>

The version and grade keys are supported.

To incorporate retrieved package information correctly, point adopt-info to the part that runs craftctl set. Without such a connection, the snap fails to build. You can find more guidance on sourcing information in External package information.

Example solutions

For example, if you want to set the version key of a snap to the latest Git tag in a part’s source, declare this override:

Project file
adopt-info: gnome-text-editor
parts:
  gnome-text-editor:
    source: https://gitlab.gnome.org/GNOME/gnome-text-editor
    override-pull: |
      craftctl default
      craftctl set version=$(git describe --tags --abbrev=10)

For another example, let’s say a snap has a manually-set version number in the project file. To append the latest Git commit hash to the version, declare this override:

Project file
version: "1.0"
adopt-info: gnome-text-editor
parts:
  gnome-text-editor:
    override-stage: |
      craftctl default
      craftctl set version="$(craftctl get version)-$(git rev-parse --short HEAD)"

Access project variables across parts and components

You can further use craftctl to get and set custom variables within different components using a combination of the adopt-info tag and a selector syntax. For example:

Project file
version: "1.0"
adopt-info: my-library

components:
  debug-symbols:
    type: standard
    adopt-info: my-binary

parts:
  my-library:
    source: ./lib
    plugin: dump
    override-pull: |
      craftctl set version="$(cat VERSION.txt)"
      craftctl default
    override-build: |
      craftctl get version

  my-binary:
    source: ./src
    plugin: dump
    override-pull: |
      craftctl set components.debug-symbols.version="$(cat VERSION.txt)"
      craftctl default

The components.debug-symbols.version selector picks up the component’s version, which is distinct from the version variable set in the my-library part.

Note also how we use adopt-info at both the top level, and within a component. In this example, the project variables set in the my-binary part will be included in the scope of the debug-symbols component.