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
|
// 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)
: 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;
};
inline bool
operator== (action x, action y)
{
return x.inner_id == y.inner_id && x.outer_id == y.outer_id;
}
inline bool
operator!= (action x, action y) {return !(x == y);}
std::ostream&
operator<< (std::ostream&, action);
// Id constants for build-in operations.
//
const meta_operation_id perform_id = 1;
// 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;
const operation_id update_id = 2;
const operation_id clean_id = 3;
const action_id perform_update_id = (perform_id << 4) | update_id;
const action_id perform_clean_id = (perform_id << 4) | clean_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_already_done; // E.g., [already] '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&);
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.
//
void
execute (action, const action_targets&);
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_already_done; // E.g., [already] '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);
};
// Build-in operations.
//
extern operation_info default_;
extern operation_info update;
extern operation_info clean;
// Meta/operation tables.
//
using meta_operation_table = butl::string_table<
meta_operation_id,
std::reference_wrapper<const meta_operation_info>>;
using operation_table = butl::string_table<
operation_id,
std::reference_wrapper<const operation_info>>;
}
namespace butl
{
template <>
struct string_table_traits<
std::reference_wrapper<const build::meta_operation_info>>
{
static const std::string&
key (const build::meta_operation_info& x) {return x.name;}
};
template <>
struct string_table_traits<
std::reference_wrapper<const build::operation_info>>
{
static const std::string&
key (const build::operation_info& x) {return x.name;}
};
}
#endif // BUILD_OPERATION
|