@dbx-tools/projen 0.9.12 → 0.9.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -25
- package/index.ts +5 -4
- package/package.json +4 -5
- package/src/_release-platform.ts +10 -0
- package/src/_rust-project.ts +40 -13
- package/src/_rust-release-workflow.ts +213 -344
- package/src/openapi.ts +191 -79
- package/src/project-js.ts +8 -13
- package/src/project-py.ts +22 -3
- package/src/project-rs.ts +50 -6
- package/src/release-github.ts +23 -0
- package/src/release-plan.ts +48 -15
- package/src/release.ts +17 -3
- package/src/watch.ts +19 -2
- package/src/workspace-lock.ts +32 -6
- package/tasks/barrels.ts +47 -30
- package/tasks/compile-workspace.ts +121 -0
- package/tasks/openapi.ts +15 -11
- package/tasks/release-assets.ts +476 -0
- package/tasks/release-pr.ts +123 -14
- package/tasks/rust.ts +62 -33
- package/tasks/uniffi-python.js +24 -12
- package/tasks/uniffi-release.mjs +40 -3
- package/tasks/uniffi.ts +2 -0
- package/src/_rust-release.ts +0 -605
- package/tasks/build-rust-release.ts +0 -68
- package/tasks/rust-release.d.ts +0 -41
- package/tasks/rust-release.mjs +0 -1717
package/tasks/release-pr.ts
CHANGED
|
@@ -15,6 +15,7 @@ import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
|
15
15
|
import { asyncUtils, json, log, object } from "@dbx-tools/shared-core";
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
import { publishLocalRelease } from "./local-publish.ts";
|
|
18
|
+
import { hasLocalReleaseTargets, missingLocalReleaseTools } from "./release-assets.ts";
|
|
18
19
|
import { generateReleaseSummary } from "./release-summary.ts";
|
|
19
20
|
import {
|
|
20
21
|
releaseArchitectureOption,
|
|
@@ -38,20 +39,57 @@ import {
|
|
|
38
39
|
githubAccountSupportsWorkflowChanges,
|
|
39
40
|
githubAuthenticatedAccounts,
|
|
40
41
|
githubRepositoryApiPath,
|
|
42
|
+
githubRepositoryCanManage,
|
|
41
43
|
githubRepositoryIdentity,
|
|
44
|
+
githubRepositoryPermissions,
|
|
42
45
|
githubRepositorySpecifier,
|
|
43
46
|
githubTokenArguments,
|
|
44
47
|
githubTokenEnvironmentName,
|
|
45
48
|
} from "../src/release-github.ts";
|
|
49
|
+
import { withWorkspaceMutationLock } from "../src/workspace-lock.ts";
|
|
46
50
|
import {
|
|
47
51
|
readWorkspaceVersion,
|
|
48
52
|
resolveBaseVersion,
|
|
49
53
|
resolveNextVersion,
|
|
50
54
|
} from "../src/workspace-version.ts";
|
|
51
|
-
import { withWorkspaceMutationLock } from "../src/workspace-lock.ts";
|
|
52
55
|
|
|
53
56
|
const logger = log.logger("projen:release");
|
|
54
57
|
|
|
58
|
+
export type ReleaseBuildMode = "auto" | "local" | "remote";
|
|
59
|
+
|
|
60
|
+
/** Resolve an explicit or environment-sensitive native release build mode. */
|
|
61
|
+
export function resolveReleaseBuildMode(options: {
|
|
62
|
+
readonly requested: ReleaseBuildMode;
|
|
63
|
+
readonly canManage: boolean;
|
|
64
|
+
readonly localTools: readonly string[];
|
|
65
|
+
readonly hasLocalTargets: boolean;
|
|
66
|
+
readonly approve: boolean;
|
|
67
|
+
readonly wait: boolean;
|
|
68
|
+
readonly hostPlatform?: NodeJS.Platform;
|
|
69
|
+
}): Exclude<ReleaseBuildMode, "auto"> {
|
|
70
|
+
const local =
|
|
71
|
+
options.canManage &&
|
|
72
|
+
options.hasLocalTargets &&
|
|
73
|
+
(options.hostPlatform ?? process.platform) === "darwin" &&
|
|
74
|
+
options.localTools.length === 0 &&
|
|
75
|
+
options.approve &&
|
|
76
|
+
options.wait;
|
|
77
|
+
if (options.requested === "auto") return local ? "local" : "remote";
|
|
78
|
+
if (options.requested === "remote") return "remote";
|
|
79
|
+
if (!options.canManage) throw new Error("--build local requires maintain or admin permission");
|
|
80
|
+
if (!options.hasLocalTargets) throw new Error("--build local requires Rust release targets");
|
|
81
|
+
if ((options.hostPlatform ?? process.platform) !== "darwin") {
|
|
82
|
+
throw new Error("--build local requires macOS");
|
|
83
|
+
}
|
|
84
|
+
if (options.localTools.length > 0) {
|
|
85
|
+
throw new Error(`--build local requires: ${options.localTools.join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
if (!options.approve || !options.wait) {
|
|
88
|
+
throw new Error("--build local requires automatic merge and waiting");
|
|
89
|
+
}
|
|
90
|
+
return "local";
|
|
91
|
+
}
|
|
92
|
+
|
|
55
93
|
function git(
|
|
56
94
|
root: string,
|
|
57
95
|
args: string[],
|
|
@@ -151,12 +189,27 @@ async function waitForReleaseWorkflow(
|
|
|
151
189
|
});
|
|
152
190
|
}
|
|
153
191
|
|
|
192
|
+
async function waitForGitHubRelease(
|
|
193
|
+
root: string,
|
|
194
|
+
tag: string,
|
|
195
|
+
env: NodeJS.ProcessEnv,
|
|
196
|
+
timeoutMs: number,
|
|
197
|
+
): Promise<void> {
|
|
198
|
+
const deadline = Date.now() + timeoutMs;
|
|
199
|
+
while (Date.now() < deadline) {
|
|
200
|
+
if (taskCommandSucceeds(root, "gh", ["release", "view", tag], { env })) return;
|
|
201
|
+
await asyncUtils.sleep(2_000);
|
|
202
|
+
}
|
|
203
|
+
throw new Error(`GitHub Release ${tag} did not appear within ${timeoutMs}ms`);
|
|
204
|
+
}
|
|
205
|
+
|
|
154
206
|
function githubAccount(root: string): {
|
|
155
207
|
hostname: string;
|
|
156
208
|
owner: string;
|
|
157
209
|
repository: string;
|
|
158
210
|
login: string;
|
|
159
211
|
token: string;
|
|
212
|
+
canManage: boolean;
|
|
160
213
|
} {
|
|
161
214
|
const repository = projectUtils.repositoryUrl(root);
|
|
162
215
|
if (!repository) throw new Error("Release preparation requires a GitHub repository");
|
|
@@ -199,21 +252,20 @@ function githubAccount(root: string): {
|
|
|
199
252
|
});
|
|
200
253
|
const repositoryData =
|
|
201
254
|
repositoryResult.exitCode === 0 ? json.parseRecord(repositoryResult.stdout ?? "") : undefined;
|
|
202
|
-
const permissions =
|
|
203
|
-
|
|
204
|
-
: undefined;
|
|
205
|
-
if (
|
|
206
|
-
permissions?.push !== true &&
|
|
207
|
-
permissions?.maintain !== true &&
|
|
208
|
-
permissions?.admin !== true
|
|
209
|
-
) {
|
|
255
|
+
const permissions = githubRepositoryPermissions(repositoryData);
|
|
256
|
+
if (!permissions.push && !permissions.maintain && !permissions.admin) {
|
|
210
257
|
continue;
|
|
211
258
|
}
|
|
212
259
|
logger.info("selected GitHub CLI account", {
|
|
213
260
|
login: account.login,
|
|
214
261
|
repository: `${identity.owner}/${identity.repository}`,
|
|
215
262
|
});
|
|
216
|
-
return {
|
|
263
|
+
return {
|
|
264
|
+
...identity,
|
|
265
|
+
login: account.login,
|
|
266
|
+
token,
|
|
267
|
+
canManage: githubRepositoryCanManage(permissions),
|
|
268
|
+
};
|
|
217
269
|
}
|
|
218
270
|
const detected =
|
|
219
271
|
accounts
|
|
@@ -271,6 +323,7 @@ program
|
|
|
271
323
|
.option("--no-validate", "skip repository validation tasks, Rust tests, and TypeScript compile")
|
|
272
324
|
.option("--no-local-publish", "skip local npm, PyPI, and Cargo publication")
|
|
273
325
|
.option("--no-local-cargo", "skip local Cargo publication")
|
|
326
|
+
.option("--build <mode>", "native release build: auto, local, or remote", "auto")
|
|
274
327
|
.option("--no-approve", "open the release pull request without enabling automatic merge")
|
|
275
328
|
.option("--no-wait", "return after enabling automatic merge without watching publication")
|
|
276
329
|
.option(
|
|
@@ -295,6 +348,7 @@ program
|
|
|
295
348
|
validate: boolean;
|
|
296
349
|
localPublish: boolean;
|
|
297
350
|
localCargo: boolean;
|
|
351
|
+
build: ReleaseBuildMode;
|
|
298
352
|
approve: boolean;
|
|
299
353
|
wait: boolean;
|
|
300
354
|
waitTimeoutMinutes: string;
|
|
@@ -316,6 +370,26 @@ program
|
|
|
316
370
|
const currentBranch = git(root, ["branch", "--show-current"], { capture: true });
|
|
317
371
|
if (!currentBranch) throw new Error("Release preparation requires a local branch");
|
|
318
372
|
const account = githubAccount(root);
|
|
373
|
+
if (!["auto", "local", "remote"].includes(opts.build)) {
|
|
374
|
+
throw new Error("--build must be auto, local, or remote");
|
|
375
|
+
}
|
|
376
|
+
const inspectLocalTools =
|
|
377
|
+
opts.build === "local" || (opts.build === "auto" && account.canManage);
|
|
378
|
+
const localTools = inspectLocalTools ? missingLocalReleaseTools(root) : [];
|
|
379
|
+
const buildMode = resolveReleaseBuildMode({
|
|
380
|
+
requested: opts.build,
|
|
381
|
+
canManage: account.canManage,
|
|
382
|
+
localTools,
|
|
383
|
+
hasLocalTargets: hasLocalReleaseTargets(root),
|
|
384
|
+
approve: opts.approve,
|
|
385
|
+
wait: opts.wait,
|
|
386
|
+
});
|
|
387
|
+
logger.info("selected native release build", {
|
|
388
|
+
requested: opts.build,
|
|
389
|
+
resolved: buildMode,
|
|
390
|
+
manager: account.canManage,
|
|
391
|
+
...(localTools.length > 0 ? { missing: localTools } : {}),
|
|
392
|
+
});
|
|
319
393
|
git(root, ["fetch", "--tags", "origin", opts.base]);
|
|
320
394
|
const comparisonBase = resolveBaseVersion(root, [opts.prefix], {
|
|
321
395
|
fetch: false,
|
|
@@ -455,7 +529,13 @@ program
|
|
|
455
529
|
git(releaseRoot, ["add", "-A"]);
|
|
456
530
|
const staged = git(releaseRoot, ["diff", "--cached", "--name-only"], { capture: true });
|
|
457
531
|
if (staged) {
|
|
458
|
-
git(releaseRoot, [
|
|
532
|
+
git(releaseRoot, [
|
|
533
|
+
"commit",
|
|
534
|
+
"-m",
|
|
535
|
+
`chore(release): ${next.version}`,
|
|
536
|
+
"-m",
|
|
537
|
+
`Release-Build: ${buildMode}`,
|
|
538
|
+
]);
|
|
459
539
|
} else if (!worktreeExists) {
|
|
460
540
|
throw new Error("Release preparation produced no changes");
|
|
461
541
|
}
|
|
@@ -471,6 +551,8 @@ program
|
|
|
471
551
|
`Source commit: ${git(releaseRoot, ["rev-parse", `${releaseBranch}^`], { capture: true })}`,
|
|
472
552
|
"",
|
|
473
553
|
"Merging this PR updates VERSION on main and starts the public release workflow.",
|
|
554
|
+
"",
|
|
555
|
+
`Native build: ${buildMode}.`,
|
|
474
556
|
...(releaseSummary ? ["", releaseSummary] : []),
|
|
475
557
|
].join("\n");
|
|
476
558
|
const githubEnvironment = {
|
|
@@ -511,8 +593,6 @@ program
|
|
|
511
593
|
env: githubEnvironment,
|
|
512
594
|
});
|
|
513
595
|
}
|
|
514
|
-
git(root, ["worktree", "remove", "--force", releaseRoot]);
|
|
515
|
-
git(root, ["branch", "--delete", "--force", releaseBranch]);
|
|
516
596
|
if (opts.approve && opts.wait) {
|
|
517
597
|
const mergeSha = await waitForPullRequestMerge(
|
|
518
598
|
root,
|
|
@@ -521,10 +601,39 @@ program
|
|
|
521
601
|
waitTimeoutMs,
|
|
522
602
|
);
|
|
523
603
|
logger.info("release pull request merged", { releaseBranch, mergeSha });
|
|
604
|
+
if (buildMode === "local") {
|
|
605
|
+
git(root, ["fetch", "origin", opts.base]);
|
|
606
|
+
git(releaseRoot, ["switch", "--detach", mergeSha]);
|
|
607
|
+
runTaskCommand(releaseRoot, process.execPath, ["install"]);
|
|
608
|
+
await waitForGitHubRelease(root, releaseTag, githubEnvironment, waitTimeoutMs);
|
|
609
|
+
const releaseAssetsScript = join(
|
|
610
|
+
releaseRoot,
|
|
611
|
+
"node_modules/@dbx-tools/projen/tasks/release-assets.ts",
|
|
612
|
+
);
|
|
613
|
+
runTaskCommand(
|
|
614
|
+
releaseRoot,
|
|
615
|
+
process.execPath,
|
|
616
|
+
[
|
|
617
|
+
releaseAssetsScript,
|
|
618
|
+
"--root",
|
|
619
|
+
releaseRoot,
|
|
620
|
+
"--version",
|
|
621
|
+
next.version,
|
|
622
|
+
"--tag",
|
|
623
|
+
releaseTag,
|
|
624
|
+
"--upload",
|
|
625
|
+
],
|
|
626
|
+
{ env: githubEnvironment },
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
git(root, ["worktree", "remove", "--force", releaseRoot]);
|
|
630
|
+
git(root, ["branch", "--delete", "--force", releaseBranch]);
|
|
524
631
|
await waitForReleaseWorkflow(root, opts.base, mergeSha, githubEnvironment, waitTimeoutMs);
|
|
525
632
|
logger.success(`published ${releaseTag} from ${mergeSha}`);
|
|
526
633
|
return;
|
|
527
634
|
}
|
|
635
|
+
git(root, ["worktree", "remove", "--force", releaseRoot]);
|
|
636
|
+
git(root, ["branch", "--delete", "--force", releaseBranch]);
|
|
528
637
|
logger.success(
|
|
529
638
|
`${
|
|
530
639
|
opts.approve ? "enabled automatic merge for" : "opened"
|
|
@@ -534,4 +643,4 @@ program
|
|
|
534
643
|
},
|
|
535
644
|
);
|
|
536
645
|
|
|
537
|
-
await program.parseAsync();
|
|
646
|
+
if (import.meta.main) await program.parseAsync();
|
package/tasks/rust.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { log } from "@dbx-tools/shared-core";
|
|
6
|
+
import { log, object } from "@dbx-tools/shared-core";
|
|
7
7
|
import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
|
|
8
8
|
import {
|
|
9
9
|
discoverRustCrates,
|
|
@@ -40,7 +40,7 @@ function currentStructure(config: RustWorkspaceMapping): RustWorkspaceMapping {
|
|
|
40
40
|
const binding = recorded.get(rust);
|
|
41
41
|
return binding ? [binding] : [{ crate: "", rust }];
|
|
42
42
|
});
|
|
43
|
-
return {
|
|
43
|
+
return { ...config, crates, bindings };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Whether discovered crate membership or UniFFI marker membership changed. */
|
|
@@ -87,18 +87,47 @@ function generate(binding: RustBindingMapping): void {
|
|
|
87
87
|
if (result.status !== 0) throw new Error(`binding generation exited with ${result.status}`);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
function* iterateAffectedRustBindings(
|
|
91
91
|
bindings: readonly RustBindingMapping[],
|
|
92
92
|
changed: ReadonlySet<string>,
|
|
93
|
-
): RustBindingMapping
|
|
93
|
+
): Generator<RustBindingMapping> {
|
|
94
94
|
const affected = new Set(changed);
|
|
95
|
-
const
|
|
96
|
-
for (const binding of ordered) {
|
|
95
|
+
for (const binding of orderRustBindings(bindings)) {
|
|
97
96
|
if (binding.dependencies?.some((dependency) => affected.has(dependency))) {
|
|
98
97
|
affected.add(binding.crate);
|
|
99
98
|
}
|
|
99
|
+
if (affected.has(binding.crate)) yield binding;
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function affectedRustBindings(
|
|
104
|
+
bindings: readonly RustBindingMapping[],
|
|
105
|
+
changed: ReadonlySet<string>,
|
|
106
|
+
): RustBindingMapping[] {
|
|
107
|
+
return [...iterateAffectedRustBindings(bindings, changed)];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Lazily resolve direct binding owners so lock checks stop at the first match.
|
|
112
|
+
*/
|
|
113
|
+
export function changedRustOwners(
|
|
114
|
+
config: RustWorkspaceMapping,
|
|
115
|
+
changed: readonly string[],
|
|
116
|
+
): object.Sequence<RustBindingMapping> {
|
|
117
|
+
return object
|
|
118
|
+
.sequence(changed)
|
|
119
|
+
.map((path) => ownerBinding(path, config.bindings))
|
|
120
|
+
.nonNull()
|
|
121
|
+
.distinct();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Collect direct owners, then lazily yield them and their dependents in order. */
|
|
125
|
+
export function changedRustBindings(
|
|
126
|
+
config: RustWorkspaceMapping,
|
|
127
|
+
changed: readonly string[],
|
|
128
|
+
): object.Sequence<RustBindingMapping> {
|
|
129
|
+
const targets = new Set(changedRustOwners(config, changed).map((binding) => binding.crate));
|
|
130
|
+
return object.sequence(iterateAffectedRustBindings(config.bindings, targets));
|
|
102
131
|
}
|
|
103
132
|
|
|
104
133
|
const config = rustConfig();
|
|
@@ -113,37 +142,37 @@ async function main(): Promise<void> {
|
|
|
113
142
|
return;
|
|
114
143
|
}
|
|
115
144
|
|
|
116
|
-
watchLoop(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
watchLoop(
|
|
146
|
+
"rust",
|
|
147
|
+
[resolve(repoRoot, config.root)],
|
|
148
|
+
(changed) => {
|
|
149
|
+
const latest = rustConfig() ?? config;
|
|
150
|
+
if (rustStructureChanged(latest)) {
|
|
151
|
+
logger.start("Rust project structure changed - re-synthesizing (+install)");
|
|
152
|
+
runSynth({ post: true });
|
|
153
|
+
logger.success("Rust project structure synchronized");
|
|
154
|
+
const refreshed = rustConfig();
|
|
155
|
+
if (!refreshed) return;
|
|
156
|
+
for (const binding of changedRustBindings(refreshed, changed)) {
|
|
157
|
+
logger.start(`generating ${binding.crate} bindings`);
|
|
158
|
+
generate(binding);
|
|
159
|
+
logger.success(`generated ${binding.crate} bindings`);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
128
162
|
}
|
|
129
|
-
for (const binding of
|
|
163
|
+
for (const binding of changedRustBindings(latest, changed)) {
|
|
130
164
|
logger.start(`generating ${binding.crate} bindings`);
|
|
131
165
|
generate(binding);
|
|
132
166
|
logger.success(`generated ${binding.crate} bindings`);
|
|
133
167
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
logger.start(`generating ${binding.crate} bindings`);
|
|
143
|
-
generate(binding);
|
|
144
|
-
logger.success(`generated ${binding.crate} bindings`);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
check: (changed) => {
|
|
171
|
+
const latest = rustConfig() ?? config;
|
|
172
|
+
return changedRustOwners(latest, changed).some(() => true) || rustStructureChanged(latest);
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
);
|
|
147
176
|
}
|
|
148
177
|
|
|
149
178
|
if (import.meta.main) await main();
|
package/tasks/uniffi-python.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
mkdirSync,
|
|
6
6
|
mkdtempSync,
|
|
7
7
|
readFileSync,
|
|
8
|
+
readdirSync,
|
|
8
9
|
rmSync,
|
|
9
10
|
writeFileSync,
|
|
10
11
|
} from "node:fs";
|
|
@@ -130,6 +131,7 @@ export const installPythonBindings = ({
|
|
|
130
131
|
module,
|
|
131
132
|
packageRoot,
|
|
132
133
|
platform,
|
|
134
|
+
generatedSource,
|
|
133
135
|
readonly = false,
|
|
134
136
|
run,
|
|
135
137
|
targetDirectory,
|
|
@@ -137,20 +139,25 @@ export const installPythonBindings = ({
|
|
|
137
139
|
const packageName = module.split(".").at(-1);
|
|
138
140
|
if (!packageName) throw new Error(`Invalid Python module ${module}`);
|
|
139
141
|
const packageDirectory = resolve(packageRoot, "src", ...module.split("."));
|
|
140
|
-
const generatedDirectory =
|
|
142
|
+
const generatedDirectory = generatedSource
|
|
143
|
+
? undefined
|
|
144
|
+
: mkdtempSync(join(tmpdir(), `${packageName}-python-`));
|
|
141
145
|
const generator = resolve(targetDirectory, pythonBindingGeneratorName(crate, platform));
|
|
142
|
-
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
143
146
|
|
|
144
147
|
try {
|
|
145
|
-
|
|
146
|
-
generator
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
if (!generatedSource) {
|
|
149
|
+
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
150
|
+
run(
|
|
151
|
+
generator,
|
|
152
|
+
pythonBindingGeneratorArgs({
|
|
153
|
+
crate,
|
|
154
|
+
library,
|
|
155
|
+
output: generatedDirectory,
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const generated =
|
|
160
|
+
generatedSource ?? join(generatedDirectory, `${crate.replaceAll("-", "_")}.py`);
|
|
154
161
|
if (!existsSync(generated)) throw new Error(`Missing generated binding ${generated}`);
|
|
155
162
|
mkdirSync(packageDirectory, { recursive: true });
|
|
156
163
|
|
|
@@ -162,6 +169,11 @@ export const installPythonBindings = ({
|
|
|
162
169
|
setWritable(init);
|
|
163
170
|
writeFileSync(init, `${generatedHeader(crate)}from .bindings import *\n`);
|
|
164
171
|
|
|
172
|
+
for (const candidate of readdirSync(packageDirectory)) {
|
|
173
|
+
if (/\.(?:dll|dylib|so)$/.test(candidate)) {
|
|
174
|
+
rmSync(join(packageDirectory, candidate), { force: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
165
177
|
const nativeLibrary = join(packageDirectory, basename(library));
|
|
166
178
|
setWritable(nativeLibrary);
|
|
167
179
|
cpSync(library, nativeLibrary);
|
|
@@ -174,6 +186,6 @@ export const installPythonBindings = ({
|
|
|
174
186
|
rmSync(join(packageDirectory, "_generated"), { recursive: true, force: true });
|
|
175
187
|
return { bindings, init, library: nativeLibrary };
|
|
176
188
|
} finally {
|
|
177
|
-
rmSync(generatedDirectory, { recursive: true, force: true });
|
|
189
|
+
if (generatedDirectory) rmSync(generatedDirectory, { recursive: true, force: true });
|
|
178
190
|
}
|
|
179
191
|
};
|
package/tasks/uniffi-release.mjs
CHANGED
|
@@ -17,7 +17,12 @@ import { fileURLToPath } from "node:url";
|
|
|
17
17
|
import { createRequire } from "node:module";
|
|
18
18
|
import { parseArgs } from "node:util";
|
|
19
19
|
import { spawnSync } from "node:child_process";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
installPythonBindings,
|
|
22
|
+
pythonBindingGeneratorArgs,
|
|
23
|
+
pythonBindingGeneratorName,
|
|
24
|
+
stampPythonProject,
|
|
25
|
+
} from "./uniffi-python.js";
|
|
21
26
|
|
|
22
27
|
const require = createRequire(import.meta.url);
|
|
23
28
|
const bundledToml = fileURLToPath(new URL("./smol-toml.cjs", import.meta.url));
|
|
@@ -34,7 +39,9 @@ const parsed = parseArgs({
|
|
|
34
39
|
"native-package": { type: "string" },
|
|
35
40
|
"python-package": { type: "string" },
|
|
36
41
|
"python-module": { type: "string" },
|
|
42
|
+
"python-bindings": { type: "string" },
|
|
37
43
|
"cargo-target": { type: "string" },
|
|
44
|
+
"generator-target": { type: "string" },
|
|
38
45
|
"node-triple": { type: "string" },
|
|
39
46
|
"python-tag": { type: "string" },
|
|
40
47
|
os: { type: "string" },
|
|
@@ -227,7 +234,9 @@ const facadeDependency = (name, dependency, catalog, workspaceVersions) => {
|
|
|
227
234
|
if (dependency.startsWith("workspace:")) {
|
|
228
235
|
const version = workspaceVersions.get(name);
|
|
229
236
|
if (!version) throw new Error(`Missing workspace version for ${name}`);
|
|
230
|
-
return dependency === "workspace:*"
|
|
237
|
+
return dependency === "workspace:*"
|
|
238
|
+
? version
|
|
239
|
+
: `${dependency.slice("workspace:".length)}${version}`;
|
|
231
240
|
}
|
|
232
241
|
if (!dependency.startsWith("catalog:")) return dependency;
|
|
233
242
|
const resolved = catalog[name];
|
|
@@ -412,8 +421,10 @@ const packagePython = ({
|
|
|
412
421
|
pythonTag,
|
|
413
422
|
version,
|
|
414
423
|
cargoTarget,
|
|
424
|
+
generatorTarget,
|
|
415
425
|
os,
|
|
416
426
|
pythonModule,
|
|
427
|
+
pythonBindings,
|
|
417
428
|
}) => {
|
|
418
429
|
const pythonRoot = resolve(output, "python-root");
|
|
419
430
|
cpSync(resolve(root, pythonDirectory), pythonRoot, { recursive: true });
|
|
@@ -440,8 +451,9 @@ const packagePython = ({
|
|
|
440
451
|
module: pythonModule,
|
|
441
452
|
packageRoot: pythonRoot,
|
|
442
453
|
platform: os,
|
|
454
|
+
generatedSource: pythonBindings,
|
|
443
455
|
run,
|
|
444
|
-
targetDirectory: resolve(cargoTargetRoot, cargoTarget, "release"),
|
|
456
|
+
targetDirectory: resolve(cargoTargetRoot, generatorTarget ?? cargoTarget, "release"),
|
|
445
457
|
});
|
|
446
458
|
|
|
447
459
|
const wheelDirectory = resolve(output, "python");
|
|
@@ -461,6 +473,25 @@ const packagePython = ({
|
|
|
461
473
|
]);
|
|
462
474
|
};
|
|
463
475
|
|
|
476
|
+
const generatePython = () => {
|
|
477
|
+
const crate = required("crate");
|
|
478
|
+
const cargoTarget = required("cargo-target");
|
|
479
|
+
const os = required("os");
|
|
480
|
+
const output = resolve(root, required("output"));
|
|
481
|
+
const generator = resolve(
|
|
482
|
+
cargoTargetRoot,
|
|
483
|
+
cargoTarget,
|
|
484
|
+
"release",
|
|
485
|
+
pythonBindingGeneratorName(crate, os),
|
|
486
|
+
);
|
|
487
|
+
const library = libraryPath(crate, cargoTarget, os);
|
|
488
|
+
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
489
|
+
if (!existsSync(library)) throw new Error(`Missing native library ${library}`);
|
|
490
|
+
rmSync(output, { recursive: true, force: true });
|
|
491
|
+
mkdirSync(output, { recursive: true });
|
|
492
|
+
run(generator, pythonBindingGeneratorArgs({ crate, library, output }));
|
|
493
|
+
};
|
|
494
|
+
|
|
464
495
|
const build = () => {
|
|
465
496
|
const crate = required("crate");
|
|
466
497
|
const cargoTarget = required("cargo-target");
|
|
@@ -481,6 +512,8 @@ const build = () => {
|
|
|
481
512
|
...(existsSync(resolve(root, "Cargo.lock")) ? ["--locked"] : []),
|
|
482
513
|
"--package",
|
|
483
514
|
crate,
|
|
515
|
+
"--features",
|
|
516
|
+
"uniffi-bindgen",
|
|
484
517
|
"--target",
|
|
485
518
|
cargoTarget,
|
|
486
519
|
]);
|
|
@@ -513,14 +546,18 @@ const build = () => {
|
|
|
513
546
|
pythonTag,
|
|
514
547
|
version,
|
|
515
548
|
cargoTarget,
|
|
549
|
+
generatorTarget: parsed.values["generator-target"],
|
|
516
550
|
os,
|
|
517
551
|
pythonModule: required("python-module"),
|
|
552
|
+
pythonBindings: parsed.values["python-bindings"],
|
|
518
553
|
});
|
|
519
554
|
}
|
|
520
555
|
};
|
|
521
556
|
|
|
522
557
|
if (parsed.positionals[0] === "build") {
|
|
523
558
|
build();
|
|
559
|
+
} else if (parsed.positionals[0] === "generate-python") {
|
|
560
|
+
generatePython();
|
|
524
561
|
} else if (parsed.positionals[0] === "facade") {
|
|
525
562
|
packageNodeFacade({
|
|
526
563
|
output: resolve(root, required("output")),
|
package/tasks/uniffi.ts
CHANGED
|
@@ -119,6 +119,8 @@ if (!values["skip-build"]) {
|
|
|
119
119
|
...(existsSync(join(root, "Cargo.lock")) ? ["--locked"] : []),
|
|
120
120
|
"--package",
|
|
121
121
|
crate,
|
|
122
|
+
"--features",
|
|
123
|
+
"uniffi-bindgen",
|
|
122
124
|
...(values["cargo-target"] ? ["--target", values["cargo-target"]] : []),
|
|
123
125
|
]);
|
|
124
126
|
}
|