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
|
// file : build/operation -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Tools CC
// license : MIT; see accompanying LICENSE file
#ifndef BUILD_OPERATION
#define BUILD_OPERATION
#include <string>
#include <iosfwd>
#include <cstdint>
#include <functional> // reference_wrapper
#include <build/string-table>
namespace build
{
// 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.
//
struct meta_operation_info
{
const std::string name;
};
// Built-in meta-operations.
//
extern meta_operation_info perform;
struct operation_info
{
const std::string name;
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 = string_table<
meta_operation_id,
std::reference_wrapper<const meta_operation_info>>;
using operation_table = string_table<
operation_id,
std::reference_wrapper<const operation_info>>;
template <>
struct string_table_traits<std::reference_wrapper<const meta_operation_info>>
{
static const std::string&
key (const meta_operation_info& x) {return x.name;}
};
template <>
struct string_table_traits<std::reference_wrapper<const operation_info>>
{
static const std::string&
key (const operation_info& x) {return x.name;}
};
}
#endif // BUILD_OPERATION
|