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.
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.
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!
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.
- First, we get the latest copy of pebble.nix using
builtins.fetchTarball
, which downloads a tarball, and extract it to the Nix store. - Next, we import the contents of the tarball we just downloaded. This gives us access to the functions that pebble.nix provides.
- 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…
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!
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!
[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
.