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.

Related

Keep up with Developments in Multi-Service Development
 
 
 
Keep up with Developments in Multi-Service Development

Already have a Dockerfile and a Kubernetes config?

You’ll be able to setup Tilt in no time and start getting things done. Check out the docs! 

Having trouble developing your servers in Kubernetes?