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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
|
Getting Started
===============
In order to use *justbuild*, first make sure that `just`, `just-mr`, and
`just-import-git` are available in your `PATH`.
Creating a new project
----------------------
*justbuild* needs to know the root of the project worked on. By default,
it searches upwards from the current directory till it finds a marker.
Currently, we support three different markers: the files `ROOT` and
`WORKSPACE` or the directory `.git`. Lets create a new project by
creating one of those markers:
``` sh
$ touch ROOT
```
Creating a generic target
-------------------------
By default, targets are described in `TARGETS` files. These files
contain a `JSON` object with the target name as key and the target
description as value. A target description is an object with at least a
single mandatory field: `"type"`. This field specifies which rule
(built-in or user-defined) to apply for this target.
A simple target that only executes commands can be created using the
built-in `"generic"` rule, which requires at least one command and one
output file or directory. To create such a target, create the file
`TARGETS` with the following content:
``` {.jsonc srcname="TARGETS"}
{ "greeter":
{ "type": "generic"
, "cmds": ["echo -n 'Hello ' > out.txt", "cat name.txt >> out.txt"]
, "outs": ["out.txt"]
, "deps": ["name.txt"]
}
}
```
In this example, the `"greeter"` target will run two commands to produce
the output file `out.txt`. The second command depends on the input file
`name.txt` that we need to create as well:
``` sh
$ echo World > name.txt
```
Building a generic target
-------------------------
To build a target, we need to run `just` with the subcommand `build`:
``` sh
$ just build greeter
INFO: Requested target is [["@","","","greeter"],{}]
INFO: Analysed target [["@","","","greeter"],{}]
INFO: Discovered 1 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","","greeter"],{}].
INFO: Processed 1 actions, 0 cache hits.
INFO: Artifacts built, logical paths are:
out.txt [557db03de997c86a4a028e1ebd3a1ceb225be238:12:f]
$
```
The subcommand `build` just builds the artifact but does not stage it to
any user-defined location on the file system. Instead it reports a
description of the artifact consisting of `git` blob identifier, size,
and type (in this case `f` for non-executable file). To also stage the
produced artifact to the working directory, use the `install` subcommand
and specify the output directory:
``` sh
$ just install greeter -o .
INFO: Requested target is [["@","","","greeter"],{}]
INFO: Analysed target [["@","","","greeter"],{}]
INFO: Discovered 1 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","","greeter"],{}].
INFO: Processed 1 actions, 1 cache hits.
INFO: Artifacts can be found in:
/tmp/tutorial/out.txt [557db03de997c86a4a028e1ebd3a1ceb225be238:12:f]
$ cat out.txt
Hello World
$
```
Note that the `install` subcommand initiates the build a second time,
without executing any actions as all actions are being served from
cache. The produced artifact is identical, which is indicated by the
same hash/size/type.
If one is only interested in one of the final artifacts, one can also
request via the `-P` option that this particular artifact be written
to standard output after the build; if the target produces only a
single artifact, the flag `-p` can be used instead as well (without
the need of specifying the artifact). As all messages are reported
to standard error, this can be used for both, interactively reading
a text file, as well as for piping the artifact to another program.
``` sh
$ just build greeter -P out.txt
INFO: Requested target is [["@","","","greeter"],{}]
INFO: Analysed target [["@","","","greeter"],{}]
INFO: Discovered 1 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","","greeter"],{}].
INFO: Processed 1 actions, 1 cache hits.
INFO: Artifacts built, logical paths are:
out.txt [557db03de997c86a4a028e1ebd3a1ceb225be238:12:f]
Hello World
$ just build -p
INFO: Requested target is [["@","","","greeter"],{}]
INFO: Analysed target [["@","","","greeter"],{}]
INFO: Discovered 1 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","","greeter"],{}].
INFO: Processed 1 actions, 1 cache hits.
INFO: Artifacts built, logical paths are:
out.txt [557db03de997c86a4a028e1ebd3a1ceb225be238:12:f]
Hello World
$
```
In the last example we used that, if no target is specified, the
lexicographically first one is taken.
Alternatively, we could also directly request the artifact `out.txt`
from *justbuild*'s CAS (content-addressable storage) and print it on
the command line via:
``` sh
$ just install-cas [557db03de997c86a4a028e1ebd3a1ceb225be238:12:f]
Hello World
$
```
The canonical way of requesting an object from the CAS is, as just
shown, to specify the full triple of hash, size, and type, separated by
colons and enclosed in square brackets. To simplify usage, the brackets
can be omitted and the size and type fields have the default values `0`
and `f`, respectively. While the default value for the size is wrong for
all but one string, the hash still determines the content of the file
and hence the local CAS is still able to retrieve the file. So the
typical invocation would simply specify the hash.
``` sh
$ just install-cas 557db03de997c86a4a028e1ebd3a1ceb225be238
Hello World
$
```
Targets versus Files: The Stage
-------------------------------
When invoking the `build` command, we had to specify the target
`greeter`, not the output file `out.txt`. While other build systems
allow requests specifying an output file, for *justbuild* this would
conflict with a fundamental design principle: staging; each target has
its own logical output space, the "stage", where it can put its
artifacts. We can, without any problem, add a second target also
generating a file `out.txt`.
``` {.jsonc srcname="TARGETS"}
...
, "upper":
{ "type": "generic"
, "cmds": ["cat name.txt | tr a-z A-Z > out.txt"]
, "outs": ["out.txt"]
, "deps": ["name.txt"]
}
...
```
As we only request targets, no conflicts arise.
``` sh
$ just build upper -p
INFO: Requested target is [["@","","","upper"],{}]
INFO: Analysed target [["@","","","upper"],{}]
INFO: Discovered 1 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","","upper"],{}].
INFO: Processed 1 actions, 0 cache hits.
INFO: Artifacts built, logical paths are:
out.txt [83cf24cdfb4891a36bee93421930dd220766299a:6:f]
WORLD
$ just build greeter -p
INFO: Requested target is [["@","","","greeter"],{}]
INFO: Analysed target [["@","","","greeter"],{}]
INFO: Discovered 1 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","","greeter"],{}].
INFO: Processed 1 actions, 1 cache hits.
INFO: Artifacts built, logical paths are:
out.txt [557db03de997c86a4a028e1ebd3a1ceb225be238:12:f]
Hello World
$
```
While one normally tries to design targets in such a way that they
don't have conflicting files if they should be used together, it is up
to the receiving target to decide what to do with those artifacts. A
built-in rule allowing to rearrange artifacts is `"install"`; a detailed
description of this rule can be found in the documentation. In the
simple case of a target producing precisely one file, the argument
`"files"` can be used to remap that file. Note that the mapping is
from the desired location to the target name (representing the single
artifact of that target) as such a mapping is necessarily conflict free.
``` {.jsonc srcname="TARGETS"}
...
, "both":
{"type": "install", "files": {"hello.txt": "greeter", "upper.txt": "upper"}}
...
```
``` sh
$ just build both
INFO: Requested target is [["@","","","both"],{}]
INFO: Analysed target [["@","","","both"],{}]
INFO: Discovered 2 actions, 0 tree overlays, 0 trees, 0 blobs
INFO: Building [["@","","","both"],{}].
INFO: Processed 2 actions, 2 cache hits.
INFO: Artifacts built, logical paths are:
hello.txt [557db03de997c86a4a028e1ebd3a1ceb225be238:12:f]
upper.txt [83cf24cdfb4891a36bee93421930dd220766299a:6:f]
$
```
Inspecting internals: Analyse
-----------------------------
For our simple targets, it is easy to remember what they look like,
and what the build steps are. For more complex projects this might
no longer be the case. Therefore, *justbuild* tries to be very
transparent about its understanding of the build. The command
to do so is called `analyse` and also takes a target as argument.
It shows all(!) the information available to a target depending
on it. In the case of `greeter` this is just a single artifact.
``` sh
$ just analyse greeter
INFO: Requested target is [["@","","","greeter"],{}]
INFO: Analysed target [["@","","","greeter"],{}]
INFO: Result of target [["@","","","greeter"],{}]: {
"artifacts": {
"out.txt": {"data":{"id":"28c9f5feb17361a5f755b19961b4c80c651586269786d93e58bfff5b0038c620","path":"out.txt"},"type":"ACTION"}
},
"provides": {
},
"runfiles": {
}
}
INFO: Target tainted ["test"].
```
As we can see, the artifact will be installed at the logical
path `out.txt`. The artifact itself is generated by a build
action, more precisely, it is the output `out.txt` of that
action. Actions, in general, can have more than one output.
For the target `both` we find the same artifact at a different
output location in this case `hello.txt`.
``` sh
$ just analyse both
INFO: Requested target is [["@","","","both"],{}]
INFO: Analysed target [["@","","","both"],{}]
INFO: Result of target [["@","","","both"],{}]: {
"artifacts": {
"hello.txt": {"data":{"id":"28c9f5feb17361a5f755b19961b4c80c651586269786d93e58bfff5b0038c620","path":"out.txt"},"type":"ACTION"},
"upper.txt": {"data":{"id":"f122c2af488a56c94d08650a9d799dbb910484409116b8e0c071e198082256ef","path":"out.txt"},"type":"ACTION"}
},
"provides": {
},
"runfiles": {
"hello.txt": {"data":{"id":"28c9f5feb17361a5f755b19961b4c80c651586269786d93e58bfff5b0038c620","path":"out.txt"},"type":"ACTION"},
"upper.txt": {"data":{"id":"f122c2af488a56c94d08650a9d799dbb910484409116b8e0c071e198082256ef","path":"out.txt"},"type":"ACTION"}
}
}
INFO: Target tainted ["test"].
```
We also see another technical detail. As an `install` target,
`both` also has the same artifact in the `runfiles` arrangement of
files. Having two dedicated arrangements of artifacts, that are both
installed when the target is installed, can be useful to distinguish
between the main artifact and files needed when building against
that target (e.g., to distinguish a library from its header files).
As a very generic built-in rule, however, `install` simply makes
all files available in both arrangements.
The canonical way to obtain all action definitions is to dump the
action graph using the `--dump-graph` option.
``` sh
$ just analyse both --dump-graph all-actions.json
$ cat all-actions.json
```
As we know that the action was generated by the `greeter` target,
was can also ask that target for the actions associated with it
using the `--dump-actions` option. The `install` target does not
generate any actions.
``` sh
$ just analyse greeter --dump-actions -
INFO: Requested target is [["@","","","greeter"],{}]
INFO: Analysed target [["@","","","greeter"],{}]
INFO: Result of target [["@","","","greeter"],{}]: {
"artifacts": {
"out.txt": {"data":{"id":"28c9f5feb17361a5f755b19961b4c80c651586269786d93e58bfff5b0038c620","path":"out.txt"},"type":"ACTION"}
},
"provides": {
},
"runfiles": {
}
}
INFO: Actions for target [["@","","","greeter"],{}]:
[
{
"command": ["sh","-c","echo -n 'Hello ' > out.txt\ncat name.txt >> out.txt\n"],
"input": {
"name.txt": {
"data": {
"path": "name.txt",
"repository": ""
},
"type": "LOCAL"
}
},
"output": ["out.txt"]
}
]
$ just analyse both --dump-actions -
INFO: Requested target is [["@","","","both"],{}]
INFO: Analysed target [["@","","","both"],{}]
INFO: Result of target [["@","","","both"],{}]: {
"artifacts": {
"hello.txt": {"data":{"id":"28c9f5feb17361a5f755b19961b4c80c651586269786d93e58bfff5b0038c620","path":"out.txt"},"type":"ACTION"},
"upper.txt": {"data":{"id":"f122c2af488a56c94d08650a9d799dbb910484409116b8e0c071e198082256ef","path":"out.txt"},"type":"ACTION"}
},
"provides": {
},
"runfiles": {
"hello.txt": {"data":{"id":"28c9f5feb17361a5f755b19961b4c80c651586269786d93e58bfff5b0038c620","path":"out.txt"},"type":"ACTION"},
"upper.txt": {"data":{"id":"f122c2af488a56c94d08650a9d799dbb910484409116b8e0c071e198082256ef","path":"out.txt"},"type":"ACTION"}
}
}
INFO: Actions for target [["@","","","both"],{}]:
[
]
$
```
However, those actions are not owned by a target in any way.
Actions are identified by their definition in the same way as
`git` identifies a blob by its sequence of bytes and names it by
a suitable hash value. So, let's add another target defining an
action in the same way.
``` {.jsonc srcname="TARGETS"}
...
, "another greeter":
{ "type": "generic"
, "cmds": ["echo -n 'Hello ' > out.txt", "cat name.txt >> out.txt"]
, "outs": ["out.txt"]
, "deps": ["name.txt"]
}
...
```
Then we find that it defines the same artifact.
``` sh
$ just analyse 'another greeter'
INFO: Requested target is [["@","","","another greeter"],{}]
INFO: Analysed target [["@","","","another greeter"],{}]
INFO: Result of target [["@","","","another greeter"],{}]: {
"artifacts": {
"out.txt": {"data":{"id":"28c9f5feb17361a5f755b19961b4c80c651586269786d93e58bfff5b0038c620","path":"out.txt"},"type":"ACTION"}
},
"provides": {
},
"runfiles": {
}
}
```
This is also the notion of equality used to detect conflicts when
analysing targets before running any actions.
|