-
-
Notifications
You must be signed in to change notification settings - Fork 197
/
Copy pathbuild.md
284 lines (229 loc) · 12.1 KB
/
build.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# 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](tutorial.md) instead.)
## Depgraph
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.)
### Available build artifacts
* `image:ubuntu`: A fixed revision of the upstream `ubuntu:rolling`
image that is used as a base for all Riju images.
* `image:packaging`: Provides an environment to build Debian packages.
Depends on `image:ubuntu`.
* `image:runtime`: Provides an environment to run the Riju server and
the test suite. Depends on `image:ubuntu`.
* `image:base`: Provides a base image upon which per-language images
can be derived. Depends on `image:ubuntu`.
* `deb:lang-xyz`: For each language `xyz`, the Debian package that
installs that language into the base image. Depends on
`image:packaging` and the build script for the language (generated
from the `install` clause of `langs/xyz.yaml`).
* `deb:shared-pqr`: Same but for shared dependencies, which are also
archived as Debian packages.
* `image:lang-xyz`: For each language `xyz`, the per-language image
used for user sessions in that language. Depends on `image:base`,
`deb:lang-xyz`, and possibly one or more `deb:shared-pqr`.
* `test:lang-xyz`: An artifact certifying that the `xyz` language
tests passed. Depends on `image:runtime`, `image:lang-xyz`, the test
suite and API protocol code, and the `xyz` language configuration.
* `image:app`: Built on top of `image:runtime` but including the Riju
server code, so that it can be run standalone. Depends on
`image:runtime` and the application code.
* `deploy:ready`: Deployment configuration, ready to upload. Depends
on `image:app` and all `test: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.
### Depgraph abstractions
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 from
`ubuntu: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. (Actually `deploy:ready` is a publish
artifact too, but that is an implementation detail because I was
lazy about my abstractions.)
### Usage of Depgraph
```
$ 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.)
## Makefile
To get a "quick" overview, run `make help`.
### Preliminary targets
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` and
`install.bash` in `build/{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.
### Building Depgraph artifacts
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
#### Docker images
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's `make image I=lang
L=foobar`.
* For `image:ubuntu`, you likely don't want to "build" it yourself
(meaning take the latest `ubuntu:rolling` from Docker Hub). You can
synchronize with upstream Riju using `make 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 as
`make shell I=runtime E=1` inside the admin shell.
* `EE=1`: Same as `E=1`, but expose ports on `0.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.
#### Debian packages
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 for `xyz`.
* `make pkg-build T=lang L=xyz`: Run the packaging script. Substitute
`build` for `debug` 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 file `build/lang/xyz/riju-lang-xyz.deb`. You can pass
`Z=(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`.
#### Tests
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.
#### Final deployment
* `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.
### Application build
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)
### Miscellaneous utilities
* `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) with `set -a; . .env; set +a`.
### Infrastructure
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.