Skip to content

Instantly share code, notes, and snippets.

@Sorixelle
Last active October 24, 2020 17:31
Show Gist options
  • Save Sorixelle/0f8fa9e22d35edc5201a38bd7e0ff54e to your computer and use it in GitHub Desktop.
Save Sorixelle/0f8fa9e22d35edc5201a38bd7e0ff54e to your computer and use it in GitHub Desktop.

Building Pebble Apps With Nix

The Pebble SDK is a bit of a fickle beast. It worked well back when Pebble was around to maintain it, but as time goes on, it’s starting to really show it’s age. With it’s dependence on shared libraries that were last built in 2015, and dead links to no-longer-operational S3 buckets, it’s starting to become a bit tricky to get up and running with the Pebble SDK, especially on macOS, where the emulator requires some tricky hacks to get working. Is there any way we can make the setup process more reliable?

Enter pebble.nix.

A Different Package Manager

pebble.nix provides a small library of tools to assist in developing and deploying Pebble apps and watchfaces, through the Nix package manager.

You might be asking yourself: what’s Nix? At it’s core, Nix is a package manager, like Homebrew or APT or pacman, but takes a radically different approach to managing packages. Nix treats packages as pure functions in functional programming languages like Haskell. This might sound a bit strange at first, but it gives us some very useful properties. For those unfamiliar with FP concepts, a pure function is a function that always produces the same output when given the same inputs. In the case of Nix, if you pass the same dependencies to a package when building it, you’ll always get the exact same package as a result, every time. This is really great for the Pebble SDK, as it means we can guarantee that every deployment of the tools will work, on any system. Another neat property is that all packages are confined to their own little sandbox in the Nix store. This ensures that all the packages installed in a system are isolated from each other, and won’t step over each other by accessing the same global system state. This allows us to have multiple versions of the same library installed on the system at once, which is really useful for when we need to package older versions of some libraries for things like… the Pebble SDK! There’s a few other cool concepts, like atomic package upgrades, multi-user profiles and full system specification with NixOS. If you’re interested to learn more about Nix, the Nix Pills and the Nix Manual are great starting points.

First Steps with Nix

Unless you’re on NixOS (which you almost certainly aren’t), you probably don’t have Nix installed, so that’s the first thing you’ll need to get done. The install process is super simple - just drop this command in your shell to run the installer:

