Building better craft linters and artifacts

In our efforts to provide developers with the best tools for packaging software, we have always prioritized the user experience. One of the most effective ways to ensure a seamless build process is automatic linting.

In this post, we’ll look at how we evolved linting from an app-specific feature into a centralized service for our growing family of craft apps, including Snapcraft, Charmcraft, and Rockcraft.

Craft linters will have opinions on your artifact

Linting is immediate feedback on the quality and correctness of a software artifact. In the context of the craft apps, linters can help prevent functional issues in your artifact before they’re published. They also check whether the artifact complies with safety policies. With both types of correctness, the linter gives you practical advice for how to resolve the issue. For example, the linters in Snapcraft detect unused system libraries that unnecessarily increase the size of the snap.

I think it’s very important that our linters recommend solutions. This design has been gaining momentum among linters throughout the industry. We draw inspiration from popular open-source linters, like Clippy for Rust and Ruff for Python. Developers value inline advice from their linters, and these two were designed to meet that need.

The history of craft linters

Initially, the craft apps independently developed their own linting capabilities. In 2021, Charmcraft 1.2 was the first to support linting. It validates the contents of charms and verifies the charm’s entrypoint. The linter runs when the charm is packed and when you run the analyze command for existing charms.

A year later, Snapcraft 7 introduced dedicated linters for verifying binary file parameters for classically-confined snaps and for detecting missing and unused system libraries. It also added a standalone lint command for existing snaps.

Snapcraft’s linting framework was designed to make it easy to add new linters. The benefits of this design became apparent in Snapcraft 8, when a community contributor, Soumyadeep Ghosh, added a new metadata linter. It checks for essential details like links to source code and issue trackers

Snapcraft 9 added a fourth linter for snaps that use GPU libraries. It recommends the use of the recent GPU extension and its accompanying content snap to reduce library duplication.

Because Snapcraft and Charmcraft developed their linting logic separately, they shared no code. The user experience differed between them, with Charmcraft using analyze while Snapcraft uses lint. To fulfill our goal of a consistent user experience, we needed a common implementation.

A unified linter service

In 2023, a year after Snapcraft introduced its linters, we created a library called Craft Application. This library implements mechanisms common to all craft apps. With this library, we now had a place to unify our efforts.

This year, we introduced a dedicated Linter Service to the Craft Application library. This service, which emerged as a community contribution from Hamdaan Ali Quatil, provides a common framework for all craft apps to implement linters.

Centralizing this functionality ensures that lint messages remains predictable and consistent across the craft apps. The following examples show the standardized format generated by the Linter Service for both project file validation and artifact validation:

$ testcraft lint

lint results:
testcraft.missing_version:
  - [WARNING] TC001: project is missing the recommended 'version' field (testcraft.yaml)
Possible issues found in testcraft.yaml
$ testcraft lint --post my-artifact-1.0-amd64.testcraft

lint results:
testcraft.empty_artifact:
  - [ERROR] TC100: artifact is empty other than metadata.yaml (metadata.yaml)
Errors found in artifacts

Next steps for us

The introduction of the Linter Service is just the beginning. We’re exploring several possibilities to extend linting.

The first is pre-linters. These would validate project files before the build lifecycle even begins, catching errors as early as possible. As a maintainer, you shouldn’t need to wait for an entire build to complete before seeing problems that can be spotted upfront.

Next would be linters in Debcraft. Our new app for building Debian packages will leverage this service to provide a consistent interface, combining traditional checks from Lintian with Debcraft-specific validation.

The last we’re considering is Snap Store readiness. All snaps uploaded to the Snap Store go through a series of readiness checks conducted by the snappy-review tool. Snapcraft can run this tooling locally in a dedicated linter.

The shift to a centralized Linter Service is more than an architectural cleanup. By moving linting into a common service, these improvements can benefit every craft app, and you can expect a consistent experience as you move between them. As our ecosystem grows, this helps ensure that quality remains an inherent property of every artifact you build, not an afterthought.

I’m excited to see how this unified service will streamline how you make artifacts.

7 Likes

I’m really interested in this. Charm Tech looked into charmcraft analyse (and even contributed a rule or two) a while back, and the main reason we gave up on this is that the value proposition is far too low when the linting is done after packing.

I find it odd that we do any linting after packing. Surely, if a problem exists that a linter detects in the packed artefact but doesn’t detect in the raw material, then that must be a bug with the packing, rather than with whatever is being packed. Useful for developing craft tools, probably, but not for people who are building snaps, rocks, charms, and so on. Even in the case of a store doing linting as part of publishing, it would be trivial to just unpack and run the linting.

Doing the linting prior to packing as a separate command (or as something like a language server, but I don’t personally feel that adds a lot of value) is even more important now that agents are writing a lot of the raw material getting packed. Tight feedback loops with highly informative tools are a big booster of quality.

We made an experimental (very proof of concept) charm linter last cycle, and this cycle we’re turning it into a real tool. Having it live in charmcraft one day in the future would be interesting to talk about at some point.

Surely, if a problem exists that a linter detects in the packed artefact but doesn’t detect in the raw material, then that must be a bug with the packing, rather than with whatever is being packed. Useful for developing craft tools, probably, but not for people who are building snaps, rocks, charms, and so on

There are a lot of problems that can only be determined after packing. For example, if you build a binary, Snapcraft’s linters can only determine which libraries will be needed by that binary after it’s been built. Debcraft’s post-linters will have to ensure files end up in the right directories.

But yes, I agree there’s a ton of use-cases for linting before building and packing, particularly for Charmcraft.

We made an experimental (very proof of concept) charm linter last cycle, and this cycle we’re turning it into a real tool. Having it live in charmcraft one day in the future would be interesting to talk about at some point.

Interesting, I hadn’t heard about this effort. Can you share a link to the project, if it’s published?