TL;DR: install docker and X410;
docker run jessitron/alloy:5.1
The essential work in software development is forming a model in our heads of the system we want. The code is one expression of this model.
The code can’t be stronger than this model. So I want a strong model: consistent, complete enough, and expressive.
I decided to express my latest app’s model in Alloy. Alloy is a declarative modeling language (don’t ask me, ask Hillel) that lets you describe what exists, what invariants you intend to enforce in your code, and what other invariants you think will result from that (and then Alloy can check those).
The first hard part is running Alloy. It’s still an academic project. It doesn’t have convenient installers for every OS. It is not in all the package manager repository thingers. Instead, the README is like “clone the repo, build it, run the jar.”
Personally, I’m on Windows, running Docker, and I install new programming languages and their runtimes in Docker containers only, please.
I didn’t find a Docker container, so figured I’d build one quickly (haha).
Alloy runs in Java. This is fine. It runs in Java 8, bummer.
Pick a base image
I wanted a Java container for development. Something with a JDK, because I intend to build from source. Something with git, because I want to clone it. Not something pared down for production.
Searching Docker Hub for “java” yields the openjdk images. Looking at the tags, I picked 8-jdk-buster because it’s the required version of Java, has the JDK, and “buster” is a version of Debian. Which is a version of Ubuntu. Which is a kind of Linux. Here is the extent of my knowledge of Docker Ubuntu base images: alpine is for production, with minimal things installed; and Debian is better for development, with convenient things like ssh.
The first line of my Dockerfile is:
To test this, I ran
docker build -t alloy . and then
docker run -it alloy /bin/bash; this gave me a command line in the container to play with. I tried out the next few commands in that command line before adding them to the docker file. This let me find problems (wrong clone URL, wrong version of Java, wrong gradle command) with faster iterations than rebuilding the container all day.
In the Dockerfile, I added RUN commands for the steps in the Alloy install constructions: git clone, change directory (that’s WORKDIR in Docker), and then run
RUN git clone https://github.com/AlloyTools/org.alloytools.alloy.git WORKDIR /org.alloytools.alloy RUN ./gradlew build
(The Alloy README used the ssh clone URL, and I had to change that to the https one. Hopefully by the time you try this, they’ll have accepted my PR and it’ll suggest https there. It clones with less setup.)
From here, I tested with
docker build -t alloy . and then
docker run -it alloy /bin/bash again. This part went on for a while, getting it to actually run.
With the gradle build completed, the command to run Alloy is
java -jar org.alloytools.alloy.dist/target/org.alloytools.alloy.dist.jar. This pops up a GUI.
haha! A GUI, from Docker, in Windows. Not so easy.
For this, you need an XWindow server for Windows. Like, the UNIX GUI thing, but on Windows. Okay. I bought X410 for $10 from the Windows Store, at Avdi‘s recommendation. There are free ones, but they take more fiddling. I’ll totally pay $10 for something that just works.
And then you need to set the DISPLAY environment variable in the container. You need to set it to
<host IP address>:0.0, where you find your host IP address by googling “docker host IP on <your OS>”. For Windows, there’s a handy DNS entry inside the container, so I can set
DISPLAY=host.docker.internal:0.0 when I run the container. From now on I use
docker run -it --env DISPLAY=host.docker.internal:0.0
alloy /bin/bash to start a test container.
For a default that will work on Windows or Mac, I add to the Dockerfile:
If that doesn’t work in your environment, you’ll need to use the
--env option to
docker run to override it.
Get the requisite native libraries
And then it still didn’t work. The error from Alloy is “Unable to start the graphical environment” followed by some pointers that didn’t help me. I needed the real exception, but the code is swallowing the exception and printing the pretty-but-insufficient message instead.
Hey, I’m building from source, I can change the code!
I added an
ex.printStackTrace() to that catch clause to get the real exception. It told me
libawt_xawt.so: libXtst.so.6: cannot open shared object file. Googling that brought me to a helpful page that suggested searching for the missing library with
Cool. That required an
apt install apt-file and an
apt-file update, and then
apt-file search libXtst.so.6 gave me the name of a library:
Installing that one gave me a different error (rejoice!), and a different missing file
libXrender.so.1, and then apt-file told me that lives in
libxrender1. These lead to a new line in the Dockerfile:
RUN apt update && apt install -y libxtst6 libxrender1
(Another note about development in Docker:
apt update builds a cache on the filesystem, a bunch of files that take up space. Most people clean those at the end of the Dockerfile; this is great for production, but it’s a size optimization. I don’t care how big my development image files are really. I like to leave this cache around so I can
apt install inside the container without doing another
apt update first. Handy for playing around.)
Well, the GUI runs. And with a change to my X410 configuration (DPI Scaling -> High Quality), I can almost read the fonts.
To celebrate, I add a final line to the Dockerfile that runs the GUI by default:
CMD ["java", "-jar", "org.alloytools.alloy.dist/target/org.alloytools.alloy.dist.jar"]
You can run it
Finally, I can run Alloy with a simple command. And you can too! I pushed this image up to Docker Hub. Try this:
docker run jessitron/alloy
… and it might work! If not, and if you can figure out why, submit a PR to the Dockerfile.
Whew, okay. Definitely not continuing with the Alloy tutorial today. But hey, I learned something about Docker, XWindow, and apt-file today. I’m calling this a success.
Someday I’ll write a model and hopefully have better software. Y’know, after I shave all these yaks.
Sleeping on this, I realized that the container contains a version of Alloy built from the tip of the master branch on whatever day I built and pushed it. That is rude and unstable.
Today I added a line before the gradle build step:
RUN git checkout v5.1.0
This sets the code to the latest (as of now) tag. That way I’m building something they meant people to use.
Then I built the image and pushed it:
docker build -t jessitron/alloy:5.1 . docker push jessitron/alloy:5.1
Now you can run
jessitron/alloy:5.1 to get this stable version. You can also copy the Dockerfile, and change the checked-out tag to whatever’s recent in your world (which is the FUTURE).