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
|
// file : build/operation -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
#ifndef BUILD_OPERATION
#define BUILD_OPERATION
#include <string>
#include <iosfwd>
#include <vector>
#include <cstdint>
#include <functional> // reference_wrapper
#include <butl/string-table>
#include <build/types>
namespace build
{
class location;
class scope;
class target_key;
// While we are using uint8_t for the meta/operation ids, we assume
// that each is limited to 4 bits (max 128 entries) so that we can
// store the combined action id in uint8_t as well. This makes our
// life easier when it comes to defining switch labels for action
// ids (no need to mess with endian-ness).
//
// Note that 0 is not a valid meta/operation/action id.
//
using meta_operation_id = std::uint8_t;
using operation_id = std::uint8_t;
using action_id = std::uint8_t;
// Meta-operations and operations are not the end of the story. We
// also have operation nesting (currently only one level deep) which
// is used to implement pre/post operations (currently, but may be
// useful for other things). Here is the idea: the test operation
// needs to make sure that the targets that it needs to test are
// up-to-date. So it runs update as its pre-operation. It is almost
// like an ordinary update except that it has test as its outer
// operation (the meta-operations are always the same). This way a
// rule can recognize that this is "update for test" and do something
// differently. For example, if an executable is not a test, then
// there is no use updating it. At the same time, most rules will
// ignore the fact that this is a nested update and for them it is
// "update as usual".
//
struct action
{
action (): inner_id (0), outer_id (0) {} // Invalid action.
bool
valid () const {return inner_id != 0;}
// If this is not a nested operation, then outer should be 0.
//
action (meta_operation_id m, operation_id inner, operation_id outer = 0)
: inner_id ((m << 4) | inner),
outer_id (outer == 0 ? 0 : (m << 4) | outer) {}
meta_operation_id
meta_operation () const {return inner_id >> 4;}
operation_id
operation () const {return inner_id & 0xF;}
operation_id
outer_operation () const {return outer_id & 0xF;}
// Implicit conversion operator to action_id for the switch()
// statement, etc. Most places will only care about the inner
// operation.
//
operator action_id () const {return inner_id;}
action_id inner_id;
action_id outer_id;
};
// This is an "overrides" comparison, i.e., it returns true
// if the recipe for x overrides recipe for y. The idea is
// that for the same inner operation, action with an outer
// operation is "weaker" than the one without.
//
inline bool
operator> (action x, action y)
{
return x.inner_id != y.inner_id ||
(x.outer_id != y.outer_id && y.outer_id != 0);
}
// Note that these ignore the outer operation.
//
inline bool
operator== (action x, action y) {return x.inner_id == y.inner_id;}
inline bool
operator!= (action x, action y) {return !(x == y);}
std::ostream&
operator<< (std::ostream&, action);
// Id constants for build-in and pre-defined meta/operations.
//
const meta_operation_id perform_id = 1;
const meta_operation_id configure_id = 2;
const meta_operation_id disfigure_id = 3;
const meta_operation_id dist_id = 4;
// The default operation is a special marker that can be used to
// indicate that no operation was explicitly specified by the user.
//
const operation_id default_id = 1; // Shall be first.
const operation_id update_id = 2;
const operation_id clean_id = 3;
const operation_id test_id = 4;
const operation_id install_id = 5;
const action_id perform_update_id = (perform_id << 4) | update_id;
const action_id perform_clean_id = (perform_id << 4) | clean_id;
const action_id perform_test_id = (perform_id << 4) | test_id;
const action_id perform_install_id = (perform_id << 4) | install_id;
// Recipe execution mode.
//
// When a target is a prerequisite of another target, its recipe can be
// executed before the dependent's recipe (the normal case) or after.
// We will call these "front" and "back" execution modes, respectively
// (think "the prerequisite is 'front-running' the dependent").
//
// There could also be several dependent targets and the prerequisite's
// recipe can be execute as part of the first dependent (the normal
// case) or last (or for all/some of them; see the recipe execution
// protocol in <target>). We will call these "first" and "last"
// execution modes, respectively.
//
// Now you may be having a hard time imagining where a mode other than
// the normal one (first/front) could be useful. An the answer is,
// compensating or inverse operations such as clean, uninstall, etc.
// If we use the last/back mode for, say, clean, then we will remove
// targets in the order inverse to the way they were updated. While
// this sounds like an elegant idea, are there any practical benefits
// of doing it this way. As it turns out there is (at least) one: when
// we are removing a directory (see fsdir{}), we want to do it after
// all the targets that depend on it (such as files, sub-directories)
// were removed. If we do it before, then the directory won't be empty
// yet.
//
// It appears that this execution mode is dictated by the essence of
// the operation. Constructive operations (those that "do") seem to
// naturally use the first/front mode. That is, we need to "do" the
// prerequisite first before we can "do" the dependent. While the
// destructive ones (those that "undo") seem to need last/back. That
// is, we need to "undo" all the dependents before we can "undo" the
// prerequisite (say, we need to remove all the files before we can
// remove their directory).
//
// If you noticed the parallel with the way C++ construction and
// destruction works for base/derived object then you earned a gold
// star!
//
// Note that the front/back mode is realized in the dependen's recipe
// (which is another indication that it is a property of the operation).
//
enum class execution_mode {first, last};
// Meta-operation info.
//
// Normally a list of resolved and matched targets to execute. But
// can be something else, depending on the meta-operation.
//
typedef std::vector<void*> action_targets;
struct meta_operation_info
{
const std::string name;
// Name derivatives for diagnostics. If empty, then the meta-
// operation need not be mentioned.
//
const std::string name_do; // E.g., [to] 'configure'.
const std::string name_doing; // E.g., [while] 'configuring'.
const std::string name_done; // E.g., 'is configured'.
// If operation_pre() is not NULL, then it may translate default_id
// (and only default_id) to some other operation. If not translated,
// then default_id is used. If, however, operation_pre() is NULL,
// then default_id is translated to update_id.
//
void (*meta_operation_pre) (); // Start of meta-operation batch.
operation_id (*operation_pre) (operation_id); // Start of operation batch.
// Meta-operation-specific logic to load the buildfile, search and match
// the targets, and execute the action on the targets.
//
void (*load) (const path& buildfile,
scope& root,
const dir_path& out_base,
const dir_path& src_base,
const location&);
void (*search) (scope& root,
const target_key&,
const location&,
action_targets&);
void (*match) (action, action_targets&);
void (*execute) (action, const action_targets&, bool quiet);
void (*operation_post) (operation_id); // End of operation batch.
void (*meta_operation_post) (); // End of meta-operation batch.
};
// Built-in meta-operations.
//
// perform
//
// Load the buildfile. This is the default implementation that first
// calls root_pre(), then creates the scope for out_base, and, finally,
// loads the buildfile unless it has already been loaded for the root
// scope.
//
void
load (const path& buildfile,
scope& root,
const dir_path& out_base,
const dir_path& src_base,
const location&);
// Search and match the target. This is the default implementation
// that does just that and adds a pointer to the target to the list.
//
void
search (scope&, const target_key&, const location&, action_targets&);
void
match (action, action_targets&);
// Execute the action on the list of targets. This is the default
// implementation that does just that while issuing appropriate
// diagnostics (unless quiet).
//
void
execute (action, const action_targets&, bool quiet);
extern meta_operation_info perform;
// Operation info.
//
struct operation_info
{
const std::string name;
// Name derivatives for diagnostics. Note that unlike meta-operations,
// these can only be empty for the default operation (id 1), And
// meta-operations that make use of the default operation shall not
// have empty derivatives (failed which only target name will be
// printed).
//
const std::string name_do; // E.g., [to] 'update'.
const std::string name_doing; // E.g., [while] 'updating'.
const std::string name_done; // E.g., 'is up to date'.
const execution_mode mode;
// If the returned operation_id's are not 0, then they are injected
// as pre/post operations for this operation. Can be NULL if unused.
// The returned operation_id shall not be default_id.
//
operation_id (*pre) (meta_operation_id);
operation_id (*post) (meta_operation_id);
};
// Built-in operations.
//
extern operation_info default_;
extern operation_info update;
extern operation_info clean;
// Global meta/operation tables. Each registered meta/operation
// is assigned an id which is used as an index in the per-project
// registered meta/operation lists.
//
// We have three types of meta/operations: built-in (e.g., perform,
// update), pre-defined (e.g., configure, test), and dynamically-
// defined. For built-in ones, both the id and implementation are
// part of the build2 core. For pre-defined, the id is registered
// as part of the core but the implementation is loaded as part of
// a module. The idea with pre-defined operations is that they have
// common, well-established semantics but could still be optional.
// Another aspect of pre-defined operations is that often rules
// across multiple modules need to know their ids. Finally,
// dynamically-defined meta/operations have their ids registered
// as part of a module load. In this case, the meta/operation is
// normally (but not necessarily) fully implemented by this module.
//
// Note also that the name of a meta/operation in a sense defines
// its semantics. It would be strange to have an operation called
// test that does two very different things in different projects.
//
extern butl::string_table<meta_operation_id> meta_operation_table;
extern butl::string_table<operation_id> operation_table;
// These are "sparse" in the sense that we may have "holes" that
// are represented as NULL pointers. Also, lookup out of bounds
// is treated as a hole.
//
template <typename T>
struct sparse_vector
{
using base_type = std::vector<T*>;
using size_type = typename base_type::size_type;
void
insert (size_type i, T& x)
{
size_type n (v_.size ());
if (i < n)
v_[i] = &x;
else
{
if (n != i)
v_.resize (i, nullptr); // Add holes.
v_.push_back (&x);
}
}
T*
operator[] (size_type i) const
{
return i < v_.size () ? v_[i] : nullptr;
}
bool
empty () const {return v_.empty ();}
// Note that this is more of a "max index" rather than size.
//
size_type
size () const {return v_.size ();}
private:
base_type v_;
};
using meta_operations = sparse_vector<const meta_operation_info>;
using operations = sparse_vector<const operation_info>;
}
#endif // BUILD_OPERATION
|