1464 return absl::FailedPreconditionError(
1465 "Project must load a hack manifest before message source sync");
1467#if defined(__EMSCRIPTEN__)
1468 if (options.
write) {
1469 return absl::FailedPreconditionError(
1470 "message-source-sync --write is unavailable in browser builds because "
1471 "durable atomic filesystem publication cannot be guaranteed");
1475 if (!layout.
source.has_value()) {
1476 return absl::FailedPreconditionError(
1477 "Hack manifest does not define messages.source");
1480 size_t capacity = 0;
1482 fs::path project_root;
1485 fs::path canonical_bundle_path;
1487 canonical_bundle_path,
1488 ResolveProjectTarget(project_root, layout.
source->canonical_bundle_path,
1489 true,
"Canonical message bundle"));
1490 fs::path asm_include_path;
1492 ResolveProjectTarget(
1493 project_root, layout.
source->generated_asm_include_path,
1494 false,
"Generated message ASM include"));
1496 ValidateDistinctTargets(canonical_bundle_path, asm_include_path));
1497 std::vector<fs::path> publication_lock_paths;
1499 publication_lock_paths,
1500 PublicationLockPaths(canonical_bundle_path, asm_include_path));
1502 publication_lock_paths, canonical_bundle_path, asm_include_path));
1504 std::error_code incoming_ec;
1505 const fs::path resolved_incoming =
1506 fs::canonical(incoming_bundle_path, incoming_ec);
1507 if (incoming_ec || !fs::is_regular_file(resolved_incoming, incoming_ec) ||
1509 return absl::NotFoundError(absl::StrFormat(
1510 "Incoming message bundle must be an existing regular file: %s",
1511 incoming_bundle_path.string()));
1514 std::string expected_sha;
1515 std::unique_ptr<MessageSourceWriteLocks> write_locks;
1516 if (options.
write) {
1518 return absl::InvalidArgumentError(
1519 "--write requires --expected-source-sha256");
1524 MessageSourceWriteLocks::Acquire(publication_lock_paths));
1527 std::string canonical_before;
1529 "canonical message bundle"));
1530 const std::string source_sha_before = Sha256Hex(canonical_before);
1531 Json canonical_document;
1534 ParseBundleDocument(canonical_before,
"Canonical message bundle"));
1535 ExpandedSourceBank merged_bank;
1539 true,
"Canonical message bundle"));
1540 const std::string expected_current_asm = RenderAsmInclude(
1543 std::string incoming_content;
1545 ReadTextFile(resolved_incoming,
"incoming message bundle"));
1546 Json incoming_document;
1549 ParseBundleDocument(incoming_content,
"Incoming message bundle"));
1550 ExpandedSourceBank incoming_bank;
1554 false,
"Incoming message bundle"));
1555 for (
auto& [
id, message] : incoming_bank) {
1556 merged_bank[id] = std::move(message);
1559 size_t encoded_size = 1;
1560 for (
const auto& entry : merged_bank) {
1561 encoded_size += entry.second.bytes.size() + 1;
1563 if (encoded_size > capacity) {
1564 return absl::ResourceExhaustedError(absl::StrFormat(
1565 "Expanded message source needs %zu bytes including terminators, but "
1566 "manifest capacity is %zu bytes [0x%06X, 0x%06X]",
1570 const std::string canonical_after = SerializeCanonicalBundle(merged_bank);
1571 const std::string proposed_source_sha = Sha256Hex(canonical_after);
1572 const std::string asm_after = RenderAsmInclude(
1575 std::optional<std::string> asm_before;
1576 std::error_code include_exists_ec;
1577 if (fs::exists(asm_include_path, include_exists_ec)) {
1578 std::string existing_include;
1581 ReadTextFile(asm_include_path,
"generated message ASM include"));
1582 asm_before = std::move(existing_include);
1583 }
else if (include_exists_ec) {
1584 return absl::FailedPreconditionError(
1585 absl::StrFormat(
"Cannot inspect generated message ASM include: %s",
1586 include_exists_ec.message()));
1588 if (asm_before.has_value() && *asm_before != expected_current_asm) {
1589 return absl::FailedPreconditionError(absl::StrFormat(
1590 "Generated message ASM include drifted from the current canonical "
1592 asm_include_path.string()));
1597 .generated_asm_include_path = asm_include_path,
1601 .incoming_updates =
static_cast<int>(incoming_bank.size()),
1602 .encoded_size = encoded_size,
1603 .capacity = capacity,
1604 .source_sha256_before = source_sha_before,
1605 .proposed_source_sha256 = proposed_source_sha,
1606 .changed = canonical_before != canonical_after ||
1607 !asm_before.has_value() || *asm_before != asm_after,
1609 if (!options.
write) {
1612 if (expected_sha != source_sha_before) {
1613 return absl::AbortedError(absl::StrFormat(
1614 "Canonical message source SHA-256 CAS failed: expected %s, got %s",
1615 expected_sha, source_sha_before));
1617 if (!result.changed) {
1621 std::vector<PublicationArtifact> artifacts;
1622 artifacts.push_back(PublicationArtifact{
1623 .target = canonical_bundle_path,
1624 .content = canonical_after,
1625 .original_content = canonical_before,
1627 artifacts.push_back(PublicationArtifact{
1628 .target = asm_include_path,
1629 .content = asm_after,
1630 .original_content = asm_before,
1636 auto reopened_canonical_or =
1637 ReadTextFile(canonical_bundle_path,
"published canonical message bundle");
1638 if (!reopened_canonical_or.ok()) {
1639 return RollBackPublication(&artifacts, reopened_canonical_or.status());
1641 std::string reopened_canonical = std::move(*reopened_canonical_or);
1642 auto reopened_document_or = ParseBundleDocument(
1643 reopened_canonical,
"Published canonical message bundle");
1644 if (!reopened_document_or.ok()) {
1645 return RollBackPublication(&artifacts, reopened_document_or.status());
1647 Json reopened_document = std::move(*reopened_document_or);
1648 auto reopened_bank_or = ParseExpandedBank(
1650 true,
"Published canonical message bundle");
1651 if (!reopened_bank_or.ok()) {
1652 return RollBackPublication(&artifacts, reopened_bank_or.status());
1654 if (Sha256Hex(reopened_canonical) != proposed_source_sha) {
1655 return RollBackPublication(
1657 absl::DataLossError(
1658 "Published canonical message bundle SHA-256 readback failed"));
1661 const absl::Status backup_cleanup = CleanupBackups(&artifacts);
1662 if (!backup_cleanup.ok()) {
1663 return RollBackPublication(&artifacts, backup_cleanup);
1665 result.wrote =
true;