-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathprebuilt.cppm
More file actions
166 lines (145 loc) · 7.12 KB
/
Copy pathprebuilt.cppm
File metadata and controls
166 lines (145 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// mcpp.pack.prebuilt — the consumer's half of a library package.
//
// A package produced by `mcpp pack` is an ordinary mcpp package: it carries a
// normal `mcpp.toml`, and mcpp's existing "the payload has its own manifest"
// route builds against it with no new code. That is the design, and it is why
// an mcpp too old to know about any of this still WORKS with these packages.
//
// What this module adds is the part an old mcpp cannot do: check that the
// binaries in the package were built for the toolchain about to link them, and
// that the interface sitting next to them is still the one they were built
// from. Both failures are otherwise silent.
//
// THE SECOND ONE IS THE DANGEROUS ONE. Measured on a real build: change one
// line of a shipped interface — swap two `int` members of a struct, which the
// Itanium ABI does not mangle — and the consumer compiles, links, runs, and
// prints transposed data. No diagnostic at any stage. A digest cannot prevent
// a producer from shipping a mismatched pair in the first place (only atomic
// production does that), but it does catch the pair coming apart afterwards,
// which is the case a path dependency or an extracted store is exposed to.
//
// WHAT MARKS A PACKAGE. `provenance` beginning with `mcpp-pack` on any runtime
// artifact. No new key, no new section: the marker is a value in a field that
// has existed since runtime artifacts did.
//
// Design: .agents/docs/2026-08-17-library-distribution-design.md §3.2.
export module mcpp.pack.prebuilt;
import std;
import mcpp.manifest;
import mcpp.pack.abi_tag;
import mcpp.pack.digest;
export namespace mcpp::pack {
inline constexpr std::string_view kPackProvenancePrefix = "mcpp-pack";
// Was this manifest written by `mcpp pack`?
bool is_distribution_package(const mcpp::manifest::Manifest& m);
struct PrebuiltCheck {
std::filesystem::path packageRoot;
std::string packageLabel; // "acme.mathkit@0.1.0", for diagnostics
AbiTag current; // the tag THIS build would publish
};
// Refuse, with a message the reader can act on, or accept.
//
// Order matters and is the order of the diagnostic: a package that is for
// another architecture entirely should say so before it complains about a
// digest, because the digest is not what the user has to fix.
std::expected<void, std::string>
check_prebuilt(const mcpp::manifest::Manifest& m, const PrebuiltCheck& in);
} // namespace mcpp::pack
namespace mcpp::pack {
namespace {
bool is_library_role(std::string_view role) {
return role == "static-library" || role == "shared-library";
}
} // namespace
bool is_distribution_package(const mcpp::manifest::Manifest& m) {
for (auto const& a : m.runtimeConfig.artifacts)
if (a.provenance.starts_with(kPackProvenancePrefix)) return true;
return false;
}
std::expected<void, std::string>
check_prebuilt(const mcpp::manifest::Manifest& m, const PrebuiltCheck& in)
{
std::error_code ec;
// ── 1. the artifacts are where the manifest says ──────────────────
//
// A package whose `lib/` was trimmed in transit links against nothing and
// fails at the linker, naming a path nobody recognises.
for (auto const& a : m.runtimeConfig.artifacts) {
if (!a.provenance.starts_with(kPackProvenancePrefix)) continue;
auto abs = a.path.is_absolute() ? a.path : in.packageRoot / a.path;
if (!std::filesystem::exists(abs, ec)) {
return std::unexpected(std::format(
"{}: the package declares an artifact at '{}', and it is not there.\n"
" The package is incomplete — re-download or re-pack it.",
in.packageLabel, a.path.string()));
}
}
// ── 2. one of the published tags accepts this toolchain ───────────
std::vector<std::string> publishedTags;
bool sawLibrary = false, accepted = false;
std::vector<TagMismatch> bestRefusal;
std::string bestRefusalTag;
for (auto const& a : m.runtimeConfig.artifacts) {
if (!a.provenance.starts_with(kPackProvenancePrefix)) continue;
if (!is_library_role(a.role)) continue;
sawLibrary = true;
if (a.abi.empty()) { // nothing declared → nothing to enforce
accepted = true;
continue;
}
publishedTags.push_back(a.abi);
auto published = parse_abi_tag(a.abi);
if (!published) { accepted = true; continue; } // unreadable → lenient
auto bad = tag_check(*published, in.current);
if (bad.empty()) { accepted = true; break; }
// Keep the CLOSEST refusal to show: the one that disagrees least is
// the one the user is most likely able to act on.
if (bestRefusal.empty() || bad.size() < bestRefusal.size()) {
bestRefusal = bad;
bestRefusalTag = a.abi;
}
}
if (sawLibrary && !accepted) {
std::string tags;
for (auto const& t : publishedTags) tags += std::format("\n {}", t);
std::string why;
for (auto const& b : bestRefusal)
why += std::format("\n {:<9} needs {}, this build has {}", b.dimension, b.need, b.got);
return std::unexpected(std::format(
"{}: no prebuilt artifact matches this toolchain.\n"
" your toolchain : {}\n"
" published tags :{}\n"
" closest is {}, and it differs on:{}\n"
" fix: ask the publisher for a build matching your toolchain, or pin\n"
" [toolchain] to the one the package was built with.",
in.packageLabel, in.current.str(), tags, bestRefusalTag, why));
}
// ── 3. the interface is the one the binaries were built from ──────
for (auto const& a : m.runtimeConfig.artifacts) {
if (!a.provenance.starts_with(kPackProvenancePrefix)) continue;
if (a.role != "interface" || a.digest.empty()) continue;
auto dir = a.path.is_absolute() ? a.path : in.packageRoot / a.path;
std::vector<std::filesystem::path> files;
if (std::filesystem::is_directory(dir, ec)) {
for (auto const& e : std::filesystem::recursive_directory_iterator(dir, ec))
if (e.is_regular_file()) files.push_back(e.path());
} else if (std::filesystem::is_regular_file(dir, ec)) {
files.push_back(dir);
}
auto now = interface_set_digest(files);
if (now != a.digest) {
return std::unexpected(std::format(
"{}: '{}' does not match what was packaged.\n"
" recorded {}\n"
" found {}\n"
" The published interface and the prebuilt binaries are produced\n"
" together and are not separately replaceable: an edited interface\n"
" compiles and links against binaries that no longer agree with it,\n"
" and the result is wrong at run time with no diagnostic.\n"
" fix: restore the package from its original archive.",
in.packageLabel, a.path.string(), a.digest, now));
}
}
return {};
}
} // namespace mcpp::pack