summaryrefslogtreecommitdiff
path: root/doc/tutorial/third-party-software.org
blob: 2e1fb77eb98b863eb6206516dde44fae4e4ba7b5 (plain)
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
* Building Third-party Software

Third-party projects usually ship with their own build description, which often
happens to be not compatible with justbuild. Nevertheless, it is highly
desireable to include external projects via their source code base, instead of
relying on the integration of out-of-band binary distributions. justbuild offers
a flexible approach to provide the required build description via an overlay
layer without the need to touch the original code base.

In the remainder of this section, we will use the open-source project
[[https://github.com/fmtlib/fmt][fmtlib]] as an example for integrating
third-party software to a justbuild project.

** Creating the target overlay layer for fmtlib

Before we construct the overlay layer for fmtlib, we need to determine its file
structure ([[https://github.com/fmtlib/fmt/tree/8.1.1][tag 8.1.1]]). The
relevant header and source files are structured as follows:

#+BEGIN_SRC
  fmt
  |
  +--include
  |  +--fmt
  |     +--*.h
  |
  +--src
     +--format.cc
     +--os.cc
#+END_SRC

The public headers can be found in ~include/fmt~, while the library's source
files are located in ~src~. For the overlay layer, the ~TARGETS~ files should be
placed in a tree structure that resembles the original code base's structure.
It is also good practice to provide a top-level ~TARGETS~ file, leading to the
following structure for the overlay:

#+BEGIN_SRC
  fmt-layer
  |
  +--TARGETS
  +--include
  |  +--fmt
  |     +--TARGETS
  |
  +--src
     +--TARGETS
#+END_SRC

Let's create the overlay structure:

#+BEGIN_SRC sh
$ mkdir -p fmt-layer/include/fmt
$ mkdir -p fmt-layer/src
#+END_SRC

The directory ~include/fmt~ contains only header files. As we want all files in
this directory to be included in the ~"header directory"~ target, we can safely
use the explicit ~TREE~ reference[fn:1], which collects /all/ directory contents
from ~"."~. Note that the ~TARGETS~ file is only part of the overlay, and
therefore will not be part of this tree. Furthermore, this tree should be staged
to ~"fmt"~, so that any consumer can include those headers via ~<fmt/...>~. The
resulting header directory target ~"hdrs"~ in ~include/fmt/TARGETS~ should be
described as:

[fn:1] Explicit ~TREE~ references are always a list of length 3, to distinguish
them from target references of length 2 (module and target name). Furthermore,
the second list element is always ~null~ as we only want to allow tree
references from the current module.


#+BEGIN_SRC js
{ "hdrs":
  { "type": ["@", "rules", "CC", "header directory"]
  , "hdrs": [["TREE", null, "."]]
  , "stage": ["fmt"]
  }
}
#+END_SRC

The actual library target is defined in the directory ~src~. For the public
headers, it refers to the previously created header directory via its
fully-qualified target name (~["include/fmt", "hdrs"]~). Source files are the
two local files ~format.cc~, and ~os.cc~. The final target description in
~src/TARGETS~ will look like this:

#+BEGIN_SRC js
{ "fmt":
  { "type": ["@", "rules", "CC", "library"]
  , "name": ["fmt"]
  , "hdrs": [["include/fmt", "hdrs"]]
  , "srcs": ["format.cc", "os.cc"]
  }
}
#+END_SRC

Finally, the top-level ~TARGETS~ file can be created. While it is technically
not strictly required, it is considered good practice to /export/ every target
that may be used by another project. Exported targets are subject to high-level
target caching, which allows to skip the analysis and traversal of entire
subgraphs in the action graph. Therefore, we create an export target that
exports the target ~["src", "fmt"]~, with only the variables in the field
~"flexible_config"~ being propagated. The top-level ~TARGET~ file contains the
following content:

#+BEGIN_SRC js
{ "fmt":
  { "type": "export"
  , "target": ["src", "fmt"]
  , "flexible_config": ["CXX", "CXXFLAGS", "AR", "ENV"]
  }
}
#+END_SRC

** Adding fmtlib to the Multi-Repository Configuration

Based on the /hello world/ tutorial, we can extend the existing ~repos.json~ by
the layer definition ~"fmt-targets-layer"~ and the repository ~"fmtlib"~, which
is based on the Git repository with its target root being overlayed.
Furthermore, we want to use ~"fmtlib"~ in the repository ~"tutorial"~, and
therefore need to introduce an additional binding ~"format"~ for it:

#+BEGIN_SRC js
{ "repositories":
  { "tutorial":
    { "repository": {"type": "file", "path": "."}
    , "bindings": {"rules": "just-rules", "format": "fmtlib"}
    }
  , "just-rules": { /* ... unchanged ... */ }
  , "tutorial-defaults": { /* ... unchanged ... */ }
  , "fmt-targets-layer":
    { "repository": {"type": "file", "path": "./fmt-layer"}
    }
  , "fmtlib":
    { "repository":
      { "type": "git"
      , "branch": "8.1.1"
      , "commit": "b6f4ceaed0a0a24ccf575fab6c56dd50ccf6f1a9"
      , "repository": "https://github.com/fmtlib/fmt.git"
      }
    , "target_root": "fmt-targets-layer"
    , "bindings": {"rules": "just-rules"}
    }
  }
}
#+END_SRC

This ~"format"~ binding can you be used to add a new dependency in
~greet/TARGETS~:

#+BEGIN_SRC js
{ "greet":
  { "type": ["@", "rules", "CC", "library"]
  , "name": ["greet"]
  , "hdrs": ["greet.hpp"]
  , "srcs": ["greet.cpp"]
  , "stage": ["greet"]
  , "deps": [["@", "format", "", "fmt"]]
  }
}
#+END_SRC

Consequently, the ~fmtlib~ library can now be used by ~greet/greet.cpp~:

#+BEGIN_SRC cpp
#include "greet.hpp"
#include <fmt/format.h>

void greet(std::string const& s) {
  fmt::print("Hello {}!\n", s);
}
#+END_SRC

Due to changes made to ~repos.json~, building this tutorial requires to rerun
~just-mr~, which will fetch the necessary sources for the external repositories:

#+BEGIN_SRC sh
$ CONF=$(/usr/src/justbuild/bin/just-mr.py -C repos.json setup tutorial)
$ just build -C $CONF helloworld
INFO: Requested target is [["@","tutorial","","helloworld"],{}]
INFO: Analysed target [["@","tutorial","","helloworld"],{}]
INFO: Export targets found: 0 cached, 0 uncached, 1 not eligible for caching
INFO: Discovered 7 actions, 5 trees, 0 blobs
INFO: Building [["@","tutorial","","helloworld"],{}].
INFO: Processed 7 actions, 1 cache hits.
INFO: Artifacts built, logical paths are:
        helloworld [e489bdd234787c49c4fefdd3b8a03c399a2d46f5:133000:x]
$
#+END_SRC

Note that the single cache hit we observe, is the compile action of ~main.cpp~,
which is unchanged.

** Employing high-level target caching

The make use of high-level target caching for exported targets, we need to
ensure that all inputs to an export target are transitively content-fixed. This
is automatically the case for Git repositories. However, the ~"fmtlib"~
repository also depends on ~"fmt-target-layer"~, ~"just-rules"~, and
~"tutorial-defaults"~. To enforce those repositories to be content-fixed as
well, the pragma ~"to_git"~ can be set, which instructs ~just-mr~ to create a
Git repository from those file roots (or determine the committed ~tree-id~ if
those roots are already under Git versioning). The ~repos.json~ with support for
high-level target caching will look like this:

#+BEGIN_SRC js
{ "repositories":
  { "tutorial":
    { "repository": {"type": "file", "path": "."}
    , "bindings": {"rules": "just-rules", "format": "fmtlib"}
    }
  , "just-rules":
    { "repository":
      { "type": "file"
      , "path": "/usr/src/justbuild/rules"
      , "pragma": {"to_git": true}
      }
    , "target_root": "tutorial-defaults"
    , "rule_root": "just-rules"
    }
  , "tutorial-defaults":
    { "repository":
      { "type": "file"
      , "path": "./tutorial-defaults"
      , "pragma": {"to_git": true}
      }
    }
  , "fmt-targets-layer":
    { "repository":
      { "type": "file"
      , "path": "./fmt-layer"
      , "pragma": {"to_git": true}
      }
    }
  , "fmtlib":
    { "repository":
      { "type": "git"
      , "branch": "master"
      , "commit": "b6f4ceaed0a0a24ccf575fab6c56dd50ccf6f1a9"
      , "repository": "https://github.com/fmtlib/fmt.git"
      }
    , "target_root": "fmt-targets-layer"
    , "bindings": {"rules": "just-rules"}
    }
  }
}
#+END_SRC

Due to changes in the repository configuration, ~just-mr~ needs to be rerun and
the benefits of the target cache should be visible on the second build:

#+BEGIN_SRC sh
$ CONF=$(/usr/src/justbuild/bin/just-mr.py -C repos.json setup tutorial)
From ~/.cache/just/tmp-workspaces/file/tmp/tutorial/tutorial-defaults
 * branch              HEAD       -> FETCH_HEAD
From ~/.cache/just/tmp-workspaces/file/tmp/tutorial/fmt-layer
 * branch              HEAD       -> FETCH_HEAD

$ just build -C $CONF helloworld
INFO: Requested target is [["@","tutorial","","helloworld"],{}]
INFO: Analysed target [["@","tutorial","","helloworld"],{}]
INFO: Export targets found: 0 cached, 1 uncached, 0 not eligible for caching
INFO: Discovered 7 actions, 5 trees, 0 blobs
INFO: Building [["@","tutorial","","helloworld"],{}].
INFO: Processed 7 actions, 7 cache hits.
INFO: Artifacts built, logical paths are:
        helloworld [e489bdd234787c49c4fefdd3b8a03c399a2d46f5:133000:x]

$ just build -C $CONF helloworld
INFO: Requested target is [["@","tutorial","","helloworld"],{}]
INFO: Analysed target [["@","tutorial","","helloworld"],{}]
INFO: Export targets found: 1 cached, 0 uncached, 0 not eligible for caching
INFO: Discovered 4 actions, 2 trees, 0 blobs
INFO: Building [["@","tutorial","","helloworld"],{}].
INFO: Processed 4 actions, 4 cache hits.
INFO: Artifacts built, logical paths are:
        helloworld [e489bdd234787c49c4fefdd3b8a03c399a2d46f5:133000:x]
$
#+END_SRC

Note that in the second run the export target ~"fmt"~ was taken from cache and
its 3 actions were eliminated, as their result has been recorded to the
high-level target cache during the first run.

** Combining overlay layers for multiple projects

Projects typically depend on multiple external repositories. Creating an overlay
layer for each external repository might unnecessarily clutter up the repository
configuration and the file structure of your repository. One solution to
mitigate this issue is to combine the ~TARGETS~ files of multiple external
repositories in a single overlay layer. To avoid conflicts, the ~TARGETS~ files
can be assigned different file names per repository. As an example, imagine a
common overlay layer with the files ~TARGETS.fmt~ and ~TARGETS.gsl~ for the
repositories ~"fmtlib"~ and ~"gsl-lite"~, respectively:

#+BEGIN_SRC
  common-layer
  |
  +--TARGETS.fmt
  +--TARGETS.gsl
  +--include
  |  +--fmt
  |     +--TARGETS.fmt
  |
  +--src
     +--TARGETS.fmt
#+END_SRC

Such a common overlay layer can be used as the target root for both repositories
with only one difference: the ~"target_file_name"~ field. By specifying this
field, the dispatch where to find the respective target description for each
repository is implemented. For the given example, the following ~repos.json~
defines the overlay ~"common-targets-layer"~, which is used by ~"fmtlib"~ and
~"gsl-lite"~:

#+BEGIN_SRC js
{ "repositories":
  { "tutorial": { /* ... unchanged ... */ }
  , "just-rules": { /* ... unchanged ... */ }
  , "tutorial-defaults": { /* ... unchanged ... */ }
  , "common-targets-layer":
    { "repository":
      { "type": "file"
      , "path": "./common-layer"
      , "pragma": {"to_git": true}
      }
    }
  , "fmtlib":
    { "repository":
      { "type": "git"
      , "branch": "8.1.1"
      , "commit": "b6f4ceaed0a0a24ccf575fab6c56dd50ccf6f1a9"
      , "repository": "https://github.com/fmtlib/fmt.git"
      }
    , "target_root": "common-targets-layer"
    , "target_file_name": "TARGETS.fmt"
    , "bindings": {"rules": "just-rules"}
    }
  , "gsl-lite":
    { "repository":
      { "type": "git"
      , "branch": "v0.40.0"
      , "commit": "d6c8af99a1d95b3db36f26b4f22dc3bad89952de"
      , "repository": "https://github.com/gsl-lite/gsl-lite.git"
      }
    , "target_root": "common-targets-layer"
    , "target_file_name": "TARGETS.gsl"
    , "bindings": {"rules": "just-rules"}
    }
  }
}
#+END_SRC