Riju's build system is complex and takes some time to explain. Bear with me. (If you just want to add or modify a language, you can read the tutorial instead.)
The high level interface to Riju's build system is a tool called Depgraph, which knows about all the build artifacts and has advanced mechanisms for determining which of them need to rebuild based on content hashes. Normally you can just use Depgraph to build the artifacts you need. However, in some cases you may want to interact with the lower level for more precise operations. This is done via Makefile. (Furthermore, there are a few one-off artifacts such as the admin image which are not part of the main build system, which means that they are managed directly by Makefile.)
image:ubuntu
: A fixed revision of the upstreamubuntu:rolling
image that is used as a base for all Riju images.image:packaging
: Provides an environment to build Debian packages. Depends onimage:ubuntu
.image:runtime
: Provides an environment to run the Riju server and the test suite. Depends onimage:ubuntu
.image:base
: Provides a base image upon which per-language images can be derived. Depends onimage:ubuntu
.deb:lang-xyz
: For each languagexyz
, the Debian package that installs that language into the base image. Depends onimage:packaging
and the build script for the language (generated from theinstall
clause oflangs/xyz.yaml
).deb:shared-pqr
: Same but for shared dependencies, which are also archived as Debian packages.image:lang-xyz
: For each languagexyz
, the per-language image used for user sessions in that language. Depends onimage:base
,deb:lang-xyz
, and possibly one or moredeb:shared-pqr
.test:lang-xyz
: An artifact certifying that thexyz
language tests passed. Depends onimage:runtime
,image:lang-xyz
, the test suite and API protocol code, and thexyz
language configuration.image:app
: Built on top ofimage:runtime
but including the Riju server code, so that it can be run standalone. Depends onimage:runtime
and the application code.deploy:ready
: Deployment configuration, ready to upload. Depends onimage:app
and alltest:lang-xyz
.deploy:live
: Pseudo-artifact corresponding to actually running the deployment, which is a blue/green cutover in which all languages and the application server are updated at once.
Each artifact has:
- A list of zero or more dependencies on other artifacts.
- A recipe to build it locally assuming that its dependencies are also available locally.
- A recipe to upload the local build artifact to a remote registry.
- A recipe to download the artifact from a remote registry to overwrite the local version.
- A way to compute a content-based hash of the artifact's dependencies and non-artifact inputs (data files and code). Crucially this does not require the dependencies to actually be built (the hash for each artifact is based only on its dependencies hashes), so it's possible to compute hashes for the entire dependency tree before doing anything.
- A way to check the hash currently attached to a local artifact, which can be compared to the desired hash to see if it needs to be rebuilt.
- A way to check the hash currently attached to an artifact in a remote registry, which can also be compared to the desired hash.
There are special types of artifacts:
- Manual artifacts do not have a hash until they are generated.
Therefore, they must be built manually before the rest of the
dependency calculations can proceed.
image:ubuntu
is a manual artifact since its hash depends on what we download fromubuntu:rolling
. - Publish artifacts do not have a hash after they are generated.
Therefore, nothing can declare a dependency on them.
deploy:live
is a publish artifact. (Actuallydeploy:ready
is a publish artifact too, but that is an implementation detail because I was lazy about my abstractions.)
$ dep --help
Usage: dep <target>...
Options:
--list list available artifacts; ignore other arguments
--manual operate explicitly on manual artifacts
--hold-manual prefer local versions of manual artifacts
--all do not skip unneeded intermediate artifacts
--registry interface with remote registry for caching
--publish publish artifacts to remote registries
--yes execute plan without confirmation
-h, --help display help for command
You can run dep --list
to list all the available artifacts. Then
dep name-of-artifact [names-of-more-artifacts...]
will generate
those artifacts. Depgraph is like Terraform in that it will compute a
plan and then ask you to confirm before proceeding.
By default Depgraph will generate artifacts locally only, although it
can be instructed to also interact with a remote registry if you've
set up the infrastructure appropriately. Pass --registry
to enable
this mode, in which case Depgraph will download remote artifacts when
appropriate versions exist in the registry. Pass --publish
along
with --registry
to also cache generated artifacts in the remote
registries. Of course --publish
is required to build deploy:live
.
For dealing with image:ubuntu
specifically, you probably just want
to fetch Riju's version (available in a public ECR repository) using
make sync-ubuntu
to keep in sync. However if you do want to update
to the latest ubuntu:rolling
, it's dep image:ubuntu --manual
.
The other options (aside from --yes
) are mostly not too useful.
Depgraph is very sophisticated and should always compute the minimum
necessary build plan based on any changes you have made. So, you don't
need to worry about the details! (Except when the hashing isn't
working properly. Then you cry.)
To get a "quick" overview, run make help
.
There are a couple of targets that are independent of Depgraph and
need to be run just to make sure various bits of state and generated
files are up to date. Depgraph and/or ci-run.bash
take care of this.
make ecr
: Authenticate to ECR, needed to push and pull. The authentication only lasts for 12 hours unfortunately, although it does survive an admin shell restart.make all-scripts
: Generate packaging scripts (build.bash
andinstall.bash
inbuild/{lang,shared}
) from YAML configuration.make system
: Compile setuid binary used for spinning up and tearing down user containers. This is needed early because we use real containers in the test suite.
First let's go through each of the Depgraph-enabled artifacts above. For each one, there's:
- a way to build it locally
- a way to publish the local version to a remote registry
- a way to download the remote version locally
Generally you build a Docker image named image:foobar
using make image I=foobar
, you publish it with make push I=foobar
, and you
download it with make pull I=foobar
. Pass NC=1
to make image
to
disable Docker cache (although this is fairly rarely useful, and in
general for this to work with Depgraph we need a more sophisticated
mechanism).
There are one or two exceptions to this, unfortunately:
- For language images (
image:lang-foobar
), it'smake image I=lang L=foobar
. - For
image:ubuntu
, you likely don't want to "build" it yourself (meaning take the latestubuntu:rolling
from Docker Hub). You can synchronize with upstream Riju usingmake sync-ubuntu
.
For any Docker image image:foobar
, you can jump into a shell using
make shell I=foobar
. This has some optional arguments:
E=1
: Expose Riju ports outside the container. Most likely used asmake shell I=runtime E=1
inside the admin shell.EE=1
: Same asE=1
, but expose ports on0.0.0.0
outside the container. This is helpful if you're running on the dev server and want to be able to access the development version of Riju in your browser.CMD="make something"
: Instead of launching an interactive Bash shell inside the container, run the specified shell command (using Bash) and exit.
Riju source code and build directories are typically cross-mounted at
/src
inside all non-user containers, so there is generally no need
to rebuild and/or restart containers when making changes.
Note that all of this section applies also to the admin
and ci
images, which are not otherwise involved with Depgraph (and are based
directly on upstream ubuntu:rolling
).
Note also that admin
uses --network=host
and maps a number of
directories such as ~/.ssh
and ~/.aws
, plus the Docker socket,
inside the container, so you can treat an admin shell more or less the
same as your external development environment.
Build a language package using make pkg T=lang L=xyz
(where there
exists langs/xyz.yaml
). Build a shared dependency package using
make pkg T=shared L=pqr
(where there exists shared/pqr.yaml
).
This has to be done in the packaging image, and will abort otherwise.
So, for short, make shell I=packaging CMD="make pkg T=lang L=xyz"
.
To debug package installation, you can jump into a persistent
packaging shell (make shell I=packaging
) and break down the process
into three steps:
make pkg-clean T=lang L=xyz
: Delete and recreate packaging directories forxyz
.make pkg-build T=lang L=xyz
: Run the packaging script. Substitutebuild
fordebug
to instead start a shell in the environment of the packaging script, where you can operate manually.make pkg-deb T=lang L=xyz
: Compress the results of the packaging script into a filebuild/lang/xyz/riju-lang-xyz.deb
. You can passZ=(gzip|xz)
to enable compression, which is disabled by default to save on time during development. Otherwise, packages are automatically recompressed before registry upload time.
Uploading a package to the registry is make upload T=lang L=xyz
, and
download is make download T=lang L=xyz
.
You can run tests for a specific language (inside the runtime
image
only, otherwise it will abort) using make test L=xyz
. L
can also
be a comma-separated list of languages. You can additionally (or
instead) filter by test type, e.g. make test L=python T=lsp
.
Uploading and downloading test hashes is only implemented at the
Depgraph layer.
make deploy-config
: Build the deployment configuration JSON that will be pushed to S3.make deploy-latest
: Push it to S3.make deploy
: Combination of the above.
We have three compiled parts of Riju:
- Frontend assets (compiled with Webpack)
- Setuid binary used for privilege deescalation (compiled with LLVM)
- Supervisor binary used on deployed images (compiled with Go tooling)
For development:
make frontend-dev
(compile frontend assets, auto recompile on change)make system-dev
(compile setuid binary, auto recompile on change)make supervisor-dev
(compile supervisor binary, auto recompile on change)make server-dev
(run server, auto restart on change)make dev
(all four of the above)
For production:
make frontend
(compile frontend assets)make system
(compile setuid binary)make supervisor
(compile supervisor binary)make build
(all three of the above)make server
(run server)
make sandbox
: Bash shell emulating a user session at the command line, with many useful functions in scope for executing various commands from the language configuration YAML.make lsp
: LSP REPL. This is not working currently as it needs to be updated for the new build system that uses per-language images.make dockerignore
: Update.dockerignore
from.gitignore
.make tmux
: Start a tmux session conveniently with variables from.env
.make env
: Load in.env
environment variables in a subshell. You can also do this for your current session (though it won't affect new tmux panes) withset -a; . .env; set +a
.
There are wrappers for packer
and terraform
in the repository
bin
that deal with some niceties automatically. Run make packer
to
do an AMI build, and use terraform
commands from any directory.