sh <(curl -L https://nixos.org/nix/install) --daemon

On macOS Catalina (10.15) or newer, you’ll need to add --darwin-use-unencrypted-nix-store-volume to that command.[fn:1] You can check if Nix was installed properly with nix-env --version. If you get a version output, you’re all set!

There’s one more thing to setup before we move on, though - Cachix. Cachix provides binary caches for packages outside of the standard Nixpkgs package set. pebble.nix has been setup to provide binary caches through Cachix so you don’t have to spend ages waiting for everything needed for your Pebble tools to compile. To get Cachix installed and setup for pebble.nix, it’s just 2 shell commands:

nix-env -i cachix
cachix use pebble

Once that’s done, reboot your system. The Nix daemon needs a restart so that it can see the new binary cache that’s been added, and rebooting the machine is the simplest OS-agnostic way to do that. If you don’t reboot, the first setup of the development environment will likely take a very long time (1 hour+)!

Now that everything’s set up - time to write your first Nix derivation!

pebbleEnv - Your First Derivation

Obviously, we want to be able to use the Pebble tools in our projects. How can we do that? The answer is with a derivation. A derivation defines a set of steps to build a package, or an environment. You might think, “What use is a package? I need to install a package, not make a new one!”

Remember how I said each package is confined to it’s own sandbox in the Nix store? When we create a derivation, we take a bunch of these packages as inputs, and create an environment to build something inside of. But we don’t want to build anything (yet), we just want the environment. Luckily, nix-shell allows us to do just that. When you run nix-shell in a folder with a shell.nix file, it reads the derivation contained in shell.nix, and opens up a shell with all of it’s dependencies set up and ready to go. This is the mechanism we’ll use to create our Pebble development environments.

To get your first environment going, make a new directory to hold your Pebble project, and create a shell.nix file with the following contents:

(import
  (builtins.fetchTarball https://github.com/Sorixelle/pebble.nix/archive/master.tar.gz)
).pebbleEnv { }

Looks a bit confusing? Let’s break it down.

  1. First, we get the latest copy of pebble.nix using builtins.fetchTarball, which downloads a tarball, and extract it to the Nix store.
  2. Next, we import the contents of the tarball we just downloaded. This gives us access to the functions that pebble.nix provides.
  3. We then call the pebbleEnv function from pebble.nix, passing an empty attribute set ({ }) as an argument. The default parameters are enough to get a basic Pebble SDK environment going.

Finally, save the file, and run nix-shell. After a bit of downloading (and possibly some compiling, though hopefully not too much if you set up Cachix), you’ll be dropped into a shell, where you can use the pebble command-line tool just like you usually would to build Pebble apps, run them in the emulator, install them on a real Pebble device, anything you need to do with it! You can leave the shell when you’re done with it by running exit, as per usual.

Every time you want access to the Pebble SDK, just run nix-shell in the project folder. If this is a bit too annoying for you, you can look into tools like direnv and nix-direnv to automatically bring up the shell every time you cd into the project folder.

But using the Pebble SDK isn’t all you can do with pebble.nix…

buildPebbleApp - Preparing Apps for the App Store

At the time of writing, the process for deploying finished apps and watchfaces to the Rebble App Store is largely manual. You’ve got to build the app, make a YAML file with all the app store metadata, gather up all your screenshots in the right place, then zip it up and send it off to Joshua to get it deployed. Isn’t there a way to automate some of this process? With pebble.nix, there is!

This time, we’ll write another derivation, but instead of just using it for it’s shell environment, we’ll be using it to actually build and package a Pebble app for deployment. Create a file in your project called default.nix, and give it the following contents:

   (import
     (builtins.fetchTarball https://github.com/Sorixelle/pebble.nix/archive/master.tar.gz)
   ).buildPebbleApp {
     name = "Pebble Watchapp";
     src = ./.;

     type = "watchapp";
     description = ''
	Fill this in with a description.
     '';
     releaseNotes = ''
	Add your release notes here.
     '';
     category = "Daily";
     banner = "assets/banner.png";
     largeIcon = "assets/icons/large.png";
     smallIcon = "assets/icons/small.png";

     screenshots = {
	aplite = [ "assets/screenshots/aplite/screenshot.png" ];
	basalt = [ "assets/screenshots/basalt/screenshot.png" ];
	chalk = [ "assets/screenshots/chalk/screenshot.png" ];
	diorite = [ "assets/screenshots/diorite/screenshot.png" ];
     };
   }

Just like last time, we import the pebble.nix functions, but this time, we call buildPebbleApp. There’s a few arguments we need to pass to this function. Some are mandatory, and some depend on if you’re building a watchapp or a watchface. There’s a full reference of all the arguments accepted here.

Once you’ve saved that file, run nix-build to build and package your Pebble app. Running nix-build will create a symlink called result, that points to your built app in the Nix store. Inside the folder will be a .pbw of your app, all the screenshots you provided, a meta.yml file, and an appstore-bundle.tar.gz tarball that’s ready to ship off to the Rebble App Store!

Next Steps

I’m always open to new features and bugfixes for pebble.nix. If there’s any issues you run into relating to it, or if you have any questions you’d like to ask, feel free to ping me in the Rebble Discord (@Sorixelle#6205).

Now go out there, and build awesome Pebble apps with ease!

Footnotes

[fn:1] In Catalina, the root directory is always read-only. This is a problem for Nix, since it needs to keep it’s store at /nix/store. This flag works around it by creating a new APFS volume, and telling the system to mount it at /nix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment