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
|
// 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;
struct action
{
action (meta_operation_id m, operation_id o): id ((m << 4) | o) {}
meta_operation_id
meta_operation () const {return id >> 4;}
operation_id
operation () const {return id & 0xF;}
// Implicit conversion operator to action_id for the switch()
// statement, etc.
//
operator action_id () const {return id;}
action_id id;
};
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'.
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, resolve 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 (*match) (action,
scope& root,
const target_key&,
const location&,
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&);
// Resolve and match the target. This is the default implementation
// that does just that and adds a pointer to the target to the list.
//
void
match (action, scope&, const target_key&, const location&, 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;
};
// 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
|