diff --git a/src/build/state.cc b/src/build/state.cc index 776c4ff0..b11b3320 100644 --- a/src/build/state.cc +++ b/src/build/state.cc @@ -684,27 +684,199 @@ auto BuildState::save(const std::filesystem::path &path) const -> void { static_cast(this->resolver_entry_count)}; const auto total_count{output_count + resolver_count}; - std::uint32_t capacity{16}; - while (capacity < total_count * 2) { - capacity <<= 1; - } + const bool can_patch{this->table_capacity > 0 && + this->string_pool != nullptr && + total_count < this->table_capacity * 3 / 4}; std::string pool; - pool.reserve(static_cast(total_count) * 100); - std::vector slots( - static_cast(capacity) * SLOT_SIZE, 0); + std::vector slots; + std::uint32_t capacity; + + std::uint32_t old_pool_size{0}; + std::unordered_set + overlay_updates; + std::unordered_set + resolver_updates; + + if (can_patch) { + capacity = this->table_capacity; + const auto old_slots_size{static_cast(capacity) * SLOT_SIZE}; + slots.resize(old_slots_size); + std::memcpy(slots.data(), this->table_slots, old_slots_size); + + old_pool_size = read_field(this->view_data, 16); + + auto find_slot{[&](std::string_view entry_key) -> std::uint32_t { + const auto hash{fnv1a(entry_key.data(), entry_key.size())}; + auto index{static_cast(hash & (capacity - 1))}; + while (slots[index * SLOT_SIZE + SLOT_OCCUPIED] != 0) { + const auto *slot{slots.data() + index * SLOT_SIZE}; + if (read_field(slot, SLOT_HASH) == hash && + slot_key(slot, this->string_pool) == entry_key) { + return index; + } + index = (index + 1) & (capacity - 1); + } + return capacity; + }}; + + for (const auto &deleted_key : this->deleted) { + const auto slot_index{find_slot(deleted_key)}; + if (slot_index < capacity) { + auto empty{slot_index}; + for (;;) { + const auto next{(empty + 1) & (capacity - 1)}; + if (slots[next * SLOT_SIZE + SLOT_OCCUPIED] == 0) { + break; + } + + const auto next_hash{read_field( + slots.data() + next * SLOT_SIZE, SLOT_HASH)}; + const auto natural{ + static_cast(next_hash & (capacity - 1))}; + const auto distance_current{(next - natural) & (capacity - 1)}; + const auto distance_new{(empty - natural) & (capacity - 1)}; + if (distance_new <= distance_current) { + std::memcpy(slots.data() + empty * SLOT_SIZE, + slots.data() + next * SLOT_SIZE, SLOT_SIZE); + empty = next; + } else { + break; + } + } + + slots[empty * SLOT_SIZE + SLOT_OCCUPIED] = 0; + } + } + + for (const auto &[overlay_key, overlay_entry] : this->overlay) { + if (find_slot(overlay_key) < capacity) { + overlay_updates.insert(overlay_key); + } + } + + for (const auto &[overlay_key, overlay_entry] : this->resolver_overlay) { + if (find_slot(overlay_key) < capacity) { + resolver_updates.insert(overlay_key); + } + } + } else { + capacity = 16; + while (capacity < total_count * 2) { + capacity <<= 1; + } - auto write_slot{[&](std::string_view entry_key, std::int64_t timestamp, - std::uint32_t data_pool_offset, std::uint16_t data_count, - std::uint8_t kind) { + pool.reserve(static_cast(total_count) * 100); + slots.resize(static_cast(capacity) * SLOT_SIZE, 0); + + for (std::uint32_t slot_index = 0; slot_index < this->table_capacity; + ++slot_index) { + const auto *old_slot{this->table_slots + slot_index * SLOT_SIZE}; + if (old_slot[SLOT_OCCUPIED] == 0) { + continue; + } + + const auto old_kind{old_slot[SLOT_KIND]}; + const auto key{slot_key(old_slot, this->string_pool)}; + + if (old_kind == KIND_OUTPUT) { + if (this->deleted.contains(key) || this->overlay.contains(key)) { + continue; + } + } else if (old_kind == KIND_RESOLVER) { + if (this->resolver_overlay.contains(key)) { + continue; + } + } + + const auto timestamp{read_field(old_slot, SLOT_TIMESTAMP)}; + const auto old_data_offset{ + read_field(old_slot, SLOT_DATA_OFFSET)}; + const auto data_count{ + read_field(old_slot, SLOT_DATA_COUNT)}; + + const auto new_data_offset{static_cast(pool.size())}; + if (data_count > 0) { + auto raw_offset{static_cast(old_data_offset)}; + for (std::uint16_t item_index = 0; item_index < data_count; + ++item_index) { + const auto item_length{ + read_field(this->string_pool, raw_offset)}; + raw_offset += sizeof(std::uint32_t) + item_length; + } + + const auto raw_size{raw_offset - old_data_offset}; + pool.append( + reinterpret_cast(this->string_pool + old_data_offset), + raw_size); + } + + const auto hash{fnv1a(key.data(), key.size())}; + auto index{static_cast(hash & (capacity - 1))}; + while (slots[index * SLOT_SIZE + SLOT_OCCUPIED] != 0) { + index = (index + 1) & (capacity - 1); + } + + auto *slot{slots.data() + index * SLOT_SIZE}; + const auto key_offset{static_cast(pool.size())}; + pool.append(key); + const auto key_length{static_cast(key.size())}; + + std::memcpy(slot + SLOT_HASH, &hash, sizeof(hash)); + std::memcpy(slot + SLOT_KEY_OFFSET, &key_offset, sizeof(key_offset)); + std::memcpy(slot + SLOT_KEY_LENGTH, &key_length, sizeof(key_length)); + std::memcpy(slot + SLOT_TIMESTAMP, ×tamp, sizeof(timestamp)); + std::memcpy(slot + SLOT_DATA_OFFSET, &new_data_offset, + sizeof(new_data_offset)); + std::memcpy(slot + SLOT_DATA_COUNT, &data_count, sizeof(data_count)); + slot[SLOT_OCCUPIED] = 1; + slot[SLOT_KIND] = old_kind; + } + } + + auto write_new_slot{[&](std::string_view entry_key, std::int64_t timestamp, + std::uint32_t data_pool_offset, + std::uint16_t data_count, std::uint8_t kind, + bool is_update) { const auto hash{fnv1a(entry_key.data(), entry_key.size())}; - auto index{static_cast(hash & (capacity - 1))}; - while (slots[index * SLOT_SIZE + SLOT_OCCUPIED] != 0) { - index = (index + 1) & (capacity - 1); + std::uint32_t index; + + if (is_update) { + index = static_cast(hash & (capacity - 1)); + while (slots[index * SLOT_SIZE + SLOT_OCCUPIED] != 0) { + const auto *slot{slots.data() + index * SLOT_SIZE}; + if (read_field(slot, SLOT_HASH) == hash) { + const auto probe_key_offset{ + read_field(slot, SLOT_KEY_OFFSET)}; + const auto probe_key_length{ + read_field(slot, SLOT_KEY_LENGTH)}; + std::string_view probe_key; + if (can_patch && probe_key_offset < old_pool_size) { + probe_key = {reinterpret_cast(this->string_pool + + probe_key_offset), + probe_key_length}; + } else { + const auto offset_in_pool{can_patch + ? probe_key_offset - old_pool_size + : probe_key_offset}; + probe_key = {pool.data() + offset_in_pool, probe_key_length}; + } + if (probe_key == entry_key) { + break; + } + } + index = (index + 1) & (capacity - 1); + } + } else { + index = static_cast(hash & (capacity - 1)); + while (slots[index * SLOT_SIZE + SLOT_OCCUPIED] != 0) { + index = (index + 1) & (capacity - 1); + } } auto *slot{slots.data() + index * SLOT_SIZE}; - const auto key_offset{static_cast(pool.size())}; + const auto key_offset{ + static_cast(old_pool_size + pool.size())}; pool.append(entry_key); const auto key_length{static_cast(entry_key.size())}; @@ -719,60 +891,14 @@ auto BuildState::save(const std::filesystem::path &path) const -> void { slot[SLOT_KIND] = kind; }}; - // Write on-disk entries (both kinds, not deleted/overlayed) - for (std::uint32_t slot_index = 0; slot_index < this->table_capacity; - ++slot_index) { - const auto *old_slot{this->table_slots + slot_index * SLOT_SIZE}; - if (old_slot[SLOT_OCCUPIED] == 0) { - continue; - } - - const auto old_kind{old_slot[SLOT_KIND]}; - const auto key{slot_key(old_slot, this->string_pool)}; - - if (old_kind == KIND_OUTPUT) { - if (this->deleted.contains(key) || this->overlay.contains(key)) { - continue; - } - } else if (old_kind == KIND_RESOLVER) { - if (this->resolver_overlay.contains(key)) { - continue; - } - } - - const auto timestamp{read_field(old_slot, SLOT_TIMESTAMP)}; - const auto old_data_offset{ - read_field(old_slot, SLOT_DATA_OFFSET)}; - const auto data_count{read_field(old_slot, SLOT_DATA_COUNT)}; - - // Copy raw data from old string pool to new pool - const auto new_data_offset{static_cast(pool.size())}; - if (data_count > 0) { - auto raw_offset{static_cast(old_data_offset)}; - for (std::uint16_t item_index = 0; item_index < data_count; - ++item_index) { - const auto item_length{ - read_field(this->string_pool, raw_offset)}; - raw_offset += sizeof(std::uint32_t) + item_length; - } - - const auto raw_size{raw_offset - old_data_offset}; - pool.append( - reinterpret_cast(this->string_pool + old_data_offset), - raw_size); - } - - write_slot(key, timestamp, new_data_offset, data_count, old_kind); - } - - // Write output overlay entries (kind=0) for (const auto &[entry_path, entry] : this->overlay) { const auto timestamp{static_cast( std::chrono::duration_cast( entry.file_mark.time_since_epoch()) .count())}; - const auto data_offset{static_cast(pool.size())}; + const auto data_offset{ + static_cast(old_pool_size + pool.size())}; assert(entry.dependencies.size() <= UINT16_MAX); const auto data_count{ static_cast(entry.dependencies.size())}; @@ -780,47 +906,55 @@ auto BuildState::save(const std::filesystem::path &path) const -> void { append_pool_string(pool, dependency.native()); } - write_slot(entry_path, timestamp, data_offset, data_count, KIND_OUTPUT); + write_new_slot(entry_path, timestamp, data_offset, data_count, KIND_OUTPUT, + overlay_updates.contains(entry_path)); } - // Write resolver overlay entries (kind=1) for (const auto &[source_path, cache_entry] : this->resolver_overlay) { const auto timestamp{static_cast( std::chrono::duration_cast( cache_entry.file_mark.time_since_epoch()) .count())}; - const auto data_offset{static_cast(pool.size())}; + const auto data_offset{ + static_cast(old_pool_size + pool.size())}; append_pool_string(pool, cache_entry.new_identifier); append_pool_string(pool, cache_entry.original_identifier); append_pool_string(pool, cache_entry.dialect); append_pool_string(pool, cache_entry.relative_path); - write_slot(source_path, timestamp, data_offset, 4, KIND_RESOLVER); + write_new_slot(source_path, timestamp, data_offset, 4, KIND_RESOLVER, + resolver_updates.contains(source_path)); } - // Assemble the file - const auto pool_size{static_cast(pool.size())}; - - std::string buffer; - buffer.reserve(HEADER_SIZE + static_cast(capacity) * SLOT_SIZE + - pool.size()); - - buffer.append(reinterpret_cast(&STATE_MAGIC), - sizeof(STATE_MAGIC)); - buffer.append(reinterpret_cast(&STATE_VERSION), - sizeof(STATE_VERSION)); - buffer.append(reinterpret_cast(&capacity), sizeof(capacity)); - buffer.append(reinterpret_cast(&output_count), - sizeof(output_count)); - buffer.append(reinterpret_cast(&pool_size), sizeof(pool_size)); - buffer.append(reinterpret_cast(&resolver_count), - sizeof(resolver_count)); - - buffer.append(reinterpret_cast(slots.data()), slots.size()); - buffer.append(pool); + const auto total_pool_size{ + static_cast(old_pool_size + pool.size())}; { + auto read_slot_key{[&](const std::uint8_t *slot) -> std::string_view { + const auto key_offset{read_field(slot, SLOT_KEY_OFFSET)}; + const auto key_length{read_field(slot, SLOT_KEY_LENGTH)}; + if (can_patch && key_offset < old_pool_size) { + return {reinterpret_cast(this->string_pool + key_offset), + key_length}; + } + const auto offset_in_pool{can_patch ? key_offset - old_pool_size + : key_offset}; + return {pool.data() + offset_in_pool, key_length}; + }}; + + auto read_slot_pool_string{[&](std::size_t &offset) -> std::string { + if (can_patch && offset < old_pool_size) { + return read_pool_string(this->string_pool, offset); + } + auto adjusted_offset{can_patch ? offset - old_pool_size : offset}; + auto result{ + read_pool_string(reinterpret_cast(pool.data()), + adjusted_offset)}; + offset = (can_patch ? old_pool_size : 0) + adjusted_offset; + return result; + }}; + const auto output_dir{path.parent_path().string()}; const auto schemas_prefix{output_dir + "/schemas/"}; const auto explorer_prefix{output_dir + "/explorer/"}; @@ -830,78 +964,224 @@ auto BuildState::save(const std::filesystem::path &path) const -> void { TransparentEqual> save_schema_index; - const auto *new_pool{reinterpret_cast(pool.data())}; - for (std::uint32_t slot_index = 0; slot_index < capacity; ++slot_index) { - const auto *slot{slots.data() + slot_index * SLOT_SIZE}; - if (slot[SLOT_OCCUPIED] == 0 || slot[SLOT_KIND] != KIND_OUTPUT) { - continue; + if (can_patch && this->persisted_schema_table != nullptr) { + const auto *record_ptr{this->persisted_schema_table}; + for (std::uint32_t record_index = 0; + record_index < this->persisted_schema_count; ++record_index) { + const auto *record{ + reinterpret_cast(record_ptr)}; + const auto relative_path{std::string{ + reinterpret_cast(record_ptr + sizeof(*record)), + record->relative_path_length}}; + auto &schema_entry{save_schema_index[relative_path]}; + using file_time = std::filesystem::file_time_type; + schema_entry.root_mtime = + file_time{std::chrono::duration_cast( + std::chrono::nanoseconds{record->root_mtime})}; + schema_entry.target_bitmap = record->target_bitmap; + schema_entry.has_cross_schema_deps = record->has_cross_schema_deps != 0; + record_ptr += sizeof(*record) + record->relative_path_length; } - const auto key{slot_key(slot, new_pool)}; + for (const auto &deleted_key : this->deleted) { + std::string_view key_prefix; + if (std::string_view{deleted_key}.starts_with(schemas_prefix)) { + key_prefix = schemas_prefix; + } else if (std::string_view{deleted_key}.starts_with(explorer_prefix)) { + key_prefix = explorer_prefix; + } else { + continue; + } - std::string_view key_prefix; - bool is_explorer{false}; - if (key.starts_with(schemas_prefix)) { - key_prefix = schemas_prefix; - } else if (key.starts_with(explorer_prefix)) { - key_prefix = explorer_prefix; - is_explorer = true; - } else { - continue; - } + const auto after{ + std::string_view{deleted_key}.substr(key_prefix.size())}; + const auto sentinel_position{after.find(sentinel_marker)}; + if (sentinel_position == std::string_view::npos) { + continue; + } - const auto after{key.substr(key_prefix.size())}; - const auto sentinel_position{after.find(sentinel_marker)}; - if (sentinel_position == std::string_view::npos) { - continue; + save_schema_index.erase( + std::string{after.substr(0, sentinel_position)}); } - const auto relative_path{after.substr(0, sentinel_position)}; - const auto filename{after.substr(sentinel_position + 3)}; - auto &schema_entry{save_schema_index[std::string{relative_path}]}; - - for (std::size_t rule_index{0}; rule_index < PER_SCHEMA_RULES.size(); - rule_index++) { - const auto &rule{PER_SCHEMA_RULES[rule_index]}; - if (filename == rule.filename && - ((rule.base == TargetBase::Explorer) == is_explorer)) { - schema_entry.target_bitmap |= - static_cast(1 << rule_index); - if (rule.is_root) { - const auto nanoseconds{ - read_field(slot, SLOT_TIMESTAMP)}; - using file_time = std::filesystem::file_time_type; - schema_entry.root_mtime = - file_time{std::chrono::duration_cast( - std::chrono::nanoseconds{nanoseconds})}; - } - break; + std::unordered_set + affected_schemas; + for (const auto &[overlay_key, overlay_entry] : this->overlay) { + std::string_view key_view{overlay_key}; + std::string_view key_prefix; + if (key_view.starts_with(schemas_prefix)) { + key_prefix = schemas_prefix; + } else if (key_view.starts_with(explorer_prefix)) { + key_prefix = explorer_prefix; + } else { + continue; + } + + const auto after{key_view.substr(key_prefix.size())}; + const auto sentinel_position{after.find(sentinel_marker)}; + if (sentinel_position == std::string_view::npos) { + continue; } + + affected_schemas.insert( + std::string{after.substr(0, sentinel_position)}); } - const auto data_count{read_field(slot, SLOT_DATA_COUNT)}; - if (data_count > 0 && !schema_entry.has_cross_schema_deps) { - auto offset{static_cast( - read_field(slot, SLOT_DATA_OFFSET))}; - for (std::uint16_t dep_index = 0; dep_index < data_count; ++dep_index) { - const auto dependency_path{read_pool_string(new_pool, offset)}; - std::string_view dep_prefix; - if (dependency_path.starts_with(schemas_prefix)) { - dep_prefix = schemas_prefix; - } else if (dependency_path.starts_with(explorer_prefix)) { - dep_prefix = explorer_prefix; + for (const auto &affected_relative : affected_schemas) { + auto &schema_entry{save_schema_index[affected_relative]}; + schema_entry = SchemaStateEntry{}; + + for (std::uint32_t slot_index = 0; slot_index < capacity; + ++slot_index) { + const auto *slot{slots.data() + slot_index * SLOT_SIZE}; + if (slot[SLOT_OCCUPIED] == 0 || slot[SLOT_KIND] != KIND_OUTPUT) { + continue; + } + + const auto key{read_slot_key(slot)}; + std::string_view key_prefix; + bool is_explorer{false}; + if (key.starts_with(schemas_prefix)) { + key_prefix = schemas_prefix; + } else if (key.starts_with(explorer_prefix)) { + key_prefix = explorer_prefix; + is_explorer = true; } else { continue; } - const auto dep_after{dependency_path.substr(dep_prefix.size())}; - const auto dep_sentinel{dep_after.find(sentinel_marker)}; - if (dep_sentinel != std::string_view::npos && - dep_after.substr(0, dep_sentinel) != relative_path) { - schema_entry.has_cross_schema_deps = true; + const auto after{key.substr(key_prefix.size())}; + const auto sentinel_position{after.find(sentinel_marker)}; + if (sentinel_position == std::string_view::npos) { + continue; + } + + if (after.substr(0, sentinel_position) != affected_relative) { + continue; + } + + const auto filename{after.substr(sentinel_position + 3)}; + for (std::size_t rule_index{0}; rule_index < PER_SCHEMA_RULES.size(); + rule_index++) { + const auto &rule{PER_SCHEMA_RULES[rule_index]}; + if (filename == rule.filename && + ((rule.base == TargetBase::Explorer) == is_explorer)) { + schema_entry.target_bitmap |= + static_cast(1 << rule_index); + if (rule.is_root) { + const auto nanoseconds{ + read_field(slot, SLOT_TIMESTAMP)}; + using file_time = std::filesystem::file_time_type; + schema_entry.root_mtime = + file_time{std::chrono::duration_cast( + std::chrono::nanoseconds{nanoseconds})}; + } + break; + } + } + + const auto data_count{ + read_field(slot, SLOT_DATA_COUNT)}; + if (data_count > 0 && !schema_entry.has_cross_schema_deps) { + auto offset{static_cast( + read_field(slot, SLOT_DATA_OFFSET))}; + for (std::uint16_t dep_index = 0; dep_index < data_count; + ++dep_index) { + const auto dependency_path{read_slot_pool_string(offset)}; + std::string_view dep_prefix; + if (dependency_path.starts_with(schemas_prefix)) { + dep_prefix = schemas_prefix; + } else if (dependency_path.starts_with(explorer_prefix)) { + dep_prefix = explorer_prefix; + } else { + continue; + } + + const auto dep_after{dependency_path.substr(dep_prefix.size())}; + const auto dep_sentinel{dep_after.find(sentinel_marker)}; + if (dep_sentinel != std::string_view::npos && + dep_after.substr(0, dep_sentinel) != affected_relative) { + schema_entry.has_cross_schema_deps = true; + break; + } + } + } + } + } + } else { + + for (std::uint32_t slot_index = 0; slot_index < capacity; ++slot_index) { + const auto *slot{slots.data() + slot_index * SLOT_SIZE}; + if (slot[SLOT_OCCUPIED] == 0 || slot[SLOT_KIND] != KIND_OUTPUT) { + continue; + } + + const auto key{read_slot_key(slot)}; + std::string_view key_prefix; + bool is_explorer{false}; + if (key.starts_with(schemas_prefix)) { + key_prefix = schemas_prefix; + } else if (key.starts_with(explorer_prefix)) { + key_prefix = explorer_prefix; + is_explorer = true; + } else { + continue; + } + + const auto after{key.substr(key_prefix.size())}; + const auto sentinel_position{after.find(sentinel_marker)}; + if (sentinel_position == std::string_view::npos) { + continue; + } + + const auto relative_path{after.substr(0, sentinel_position)}; + const auto filename{after.substr(sentinel_position + 3)}; + auto &schema_entry{save_schema_index[std::string{relative_path}]}; + + for (std::size_t rule_index{0}; rule_index < PER_SCHEMA_RULES.size(); + rule_index++) { + const auto &rule{PER_SCHEMA_RULES[rule_index]}; + if (filename == rule.filename && + ((rule.base == TargetBase::Explorer) == is_explorer)) { + schema_entry.target_bitmap |= + static_cast(1 << rule_index); + if (rule.is_root) { + const auto nanoseconds{ + read_field(slot, SLOT_TIMESTAMP)}; + using file_time = std::filesystem::file_time_type; + schema_entry.root_mtime = + file_time{std::chrono::duration_cast( + std::chrono::nanoseconds{nanoseconds})}; + } break; } } + + const auto data_count{read_field(slot, SLOT_DATA_COUNT)}; + if (data_count > 0 && !schema_entry.has_cross_schema_deps) { + auto offset{static_cast( + read_field(slot, SLOT_DATA_OFFSET))}; + for (std::uint16_t dep_index = 0; dep_index < data_count; + ++dep_index) { + const auto dependency_path{read_slot_pool_string(offset)}; + std::string_view dep_prefix; + if (dependency_path.starts_with(schemas_prefix)) { + dep_prefix = schemas_prefix; + } else if (dependency_path.starts_with(explorer_prefix)) { + dep_prefix = explorer_prefix; + } else { + continue; + } + + const auto dep_after{dependency_path.substr(dep_prefix.size())}; + const auto dep_sentinel{dep_after.find(sentinel_marker)}; + if (dep_sentinel != std::string_view::npos && + dep_after.substr(0, dep_sentinel) != relative_path) { + schema_entry.has_cross_schema_deps = true; + break; + } + } + } } } @@ -933,12 +1213,38 @@ auto BuildState::save(const std::filesystem::path &path) const -> void { schema_index_buffer.append(relative_path); } - buffer.append(schema_index_buffer); - } + const auto temp_path{path.native() + ".tmp"}; + std::ofstream stream{temp_path, std::ios::binary}; + assert(!stream.fail()); + + stream.write(reinterpret_cast(&STATE_MAGIC), + sizeof(STATE_MAGIC)); + stream.write(reinterpret_cast(&STATE_VERSION), + sizeof(STATE_VERSION)); + stream.write(reinterpret_cast(&capacity), sizeof(capacity)); + stream.write(reinterpret_cast(&output_count), + sizeof(output_count)); + stream.write(reinterpret_cast(&total_pool_size), + sizeof(total_pool_size)); + stream.write(reinterpret_cast(&resolver_count), + sizeof(resolver_count)); + + stream.write(reinterpret_cast(slots.data()), + static_cast(slots.size())); + + if (can_patch && old_pool_size > 0) { + stream.write(reinterpret_cast(this->string_pool), + static_cast(old_pool_size)); + } + if (!pool.empty()) { + stream.write(pool.data(), static_cast(pool.size())); + } - std::ofstream stream{path, std::ios::binary}; - assert(!stream.fail()); - stream.write(buffer.data(), static_cast(buffer.size())); + stream.write(schema_index_buffer.data(), + static_cast(schema_index_buffer.size())); + stream.close(); + std::filesystem::rename(temp_path, path); + } } } // namespace sourcemeta::one