-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathpublisher.cppm
More file actions
260 lines (229 loc) · 9.73 KB
/
publisher.cppm
File metadata and controls
260 lines (229 loc) · 9.73 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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// mcpp.pm.publisher — generate xpkg Lua entry from mcpp.toml + scanner.
//
// See docs/04-schema-xpkg-extension.md for the produced layout.
module;
export module mcpp.pm.publisher;
import std;
import mcpp.manifest;
import mcpp.modgraph.graph;
import mcpp.platform;
export namespace mcpp::pm {
struct ReleaseInfo {
std::string version; // tag/version, e.g. "0.1.0"
struct PerPlatform {
std::string url;
std::string sha256;
};
PerPlatform linux;
PerPlatform macosx;
PerPlatform windows;
};
// Generate the xpkg Lua content for a package.
std::string emit_xpkg(const mcpp::manifest::Manifest& manifest,
const mcpp::modgraph::Graph& graph,
const ReleaseInfo& release);
// Convenience: synthesize a placeholder ReleaseInfo for `mcpp emit xpkg --version V`
// before publish infrastructure exists. Uses {url, sha256} sentinels.
ReleaseInfo placeholder_release(std::string_view version);
// Compute the convention-based GitHub Release tarball URL for a package:
// "<repo>/releases/download/v<version>/<name>-<version>.tar.gz"
// Returns empty string if `repo` is empty or doesn't look like a https URL.
std::string release_tarball_url(std::string_view repo,
std::string_view name,
std::string_view version);
// Compute SHA-256 of `file` by shelling out to `sha256sum` (universally
// available on Linux). Returns empty string on failure.
std::string sha256_of_file(const std::filesystem::path& file);
// Pack the package source tree at `root` into a tarball at `output` using
// `git archive` (so .gitignore'd files are excluded automatically). The
// tarball uses prefix "<name>-<version>/" so unpacking yields a clean
// versioned directory.
//
// Requires the project to be in a git repo.
//
// Returns a non-empty error message on failure (empty on success).
std::string make_release_tarball(const std::filesystem::path& root,
std::string_view name,
std::string_view version,
const std::filesystem::path& output);
// Convenience: build a real ReleaseInfo for v0.0.3-style local publish
// where all three platforms point at the same source tarball. Caller has
// already produced the tarball + sha256 by other means.
ReleaseInfo make_release_info(std::string_view version,
std::string_view url,
std::string_view sha256);
} // namespace mcpp::pm
namespace mcpp::pm {
namespace {
// Quote `s` as a Lua double-quoted string literal: `"..."`.
//
// We deliberately use `"..."` (not the long-bracket `[[...]]` form)
// so the only meta-characters that need escaping are the standard set
// for `"` strings:
// - `"` and `\\` must be backslash-escaped
// - newline / carriage-return / NUL break the string literal
// - other control bytes are escaped numerically (`\xHH`) for safety
// so the emitted .lua is purely printable ASCII even when the input
// contains exotic bytes
//
// Long-bracket sequences like `]=]` are NOT a vector here because we
// never emit `[[`/`]=]` ourselves — the output is always `"..."`.
std::string lua_escape(std::string_view s) {
std::string out;
out.reserve(s.size() + 2);
out.push_back('"');
for (unsigned char c : s) {
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
case 0: out += "\\0"; break;
default:
if (c < 0x20 || c == 0x7f) {
// Other C0 controls + DEL — emit as \xHH to keep the
// .lua text purely printable.
char buf[5];
std::snprintf(buf, sizeof(buf), "\\x%02x", c);
out += buf;
} else {
out.push_back(static_cast<char>(c));
}
break;
}
}
out.push_back('"');
return out;
}
std::string platform_block(std::string_view version, const ReleaseInfo::PerPlatform& pp) {
return std::format(
" ['{0}'] = {{ url = {1}, sha256 = {2} }},\n",
version, lua_escape(pp.url), lua_escape(pp.sha256));
}
} // namespace
std::string emit_xpkg(const mcpp::manifest::Manifest& manifest,
const mcpp::modgraph::Graph& graph,
const ReleaseInfo& release)
{
std::string out;
out += "-- AUTO-GENERATED by `mcpp emit xpkg`. Do not edit by hand.\n";
out += std::format("-- Source: mcpp.toml @ v{}\n", release.version);
out += "package = {\n";
out += " spec = \"1\",\n";
out += std::format(" name = {},\n", lua_escape(manifest.package.name));
if (!manifest.package.description.empty())
out += std::format(" description = {},\n", lua_escape(manifest.package.description));
if (!manifest.package.license.empty())
out += std::format(" licenses = {{{}}},\n", lua_escape(manifest.package.license));
if (!manifest.package.repo.empty())
out += std::format(" repo = {},\n", lua_escape(manifest.package.repo));
out += " type = \"package\",\n\n";
out += " xpm = {\n";
out += " linux = {\n" + platform_block(release.version, release.linux) + " },\n";
out += " macosx = {\n" + platform_block(release.version, release.macosx) + " },\n";
out += " windows = {\n" + platform_block(release.version, release.windows) + " },\n";
out += " },\n\n";
out += " mcpp = {\n";
out += " schema = \"0.1\",\n";
out += std::format(" language = {},\n", lua_escape(manifest.language.standard));
out += std::format(" import_std = {},\n", manifest.language.importStd ? "true" : "false");
// Module list (from scanner)
out += " modules = {\n";
for (auto& u : graph.units) {
if (!u.provides) continue;
// Skip partition-only units: their logical name contains ':'
if (u.provides->logicalName.find(':') != std::string::npos) continue;
out += std::format(" {},\n", lua_escape(u.provides->logicalName));
}
out += " },\n";
// Dependencies (excluding dev-dependencies). Path-based deps are
// local-only and intentionally not exposed in the published xpkg
// descriptor; only version-based deps are emitted.
out += " deps = {\n";
for (auto& [k, v] : manifest.dependencies) {
if (v.isPath() || v.version.empty()) continue;
out += std::format(" [{}] = {},\n", lua_escape(k), lua_escape(v.version));
}
out += " },\n";
out += " manifest = \"mcpp.toml\",\n";
out += " },\n";
out += "}\n";
return out;
}
ReleaseInfo placeholder_release(std::string_view version) {
ReleaseInfo r;
r.version = std::string(version);
auto fill = [&](ReleaseInfo::PerPlatform& pp, std::string_view ext) {
pp.url = std::format("<TBD: release tarball URL>.{}", ext);
pp.sha256 = "<TBD: sha256>";
};
fill(r.linux, "tar.gz");
fill(r.macosx, "tar.gz");
fill(r.windows, "zip");
return r;
}
std::string release_tarball_url(std::string_view repo,
std::string_view name,
std::string_view version)
{
// Strip trailing ".git" if present.
std::string r{repo};
if (r.ends_with(".git")) r.resize(r.size() - 4);
if (r.empty()) return {};
if (!r.starts_with("https://") && !r.starts_with("http://")) return {};
return std::format("{}/releases/download/v{}/{}-{}.tar.gz",
r, version, name, version);
}
std::string sha256_of_file(const std::filesystem::path& file) {
if (!std::filesystem::exists(file)) return {};
auto cmd = std::format("sha256sum {} 2>/dev/null",
mcpp::platform::shell::quote(file.string()));
auto r = mcpp::platform::process::capture(cmd);
if (r.exit_code != 0) return {};
// sha256sum format: "<64-hex> <filename>\n"
auto sp = r.output.find(' ');
if (sp == std::string::npos || sp != 64) return {};
return r.output.substr(0, 64);
}
std::string make_release_tarball(const std::filesystem::path& root,
std::string_view name,
std::string_view version,
const std::filesystem::path& output)
{
std::error_code ec;
std::filesystem::create_directories(output.parent_path(), ec);
auto prefix = std::format("{}-{}/", name, version);
auto cmd = std::format(
"git -C {} archive --format=tar.gz "
"--prefix={} "
"-o {} HEAD 2>&1",
mcpp::platform::shell::quote(root.string()),
mcpp::platform::shell::quote(prefix),
mcpp::platform::shell::quote(output.string()));
auto r = mcpp::platform::process::capture(cmd);
if (r.exit_code != 0) {
return std::format("git archive failed (rc={}): {}", r.exit_code, r.output);
}
if (!std::filesystem::exists(output)) {
return std::format("git archive exited 0 but no tarball at '{}'",
output.string());
}
return {};
}
ReleaseInfo make_release_info(std::string_view version,
std::string_view url,
std::string_view sha256)
{
ReleaseInfo r;
r.version = std::string(version);
auto fill = [&](ReleaseInfo::PerPlatform& pp) {
pp.url = std::string(url);
pp.sha256 = std::string(sha256);
};
fill(r.linux);
fill(r.macosx);
fill(r.windows);
return r;
}
} // namespace mcpp::pm