diff --git a/src/build/build.cc b/src/build/build.cc index 81a6f57f..81f7fda1 100644 --- a/src/build/build.cc +++ b/src/build/build.cc @@ -2,10 +2,11 @@ #include -#include // assert -#include // std::chrono::nanoseconds, std::chrono::duration_cast -#include // std::int64_t -#include // std::ofstream, std::ifstream +#include // std::ranges::none_of +#include // assert +#include // std::chrono::nanoseconds, std::chrono::duration_cast +#include // std::int64_t +#include // std::ofstream, std::ifstream #include // std::unique_lock #include // std::string @@ -13,13 +14,27 @@ static constexpr std::string_view DEPENDENCIES_FILE{"deps.txt"}; -namespace sourcemeta::one { +using mark_type = sourcemeta::one::Build::mark_type; +using Entry = sourcemeta::one::Build::Entry; -auto Build::dependency(std::filesystem::path node) - -> std::pair { - return {DependencyKind::Static, std::move(node)}; +static auto mark_locked(const std::unordered_map &entries, + const std::filesystem::path &path) + -> std::optional { + assert(path.is_absolute()); + const auto match{entries.find(path.native())}; + if (match != entries.end() && match->second.file_mark.has_value()) { + return match->second.file_mark; + } + + try { + return std::filesystem::last_write_time(path); + } catch (const std::filesystem::filesystem_error &) { + return std::nullopt; + } } +namespace sourcemeta::one { + Build::Build(const std::filesystem::path &output_root) : root{(static_cast(std::filesystem::create_directories(output_root)), std::filesystem::canonical(output_root))}, @@ -31,13 +46,13 @@ Build::Build(const std::filesystem::path &output_root) map_entry.is_directory = entry.is_directory(); } - const auto deps_path{this->root / DEPENDENCIES_FILE}; - if (!std::filesystem::exists(deps_path)) { + const auto dependencies_path{this->root / DEPENDENCIES_FILE}; + if (!std::filesystem::exists(dependencies_path)) { return; } try { - std::ifstream stream{deps_path}; + std::ifstream stream{dependencies_path}; if (!stream.is_open()) { return; } @@ -46,7 +61,8 @@ Build::Build(const std::filesystem::path &output_root) std::istreambuf_iterator()}; std::string current_key; - Build::Dependencies current_deps; + std::vector current_static_dependencies; + std::vector current_dynamic_dependencies; std::size_t position{0}; while (position < contents.size()) { @@ -67,8 +83,13 @@ Build::Build(const std::filesystem::path &output_root) switch (tag) { case 't': if (!current_key.empty()) { - this->entries_[current_key].dependencies = std::move(current_deps); - current_deps = {}; + auto &previous_entry{this->entries_[current_key]}; + previous_entry.static_dependencies = + std::move(current_static_dependencies); + previous_entry.dynamic_dependencies = + std::move(current_dynamic_dependencies); + current_static_dependencies = {}; + current_dynamic_dependencies = {}; } current_key = this->root_string; @@ -76,24 +97,20 @@ Build::Build(const std::filesystem::path &output_root) current_key += value; break; case 's': - current_deps.emplace_back( - Build::DependencyKind::Static, + current_static_dependencies.emplace_back( (this->root / std::string{value}).lexically_normal()); - break; case 'd': - current_deps.emplace_back( - Build::DependencyKind::Dynamic, + current_dynamic_dependencies.emplace_back( (this->root / std::string{value}).lexically_normal()); - break; case 'm': { const auto space{value.find(' ')}; if (space != std::string_view::npos) { const auto path_part{value.substr(0, space)}; - const auto ns_part{value.substr(space + 1)}; + const auto nanoseconds_part{value.substr(space + 1)}; const std::chrono::nanoseconds nanoseconds{ - std::stoll(std::string{ns_part})}; + std::stoll(std::string{nanoseconds_part})}; const auto mark_value{mark_type{ std::chrono::duration_cast(nanoseconds)}}; std::string absolute_key{this->root_string}; @@ -112,7 +129,9 @@ Build::Build(const std::filesystem::path &output_root) } if (!current_key.empty()) { - this->entries_[current_key].dependencies = std::move(current_deps); + auto &last_entry{this->entries_[current_key]}; + last_entry.static_dependencies = std::move(current_static_dependencies); + last_entry.dynamic_dependencies = std::move(current_dynamic_dependencies); } this->has_previous_run = true; } catch (...) { @@ -124,12 +143,14 @@ auto Build::has_dependencies(const std::filesystem::path &path) const -> bool { assert(path.is_absolute()); std::shared_lock lock{this->mutex}; const auto match{this->entries_.find(path.native())}; - return match != this->entries_.end() && !match->second.dependencies.empty(); + return match != this->entries_.end() && + (!match->second.static_dependencies.empty() || + !match->second.dynamic_dependencies.empty()); } auto Build::finish() -> void { - const auto deps_path{this->root / DEPENDENCIES_FILE}; - std::ofstream stream{deps_path}; + const auto dependencies_path{this->root / DEPENDENCIES_FILE}; + std::ofstream stream{dependencies_path}; assert(!stream.fail()); const auto root_prefix_size{this->root_string.size() + 1}; @@ -139,7 +160,8 @@ auto Build::finish() -> void { continue; } - if (!entry.second.dependencies.empty()) { + if (!entry.second.static_dependencies.empty() || + !entry.second.dynamic_dependencies.empty()) { if (entry.first.size() > root_prefix_size && entry.first.starts_with(this->root_string)) { stream << "t " << std::string_view{entry.first}.substr(root_prefix_size) @@ -148,17 +170,27 @@ auto Build::finish() -> void { stream << "t " << entry.first << '\n'; } - for (const auto &dependency : entry.second.dependencies) { - const char kind_char{ - dependency.first == Build::DependencyKind::Dynamic ? 'd' : 's'}; - const auto &dep_string{dependency.second.native()}; - if (dep_string.size() > root_prefix_size && - dep_string.starts_with(this->root_string)) { - stream << kind_char << ' ' - << std::string_view{dep_string}.substr(root_prefix_size) + for (const auto &dependency : entry.second.static_dependencies) { + const auto &dependency_string{dependency.native()}; + if (dependency_string.size() > root_prefix_size && + dependency_string.starts_with(this->root_string)) { + stream << "s " + << std::string_view{dependency_string}.substr(root_prefix_size) << '\n'; } else { - stream << kind_char << ' ' << dep_string << '\n'; + stream << "s " << dependency_string << '\n'; + } + } + + for (const auto &dependency : entry.second.dynamic_dependencies) { + const auto &dependency_string{dependency.native()}; + if (dependency_string.size() > root_prefix_size && + dependency_string.starts_with(this->root_string)) { + stream << "d " + << std::string_view{dependency_string}.substr(root_prefix_size) + << '\n'; + } else { + stream << "d " << dependency_string << '\n'; } } } @@ -179,7 +211,7 @@ auto Build::finish() -> void { stream.flush(); stream.close(); lock.unlock(); - this->track(deps_path); + this->track(dependencies_path); // Remove untracked files inside the output directory std::shared_lock read_lock{this->mutex}; @@ -192,55 +224,42 @@ auto Build::finish() -> void { } } -auto Build::refresh(const std::filesystem::path &path) -> void { - const auto value{std::filesystem::file_time_type::clock::now()}; - std::unique_lock lock{this->mutex}; - this->entries_[path.native()].file_mark = value; -} - -auto Build::mark(const std::filesystem::path &path) - -> std::optional { - assert(path.is_absolute()); - - { - std::shared_lock lock{this->mutex}; - const auto match{this->entries_.find(path.native())}; - if (match != this->entries_.end() && match->second.file_mark.has_value()) { - return match->second.file_mark; - } +auto Build::dispatch_is_cached(const Entry &entry, + const bool static_dependencies_match) const + -> bool { + if (!static_dependencies_match) { + return false; } - // Output files should always have their marks cached - // Only input files or new output files are not - assert(!this->has_previous_run || - path.native().starts_with(this->root_string) == false || - !std::filesystem::exists(path)); + const auto check_mtime{[this, &entry]( + const std::filesystem::path &dependency_path) { + const auto dependency_mark{mark_locked(this->entries_, dependency_path)}; + return !dependency_mark.has_value() || + dependency_mark.value() > entry.file_mark.value(); + }}; - try { - const auto value{std::filesystem::last_write_time(path)}; - std::unique_lock lock{this->mutex}; - this->entries_[path.native()].file_mark = value; - return value; - } catch (const std::filesystem::filesystem_error &) { - return std::nullopt; - } + return std::ranges::none_of(entry.static_dependencies, check_mtime) && + std::ranges::none_of(entry.dynamic_dependencies, check_mtime); } -auto Build::mark_locked(const std::filesystem::path &path) const - -> std::optional { - assert(path.is_absolute()); - const auto match{this->entries_.find(path.native())}; - if (match != this->entries_.end() && match->second.file_mark.has_value()) { - return match->second.file_mark; - } +auto Build::dispatch_commit( + const std::filesystem::path &destination, + std::vector &&static_dependencies, + std::vector &&dynamic_dependencies) -> void { + assert(destination.is_absolute()); + assert(std::filesystem::exists(destination)); + this->refresh(destination); + this->track(destination); + std::unique_lock lock{this->mutex}; + auto &entry{this->entries_[destination.native()]}; + entry.static_dependencies = std::move(static_dependencies); + entry.dynamic_dependencies = std::move(dynamic_dependencies); +} - // For the locked variant used in dispatch cache-hit checks, - // if the mark isn't cached, fall back to stat - try { - return std::filesystem::last_write_time(path); - } catch (const std::filesystem::filesystem_error &) { - return std::nullopt; - } +auto Build::refresh(const std::filesystem::path &path) -> void { + const auto value{std::filesystem::file_time_type::clock::now()}; + std::unique_lock lock{this->mutex}; + this->entries_[path.native()].file_mark = value; } auto Build::track(const std::filesystem::path &path) -> void { @@ -272,31 +291,24 @@ auto Build::is_untracked_file(const std::filesystem::path &path) const -> bool { return match == this->entries_.cend() || !match->second.tracked; } -auto Build::output_write_json(const std::filesystem::path &path, - const sourcemeta::core::JSON &document) -> void { - assert(path.is_absolute()); - std::filesystem::create_directories(path.parent_path()); - std::ofstream stream{path}; - assert(!stream.fail()); - sourcemeta::core::stringify(document, stream); - this->track(path); -} - auto Build::write_json_if_different(const std::filesystem::path &path, const sourcemeta::core::JSON &document) -> void { if (std::filesystem::exists(path)) { const auto current{sourcemeta::core::read_json(path)}; - if (current != document) { - this->output_write_json(path, document); - this->refresh(path); - } else { + if (current == document) { this->track(path); + return; } - } else { - this->output_write_json(path, document); - this->refresh(path); } + + assert(path.is_absolute()); + std::filesystem::create_directories(path.parent_path()); + std::ofstream stream{path}; + assert(!stream.fail()); + sourcemeta::core::stringify(document, stream); + this->track(path); + this->refresh(path); } } // namespace sourcemeta::one diff --git a/src/build/include/sourcemeta/one/build.h b/src/build/include/sourcemeta/one/build.h index 3a3bcd69..95490761 100644 --- a/src/build/include/sourcemeta/one/build.h +++ b/src/build/include/sourcemeta/one/build.h @@ -7,27 +7,23 @@ #include -#include // std::ranges::none_of -#include // assert -#include // std::uint8_t +#include // std::array #include // std::filesystem -#include // std::function -#include // std::unique_lock +#include // std::function, std::reference_wrapper, std::cref #include // std::optional #include // std::shared_mutex, std::shared_lock #include // std::string +#include // std::is_same_v #include // std::unordered_map -#include // std::pair, std::move +#include // std::move #include // std::vector namespace sourcemeta::one { class SOURCEMETA_ONE_BUILD_EXPORT Build { public: - enum class DependencyKind : std::uint8_t { Static, Dynamic }; - using Dependencies = - std::vector>; + std::vector>; using DynamicCallback = std::function; @@ -42,53 +38,37 @@ class SOURCEMETA_ONE_BUILD_EXPORT Build { auto path() const -> const std::filesystem::path & { return this->root; } - static auto dependency(std::filesystem::path node) - -> std::pair; - [[nodiscard]] auto has_dependencies(const std::filesystem::path &path) const -> bool; auto finish() -> void; auto refresh(const std::filesystem::path &path) -> void; - [[nodiscard]] auto mark(const std::filesystem::path &path) - -> std::optional; template auto dispatch(const Handler &handler, const std::filesystem::path &destination, - const Dependencies &dependencies, const Context &context) + const Context &context, + const std::vector &dependencies) -> bool { const auto &destination_string{destination.native()}; std::shared_lock lock{this->mutex}; const auto cached_match{this->entries_.find(destination_string)}; if (cached_match != this->entries_.end()) { const auto &entry{cached_match->second}; - if (entry.file_mark.has_value() && !entry.dependencies.empty()) { - std::size_t static_index{0}; - bool static_dependencies_match{true}; - for (const auto &cached_dependency : entry.dependencies) { - if (cached_dependency.first == DependencyKind::Static) { - if (static_index >= dependencies.size() || - dependencies[static_index].second != cached_dependency.second) { + if (entry.file_mark.has_value() && + (!entry.static_dependencies.empty() || + !entry.dynamic_dependencies.empty())) { + bool static_dependencies_match{dependencies.size() == + entry.static_dependencies.size()}; + if (static_dependencies_match) { + for (std::size_t index = 0; index < dependencies.size(); ++index) { + if (dependencies[index] != entry.static_dependencies[index]) { static_dependencies_match = false; break; } - - static_index += 1; } } - if (static_dependencies_match && static_index != dependencies.size()) { - static_dependencies_match = false; - } - - if (static_dependencies_match && - std::ranges::none_of( - entry.dependencies, [this, &entry](const auto &dependency) { - const auto dependency_mark{ - this->mark_locked(dependency.second)}; - return !dependency_mark.has_value() || - dependency_mark.value() > entry.file_mark.value(); - })) { + if (this->dispatch_is_cached(entry, static_dependencies_match)) { lock.unlock(); this->track(destination); return false; @@ -98,38 +78,91 @@ class SOURCEMETA_ONE_BUILD_EXPORT Build { lock.unlock(); - Dependencies output_dependencies; + Dependencies static_dependency_references; + static_dependency_references.reserve(dependencies.size()); for (const auto &dependency : dependencies) { - assert(this->mark(dependency.second).has_value()); - output_dependencies.emplace_back(DependencyKind::Static, - dependency.second); + static_dependency_references.emplace_back(std::cref(dependency)); } + std::vector dynamic_dependencies; handler( - destination, dependencies, + destination, static_dependency_references, [&](const auto &new_dependency) { - assert(this->mark(new_dependency).has_value()); - output_dependencies.emplace_back(DependencyKind::Dynamic, - new_dependency); + dynamic_dependencies.emplace_back(new_dependency); }, context); - assert(destination.is_absolute()); - assert(std::filesystem::exists(destination)); - this->refresh(destination); - this->track(destination); - { - std::unique_lock write_lock{this->mutex}; - this->entries_[destination_string].dependencies = - std::move(output_dependencies); + this->dispatch_commit(destination, + std::vector( + dependencies.begin(), dependencies.end()), + std::move(dynamic_dependencies)); + return true; + } + + template + requires(sizeof...(DependencyTypes) == 0 || + (std::is_same_v && ...)) + auto dispatch(const Handler &handler, + const std::filesystem::path &destination, + const Context &context, const DependencyTypes &...dependencies) + -> bool { + const auto &destination_string{destination.native()}; + std::shared_lock lock{this->mutex}; + const auto cached_match{this->entries_.find(destination_string)}; + if (cached_match != this->entries_.end()) { + const auto &entry{cached_match->second}; + if (entry.file_mark.has_value() && + (!entry.static_dependencies.empty() || + !entry.dynamic_dependencies.empty())) { + const std::array + dependency_pointers{{&dependencies...}}; + constexpr auto dependency_count{sizeof...(DependencyTypes)}; + bool static_dependencies_match{entry.static_dependencies.size() == + dependency_count}; + if (static_dependencies_match) { + for (std::size_t index = 0; index < dependency_count; ++index) { + if (*dependency_pointers[index] != + entry.static_dependencies[index]) { + static_dependencies_match = false; + break; + } + } + } + + if (this->dispatch_is_cached(entry, static_dependencies_match)) { + lock.unlock(); + this->track(destination); + return false; + } + } } + lock.unlock(); + + const Dependencies static_dependency_references{std::cref(dependencies)...}; + + std::vector dynamic_dependencies; + handler( + destination, static_dependency_references, + [&](const auto &new_dependency) { + dynamic_dependencies.emplace_back(new_dependency); + }, + context); + + std::vector stored_static_dependencies; + stored_static_dependencies.reserve(sizeof...(DependencyTypes)); + ((void)stored_static_dependencies.emplace_back(dependencies), ...); + + this->dispatch_commit(destination, std::move(stored_static_dependencies), + std::move(dynamic_dependencies)); return true; } struct Entry { std::optional file_mark; - Dependencies dependencies; + std::vector static_dependencies; + std::vector dynamic_dependencies; bool tracked{false}; bool is_directory{false}; }; @@ -146,10 +179,14 @@ class SOURCEMETA_ONE_BUILD_EXPORT Build { const sourcemeta::core::JSON &document) -> void; private: - [[nodiscard]] auto mark_locked(const std::filesystem::path &path) const - -> std::optional; - auto output_write_json(const std::filesystem::path &path, - const sourcemeta::core::JSON &document) -> void; + [[nodiscard]] auto dispatch_is_cached(const Entry &entry, + bool static_dependencies_match) const + -> bool; + auto + dispatch_commit(const std::filesystem::path &destination, + std::vector &&static_dependencies, + std::vector &&dynamic_dependencies) + -> void; std::filesystem::path root; std::string root_string; diff --git a/src/index/explorer.h b/src/index/explorer.h index d648ba53..7c7640cd 100644 --- a/src/index/explorer.h +++ b/src/index/explorer.h @@ -118,7 +118,7 @@ struct GENERATE_EXPLORER_SCHEMA_METADATA { const Context &context) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; const auto schema{ - sourcemeta::one::read_json_with_metadata(dependencies.front().second)}; + sourcemeta::one::read_json_with_metadata(dependencies.front().get())}; const auto id{sourcemeta::core::identify( schema.data, [&callback, &context](const auto identifier) { return std::get<0>(context).get()(identifier, callback); @@ -179,11 +179,11 @@ struct GENERATE_EXPLORER_SCHEMA_METADATA { result.assign("examples", std::move(examples_array)); } - const auto health{sourcemeta::one::read_json(dependencies.at(1).second)}; + const auto health{sourcemeta::one::read_json(dependencies.at(1).get())}; result.assign("health", health.at("score")); const auto schema_dependencies{ - sourcemeta::one::read_json(dependencies.at(2).second)}; + sourcemeta::one::read_json(dependencies.at(2).get())}; result.assign("dependencies", sourcemeta::core::to_json(schema_dependencies.size())); @@ -228,7 +228,7 @@ struct GENERATE_EXPLORER_SEARCH_INDEX { result.reserve(dependencies.size()); for (const auto &dependency : dependencies) { - auto metadata_json{sourcemeta::one::read_json(dependency.second)}; + auto metadata_json{sourcemeta::one::read_json(dependency.get())}; if (!sourcemeta::core::is_schema(metadata_json)) { continue; } diff --git a/src/index/generators.h b/src/index/generators.h index 39026f4d..d1c13e7e 100644 --- a/src/index/generators.h +++ b/src/index/generators.h @@ -110,7 +110,7 @@ struct GENERATE_POINTER_POSITIONS { const Context &) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; sourcemeta::core::PointerPositionTracker tracker; - sourcemeta::one::read_json(dependencies.front().second, std::ref(tracker)); + sourcemeta::one::read_json(dependencies.front().get(), std::ref(tracker)); const auto result{sourcemeta::core::to_json(tracker)}; const auto timestamp_end{std::chrono::steady_clock::now()}; std::filesystem::create_directories(destination.parent_path()); @@ -130,7 +130,7 @@ struct GENERATE_FRAME_LOCATIONS { const Context &resolver) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; sourcemeta::core::PointerPositionTracker tracker; - const auto contents{sourcemeta::one::read_json(dependencies.front().second, + const auto contents{sourcemeta::one::read_json(dependencies.front().get(), std::ref(tracker))}; sourcemeta::core::SchemaFrame frame{ sourcemeta::core::SchemaFrame::Mode::Locations}; @@ -156,8 +156,7 @@ struct GENERATE_DEPENDENCIES { const sourcemeta::one::Build::DynamicCallback &callback, const Context &resolver) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto contents{ - sourcemeta::one::read_json(dependencies.front().second)}; + const auto contents{sourcemeta::one::read_json(dependencies.front().get())}; auto result{sourcemeta::core::JSON::make_array()}; sourcemeta::core::dependencies( contents, sourcemeta::core::schema_walker, @@ -209,7 +208,7 @@ struct GENERATE_DEPENDENCY_TREE { std::unordered_set>; DirectMap direct; for (const auto &dependency : dependencies) { - const auto contents{sourcemeta::one::read_json(dependency.second)}; + const auto contents{sourcemeta::one::read_json(dependency.get())}; assert(contents.is_array()); for (const auto &entry : contents.as_array()) { direct[entry.at("to").to_string()].emplace( @@ -265,8 +264,7 @@ struct GENERATE_DEPENDENTS { const sourcemeta::one::Build::DynamicCallback &, const Context &context) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto contents{ - sourcemeta::one::read_json(dependencies.front().second)}; + const auto contents{sourcemeta::one::read_json(dependencies.front().get())}; assert(contents.is_object()); auto result{sourcemeta::core::JSON::make_array()}; const auto *match{contents.try_at(context)}; @@ -302,8 +300,7 @@ struct GENERATE_HEALTH { const auto &resolver{context.first.get()}; const auto &configuration{context.second.get()}; const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto contents{ - sourcemeta::one::read_json(dependencies.front().second)}; + const auto contents{sourcemeta::one::read_json(dependencies.front().get())}; auto &cache_entry{bundle_for(configuration, resolver, callback)}; auto errors{sourcemeta::core::JSON::make_array()}; @@ -399,7 +396,7 @@ struct GENERATE_BUNDLE { const sourcemeta::one::Build::DynamicCallback &callback, const Context &resolver) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - auto schema{sourcemeta::one::read_json(dependencies.front().second)}; + auto schema{sourcemeta::one::read_json(dependencies.front().get())}; sourcemeta::core::bundle(schema, sourcemeta::core::schema_walker, [&callback, &resolver](const auto identifier) { return resolver(identifier, callback); @@ -431,7 +428,7 @@ struct GENERATE_EDITOR { const sourcemeta::one::Build::DynamicCallback &callback, const Context &resolver) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - auto schema{sourcemeta::one::read_json(dependencies.front().second)}; + auto schema{sourcemeta::one::read_json(dependencies.front().get())}; sourcemeta::core::for_editor(schema, sourcemeta::core::schema_walker, [&callback, &resolver](const auto identifier) { return resolver(identifier, callback); @@ -463,8 +460,7 @@ struct GENERATE_BLAZE_TEMPLATE { const sourcemeta::one::Build::DynamicCallback &, const Context &mode) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto contents{ - sourcemeta::one::read_json(dependencies.front().second)}; + const auto contents{sourcemeta::one::read_json(dependencies.front().get())}; sourcemeta::core::SchemaFrame frame{ sourcemeta::core::SchemaFrame::Mode::References}; frame.analyse(contents, sourcemeta::core::schema_walker, @@ -491,7 +487,7 @@ struct GENERATE_STATS { const sourcemeta::one::Build::DynamicCallback &callback, const Context &resolver) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto schema{sourcemeta::one::read_json(dependencies.front().second)}; + const auto schema{sourcemeta::one::read_json(dependencies.front().get())}; std::map> result; diff --git a/src/index/index.cc b/src/index/index.cc index c711b517..3c0a10db 100644 --- a/src/index/index.cc +++ b/src/index/index.cc @@ -74,16 +74,16 @@ static auto print_progress(std::mutex &mutex, const std::size_t threads, << " [" << std::this_thread::get_id() << "/" << threads << "]\n"; } -template +template static auto DISPATCH(const std::filesystem::path &destination, - const sourcemeta::one::Build::Dependencies &dependencies, const typename Handler::Context &context, std::mutex &mutex, const std::string_view title, const std::string_view prefix, const std::string_view suffix, - sourcemeta::one::Build &output) -> bool { + sourcemeta::one::Build &output, const Deps &...deps) + -> bool { const auto was_built{output.dispatch( - Handler::handler, destination, dependencies, context)}; + Handler::handler, destination, context, deps...)}; if (!was_built) { std::lock_guard lock{mutex}; std::cerr << "(skip) " << title << ": " << prefix << " [" << suffix @@ -310,12 +310,9 @@ static auto index_main(const std::string_view &program, const auto destination{schemas_path / schema.second.relative_path / SENTINEL / "schema.metapack"}; DISPATCH( - destination, - {sourcemeta::one::Build::dependency(schema.second.path), - sourcemeta::one::Build::dependency(mark_configuration_path), - sourcemeta::one::Build::dependency(mark_version_path)}, - {schema.first, resolver}, mutex, "Ingesting", schema.first, - "materialise", output); + destination, {schema.first, resolver}, mutex, "Ingesting", + schema.first, "materialise", output, schema.second.path, + mark_configuration_path, mark_version_path); // Mark the materialised schema in the resolver resolver.cache_path(schema.first, destination); @@ -344,85 +341,67 @@ static auto index_main(const std::string_view &program, resolver.size()); const auto base_path{schemas_path / schema.second.relative_path / SENTINEL}; + const auto schema_metapack{base_path / "schema.metapack"}; + const auto dependencies_metapack{base_path / "dependencies.metapack"}; + const auto bundle_metapack{base_path / "bundle.metapack"}; + + const auto health_metapack{base_path / "health.metapack"}; DISPATCH( - base_path / "positions.metapack", - {sourcemeta::one::Build::dependency(base_path / "schema.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, - resolver, mutex, "Analysing", schema.first, "positions", output); + base_path / "positions.metapack", resolver, mutex, "Analysing", + schema.first, "positions", output, schema_metapack, + mark_version_path); DISPATCH( - base_path / "locations.metapack", - {sourcemeta::one::Build::dependency(base_path / "schema.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, - resolver, mutex, "Analysing", schema.first, "locations", output); + base_path / "locations.metapack", resolver, mutex, "Analysing", + schema.first, "locations", output, schema_metapack, + mark_version_path); DISPATCH( - base_path / "dependencies.metapack", - {sourcemeta::one::Build::dependency(base_path / "schema.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, - resolver, mutex, "Analysing", schema.first, "dependencies", output); + dependencies_metapack, resolver, mutex, "Analysing", schema.first, + "dependencies", output, schema_metapack, mark_version_path); DISPATCH( - base_path / "stats.metapack", - {sourcemeta::one::Build::dependency(base_path / "schema.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, - resolver, mutex, "Analysing", schema.first, "stats", output); + base_path / "stats.metapack", resolver, mutex, "Analysing", + schema.first, "stats", output, schema_metapack, mark_version_path); DISPATCH( - base_path / "health.metapack", - {sourcemeta::one::Build::dependency(base_path / "schema.metapack"), - sourcemeta::one::Build::dependency(base_path / - "dependencies.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, + health_metapack, {std::ref(resolver), std::cref(schema.second.collection.get())}, - mutex, "Analysing", schema.first, "health", output); + mutex, "Analysing", schema.first, "health", output, schema_metapack, + dependencies_metapack, mark_version_path); DISPATCH( - base_path / "bundle.metapack", - {sourcemeta::one::Build::dependency(base_path / "schema.metapack"), - sourcemeta::one::Build::dependency(base_path / - "dependencies.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, - resolver, mutex, "Analysing", schema.first, "bundle", output); + bundle_metapack, resolver, mutex, "Analysing", schema.first, + "bundle", output, schema_metapack, dependencies_metapack, + mark_version_path); DISPATCH( - base_path / "editor.metapack", - {sourcemeta::one::Build::dependency(base_path / "bundle.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, - resolver, mutex, "Analysing", schema.first, "editor", output); + base_path / "editor.metapack", resolver, mutex, "Analysing", + schema.first, "editor", output, bundle_metapack, mark_version_path); if (attribute_not_disabled(schema.second.collection.get(), "x-sourcemeta-one:evaluate")) { DISPATCH( base_path / "blaze-exhaustive.metapack", - {sourcemeta::one::Build::dependency(base_path / - "bundle.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, sourcemeta::blaze::Mode::Exhaustive, mutex, "Analysing", - schema.first, "blaze-exhaustive", output); + schema.first, "blaze-exhaustive", output, bundle_metapack, + mark_version_path); DISPATCH( base_path / "blaze-fast.metapack", - {sourcemeta::one::Build::dependency(base_path / - "bundle.metapack"), - sourcemeta::one::Build::dependency(mark_version_path)}, sourcemeta::blaze::Mode::FastValidation, mutex, "Analysing", - schema.first, "blaze-fast", output); + schema.first, "blaze-fast", output, bundle_metapack, + mark_version_path); } DISPATCH( explorer_path / schema.second.relative_path / SENTINEL / "schema.metapack", - {sourcemeta::one::Build::dependency(base_path / "schema.metapack"), - sourcemeta::one::Build::dependency(base_path / "health.metapack"), - sourcemeta::one::Build::dependency(base_path / - "dependencies.metapack"), - // As this target reads the alert from the configuration file - sourcemeta::one::Build::dependency(mark_configuration_path), - sourcemeta::one::Build::dependency(mark_version_path)}, {resolver, schema.second.collection.get(), schema.second.relative_path}, - mutex, "Analysing", schema.first, "metadata", output); + mutex, "Analysing", schema.first, "metadata", output, + schema_metapack, health_metapack, dependencies_metapack, + mark_configuration_path, mark_version_path); }, concurrency, THREAD_STACK_SIZE); @@ -440,10 +419,10 @@ static auto index_main(const std::string_view &program, std::vector directories; // The top-level is itself a directory directories.emplace_back(schemas_path); - sourcemeta::one::Build::Dependencies summaries; - std::unordered_map + std::vector summaries; + std::unordered_map> directory_summaries; - sourcemeta::one::Build::Dependencies dependencies; + std::vector dependencies; if (std::filesystem::exists(schemas_path)) { const auto &entries{output.entries()}; const auto &schemas_path_string{schemas_path.native()}; @@ -511,7 +490,6 @@ static auto index_main(const std::string_view &program, std::filesystem::path{relative} / SENTINEL / "directory.metapack"}; directory_summaries[path_key.substr(0, last_slash)].emplace_back( - sourcemeta::one::Build::DependencyKind::Static, child_directory_metapack); } else if (!schema_entry.is_directory && filename == "schema.metapack") { const std::string_view parent_path{path_key.data(), last_slash}; @@ -531,21 +509,18 @@ static auto index_main(const std::string_view &program, path_key.size() - schemas_prefix_size}; const auto explorer_summary_path{explorer_path / std::filesystem::path{relative}}; - summaries.emplace_back(sourcemeta::one::Build::DependencyKind::Static, - explorer_summary_path); + summaries.emplace_back(explorer_summary_path); const auto grandparent_slash{ parent_path.substr(0, parent_last_slash).rfind('/')}; if (grandparent_slash != std::string_view::npos) { directory_summaries[std::string{ parent_path.substr(0, grandparent_slash)}] - .emplace_back(sourcemeta::one::Build::DependencyKind::Static, - explorer_summary_path); + .emplace_back(explorer_summary_path); } - dependencies.emplace_back( - sourcemeta::one::Build::DependencyKind::Static, - std::filesystem::path{parent_path} / "dependencies.metapack"); + dependencies.emplace_back(std::filesystem::path{parent_path} / + "dependencies.metapack"); } } @@ -577,8 +552,8 @@ static auto index_main(const std::string_view &program, const auto dependency_tree_rebuilt{ DISPATCH( - dependency_tree_path, dependencies, resolver, mutex, "Reviewing", - display_schemas_path.string(), "dependencies", output)}; + dependency_tree_path, resolver, mutex, "Reviewing", + display_schemas_path.string(), "dependencies", output, dependencies)}; // Determine which schemas' dependents actually changed by diffing // the old and new dependency trees per key. We keep new_dependency_tree @@ -642,10 +617,8 @@ static auto index_main(const std::string_view &program, print_progress(mutex, threads, "Reworking", entry.uri.get(), cursor, rework_entries.size()); DISPATCH( - entry.dependents_path, - {sourcemeta::one::Build::dependency(dependency_tree_path)}, - entry.uri.get(), mutex, "Reworking", entry.uri.get(), "dependents", - output); + entry.dependents_path, entry.uri.get(), mutex, "Reworking", + entry.uri.get(), "dependents", output, dependency_tree_path); }, concurrency, THREAD_STACK_SIZE); @@ -658,8 +631,8 @@ static auto index_main(const std::string_view &program, print_progress(mutex, concurrency, "Producing", display_explorer_path.string(), 0, 100); DISPATCH( - explorer_path / SENTINEL / "search.metapack", summaries, nullptr, mutex, - "Producing", display_explorer_path.string(), "search", output); + explorer_path / SENTINEL / "search.metapack", nullptr, mutex, "Producing", + display_explorer_path.string(), "search", output, summaries); PROFILE_END(profiling, "Search"); @@ -677,18 +650,17 @@ static auto index_main(const std::string_view &program, (explorer_path / relative_path / SENTINEL / "directory.metapack") .lexically_normal()}; auto &local_summaries{directory_summaries[entry.string()]}; - local_summaries.emplace_back(sourcemeta::one::Build::DependencyKind::Static, - mark_configuration_path); - local_summaries.emplace_back(sourcemeta::one::Build::DependencyKind::Static, - mark_version_path); + local_summaries.emplace_back(mark_configuration_path); + local_summaries.emplace_back(mark_version_path); DISPATCH( - destination, local_summaries, + destination, {.directory = entry, .configuration = configuration, .output = output, .explorer_path = explorer_path, .schemas_path = schemas_path}, - mutex, "Producing", relative_path.string(), "directory", output); + mutex, "Producing", relative_path.string(), "directory", output, + local_summaries); local_summaries.pop_back(); local_summaries.pop_back(); } @@ -712,34 +684,26 @@ static auto index_main(const std::string_view &program, cursor, directories.size() + summaries.size()); if (relative_path == ".") { + const auto directory_metapack{explorer_path / SENTINEL / + "directory.metapack"}; DISPATCH( explorer_path / SENTINEL / "directory-html.metapack", - {sourcemeta::one::Build::dependency(explorer_path / SENTINEL / - "directory.metapack"), - // We rely on the configuration for site metadata - sourcemeta::one::Build::dependency(mark_configuration_path), - sourcemeta::one::Build::dependency(mark_version_path)}, configuration, mutex, "Rendering", relative_path.string(), - "index", output); + "index", output, directory_metapack, mark_configuration_path, + mark_version_path); DISPATCH( - explorer_path / SENTINEL / "404.metapack", - {// We rely on the configuration for site metadata - sourcemeta::one::Build::dependency(mark_configuration_path), - sourcemeta::one::Build::dependency(mark_version_path)}, - configuration, mutex, "Rendering", relative_path.string(), - "not-found", output); + explorer_path / SENTINEL / "404.metapack", configuration, mutex, + "Rendering", relative_path.string(), "not-found", output, + mark_configuration_path, mark_version_path); } else { + const auto directory_metapack{explorer_path / relative_path / + SENTINEL / "directory.metapack"}; DISPATCH( explorer_path / relative_path / SENTINEL / "directory-html.metapack", - {sourcemeta::one::Build::dependency(explorer_path / - relative_path / SENTINEL / - "directory.metapack"), - // We rely on the configuration for site metadata - sourcemeta::one::Build::dependency(mark_configuration_path), - sourcemeta::one::Build::dependency(mark_version_path)}, configuration, mutex, "Rendering", relative_path.string(), - "directory", output); + "directory", output, directory_metapack, + mark_configuration_path, mark_version_path); } }, concurrency); @@ -751,27 +715,24 @@ static auto index_main(const std::string_view &program, &mark_version_path](const auto &entry, const auto threads, const auto cursor) { const auto relative_path{ - std::filesystem::relative(entry.second, explorer_path) + std::filesystem::relative(entry, explorer_path) .parent_path() .parent_path()}; print_progress(mutex, threads, "Rendering", relative_path.string(), cursor + directories.size(), summaries.size() + directories.size()); const auto schema_path{schemas_path / relative_path / SENTINEL}; + const auto schema_deps_metapack{schema_path / + "dependencies.metapack"}; + const auto schema_health_metapack{schema_path / "health.metapack"}; + const auto schema_dependents_metapack{schema_path / + "dependents.metapack"}; DISPATCH( - entry.second.parent_path() / "schema-html.metapack", - {sourcemeta::one::Build::dependency(entry.second), - sourcemeta::one::Build::dependency(schema_path / - "dependencies.metapack"), - sourcemeta::one::Build::dependency(schema_path / - "health.metapack"), - sourcemeta::one::Build::dependency(schema_path / - "dependents.metapack"), - // We rely on the configuration for site metadata - sourcemeta::one::Build::dependency(mark_configuration_path), - sourcemeta::one::Build::dependency(mark_version_path)}, - configuration, mutex, "Rendering", relative_path.string(), - "schema", output); + entry.parent_path() / "schema-html.metapack", configuration, + mutex, "Rendering", relative_path.string(), "schema", output, + entry, schema_deps_metapack, schema_health_metapack, + schema_dependents_metapack, mark_configuration_path, + mark_version_path); }, concurrency); } @@ -818,11 +779,8 @@ static auto index_main(const std::string_view &program, const auto display_routes_path{ std::filesystem::relative(routes_path, output.path())}; DISPATCH( - routes_path, - {sourcemeta::one::Build::dependency(mark_configuration_path), - sourcemeta::one::Build::dependency(mark_version_path)}, - router, mutex, "Producing", display_routes_path.string(), "routes", - output); + routes_path, router, mutex, "Producing", display_routes_path.string(), + "routes", output, mark_configuration_path, mark_version_path); PROFILE_END(profiling, "Routes"); diff --git a/src/web/pages/directory.cc b/src/web/pages/directory.cc index 2d8d55ea..fe7443ae 100644 --- a/src/web/pages/directory.cc +++ b/src/web/pages/directory.cc @@ -18,7 +18,7 @@ auto GENERATE_WEB_DIRECTORY::handler( const Context &configuration) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto directory{read_json(dependencies.front().second)}; + const auto directory{read_json(dependencies.front().get())}; const auto &canonical{directory.at("url").to_string()}; const auto &title{directory.defines("title") ? directory.at("title").to_string() diff --git a/src/web/pages/index.cc b/src/web/pages/index.cc index 7350f525..b50ef52e 100644 --- a/src/web/pages/index.cc +++ b/src/web/pages/index.cc @@ -35,7 +35,7 @@ auto GENERATE_WEB_INDEX::handler( const Context &configuration) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto directory{read_json(dependencies.front().second)}; + const auto directory{read_json(dependencies.front().get())}; const auto &canonical{directory.at("url").to_string()}; const auto title{configuration.html->name + " Schemas"}; const auto &description{configuration.html->description}; diff --git a/src/web/pages/schema.cc b/src/web/pages/schema.cc index d93a4b97..3e1b15f4 100644 --- a/src/web/pages/schema.cc +++ b/src/web/pages/schema.cc @@ -23,7 +23,7 @@ auto GENERATE_WEB_SCHEMA::handler( const Context &configuration) -> void { const auto timestamp_start{std::chrono::steady_clock::now()}; - const auto meta{read_json(dependencies.front().second)}; + const auto meta{read_json(dependencies.front().get())}; const auto &canonical{meta.at("identifier").to_string()}; const auto &title{meta.defines("title") ? meta.at("title").to_string() : meta.at("path").to_string()}; @@ -196,11 +196,11 @@ auto GENERATE_WEB_SCHEMA::handler( {"data-sourcemeta-ui-editor-language", "json"}}, "Loading schema...")); - const auto dependencies_json{read_json(dependencies.at(1).second)}; - const auto health{read_json(dependencies.at(2).second)}; + const auto dependencies_json{read_json(dependencies.at(1).get())}; + const auto health{read_json(dependencies.at(2).get())}; assert(health.is_object()); assert(health.defines("errors")); - const auto dependents_json{read_json(dependencies.at(3).second)}; + const auto dependents_json{read_json(dependencies.at(3).get())}; assert(dependents_json.is_array()); // Collect unique dependent schemas, preferring direct over indirect. diff --git a/test/unit/build/build_e2e_test.cc b/test/unit/build/build_e2e_test.cc index 26809bfc..9cd069fd 100644 --- a/test/unit/build/build_e2e_test.cc +++ b/test/unit/build/build_e2e_test.cc @@ -24,7 +24,7 @@ handler_multiply(const std::filesystem::path &destination, const std::uint64_t &value) -> void { assert(dependencies.size() == 1); write_uint64_t(destination, - read_uint64_t(dependencies.front().second) * value); + read_uint64_t(dependencies.front().get()) * value); } static auto @@ -34,7 +34,7 @@ handler_sum(const std::filesystem::path &destination, const std::nullptr_t &) -> void { std::uint64_t total{0}; for (const auto &dependency : dependencies) { - total += read_uint64_t(dependency.second); + total += read_uint64_t(dependency.get()); } write_uint64_t(destination, total); @@ -66,7 +66,7 @@ static auto handler_sum_with_dynamic( const std::filesystem::path &context) -> void { std::uint64_t total{0}; for (const auto &dependency : dependencies) { - total += read_uint64_t(dependency.second); + total += read_uint64_t(dependency.get()); } total += read_uint64_t(context); @@ -83,22 +83,25 @@ TEST(Build_e2e, simple_cache_miss_hit) { std::filesystem::create_directories(input_path); write_uint64_t(input_path / "initial.txt", 8); + const auto initial_txt{input_path / "initial.txt"}; + const auto first_txt{output_path / "first.txt"}; + const auto second_txt{output_path / "second.txt"}; + // First run: build first.txt and second.txt (cache miss) { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "initial.txt"); + build.refresh(initial_txt); const auto result_1 = build.dispatch( - handler_multiply, output_path / "first.txt", - {sourcemeta::one::Build::dependency(input_path / "initial.txt")}, 2); + handler_multiply, first_txt, static_cast(2), + initial_txt); EXPECT_TRUE(result_1); - EXPECT_EQ(read_uint64_t(output_path / "first.txt"), 16); + EXPECT_EQ(read_uint64_t(first_txt), 16); const auto result_2 = build.dispatch( - handler_multiply, output_path / "second.txt", - {sourcemeta::one::Build::dependency(output_path / "first.txt")}, 3); + handler_multiply, second_txt, static_cast(3), first_txt); EXPECT_TRUE(result_2); - EXPECT_EQ(read_uint64_t(output_path / "second.txt"), 48); + EXPECT_EQ(read_uint64_t(second_txt), 48); build.finish(); } @@ -108,40 +111,38 @@ TEST(Build_e2e, simple_cache_miss_hit) { sourcemeta::one::Build build{output_path}; const auto result_1 = build.dispatch( - handler_multiply, output_path / "first.txt", - {sourcemeta::one::Build::dependency(input_path / "initial.txt")}, 2); + handler_multiply, first_txt, static_cast(2), + initial_txt); EXPECT_FALSE(result_1); - EXPECT_EQ(read_uint64_t(output_path / "first.txt"), 16); + EXPECT_EQ(read_uint64_t(first_txt), 16); const auto result_2 = build.dispatch( - handler_multiply, output_path / "second.txt", - {sourcemeta::one::Build::dependency(output_path / "first.txt")}, 3); + handler_multiply, second_txt, static_cast(3), first_txt); EXPECT_FALSE(result_2); - EXPECT_EQ(read_uint64_t(output_path / "second.txt"), 48); + EXPECT_EQ(read_uint64_t(second_txt), 48); build.finish(); } // Third run: update initial.txt, should cache miss - write_uint64_t(input_path / "initial.txt", 7); + write_uint64_t(initial_txt, 7); std::filesystem::last_write_time( - input_path / "initial.txt", + initial_txt, std::filesystem::file_time_type::clock::now() + std::chrono::seconds(10)); { sourcemeta::one::Build build{output_path}; const auto result_1 = build.dispatch( - handler_multiply, output_path / "first.txt", - {sourcemeta::one::Build::dependency(input_path / "initial.txt")}, 2); + handler_multiply, first_txt, static_cast(2), + initial_txt); EXPECT_TRUE(result_1); - EXPECT_EQ(read_uint64_t(output_path / "first.txt"), 14); + EXPECT_EQ(read_uint64_t(first_txt), 14); const auto result_2 = build.dispatch( - handler_multiply, output_path / "second.txt", - {sourcemeta::one::Build::dependency(output_path / "first.txt")}, 3); + handler_multiply, second_txt, static_cast(3), first_txt); EXPECT_TRUE(result_2); - EXPECT_EQ(read_uint64_t(output_path / "second.txt"), 42); + EXPECT_EQ(read_uint64_t(second_txt), 42); } } @@ -154,16 +155,18 @@ TEST(Build_e2e, dynamic_dependency) { std::filesystem::create_directories(input_path); write_uint64_t(input_path / "initial.txt", 8); + const auto initial_txt{input_path / "initial.txt"}; + const auto copy_txt{output_path / "copy.txt"}; + // First run: cache miss, dynamic dependency registered { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "initial.txt"); + build.refresh(initial_txt); const auto result = build.dispatch( - handler_mirror_context_node, output_path / "copy.txt", {}, - input_path / "initial.txt"); + handler_mirror_context_node, copy_txt, initial_txt); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "copy.txt"), 8); + EXPECT_EQ(read_uint64_t(copy_txt), 8); build.finish(); } @@ -173,10 +176,9 @@ TEST(Build_e2e, dynamic_dependency) { sourcemeta::one::Build build{output_path}; const auto result = build.dispatch( - handler_mirror_context_node, output_path / "copy.txt", {}, - input_path / "initial.txt"); + handler_mirror_context_node, copy_txt, initial_txt); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "copy.txt"), 8); + EXPECT_EQ(read_uint64_t(copy_txt), 8); } } @@ -189,16 +191,18 @@ TEST(Build_e2e, missing_dynamic_dependency) { std::filesystem::create_directories(input_path); write_uint64_t(input_path / "initial.txt", 8); + const auto initial_txt{input_path / "initial.txt"}; + const auto copy_txt{output_path / "copy.txt"}; + // First run: cache miss, dynamic dependency NOT registered { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "initial.txt"); + build.refresh(initial_txt); const auto result = build.dispatch( - handler_mirror_context_node_without_callback, output_path / "copy.txt", - {}, input_path / "initial.txt"); + handler_mirror_context_node_without_callback, copy_txt, initial_txt); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "copy.txt"), 8); + EXPECT_EQ(read_uint64_t(copy_txt), 8); build.finish(); } @@ -208,10 +212,9 @@ TEST(Build_e2e, missing_dynamic_dependency) { sourcemeta::one::Build build{output_path}; const auto result = build.dispatch( - handler_mirror_context_node_without_callback, output_path / "copy.txt", - {}, input_path / "initial.txt"); + handler_mirror_context_node_without_callback, copy_txt, initial_txt); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "copy.txt"), 8); + EXPECT_EQ(read_uint64_t(copy_txt), 8); } } @@ -224,17 +227,19 @@ TEST(Build_e2e, new_dependency_invalidates) { std::filesystem::create_directories(input_path); write_uint64_t(input_path / "dep_a.txt", 10); + const auto dep_a{input_path / "dep_a.txt"}; + const auto dep_b{input_path / "dep_b.txt"}; + const auto target{output_path / "target.txt"}; + // First run: build with one dependency { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_a.txt"); + build.refresh(dep_a); - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_a); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 10); + EXPECT_EQ(read_uint64_t(target), 10); build.finish(); } @@ -243,30 +248,25 @@ TEST(Build_e2e, new_dependency_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_a); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 10); + EXPECT_EQ(read_uint64_t(target), 10); build.finish(); } // Third run: add a new dependency, cache miss - write_uint64_t(input_path / "dep_b.txt", 20); + write_uint64_t(dep_b, 20); { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_b.txt"); + build.refresh(dep_b); - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt"), - sourcemeta::one::Build::dependency(input_path / "dep_b.txt")}, - nullptr); + const auto result = build.dispatch(handler_sum, target, + nullptr, dep_a, dep_b); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 30); + EXPECT_EQ(read_uint64_t(target), 30); } } @@ -280,19 +280,20 @@ TEST(Build_e2e, removed_make_dependency_invalidates) { write_uint64_t(input_path / "dep_a.txt", 10); write_uint64_t(input_path / "dep_b.txt", 20); + const auto dep_a{input_path / "dep_a.txt"}; + const auto dep_b{input_path / "dep_b.txt"}; + const auto target{output_path / "target.txt"}; + // First run: build with two dependencies { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_a.txt"); - build.refresh(input_path / "dep_b.txt"); - - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt"), - sourcemeta::one::Build::dependency(input_path / "dep_b.txt")}, - nullptr); + build.refresh(dep_a); + build.refresh(dep_b); + + const auto result = build.dispatch(handler_sum, target, + nullptr, dep_a, dep_b); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 30); + EXPECT_EQ(read_uint64_t(target), 30); build.finish(); } @@ -301,13 +302,10 @@ TEST(Build_e2e, removed_make_dependency_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt"), - sourcemeta::one::Build::dependency(input_path / "dep_b.txt")}, - nullptr); + const auto result = build.dispatch(handler_sum, target, + nullptr, dep_a, dep_b); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 30); + EXPECT_EQ(read_uint64_t(target), 30); build.finish(); } @@ -316,12 +314,10 @@ TEST(Build_e2e, removed_make_dependency_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_a); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 10); + EXPECT_EQ(read_uint64_t(target), 10); } } @@ -334,17 +330,18 @@ TEST(Build_e2e, remove_all_static_dependencies_invalidates) { std::filesystem::create_directories(input_path); write_uint64_t(input_path / "dep_a.txt", 10); + const auto dep_a{input_path / "dep_a.txt"}; + const auto target{output_path / "target.txt"}; + // First run: build with one dependency { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_a.txt"); + build.refresh(dep_a); - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_a); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 10); + EXPECT_EQ(read_uint64_t(target), 10); build.finish(); } @@ -353,12 +350,10 @@ TEST(Build_e2e, remove_all_static_dependencies_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_a); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 10); + EXPECT_EQ(read_uint64_t(target), 10); build.finish(); } @@ -367,10 +362,10 @@ TEST(Build_e2e, remove_all_static_dependencies_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", {}, nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 0); + EXPECT_EQ(read_uint64_t(target), 0); } } @@ -384,18 +379,20 @@ TEST(Build_e2e, replaced_make_dependency_invalidates) { write_uint64_t(input_path / "dep_a.txt", 10); write_uint64_t(input_path / "dep_b.txt", 20); + const auto dep_a{input_path / "dep_a.txt"}; + const auto dep_b{input_path / "dep_b.txt"}; + const auto target{output_path / "target.txt"}; + // First run: build with dep_a { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_a.txt"); - build.refresh(input_path / "dep_b.txt"); + build.refresh(dep_a); + build.refresh(dep_b); - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_a); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 10); + EXPECT_EQ(read_uint64_t(target), 10); build.finish(); } @@ -404,12 +401,10 @@ TEST(Build_e2e, replaced_make_dependency_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_a); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 10); + EXPECT_EQ(read_uint64_t(target), 10); build.finish(); } @@ -418,12 +413,10 @@ TEST(Build_e2e, replaced_make_dependency_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_b.txt")}, - nullptr); + const auto result = + build.dispatch(handler_sum, target, nullptr, dep_b); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 20); + EXPECT_EQ(read_uint64_t(target), 20); } } @@ -437,19 +430,20 @@ TEST(Build_e2e, reordered_static_dependencies_invalidates) { write_uint64_t(input_path / "dep_a.txt", 10); write_uint64_t(input_path / "dep_b.txt", 20); + const auto dep_a{input_path / "dep_a.txt"}; + const auto dep_b{input_path / "dep_b.txt"}; + const auto target{output_path / "target.txt"}; + // First run: build with {a, b} { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_a.txt"); - build.refresh(input_path / "dep_b.txt"); - - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt"), - sourcemeta::one::Build::dependency(input_path / "dep_b.txt")}, - nullptr); + build.refresh(dep_a); + build.refresh(dep_b); + + const auto result = build.dispatch(handler_sum, target, + nullptr, dep_a, dep_b); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 30); + EXPECT_EQ(read_uint64_t(target), 30); build.finish(); } @@ -458,13 +452,10 @@ TEST(Build_e2e, reordered_static_dependencies_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_a.txt"), - sourcemeta::one::Build::dependency(input_path / "dep_b.txt")}, - nullptr); + const auto result = build.dispatch(handler_sum, target, + nullptr, dep_a, dep_b); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 30); + EXPECT_EQ(read_uint64_t(target), 30); build.finish(); } @@ -473,13 +464,10 @@ TEST(Build_e2e, reordered_static_dependencies_invalidates) { { sourcemeta::one::Build build{output_path}; - const auto result = build.dispatch( - handler_sum, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_b.txt"), - sourcemeta::one::Build::dependency(input_path / "dep_a.txt")}, - nullptr); + const auto result = build.dispatch(handler_sum, target, + nullptr, dep_b, dep_a); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 30); + EXPECT_EQ(read_uint64_t(target), 30); } } @@ -493,18 +481,20 @@ TEST(Build_e2e, dynamic_deps_do_not_interfere_with_static_comparison) { write_uint64_t(input_path / "dep_static.txt", 10); write_uint64_t(input_path / "dep_dynamic.txt", 5); + const auto dep_static{input_path / "dep_static.txt"}; + const auto dep_dynamic{input_path / "dep_dynamic.txt"}; + const auto target{output_path / "target.txt"}; + // First run: build with one static dep and one dynamic dep { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_static.txt"); - build.refresh(input_path / "dep_dynamic.txt"); + build.refresh(dep_static); + build.refresh(dep_dynamic); const auto result = build.dispatch( - handler_sum_with_dynamic, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_static.txt")}, - input_path / "dep_dynamic.txt"); + handler_sum_with_dynamic, target, dep_dynamic, dep_static); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 15); + EXPECT_EQ(read_uint64_t(target), 15); build.finish(); } @@ -514,11 +504,9 @@ TEST(Build_e2e, dynamic_deps_do_not_interfere_with_static_comparison) { sourcemeta::one::Build build{output_path}; const auto result = build.dispatch( - handler_sum_with_dynamic, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_static.txt")}, - input_path / "dep_dynamic.txt"); + handler_sum_with_dynamic, target, dep_dynamic, dep_static); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 15); + EXPECT_EQ(read_uint64_t(target), 15); } } @@ -531,16 +519,19 @@ TEST(Build_e2e, persistence_across_runs) { std::filesystem::create_directories(input_path); write_uint64_t(input_path / "initial.txt", 8); + const auto initial_txt{input_path / "initial.txt"}; + const auto first_txt{output_path / "first.txt"}; + // First run: build and write dependencies { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "initial.txt"); + build.refresh(initial_txt); const auto result = build.dispatch( - handler_multiply, output_path / "first.txt", - {sourcemeta::one::Build::dependency(input_path / "initial.txt")}, 2); + handler_multiply, first_txt, static_cast(2), + initial_txt); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "first.txt"), 16); + EXPECT_EQ(read_uint64_t(first_txt), 16); build.finish(); } @@ -550,10 +541,10 @@ TEST(Build_e2e, persistence_across_runs) { sourcemeta::one::Build build{output_path}; const auto result = build.dispatch( - handler_multiply, output_path / "first.txt", - {sourcemeta::one::Build::dependency(input_path / "initial.txt")}, 2); + handler_multiply, first_txt, static_cast(2), + initial_txt); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "first.txt"), 16); + EXPECT_EQ(read_uint64_t(first_txt), 16); } } @@ -566,25 +557,28 @@ TEST(Build_e2e, persistence_invalidates_on_change) { std::filesystem::create_directories(input_path); write_uint64_t(input_path / "initial.txt", 8); + const auto initial_txt{input_path / "initial.txt"}; + const auto first_txt{output_path / "first.txt"}; + // First run { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "initial.txt"); + build.refresh(initial_txt); const auto result = build.dispatch( - handler_multiply, output_path / "first.txt", - {sourcemeta::one::Build::dependency(input_path / "initial.txt")}, 2); + handler_multiply, first_txt, static_cast(2), + initial_txt); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "first.txt"), 16); + EXPECT_EQ(read_uint64_t(first_txt), 16); build.finish(); } // Modify the input between runs and ensure the timestamp is // clearly newer, as some filesystems have second-level resolution - write_uint64_t(input_path / "initial.txt", 100); + write_uint64_t(initial_txt, 100); std::filesystem::last_write_time( - input_path / "initial.txt", + initial_txt, std::filesystem::file_time_type::clock::now() + std::chrono::seconds(10)); // Second run: should cache miss because initial.txt changed @@ -592,10 +586,10 @@ TEST(Build_e2e, persistence_invalidates_on_change) { sourcemeta::one::Build build{output_path}; const auto result = build.dispatch( - handler_multiply, output_path / "first.txt", - {sourcemeta::one::Build::dependency(input_path / "initial.txt")}, 2); + handler_multiply, first_txt, static_cast(2), + initial_txt); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "first.txt"), 200); + EXPECT_EQ(read_uint64_t(first_txt), 200); } } @@ -609,18 +603,20 @@ TEST(Build_e2e, dynamic_dependency_stale_invalidates) { write_uint64_t(input_path / "dep_static.txt", 10); write_uint64_t(input_path / "dep_dynamic.txt", 5); + const auto dep_static{input_path / "dep_static.txt"}; + const auto dep_dynamic{input_path / "dep_dynamic.txt"}; + const auto target{output_path / "target.txt"}; + // First run: build { sourcemeta::one::Build build{output_path}; - build.refresh(input_path / "dep_static.txt"); - build.refresh(input_path / "dep_dynamic.txt"); + build.refresh(dep_static); + build.refresh(dep_dynamic); const auto result = build.dispatch( - handler_sum_with_dynamic, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_static.txt")}, - input_path / "dep_dynamic.txt"); + handler_sum_with_dynamic, target, dep_dynamic, dep_static); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 15); + EXPECT_EQ(read_uint64_t(target), 15); build.finish(); } @@ -630,19 +626,17 @@ TEST(Build_e2e, dynamic_dependency_stale_invalidates) { sourcemeta::one::Build build{output_path}; const auto result = build.dispatch( - handler_sum_with_dynamic, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_static.txt")}, - input_path / "dep_dynamic.txt"); + handler_sum_with_dynamic, target, dep_dynamic, dep_static); EXPECT_FALSE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 15); + EXPECT_EQ(read_uint64_t(target), 15); build.finish(); } // Update the dynamic dep file between runs - write_uint64_t(input_path / "dep_dynamic.txt", 100); + write_uint64_t(dep_dynamic, 100); std::filesystem::last_write_time( - input_path / "dep_dynamic.txt", + dep_dynamic, std::filesystem::file_time_type::clock::now() + std::chrono::seconds(10)); // Third run: should cache miss because dynamic dep changed @@ -650,10 +644,8 @@ TEST(Build_e2e, dynamic_dependency_stale_invalidates) { sourcemeta::one::Build build{output_path}; const auto result = build.dispatch( - handler_sum_with_dynamic, output_path / "target.txt", - {sourcemeta::one::Build::dependency(input_path / "dep_static.txt")}, - input_path / "dep_dynamic.txt"); + handler_sum_with_dynamic, target, dep_dynamic, dep_static); EXPECT_TRUE(result); - EXPECT_EQ(read_uint64_t(output_path / "target.txt"), 110); + EXPECT_EQ(read_uint64_t(target), 110); } }