Developers have been using the Craft tools, such as Snapcraft and Rockcraft, to package software for years. After setting up a project file, a popular next step is to automate builds using continuous integration (CI), whether to publish new releases or just to check that packaging still works. The most popular Craft tools have reusable workflows that automate this process.
Today, the Starcraft team is working on improving the workflows for projects building packages with the Craft tools. These workflows were designed long ago and are due for a face lift. We’re changing our approach by moving from purely functional GitHub Actions integrations to a unified, robust collection of actions that feel more familiar and consistent for developers. Let’s talk about how we planned this upgrade, what we’ve done, and what our next steps will look like.
The current state of CI actions
Today, there are GitHub Actions for Snapcraft, Rockcraft, and Charmcraft. Each was written independently and shares little with their counterparts from the other tools. The pack action for Rockcraft is written in TypeScript and can pack with Ubuntu Pro features and run the test subcommand. The pack action for Charmcraft, however, is written as a composite action, which is effectively a Bash script, and does not support Ubuntu Pro features or the test subcommand. Charmcraft and Snapcraft also offer standalone “setup” actions that install the tool into your CI workspace, but Rockcraft does not. Lastly, there are at least three different implementations of the Snapcraft “pack” action that all behave slightly differently.
While basic functionality for the Craft tools is available in CI, we want a well-maintained, idiomatic, unified experience. Thoughtfully engineered workflows should reflect how the tools they wrap are used in the wild. Furthermore, standardization across the workflows makes them easier for developers and agents alike to recognize and use them.
What would be awesome?
One of our favorite questions at Canonical is “what would be awesome?”. It’s a much more fun question than “what would be fast?” or “what would be easy?”. We posed this question to team members and users to understand what they want to see in a workflow. These two groups have very separate interests – maintainers are naturally going to be concerned with the health of the code, while users primarily care about how well the thing works.
Within the Starcraft team, the answer was clear: a single location for our actions, with reusable code and clear conventions. Our mission as a developer experience team is to create a shared language for our tools – someone adept at packing with Rockcraft should have an easier time learning Snapcraft. By extension, a script that knows how to call Rockcraft should also know how to call Snapcraft. We want a rewrite of the actions to have as much universal functionality as possible so each tool only needs minor specializations for their CI actions.
For opinions outside the team, we put out a survey (it’s still open if you want to share your perspective!). The answer was a bit more varied, but one request stood out: a better publishing story. Since the Craft tools package software, they’re commonly used in automated “build-test-publish” workflows. Only the Rockcraft “pack” action supported testing artifacts. Expanding this workflow to the other crafts was the obvious path to take.
This feedback helped us understand what improvements to prioritize, laying the groundwork for the upcoming efforts.
The work so far
The first step in modernization was deciding on tooling. The primary decision was between “composite” or Node.js-based actions. “Composite” actions are fantastic for simple, shell-script-like workflows, and they are highly composable and modular. We found some rough edges around this use-case, though. For instance, arcane workarounds were necessary to make sure that when one composite action calls another from the same repository, it calls with the appropriate workflow version number. Since this attribute was critical for our desired actions, we opted to use Node.js-based actions instead.
The next step was to determine a good base to flesh out. I selected the “pack” action for Rockcraft, as it was the most featureful of the existing actions, had a solid UX, and was already written in TypeScript.
# Old way of packing a rock in CI
pack:
uses: canonical/craft-actions/rockcraft-pack@v0
with:
path: src/
rockcraft-channel: latest/beta
I started by updating the version of Node it ran on, cleaning up its dependencies, and changing the package manager to something more modern.
Then, I broke out the sledgehammer and started moving closer to the generic implementation we wanted. I read through the code of Rockcraft’s “pack” action, identified the pieces of code that were Rockcraft-specific, and split them into a new file. What remained was the generic code that a pack action for Snapcraft, Charmcraft, or even the upcoming Imagecraft could use, and it would just take a few more lines of code to create those actions once we were ready.
After these updates, packing a rock in CI looked like this:
# New way of packing a rock in CI
pack:
uses: canonical/craft-actions/rockcraft/pack@v1 # v1 not tagged at the time of this post
with:
path: src/
channel: latest/beta
The two major UX updates here are the invocation path (under the uses key) and the rockcraft-channel input being renamed. Updating the invocation path creates a clear notation of what Craft tool this workflow is for, and what action it will make. Want to pack a snap instead? Just change “rockcraft” to “snapcraft”. Want to just set up the tool instead of running a whole pack lifecycle? Change “pack” to “setup”! These updates help maintainers, too. The uses key now reflects where the action’s code lives in the repository, and the new naming convention helps to disambiguate what goes where.
The future
The publish workflow will require a lot of research on what the “good defaults” are and what sorts of levers would be useful to users. There are existing workflows, such as those from the snapcrafters/ci collection, that we can draw inspiration from. For instance, we like that their release-to-candidate workflow creates a revision and that they separate candidate and stable releases into different steps. We also like the support for building on foreign architectures.
Our goal is modularity. This shared implementation sets a standard that will readily scale to more current and future Craft tools. By consolidating common logic into a unified library, we ensure that as the Craft ecosystem grows, the workflows for Craft tools will be more discoverable, consistent, and maintainable. Now, when a developer uses any of the new Craft tool workflows, they’ll know exactly where to find the other workflows, and they’ll have a good idea of how to use them, too.
As a reminder, the survey is available if you’d like to add your opinion on what makes an action great. You can also watch the development in the Craft Actions repository or come chat with us on Matrix!