22
33#include <sourcemeta/core/jsonschema.h>
44
5- #include <cassert> // assert
6- #include <chrono> // std::chrono::nanoseconds, std::chrono::duration_cast
7- #include <cstdint> // std::int64_t
8- #include <fstream> // std::ofstream, std::ifstream
5+ #include <algorithm> // std::ranges::none_of
6+ #include <cassert> // assert
7+ #include <chrono> // std::chrono::nanoseconds, std::chrono::duration_cast
8+ #include <cstdint> // std::int64_t
9+ #include <fstream> // std::ofstream, std::ifstream
910
1011#include <mutex> // std::unique_lock
1112#include <string> // std::string
1213#include <string_view> // std::string_view
1314
1415static constexpr std::string_view DEPENDENCIES_FILE{"deps.txt"};
1516
16- namespace sourcemeta::one {
17+ using mark_type = sourcemeta::one::Build::mark_type;
18+ using Entry = sourcemeta::one::Build::Entry;
1719
18- auto Build::dependency(std::filesystem::path node)
19- -> std::pair<DependencyKind, std::filesystem::path> {
20- return {DependencyKind::Static, std::move(node)};
20+ static auto mark_locked(const std::unordered_map<std::string, Entry> &entries,
21+ const std::filesystem::path &path)
22+ -> std::optional<mark_type> {
23+ assert(path.is_absolute());
24+ const auto match{entries.find(path.native())};
25+ if (match != entries.end() && match->second.file_mark.has_value()) {
26+ return match->second.file_mark;
27+ }
28+
29+ try {
30+ return std::filesystem::last_write_time(path);
31+ } catch (const std::filesystem::filesystem_error &) {
32+ return std::nullopt;
33+ }
2134}
2235
36+ namespace sourcemeta::one {
37+
2338Build::Build(const std::filesystem::path &output_root)
2439 : root{(static_cast<void>(std::filesystem::create_directories(output_root)),
2540 std::filesystem::canonical(output_root))},
@@ -31,13 +46,13 @@ Build::Build(const std::filesystem::path &output_root)
3146 map_entry.is_directory = entry.is_directory();
3247 }
3348
34- const auto deps_path {this->root / DEPENDENCIES_FILE};
35- if (!std::filesystem::exists(deps_path )) {
49+ const auto dependencies_path {this->root / DEPENDENCIES_FILE};
50+ if (!std::filesystem::exists(dependencies_path )) {
3651 return;
3752 }
3853
3954 try {
40- std::ifstream stream{deps_path };
55+ std::ifstream stream{dependencies_path };
4156 if (!stream.is_open()) {
4257 return;
4358 }
@@ -46,7 +61,8 @@ Build::Build(const std::filesystem::path &output_root)
4661 std::istreambuf_iterator<char>()};
4762
4863 std::string current_key;
49- Build::Dependencies current_deps;
64+ std::vector<std::filesystem::path> current_static_dependencies;
65+ std::vector<std::filesystem::path> current_dynamic_dependencies;
5066 std::size_t position{0};
5167
5268 while (position < contents.size()) {
@@ -67,33 +83,34 @@ Build::Build(const std::filesystem::path &output_root)
6783 switch (tag) {
6884 case 't':
6985 if (!current_key.empty()) {
70- this->entries_[current_key].dependencies = std::move(current_deps);
71- current_deps = {};
86+ auto &previous_entry{this->entries_[current_key]};
87+ previous_entry.static_dependencies =
88+ std::move(current_static_dependencies);
89+ previous_entry.dynamic_dependencies =
90+ std::move(current_dynamic_dependencies);
91+ current_static_dependencies = {};
92+ current_dynamic_dependencies = {};
7293 }
7394
7495 current_key = this->root_string;
7596 current_key += '/';
7697 current_key += value;
7798 break;
7899 case 's':
79- current_deps.emplace_back(
80- Build::DependencyKind::Static,
100+ current_static_dependencies.emplace_back(
81101 (this->root / std::string{value}).lexically_normal());
82-
83102 break;
84103 case 'd':
85- current_deps.emplace_back(
86- Build::DependencyKind::Dynamic,
104+ current_dynamic_dependencies.emplace_back(
87105 (this->root / std::string{value}).lexically_normal());
88-
89106 break;
90107 case 'm': {
91108 const auto space{value.find(' ')};
92109 if (space != std::string_view::npos) {
93110 const auto path_part{value.substr(0, space)};
94- const auto ns_part {value.substr(space + 1)};
111+ const auto nanoseconds_part {value.substr(space + 1)};
95112 const std::chrono::nanoseconds nanoseconds{
96- std::stoll(std::string{ns_part })};
113+ std::stoll(std::string{nanoseconds_part })};
97114 const auto mark_value{mark_type{
98115 std::chrono::duration_cast<mark_type::duration>(nanoseconds)}};
99116 std::string absolute_key{this->root_string};
@@ -112,7 +129,9 @@ Build::Build(const std::filesystem::path &output_root)
112129 }
113130
114131 if (!current_key.empty()) {
115- this->entries_[current_key].dependencies = std::move(current_deps);
132+ auto &last_entry{this->entries_[current_key]};
133+ last_entry.static_dependencies = std::move(current_static_dependencies);
134+ last_entry.dynamic_dependencies = std::move(current_dynamic_dependencies);
116135 }
117136 this->has_previous_run = true;
118137 } catch (...) {
@@ -124,12 +143,14 @@ auto Build::has_dependencies(const std::filesystem::path &path) const -> bool {
124143 assert(path.is_absolute());
125144 std::shared_lock lock{this->mutex};
126145 const auto match{this->entries_.find(path.native())};
127- return match != this->entries_.end() && !match->second.dependencies.empty();
146+ return match != this->entries_.end() &&
147+ (!match->second.static_dependencies.empty() ||
148+ !match->second.dynamic_dependencies.empty());
128149}
129150
130151auto Build::finish() -> void {
131- const auto deps_path {this->root / DEPENDENCIES_FILE};
132- std::ofstream stream{deps_path };
152+ const auto dependencies_path {this->root / DEPENDENCIES_FILE};
153+ std::ofstream stream{dependencies_path };
133154 assert(!stream.fail());
134155
135156 const auto root_prefix_size{this->root_string.size() + 1};
@@ -139,7 +160,8 @@ auto Build::finish() -> void {
139160 continue;
140161 }
141162
142- if (!entry.second.dependencies.empty()) {
163+ if (!entry.second.static_dependencies.empty() ||
164+ !entry.second.dynamic_dependencies.empty()) {
143165 if (entry.first.size() > root_prefix_size &&
144166 entry.first.starts_with(this->root_string)) {
145167 stream << "t " << std::string_view{entry.first}.substr(root_prefix_size)
@@ -148,17 +170,27 @@ auto Build::finish() -> void {
148170 stream << "t " << entry.first << '\n';
149171 }
150172
151- for (const auto &dependency : entry.second.dependencies) {
152- const char kind_char{
153- dependency.first == Build::DependencyKind::Dynamic ? 'd' : 's'};
154- const auto &dep_string{dependency.second.native()};
155- if (dep_string.size() > root_prefix_size &&
156- dep_string.starts_with(this->root_string)) {
157- stream << kind_char << ' '
158- << std::string_view{dep_string}.substr(root_prefix_size)
173+ for (const auto &dependency : entry.second.static_dependencies) {
174+ const auto &dependency_string{dependency.native()};
175+ if (dependency_string.size() > root_prefix_size &&
176+ dependency_string.starts_with(this->root_string)) {
177+ stream << "s "
178+ << std::string_view{dependency_string}.substr(root_prefix_size)
159179 << '\n';
160180 } else {
161- stream << kind_char << ' ' << dep_string << '\n';
181+ stream << "s " << dependency_string << '\n';
182+ }
183+ }
184+
185+ for (const auto &dependency : entry.second.dynamic_dependencies) {
186+ const auto &dependency_string{dependency.native()};
187+ if (dependency_string.size() > root_prefix_size &&
188+ dependency_string.starts_with(this->root_string)) {
189+ stream << "d "
190+ << std::string_view{dependency_string}.substr(root_prefix_size)
191+ << '\n';
192+ } else {
193+ stream << "d " << dependency_string << '\n';
162194 }
163195 }
164196 }
@@ -179,7 +211,7 @@ auto Build::finish() -> void {
179211 stream.flush();
180212 stream.close();
181213 lock.unlock();
182- this->track(deps_path );
214+ this->track(dependencies_path );
183215
184216 // Remove untracked files inside the output directory
185217 std::shared_lock read_lock{this->mutex};
@@ -192,55 +224,42 @@ auto Build::finish() -> void {
192224 }
193225}
194226
195- auto Build::refresh(const std::filesystem::path &path) -> void {
196- const auto value{std::filesystem::file_time_type::clock::now()};
197- std::unique_lock lock{this->mutex};
198- this->entries_[path.native()].file_mark = value;
199- }
200-
201- auto Build::mark(const std::filesystem::path &path)
202- -> std::optional<mark_type> {
203- assert(path.is_absolute());
204-
205- {
206- std::shared_lock lock{this->mutex};
207- const auto match{this->entries_.find(path.native())};
208- if (match != this->entries_.end() && match->second.file_mark.has_value()) {
209- return match->second.file_mark;
210- }
227+ auto Build::dispatch_is_cached(const Entry &entry,
228+ const bool static_dependencies_match) const
229+ -> bool {
230+ if (!static_dependencies_match) {
231+ return false;
211232 }
212233
213- // Output files should always have their marks cached
214- // Only input files or new output files are not
215- assert(!this->has_previous_run ||
216- path.native().starts_with(this->root_string) == false ||
217- !std::filesystem::exists(path));
234+ const auto check_mtime{[this, &entry](
235+ const std::filesystem::path &dependency_path) {
236+ const auto dependency_mark{mark_locked(this->entries_, dependency_path)};
237+ return !dependency_mark.has_value() ||
238+ dependency_mark.value() > entry.file_mark.value();
239+ }};
218240
219- try {
220- const auto value{std::filesystem::last_write_time(path)};
221- std::unique_lock lock{this->mutex};
222- this->entries_[path.native()].file_mark = value;
223- return value;
224- } catch (const std::filesystem::filesystem_error &) {
225- return std::nullopt;
226- }
241+ return std::ranges::none_of(entry.static_dependencies, check_mtime) &&
242+ std::ranges::none_of(entry.dynamic_dependencies, check_mtime);
227243}
228244
229- auto Build::mark_locked(const std::filesystem::path &path) const
230- -> std::optional<mark_type> {
231- assert(path.is_absolute());
232- const auto match{this->entries_.find(path.native())};
233- if (match != this->entries_.end() && match->second.file_mark.has_value()) {
234- return match->second.file_mark;
235- }
245+ auto Build::dispatch_commit(
246+ const std::filesystem::path &destination,
247+ std::vector<std::filesystem::path> &&static_dependencies,
248+ std::vector<std::filesystem::path> &&dynamic_dependencies) -> void {
249+ assert(destination.is_absolute());
250+ assert(std::filesystem::exists(destination));
251+ this->refresh(destination);
252+ this->track(destination);
253+ std::unique_lock lock{this->mutex};
254+ auto &entry{this->entries_[destination.native()]};
255+ entry.static_dependencies = std::move(static_dependencies);
256+ entry.dynamic_dependencies = std::move(dynamic_dependencies);
257+ }
236258
237- // For the locked variant used in dispatch cache-hit checks,
238- // if the mark isn't cached, fall back to stat
239- try {
240- return std::filesystem::last_write_time(path);
241- } catch (const std::filesystem::filesystem_error &) {
242- return std::nullopt;
243- }
259+ auto Build::refresh(const std::filesystem::path &path) -> void {
260+ const auto value{std::filesystem::file_time_type::clock::now()};
261+ std::unique_lock lock{this->mutex};
262+ this->entries_[path.native()].file_mark = value;
244263}
245264
246265auto Build::track(const std::filesystem::path &path) -> void {
@@ -272,31 +291,24 @@ auto Build::is_untracked_file(const std::filesystem::path &path) const -> bool {
272291 return match == this->entries_.cend() || !match->second.tracked;
273292}
274293
275- auto Build::output_write_json(const std::filesystem::path &path,
276- const sourcemeta::core::JSON &document) -> void {
277- assert(path.is_absolute());
278- std::filesystem::create_directories(path.parent_path());
279- std::ofstream stream{path};
280- assert(!stream.fail());
281- sourcemeta::core::stringify(document, stream);
282- this->track(path);
283- }
284-
285294auto Build::write_json_if_different(const std::filesystem::path &path,
286295 const sourcemeta::core::JSON &document)
287296 -> void {
288297 if (std::filesystem::exists(path)) {
289298 const auto current{sourcemeta::core::read_json(path)};
290- if (current != document) {
291- this->output_write_json(path, document);
292- this->refresh(path);
293- } else {
299+ if (current == document) {
294300 this->track(path);
301+ return;
295302 }
296- } else {
297- this->output_write_json(path, document);
298- this->refresh(path);
299303 }
304+
305+ assert(path.is_absolute());
306+ std::filesystem::create_directories(path.parent_path());
307+ std::ofstream stream{path};
308+ assert(!stream.fail());
309+ sourcemeta::core::stringify(document, stream);
310+ this->track(path);
311+ this->refresh(path);
300312}
301313
302314} // namespace sourcemeta::one
0 commit comments