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
|
// file : libbuild2/cc/compiledb.hxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
#ifndef LIBBUILD2_CC_COMPILEDB_HXX
#define LIBBUILD2_CC_COMPILEDB_HXX
#include <unordered_map>
#ifndef BUILD2_BOOTSTRAP
# include <libbutl/json/serializer.hxx>
#endif
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/action.hxx>
#include <libbuild2/context.hxx>
namespace build2
{
namespace cc
{
using compiledb_name_filter = vector<pair<optional<string>, bool>>;
using compiledb_type_filter = vector<pair<optional<string>, string>>;
class compiledb
{
public:
// Match callback where we confirm an entry in the database and also
// signal whether it has changes (based on change tracking in depdb).
// Return true to force compilation of this target and thus make sure
// the below execute() is called (unless something before that failed).
//
// Besides noticing changes, this callback is also necessary to notice
// and delete entries that should no longer be in the database (e.g., a
// source file was removed from the project).
//
// Note that output is either obj*{}, bmi*{}, of hbmi*{}.
//
static bool
match (const scope& bs,
const file& output, const path& output_path,
const file& input,
bool changed);
// Execute callback where we insert or update an entry in the database.
//
// The {relo, abso}, and {relm, absm} pairs are used to "untranslate"
// relative paths to absolute. Specifically, any argument that has rel?
// as a prefix has this prefix replaced with the corresponding abs?.
// Note that this means we won't be able to handle old MSVC and
// clang-cl, which don't support the `/F?: <path>` form, only
// `/F?<path>`. Oh, well. Note also that either relo or relm (but not
// both) could be empty if unused.
//
// Note that we assume the source file is always absolute and is the
// last argument.
//
// Why do we want absolute paths? That's a good question. Our initial
// plan was to compare command lines in order to detect when we need to
// update the database. And if those changed with every change of CWD,
// that would be of little use. But then we realized we could do better
// by using depdb to detect changes. So now we actually don't have a
// need to get rid of the relative paths in the command line. But seeing
// that we already have it, let's keep it for now in case it makes a
// different to some broken/legacy consumers. Note also that C++ module
// name-to-BMI mapping is not untranslated (see append_module_options()).
//
static void
execute (const scope& bs,
const file& output, const path& output_path,
const file& input, const path& input_path,
const process_path& cpath, const cstrings& args,
const path& relo, const path& abso,
const path& relm, const path& absm);
public:
using path_type = build2::path;
string name;
path_type path;
// The path is expected to be absolute and normalized or empty if the
// name is `-` (stdout).
//
compiledb (string n, path_type p)
: name (move (n)), path (move (p))
{
}
virtual void
pre (context&) = 0;
virtual bool
match (const file& output, const path_type& output_path,
bool changed) = 0;
virtual void
execute (const file& output, const path_type& output_path,
const file& input, const path_type& input_path,
const process_path& cpath, const cstrings& args,
const path_type& relo, const path_type& abso,
const path_type& relm, const path_type& absm) = 0;
virtual void
post (context&, const action_targets&, bool failed) = 0;
virtual
~compiledb ();
};
using compiledb_set = vector<unique_ptr<compiledb>>;
// Populated by core_config_init() during serial load.
//
extern compiledb_set compiledbs;
// Context operation callbacks.
//
void
compiledb_pre (context&, action, const action_targets&);
void
compiledb_post (context&, action, const action_targets&, bool failed);
#ifndef BUILD2_BOOTSTRAP
// Implementation that writes to stdout.
//
// Note that this implementation forces compilation of all the targets for
// which it is called to make sure their entries are in the database. So
// typically used in the dry run mode.
//
class compiledb_stdout: public compiledb
{
public:
// The path is expected to be empty.
//
explicit
compiledb_stdout (string name);
virtual void
pre (context&) override;
virtual bool
match (const file& output, const path_type& output_path,
bool changed) override;
virtual void
execute (const file& output, const path_type& output_path,
const file& input, const path_type& input_path,
const process_path& cpath, const cstrings& args,
const path_type& relo, const path_type& abso,
const path_type& relm, const path_type& absm) override;
virtual void
post (context&, const action_targets&, bool failed) override;
private:
mutex mutex_;
enum class state {init, empty, full, failed} state_;
size_t nesting_;
butl::json::stream_serializer js_;
};
// Implementation that maintains a file.
//
class compiledb_file: public compiledb
{
public:
compiledb_file (string name, path_type path);
virtual void
pre (context&) override;
virtual bool
match (const file& output, const path_type& output_path,
bool changed) override;
virtual void
execute (const file& output, const path_type& output_path,
const file& input, const path_type& input_path,
const process_path& cpath, const cstrings& args,
const path_type& relo, const path_type& abso,
const path_type& relm, const path_type& absm) override;
virtual void
post (context&, const action_targets&, bool failed) override;
private:
mutex mutex_;
enum class state {closed, open, failed} state_;
size_t nesting_;
// We want to optimize the performance for the incremental update case
// where only a few files will be recompiled and most of the time there
// will be no change in the command line, which means we won't need to
// rewrite the file.
//
// As a result, our in-memory representation is a hashmap (we could have
// thousands of entries) of absolute and normalized output file paths
// (stored as strings for lookup efficiency) to their serialized JSON
// text lines plus the status: absent, present, changed, or missing
// (entry should be there but is not). This way we don't waste
// (completely) parsing (and re-serializing) each line knowing that we
// won't need to touch most of them.
//
// In fact, we could have gone even further and used a sorted vector
// since insertions will be rare in this case. But we will need to
// lookup every entry on each update, so it's unclear this is a win.
//
enum class entry_status {absent, present, changed, missing};
struct entry
{
entry_status status;
string json;
};
using map_type = std::unordered_map<string, entry>;
map_type db_;
// Number/presence of various entries in the database (used to determine
// whether we need to update the file without iterating over all the
// entries).
//
size_t absent_; // Number of absent entries.
bool changed_; // Presence of changed or missing entries.
};
#endif // BUILD2_BOOTSTRAP
}
}
#endif // LIBBUILD2_CC_COMPILEDB_HXX
|