@dbx-tools/projen 0.6.223 → 0.6.225
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 +55 -8
- package/index.ts +7 -4
- package/package.json +5 -5
- package/src/barrels.ts +10 -4
- package/src/bun-workflow.ts +36 -17
- package/src/clean.ts +3 -3
- package/src/codegen.ts +16 -8
- package/src/packages.ts +14 -13
- package/src/project-js.ts +320 -148
- package/src/project-py.ts +8 -25
- package/src/project-rs.ts +100 -71
- package/src/release-dispatch.ts +10 -0
- package/src/release-github.ts +37 -0
- package/src/release.ts +57 -38
- package/src/tsconfig.ts +17 -17
- package/tasks/local-publish.ts +3 -3
- package/tasks/publish-npm.ts +75 -4
- package/tasks/publish-python.ts +13 -29
- package/tasks/publish-uniffi-local.ts +12 -9
- package/tasks/publish.ts +2 -2
- package/tasks/release-pr.ts +64 -10
- package/tasks/release-summary.ts +270 -0
- package/tasks/uniffi-python.d.ts +56 -0
- package/tasks/uniffi-python.js +160 -0
- package/tasks/uniffi-release.mjs +25 -49
- package/tasks/uniffi.ts +11 -45
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface TomlCodec {
|
|
2
|
+
parse(source: string): unknown;
|
|
3
|
+
stringify(document: unknown): string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PythonPackageMapping {
|
|
7
|
+
readonly directory: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PythonProjectInfo {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly private: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Read the project identity and publication flag from parsed TOML metadata. */
|
|
17
|
+
export function pythonProjectInfo(source: string, toml: TomlCodec): PythonProjectInfo;
|
|
18
|
+
|
|
19
|
+
/** Stamp a temporary publication version and exact sibling dependencies. */
|
|
20
|
+
export function stampPythonProject(
|
|
21
|
+
source: string,
|
|
22
|
+
options: {
|
|
23
|
+
readonly packages: readonly PythonPackageMapping[];
|
|
24
|
+
readonly rewriteDependencies?: boolean;
|
|
25
|
+
readonly toml: TomlCodec;
|
|
26
|
+
readonly version: string;
|
|
27
|
+
},
|
|
28
|
+
): string;
|
|
29
|
+
|
|
30
|
+
/** Return the target-specific UniFFI generator executable name. */
|
|
31
|
+
export function pythonBindingGeneratorName(crate: string, platform: string): string;
|
|
32
|
+
|
|
33
|
+
/** Build the canonical UniFFI Python generator argument list. */
|
|
34
|
+
export function pythonBindingGeneratorArgs(options: {
|
|
35
|
+
readonly crate: string;
|
|
36
|
+
readonly library: string;
|
|
37
|
+
readonly output: string;
|
|
38
|
+
}): string[];
|
|
39
|
+
|
|
40
|
+
export interface InstalledPythonBindings {
|
|
41
|
+
readonly bindings: string;
|
|
42
|
+
readonly init: string;
|
|
43
|
+
readonly library: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Generate and place one Python binding module and its native library. */
|
|
47
|
+
export function installPythonBindings(options: {
|
|
48
|
+
readonly crate: string;
|
|
49
|
+
readonly library: string;
|
|
50
|
+
readonly module: string;
|
|
51
|
+
readonly packageRoot: string;
|
|
52
|
+
readonly platform: string;
|
|
53
|
+
readonly readonly?: boolean;
|
|
54
|
+
readonly run: (command: string, args: string[]) => void;
|
|
55
|
+
readonly targetDirectory: string;
|
|
56
|
+
}): InstalledPythonBindings;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
cpSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { basename, join, resolve } from "node:path";
|
|
13
|
+
|
|
14
|
+
const READONLY = 0o444;
|
|
15
|
+
const WRITABLE = 0o644;
|
|
16
|
+
|
|
17
|
+
const projectTable = (document) => {
|
|
18
|
+
const project = document?.project;
|
|
19
|
+
if (!project || typeof project !== "object" || Array.isArray(project)) {
|
|
20
|
+
throw new Error("Missing Python project metadata");
|
|
21
|
+
}
|
|
22
|
+
return project;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const directSibling = (dependency, sibling) => {
|
|
26
|
+
const prefix = `${sibling.name} @ git+`;
|
|
27
|
+
if (!dependency.startsWith(prefix)) return false;
|
|
28
|
+
const marker = "#subdirectory=";
|
|
29
|
+
const markerIndex = dependency.indexOf(marker, prefix.length);
|
|
30
|
+
if (markerIndex < 0) return false;
|
|
31
|
+
const directory = dependency.slice(markerIndex + marker.length);
|
|
32
|
+
return directory === sibling.directory || directory.endsWith(`/${sibling.directory}`);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const setWritable = (path) => {
|
|
36
|
+
if (existsSync(path)) chmodSync(path, WRITABLE);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const setReadonly = (path) => {
|
|
40
|
+
if (existsSync(path)) chmodSync(path, READONLY);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const generatedHeader = (crate) =>
|
|
44
|
+
[
|
|
45
|
+
"# GENERATED by UniFFI binding generation - DO NOT EDIT.",
|
|
46
|
+
`# Regenerated from the ${crate} Rust exports.`,
|
|
47
|
+
"# Hand edits are overwritten on the next watch; this file is read-only.",
|
|
48
|
+
"",
|
|
49
|
+
].join("\n");
|
|
50
|
+
|
|
51
|
+
export const pythonProjectInfo = (source, toml) => {
|
|
52
|
+
const document = toml.parse(source);
|
|
53
|
+
const project = projectTable(document);
|
|
54
|
+
if (typeof project.name !== "string" || !project.name) {
|
|
55
|
+
throw new Error("Missing Python project name");
|
|
56
|
+
}
|
|
57
|
+
const config = document?.tool?.["dbx-tools"];
|
|
58
|
+
return {
|
|
59
|
+
name: project.name,
|
|
60
|
+
private: Boolean(config && typeof config === "object" && config.private === true),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const stampPythonProject = (
|
|
65
|
+
source,
|
|
66
|
+
{ packages, rewriteDependencies = true, toml, version },
|
|
67
|
+
) => {
|
|
68
|
+
const marker = source.startsWith("# ") ? source.slice(0, source.indexOf("\n")) : undefined;
|
|
69
|
+
const document = toml.parse(source);
|
|
70
|
+
const project = projectTable(document);
|
|
71
|
+
if (typeof project.version !== "string") {
|
|
72
|
+
throw new Error("Missing Python project version");
|
|
73
|
+
}
|
|
74
|
+
project.version = version;
|
|
75
|
+
if (rewriteDependencies) {
|
|
76
|
+
const dependencies = project.dependencies;
|
|
77
|
+
if (dependencies !== undefined && !Array.isArray(dependencies)) {
|
|
78
|
+
throw new Error("Python project dependencies must be an array");
|
|
79
|
+
}
|
|
80
|
+
if (dependencies) {
|
|
81
|
+
project.dependencies = dependencies.map((dependency) => {
|
|
82
|
+
if (typeof dependency !== "string") {
|
|
83
|
+
throw new Error("Python project dependencies must be strings");
|
|
84
|
+
}
|
|
85
|
+
const sibling = packages.find((candidate) => directSibling(dependency, candidate));
|
|
86
|
+
return sibling ? `${sibling.name}==${version}` : dependency;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const body = toml.stringify(document).trimEnd();
|
|
91
|
+
return `${marker ? `${marker}\n\n` : ""}${body}\n`;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const pythonBindingGeneratorName = (crate, platform) =>
|
|
95
|
+
`${crate}-uniffi-bindgen${platform === "win32" ? ".exe" : ""}`;
|
|
96
|
+
|
|
97
|
+
export const pythonBindingGeneratorArgs = ({ crate, library, output }) => [
|
|
98
|
+
"generate",
|
|
99
|
+
"--language",
|
|
100
|
+
"python",
|
|
101
|
+
"--crate",
|
|
102
|
+
crate.replaceAll("-", "_"),
|
|
103
|
+
"--out-dir",
|
|
104
|
+
output,
|
|
105
|
+
library,
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
export const installPythonBindings = ({
|
|
109
|
+
crate,
|
|
110
|
+
library,
|
|
111
|
+
module,
|
|
112
|
+
packageRoot,
|
|
113
|
+
platform,
|
|
114
|
+
readonly = false,
|
|
115
|
+
run,
|
|
116
|
+
targetDirectory,
|
|
117
|
+
}) => {
|
|
118
|
+
const packageName = module.split(".").at(-1);
|
|
119
|
+
if (!packageName) throw new Error(`Invalid Python module ${module}`);
|
|
120
|
+
const packageDirectory = resolve(packageRoot, "src", ...module.split("."));
|
|
121
|
+
const generatedDirectory = mkdtempSync(join(tmpdir(), `${packageName}-python-`));
|
|
122
|
+
const generator = resolve(targetDirectory, pythonBindingGeneratorName(crate, platform));
|
|
123
|
+
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
run(
|
|
127
|
+
generator,
|
|
128
|
+
pythonBindingGeneratorArgs({
|
|
129
|
+
crate,
|
|
130
|
+
library,
|
|
131
|
+
output: generatedDirectory,
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
const generated = join(generatedDirectory, `${crate.replaceAll("-", "_")}.py`);
|
|
135
|
+
if (!existsSync(generated)) throw new Error(`Missing generated binding ${generated}`);
|
|
136
|
+
mkdirSync(packageDirectory, { recursive: true });
|
|
137
|
+
|
|
138
|
+
const bindings = join(packageDirectory, "bindings.py");
|
|
139
|
+
setWritable(bindings);
|
|
140
|
+
writeFileSync(bindings, `${generatedHeader(crate)}${readFileSync(generated, "utf8")}`);
|
|
141
|
+
|
|
142
|
+
const init = join(packageDirectory, "__init__.py");
|
|
143
|
+
setWritable(init);
|
|
144
|
+
writeFileSync(init, "");
|
|
145
|
+
|
|
146
|
+
const nativeLibrary = join(packageDirectory, basename(library));
|
|
147
|
+
setWritable(nativeLibrary);
|
|
148
|
+
cpSync(library, nativeLibrary);
|
|
149
|
+
|
|
150
|
+
if (readonly) {
|
|
151
|
+
setReadonly(bindings);
|
|
152
|
+
setReadonly(init);
|
|
153
|
+
setReadonly(nativeLibrary);
|
|
154
|
+
}
|
|
155
|
+
rmSync(join(packageDirectory, "_generated"), { recursive: true, force: true });
|
|
156
|
+
return { bindings, init, library: nativeLibrary };
|
|
157
|
+
} finally {
|
|
158
|
+
rmSync(generatedDirectory, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
};
|
package/tasks/uniffi-release.mjs
CHANGED
|
@@ -14,8 +14,14 @@ import {
|
|
|
14
14
|
import { basename, dirname, join, resolve } from "node:path";
|
|
15
15
|
import { tmpdir } from "node:os";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { createRequire } from "node:module";
|
|
17
18
|
import { parseArgs } from "node:util";
|
|
18
19
|
import { spawnSync } from "node:child_process";
|
|
20
|
+
import { installPythonBindings, stampPythonProject } from "./uniffi-python.js";
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const bundledToml = fileURLToPath(new URL("./smol-toml.cjs", import.meta.url));
|
|
24
|
+
const toml = require(existsSync(bundledToml) ? bundledToml : "smol-toml");
|
|
19
25
|
|
|
20
26
|
const parsed = parseArgs({
|
|
21
27
|
allowPositionals: true,
|
|
@@ -215,9 +221,6 @@ const testNodeFacade = ({
|
|
|
215
221
|
}
|
|
216
222
|
};
|
|
217
223
|
|
|
218
|
-
const replaceVersion = (source, version) =>
|
|
219
|
-
source.replace(/^version = "[^"]+"$/m, `version = "${version}"`);
|
|
220
|
-
|
|
221
224
|
/** Resolve workspace and generated catalog protocols for a publishable facade. */
|
|
222
225
|
const facadeDependency = (name, dependency, version, catalog) => {
|
|
223
226
|
if (typeof dependency !== "string") return dependency;
|
|
@@ -411,57 +414,30 @@ const packagePython = ({
|
|
|
411
414
|
cpSync(resolve(root, pythonDirectory), pythonRoot, { recursive: true });
|
|
412
415
|
const pyproject = join(pythonRoot, "pyproject.toml");
|
|
413
416
|
writable(pyproject);
|
|
414
|
-
writeFileSync(pyproject, replaceVersion(readFileSync(pyproject, "utf8"), version));
|
|
415
417
|
const workspaceBindings =
|
|
416
418
|
JSON.parse(readFileSync(join(root, "package.json"), "utf8")).dbxToolsConfig?.rust?.bindings ??
|
|
417
419
|
[];
|
|
418
|
-
let metadata = readFileSync(pyproject, "utf8");
|
|
419
|
-
for (const binding of workspaceBindings) {
|
|
420
|
-
if (!binding.pythonPackage) continue;
|
|
421
|
-
const escaped = binding.pythonPackage.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
422
|
-
metadata = metadata.replace(
|
|
423
|
-
new RegExp(`"${escaped} @ git\\+[^"\\n]+"`, "g"),
|
|
424
|
-
JSON.stringify(`${binding.pythonPackage}==${version}`),
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
writeFileSync(pyproject, metadata);
|
|
428
|
-
|
|
429
|
-
const packageName = pythonModule.split(".").at(-1);
|
|
430
|
-
if (!packageName) throw new Error(`Invalid Python module ${pythonModule}`);
|
|
431
|
-
const packageDirectory = resolve(pythonRoot, "src", ...pythonModule.split("."));
|
|
432
|
-
const generatedDirectory = mkdtempSync(join(tmpdir(), `${packageName}-python-`));
|
|
433
|
-
const generator = resolve(
|
|
434
|
-
cargoTargetRoot,
|
|
435
|
-
cargoTarget,
|
|
436
|
-
"release",
|
|
437
|
-
`${crate}-uniffi-bindgen${os === "win32" ? ".exe" : ""}`,
|
|
438
|
-
);
|
|
439
|
-
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
440
|
-
run(generator, [
|
|
441
|
-
"generate",
|
|
442
|
-
"--language",
|
|
443
|
-
"python",
|
|
444
|
-
"--crate",
|
|
445
|
-
crate.replaceAll("-", "_"),
|
|
446
|
-
"--out-dir",
|
|
447
|
-
generatedDirectory,
|
|
448
|
-
library,
|
|
449
|
-
]);
|
|
450
|
-
const bindings = join(packageDirectory, "bindings.py");
|
|
451
|
-
writable(bindings);
|
|
452
|
-
const body = readFileSync(join(generatedDirectory, `${crate.replaceAll("-", "_")}.py`), "utf8");
|
|
453
420
|
writeFileSync(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
421
|
+
pyproject,
|
|
422
|
+
stampPythonProject(readFileSync(pyproject, "utf8"), {
|
|
423
|
+
packages: workspaceBindings.flatMap((binding) =>
|
|
424
|
+
binding.pythonPackage && binding.python
|
|
425
|
+
? [{ name: binding.pythonPackage, directory: binding.python }]
|
|
426
|
+
: [],
|
|
427
|
+
),
|
|
428
|
+
toml,
|
|
429
|
+
version,
|
|
430
|
+
}),
|
|
462
431
|
);
|
|
463
|
-
|
|
464
|
-
|
|
432
|
+
installPythonBindings({
|
|
433
|
+
crate,
|
|
434
|
+
library,
|
|
435
|
+
module: pythonModule,
|
|
436
|
+
packageRoot: pythonRoot,
|
|
437
|
+
platform: os,
|
|
438
|
+
run,
|
|
439
|
+
targetDirectory: resolve(cargoTargetRoot, cargoTarget, "release"),
|
|
440
|
+
});
|
|
465
441
|
|
|
466
442
|
const wheelDirectory = resolve(output, "python");
|
|
467
443
|
mkdirSync(wheelDirectory, { recursive: true });
|
package/tasks/uniffi.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { tmpdir } from "node:os";
|
|
|
15
15
|
import { basename, dirname, join, resolve } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { parseArgs } from "node:util";
|
|
18
|
+
import { installPythonBindings } from "./uniffi-python.js";
|
|
18
19
|
import { makeReadonly, makeWritable, stampGenerated } from "../src/generated.ts";
|
|
19
20
|
import type { RustWorkspaceMapping } from "../src/project-rs.ts";
|
|
20
21
|
import {
|
|
@@ -110,22 +111,6 @@ const replaceGenerated = (source: string, destination: string): void => {
|
|
|
110
111
|
}
|
|
111
112
|
};
|
|
112
113
|
|
|
113
|
-
const stampGeneratedPython = (file: string): void => {
|
|
114
|
-
makeWritable(file);
|
|
115
|
-
const body = readFileSync(file, "utf8");
|
|
116
|
-
writeFileSync(
|
|
117
|
-
file,
|
|
118
|
-
[
|
|
119
|
-
"# GENERATED by UniFFI binding generation - DO NOT EDIT.",
|
|
120
|
-
`# Regenerated from the ${crate} Rust exports.`,
|
|
121
|
-
"# Hand edits are overwritten on the next watch; this file is read-only.",
|
|
122
|
-
"",
|
|
123
|
-
body,
|
|
124
|
-
].join("\n"),
|
|
125
|
-
);
|
|
126
|
-
makeReadonly(file);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
114
|
if (!values["skip-build"]) {
|
|
130
115
|
run("cargo", [
|
|
131
116
|
"build",
|
|
@@ -265,33 +250,14 @@ if (values.python) {
|
|
|
265
250
|
if (!values["python-module"]) {
|
|
266
251
|
throw new Error("Expected --python-module with --python");
|
|
267
252
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
"--out-dir",
|
|
279
|
-
pythonOutput,
|
|
280
|
-
library,
|
|
281
|
-
],
|
|
282
|
-
);
|
|
283
|
-
const generated = join(pythonOutput, `${libraryName}.py`);
|
|
284
|
-
const pythonBindings = join(pythonPackage, "bindings.py");
|
|
285
|
-
replaceGenerated(generated, pythonBindings);
|
|
286
|
-
stampGeneratedPython(pythonBindings);
|
|
287
|
-
const pythonInit = join(pythonPackage, "__init__.py");
|
|
288
|
-
makeWritable(pythonInit);
|
|
289
|
-
writeFileSync(pythonInit, "");
|
|
290
|
-
makeReadonly(pythonInit);
|
|
291
|
-
const pythonLibrary = join(pythonPackage, basename(library));
|
|
292
|
-
makeWritable(pythonLibrary);
|
|
293
|
-
cpSync(library, pythonLibrary);
|
|
294
|
-
makeReadonly(pythonLibrary);
|
|
295
|
-
rmSync(join(pythonPackage, "_generated"), { recursive: true, force: true });
|
|
296
|
-
rmSync(pythonOutput, { recursive: true, force: true });
|
|
253
|
+
installPythonBindings({
|
|
254
|
+
crate,
|
|
255
|
+
library,
|
|
256
|
+
module: values["python-module"],
|
|
257
|
+
packageRoot: resolve(root, values.python),
|
|
258
|
+
platform: process.platform,
|
|
259
|
+
readonly: true,
|
|
260
|
+
run,
|
|
261
|
+
targetDirectory,
|
|
262
|
+
});
|
|
297
263
|
}
|