7.3 KiB
Riju build system
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.)
To get a quick overview, run make help
.
Build artifacts
We have two kinds of artifacts: Docker images (I=
in the Makefile)
and Debian packages (L=
and T=
in the Makefile).
Docker images
admin
: The first thing you build, and then everything else (including building other Docker images) is done from inside.ci
: Same asadmin
but for CI, so it has only the minimum number of dependencies.packaging
: Provides an environment to build Debian packages.runtime
: Base runtime environment for Riju into which Debian packages are installed and in which the server is expected to run.composite
: Based onruntime
, but with all languages' Debian packages installed.compile
: Compiles the Riju application code (i.e. everything that's not per-language).app
: Based oncomposite
, but with compiled code copied over fromcompile
. This container serves traffic in production.
Docker images are built by running make image I=<image> [NC=1]
, and
run by make shell I=<image> [E=1]
.
Riju source code and build directories are typically mounted at /src
inside the container, so there is generally no need to rebuild and/or
restart containers when making changes. (Exception: compile
and
app
.)
NC=1
: pass--no-cache
todocker build
. Note that caching is always disabled forcomposite
due to the unique way in which the build process is implemented for that image (to ensure good performance).E=1
: map ports to the host. Generally desired forruntime
, not needed foradmin
.
Note 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.
Note also that Docker builds do not pull new base images. For that,
use make pull-base
.
Debian packages
There are three types of Debian packages:
lang
, e.g.riju-lang-python
(T=lang L=python
): Installs the actual language and any associated tools. May declare dependencies on other Ubuntu packages, and may include files directly.config
, e.g.riju-config-python
(T=config L=python
): Installs a JSON configuration file into/opt/riju/langs
. The server looks in this directory to find which languages are supported.shared
, e.g.riju-shared-pandoc
(T=shared L=pandoc
): Shared dependency. This is for when multiple different languages need the same tool, and there's no Ubuntu package for it.
There are three basic actions for any particular Debian package:
- From any container, run
make script L=<lang> T=<type>
to generate the build script for a package. This is placed inbuild/<type>/<lang>/build.bash
. - From a packaging container, run
make pkg L=<lang> T=<type>
to (re)build a Debian package by executing its build script in a fresh directory. This is placed inbuild/<type>/<lang>/riju-<type>-<lang>.deb
. - From a runtime container, run
make install L=<lang> T=<type>
to install it.
Each language consists of a lang
and config
package, so you need
to follow the above steps for both. The make scripts L=<lang>
, make pkgs L=<lang>
, and make installs L=<lang>
commands automate this.
For further convenience, if you already have a runtime container up,
from the admin shell you can use make repkg L=<lang> T=<type>
and/or
make repkgs L=<lang>
to automate the three steps above (run make script
, run make pkg
inside a fresh packaging container, and then
run make install
inside the existing runtime container).
Some lang
packages declare shared
dependencies, in which case they
won't install until the shared
package is built and installed
already. This can't be done with make scripts
, make pkgs
, make installs
, or make repkgs
: use make script T=shared L=<lang>
,
make pkg T=shared L=<lang>
, make install T=shared L=<lang>
, or
make repkg T=shared L=<lang>
, respectively. (Check the
install.riju
key in a language's YAML configuration to see if it
declares any such dependencies.)
Package build details
The build script is executed with a working directory of
build/<type>/<lang>/src
, and it installs package files into
build/<type>/<lang>/pkg
.
If make pkg
is too high-level, there are more specific commands:
make pkg-clean
: Wipe and recreate thesrc
andpkg
directories.make pkg-build
: Just run the package build script (you also need to runmake script
if the language configuration has changed).make pkg-deb
: Build thepkg
directory into the actual Debian package.
All Makefile targets with pkg
in the name take an optional Z
parameter for the .deb
compression level, defaulting to none
. This
can be increased to gzip
or even further to xz
. Increasing the
compression massively increases build time, but massively decreases
the resulting package size.
Artifact caching
All artifacts can be cached on remote registries to avoid being rebuilt in CI unnecessarily.
- Docker images are cached on Docker Hub. Push with
make push I=<image>
and pull withmake pull I=<image>
. - Debian packages are cached on S3. Push with
make upload T=<type> L=<lang>
and pull withmake download T=<type> L=<lang>
.
CI will take care of managing the remote registries automatically. It is generally recommended to let CI handle this, and not push anything yourself.
Application build
We have two compiled parts of Riju:
- Frontend assets (compiled with Webpack)
- Setuid binary used for privilege deescalation (compiled with LLVM)
For development:
make frontend-dev
(compile frontend assets, auto recompile on change)make system-dev
(compile setuid binary, auto recompile on change)make server-dev
(run server, auto restart on change)make dev
(all three of the above)
For production:
make frontend
(compile frontend assets)make system
(compile setuid binary)make build
(both of the above)make server
(run server)
Incremental builds and hashing
CI is set up so that artifacts are only rebuilt when changes have occurred. This is done through an extensive hashing algorithm which produces a consistent hash for each artifact based on its inputs. We can then check whether the hash has changed, meaning the artifact should be rebuilt.
This is implemented mostly behind the scenes, but you can run make plan
to execute the hashing algorithm and dump a plan of the minimal
set of actions that would be run if this were CI:
- If local artifact is missing, but remote artifact is up to date: download remote to local.
- If remote artifact is missing or outdated, but local artifact is up to date: upload local to remote.
- If neither local nor remote artifact is up to date: rebuild local and upload to remote.
You can run make sync
to execute this plan, excepting the upload
part (that should, for safety, generally be done only in CI). So, in
principle, make sync
should bring all your local artifacts up to
date with the latest source (rebuilding some if needed).
To run a full deployment, use make publish
. This should definitely
be done only from CI, and with the Z=xz
flag to enable Debian
package compression.