Draggin' the Entr
A brief love letter to 'entr'
entr stands for Event Notify Test Runner.
It’s a command line tool that makes it easy to re-run tests and live reload servers.
You give entr:
1) A list of files
2) A command to run
entr listens to those files and restarts the command when they change. You can
trigger restarts manually by pressing [spacebar].
entr is so simple and Unix-y, it’s hard to believe someone didn’t come up with
it at Bell Labs in the 70s. But it’s actually a pretty recent tool that Eric
Radman first released in
2012.
This post is a brief love letter to entr: what its options are, why they’re
there, and how to build a similar experience on top of the Tilt API once your
logic gets too complex for entr.
We will also have some good entr puns!
entr the Options
The old rule of thumb of bodegas is that every sign behind the cash register has a great story where someone tried to do something so ridiculous that they had to post a sign to prevent it from happening again.
I feel the same way about man pages! And the entr manual is no exception.
The entr manual is a history of
all the reasons why a command re-runner might seem simple but is more
complicated than you would expect. Here’s an abridged version of how that manual
reads to me:
-a: What happens if to the command writes to a file that makes the command restart? You don’t really want infinite loops…or do you?
-d: Can new files trigger commands?
-p: OK, I get how restarts are triggered, but what triggers the initial start?
-r: Are servers and test commands the same? Or are they fundamentally different and need different restart behavior? What about subprocesses?
-z: Does it really make sense to restart a server that’s crashing?
These are all good questions! And how you answer them can have a big impact on how you
run entr.
Example #1: fast iterative testing
For day-to-day running of unit tests, something like
find pkg/model | entr go test ./pkg/model/...
does what I need. find pkg/model lists all tests in that directory. entr
reruns the test when any of them change.
Example #2: reload a server
We use entr in Tilt for restarting servers! You can read the full script in
our restart_process
extension.
A simpler version of this script looks like:
echo /.restart-proc | entr -rz run-server
with some custom handling of exit codes when the server is crashing. Then Tilt
can touch the /.restart-proc file to trigger a restart.
Speak, Friend, and entr
For even more complex logic, Tilt has a FileWatch
API and a Cmd
API that you can stitch together
to build entr-like reloading.
Let’s reimplement our test re-runner above on the Tilt API.
find pkg/model | entr go test ./pkg/model/...
First, start a Tilt server for your project’s dev environment:
tilt up &
Then run three lines:
tilt create filewatch pkg-model ./pkg/model
tilt create cmd --filewatch=pkg-model pkg-model-test go test ./pkg/model/...
tilt logs -f
Let’s break down these lines one at a time.
create filewatch
The first line creates a filewatch object.
$ tilt create filewatch pkg-model ./pkg/model
filewatch.tilt.dev/pkg-model created
Because a filewatch is a full-fledged Tilt API object, I can inspect it directly to make sure it’s working.
$ tilt get filewatch pkg-model
NAME CREATED AT
pkg-model 2021-05-12T23:42:20Z
$ touch pkg/model/secret.go
$ tilt describe filewatch pkg-model
Name: pkg-model
Namespace:
Labels: <none>
Annotations: <none>
API Version: tilt.dev/v1alpha1
Kind: FileWatch
Metadata:
Creation Timestamp: 2021-05-12T23:42:20Z
Resource Version: 4
UID: 9c3a72df-18e5-4ea7-9ea5-4dcffa305667
Spec:
Watched Paths:
/home/nick/src/tilt/pkg/model
Status:
File Events:
Seen Files:
/home/nick/src/tilt/pkg/model/secret.go
Time: 2021-05-12T23:42:27.064831Z
Last Event Time: 2021-05-12T23:42:27.064831Z
Monitor Start Time: 2021-05-12T23:42:20.056037Z
For more on how to inspect Tilt’s file watches, see this post.
create cmd
The second line creates a command that restarts whenever the FileWatch sees new events.
$ tilt create cmd --filewatch=pkg-model pkg-model-test go test ./pkg/model/...
cmd.tilt.dev/pkg-model-test created
Cmd is also a first-class Tilt API object, so we can inspect it:
$ tilt describe cmd pkg-model-test
Name: pkg-model-test
Namespace:
Labels: <none>
Annotations: <none>
API Version: tilt.dev/v1alpha1
Kind: Cmd
Metadata:
Creation Timestamp: 2021-05-12T23:45:53Z
Resource Version: 3
UID: 7c0645ce-ca60-4849-910a-f44cd9d733f2
Spec:
Args:
go
test
./pkg/model/...
Dir: /home/nick/src/tilt
Restart On:
File Watches:
pkg-model
Status:
Ready: true
Terminated:
Exit Code: 0
Finished At: 2021-05-12T23:45:54.911704Z
Pid: 1032214
Started At: 2021-05-12T23:45:53.915968Z
In this example, I can see that the command immediately ran in about 1 second.
logs -f
The third line tails the Tilt logs.
The -f argument streams the logs and waits for new logs.
To put it all together, I can delete a file and look at the logs to see the result of the command re-run:
$ rm pkg/model/secret.go
$ tilt logs | tail
Running cmd: go test ./pkg/model/...
# github.com/tilt-dev/tilt/pkg/model/logstore [github.com/tilt-dev/tilt/pkg/model/logstore.test]
pkg/model/logstore/logstore.go:237:51: undefined: model.SecretSet
pkg/model/logstore/logstore.go:246:48: undefined: model.SecretSet
pkg/model/logstore/logstore_test.go:122:15: undefined: model.SecretSet
ok github.com/tilt-dev/tilt/pkg/model (cached)
FAIL github.com/tilt-dev/tilt/pkg/model/logstore [build failed]
FAIL
go test ./pkg/model/... exited with exit code 2
Tilt immediately ran the tests, which failed, because the symbols
defined in secret.go are missing.
And with only three lines of tilt invocations, we recreated our entr one-liner.
entr Stage Right
I honestly think entr should be a go-to tool for anyone that messes around
a lot in the terminal.
We don’t want Tilt to replace entr! But we think it can complement entr when
you’re ready to move from hackable one-offs to composable systems.
One of the goals we’re trying to accomplish with the Tilt API server is to have
a more robust, Kubernetes-inspired API for building dev environments. It will definitely
be more verbose than entr! But it will also be more debuggable and maintainable
for the next person on your team.