@dbx-tools/projen 0.6.185 → 0.6.188
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 +19 -11
- package/index.ts +2 -2
- package/package.json +4 -4
- package/src/project-js.ts +17 -15
- package/src/project-rs.ts +169 -58
- package/src/release.ts +4 -4
- package/src/tags.ts +1 -0
- package/src/uniffi.ts +0 -37
- package/tasks/bump.ts +2 -2
- package/tasks/publish-uniffi-local.ts +2 -0
- package/tasks/rust.ts +7 -4
- package/tasks/uniffi-release.mjs +25 -5
- package/tasks/uniffi.ts +28 -19
package/README.md
CHANGED
|
@@ -94,19 +94,20 @@ configurable `root` (default `packages/rs`), generates its Cargo manifest, and
|
|
|
94
94
|
derives the crate name and repository from the parent project. A crate containing
|
|
95
95
|
`uniffi::setup_scaffolding!()`
|
|
96
96
|
automatically wires matching public Node and Python binding packages using the
|
|
97
|
-
|
|
97
|
+
`<name>-rs` folder suffix. Node packages are named `@<scope>/<name>-rs`;
|
|
98
|
+
Python distributions are named `<scope>-<name>-rs` and import generated values
|
|
99
|
+
from `<scope>.<name>_rs.bindings`. Binding packages are always dedicated and
|
|
100
|
+
never merge generated code into handwritten Node or Python packages.
|
|
101
|
+
Repository-specific dependencies and features remain
|
|
98
102
|
declarative options in `.projenrc.ts`; generated bindings are built separately
|
|
99
103
|
from projen synthesis. Target-independent Node binding TypeScript is committed
|
|
100
104
|
and remains generated/read-only, while native libraries stay ignored. Node
|
|
101
105
|
facades compile to `lib/` and publish JavaScript entry points that plain Node
|
|
102
106
|
can load from `node_modules`. A complete `bindings.ts` / `_bindings.ts` /
|
|
103
107
|
`_bindings-ffi.ts` triplet is exported directly from the generated package
|
|
104
|
-
barrel. Python keeps `bindings.py` as the generated implementation and
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
package-root export; Python generation refuses to overwrite a non-generated
|
|
108
|
-
package root. Do not create a `nodeExports` binding subpath or a handwritten
|
|
109
|
-
type facade.
|
|
108
|
+
barrel. Python keeps `bindings.py` as the generated implementation and leaves
|
|
109
|
+
`__init__.py` empty. Node generation fails when direct binding names conflict.
|
|
110
|
+
Do not create a `nodeExports` binding subpath or a handwritten type facade.
|
|
110
111
|
|
|
111
112
|
Rust dependencies between binding-enabled workspace crates become Node
|
|
112
113
|
`workspace:*` and Python `internalDependencies` automatically. Python generation
|
|
@@ -153,10 +154,17 @@ skips matching files and fails when an existing filename has different content.
|
|
|
153
154
|
|
|
154
155
|
The Rust matrix has one row per target. Each row installs native dependencies,
|
|
155
156
|
restores Cargo and sccache once, builds the Cargo workspace once, then packages
|
|
156
|
-
every discovered output from that shared build. Set a source-only crate's
|
|
157
|
-
`releaseExcludeOs` package option to omit it from
|
|
158
|
-
Cargo `--exclude
|
|
159
|
-
|
|
157
|
+
every discovered output from that shared build. Set a source-only crate's or
|
|
158
|
+
release-enabled binary's `releaseExcludeOs` package option to omit it from
|
|
159
|
+
incompatible rows through Cargo `--exclude`. Release binary packaging and
|
|
160
|
+
artifact upload are skipped in those rows. UniFFI crates cannot use this option
|
|
161
|
+
because every configured target must produce their artifacts.
|
|
162
|
+
Set a release binary's `cli` package option and the workspace
|
|
163
|
+
`cliRegistryPath` to generate a typed `dbx` command registry from the same
|
|
164
|
+
selected targets. The generated entries carry only the archives that the
|
|
165
|
+
release matrix publishes, so an excluded operating system fails locally before
|
|
166
|
+
any download is attempted. The runtime command uses `@dbx-tools/core`
|
|
167
|
+
`bin.ensure`; the core installer itself contains no product registry.
|
|
160
168
|
Rust rows prepare native npm
|
|
161
169
|
archives and Python wheels; they do not install Bun or UBRN and do not build
|
|
162
170
|
Node facades. A binary-only row therefore installs no language package tool.
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
|
|
6
|
-
export const PACKAGE_VERSION = "0.6.
|
|
6
|
+
export const PACKAGE_VERSION = "0.6.187";
|
|
7
7
|
export * as barrels from "./src/barrels.ts";
|
|
8
8
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
9
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
|
@@ -51,7 +51,7 @@ export type { DBXToolsJavaScriptProject, DBXToolsJavaScriptProjectOptions, DBXTo
|
|
|
51
51
|
export { DBXToolsPythonProject, DBXToolsPythonWorkspace } from "./src/project-py.ts";
|
|
52
52
|
export type { PythonRepositoryOptions, PythonPackageOptions, PythonTrustedPublisherOptions, DBXToolsPythonProjectOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
|
|
53
53
|
export { RustReleaseOs, RustReleaseCpu, UNIFFI_RELEASE_TARGETS, DBXToolsRustProject, DBXToolsRustWorkspace } from "./src/project-rs.ts";
|
|
54
|
-
export type { CargoDependencyOptions, CargoDependency, RustPackageOptions, DBXToolsRustWorkspaceOptions, RustReleasePlatform, UniFFIReleaseTarget, RustBindingMapping, RustWorkspaceMapping } from "./src/project-rs.ts";
|
|
54
|
+
export type { CargoDependencyOptions, CargoDependency, RustCliOptions, RustPackageOptions, DBXToolsRustWorkspaceOptions, RustReleasePlatform, UniFFIReleaseTarget, RustBindingMapping, RustWorkspaceMapping, RustReleaseBinaryAssetMapping, RustReleaseBinaryMapping } from "./src/project-rs.ts";
|
|
55
55
|
export { COMPILED_DIR, COMPILED_COMPILER_OPTIONS } from "./src/publish.ts";
|
|
56
56
|
export { DBXToolsRelease } from "./src/release.ts";
|
|
57
57
|
export type { ReleaseStage, ReleaseDocsOptions, DBXToolsReleaseOptions } from "./src/release.ts";
|
package/package.json
CHANGED
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@clack/prompts": "^1.7.0",
|
|
28
|
-
"@dbx-tools/core": "0.6.
|
|
29
|
-
"@dbx-tools/path": "0.6.
|
|
30
|
-
"@dbx-tools/shared-core": "0.6.
|
|
28
|
+
"@dbx-tools/core": "0.6.188",
|
|
29
|
+
"@dbx-tools/path": "0.6.188",
|
|
30
|
+
"@dbx-tools/shared-core": "0.6.188",
|
|
31
31
|
"commander": "^15.0.0",
|
|
32
32
|
"concurrently": "^10.0.3",
|
|
33
33
|
"constructs": "^10.6.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"main": "index.ts",
|
|
49
49
|
"license": "Apache-2.0",
|
|
50
|
-
"version": "0.6.
|
|
50
|
+
"version": "0.6.188",
|
|
51
51
|
"types": "index.ts",
|
|
52
52
|
"type": "module",
|
|
53
53
|
"exports": {
|
package/src/project-js.ts
CHANGED
|
@@ -446,13 +446,13 @@ function defaultProjectOptions(
|
|
|
446
446
|
devDeps: ["@types/node@^24.6.0"],
|
|
447
447
|
...(isRoot
|
|
448
448
|
? {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
449
|
+
prettier: true,
|
|
450
|
+
prettierOptions: {
|
|
451
|
+
settings: PRETTIER_SETTINGS,
|
|
452
|
+
ignoreFile: true,
|
|
453
|
+
ignoreFileOptions: { ignorePatterns: [...ignore.ignorePatterns({ test: false })] },
|
|
454
|
+
},
|
|
455
|
+
}
|
|
456
456
|
: {}),
|
|
457
457
|
...options,
|
|
458
458
|
...copiedGitIgnoreOptions(options),
|
|
@@ -508,10 +508,10 @@ const DEV_DEPS_ROOT: string[] = ["typescript@^5.9.3", `@types/bun@${BUN_VERSION}
|
|
|
508
508
|
/** Options for {@link DBXToolsNodeProject} (the monorepo root). */
|
|
509
509
|
export interface DBXToolsJavaScriptProjectOptions
|
|
510
510
|
extends
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
511
|
+
CommonProjectOptions,
|
|
512
|
+
Partial<javascript.NodeProjectOptions>,
|
|
513
|
+
DBXToolsConfigOptions,
|
|
514
|
+
DBXToolsPNPMWorkspaceOptions {
|
|
515
515
|
/**
|
|
516
516
|
* The npm scope for generated package names (`@<scope>/<seg-...>`). Defaults to
|
|
517
517
|
* the (resolved) project name; a leading `@` is optional.
|
|
@@ -584,7 +584,8 @@ export interface DBXToolsTypeScriptProjectOptions
|
|
|
584
584
|
*/
|
|
585
585
|
export class DBXToolsNodeProject
|
|
586
586
|
extends javascript.NodeProject
|
|
587
|
-
implements DBXToolsJavaScriptProject
|
|
587
|
+
implements DBXToolsJavaScriptProject
|
|
588
|
+
{
|
|
588
589
|
readonly language = "javascript" as const;
|
|
589
590
|
readonly scope: string;
|
|
590
591
|
readonly dbxToolsConfig: DBXToolsConfig;
|
|
@@ -684,8 +685,8 @@ export const ROOT_INSTALL_ONLY_MIXIN = mixin.create(
|
|
|
684
685
|
installDependencies(trigger: unknown): void;
|
|
685
686
|
logInstallTrigger(trigger: unknown): void;
|
|
686
687
|
};
|
|
687
|
-
nodePackage.installDependencies = () => {
|
|
688
|
-
nodePackage.logInstallTrigger = () => {
|
|
688
|
+
nodePackage.installDependencies = () => {};
|
|
689
|
+
nodePackage.logInstallTrigger = () => {};
|
|
689
690
|
},
|
|
690
691
|
);
|
|
691
692
|
|
|
@@ -698,7 +699,8 @@ export const ROOT_INSTALL_ONLY_MIXIN = mixin.create(
|
|
|
698
699
|
*/
|
|
699
700
|
export class DBXToolsTypeScriptProject
|
|
700
701
|
extends typescript.TypeScriptProject
|
|
701
|
-
implements DBXToolsJavaScriptProject
|
|
702
|
+
implements DBXToolsJavaScriptProject
|
|
703
|
+
{
|
|
702
704
|
readonly language = "javascript" as const;
|
|
703
705
|
readonly scope: string;
|
|
704
706
|
readonly dbxToolsConfig: DBXToolsConfig;
|
package/src/project-rs.ts
CHANGED
|
@@ -39,6 +39,13 @@ export interface CargoDependencyOptions {
|
|
|
39
39
|
|
|
40
40
|
export type CargoDependency = string | CargoDependencyOptions;
|
|
41
41
|
|
|
42
|
+
export interface RustCliOptions {
|
|
43
|
+
/** `dbx` subcommand. Defaults to the Rust package directory. */
|
|
44
|
+
readonly command?: string;
|
|
45
|
+
/** Root CLI help text. Defaults to the Rust package description. */
|
|
46
|
+
readonly description?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
export interface RustPackageOptions {
|
|
43
50
|
readonly directory: string;
|
|
44
51
|
readonly description?: string;
|
|
@@ -46,7 +53,7 @@ export interface RustPackageOptions {
|
|
|
46
53
|
readonly private?: boolean;
|
|
47
54
|
/** Build this crate's binary for each target and attach it to the GitHub release. */
|
|
48
55
|
readonly release?: boolean;
|
|
49
|
-
/** Omit this
|
|
56
|
+
/** Omit this crate and any release binary artifact from these operating systems. */
|
|
50
57
|
readonly releaseExcludeOs?: readonly RustReleaseOs[];
|
|
51
58
|
readonly dependencies?: Readonly<Record<string, CargoDependency>>;
|
|
52
59
|
readonly devDependencies?: Readonly<Record<string, CargoDependency>>;
|
|
@@ -54,9 +61,9 @@ export interface RustPackageOptions {
|
|
|
54
61
|
readonly defaultFeatures?: readonly string[];
|
|
55
62
|
/** Cargo and release executable name. Defaults to the generated package name. */
|
|
56
63
|
readonly binaryName?: string;
|
|
64
|
+
/** Publish this release binary through the generated `dbx` command registry. */
|
|
65
|
+
readonly cli?: boolean | RustCliOptions;
|
|
57
66
|
readonly bindings?: readonly ("node" | "python")[];
|
|
58
|
-
readonly nodeDependencies?: readonly string[];
|
|
59
|
-
readonly nodeDevDependencies?: readonly string[];
|
|
60
67
|
readonly uniffiConfig?: Readonly<Record<string, unknown>>;
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -82,6 +89,8 @@ export interface DBXToolsRustWorkspaceOptions {
|
|
|
82
89
|
readonly releaseTargets?: readonly UniFFIReleaseTarget[];
|
|
83
90
|
/** Maintained OS/CPU combinations to release. Defaults to every supported target. */
|
|
84
91
|
readonly releasePlatforms?: readonly RustReleasePlatform[];
|
|
92
|
+
/** Optional generated TypeScript registry for `cli`-enabled release binaries. */
|
|
93
|
+
readonly cliRegistryPath?: string;
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
export enum RustReleaseOs {
|
|
@@ -230,6 +239,21 @@ function releasePlatformsFromEnvironment(
|
|
|
230
239
|
});
|
|
231
240
|
}
|
|
232
241
|
|
|
242
|
+
function releaseBinaryAssetName(binaryName: string, nodeTarget: string, os: RustReleaseOs): string {
|
|
243
|
+
const extension = os === RustReleaseOs.WINDOWS ? "zip" : "tar.gz";
|
|
244
|
+
return `${binaryName}-${nodeTarget}.${extension}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function rustCliRegistrySource(binaries: readonly RustReleaseBinaryMapping[]): string {
|
|
248
|
+
return [
|
|
249
|
+
"// GENERATED by projen - DO NOT EDIT.",
|
|
250
|
+
"// Rust release package options are the source of truth.",
|
|
251
|
+
"",
|
|
252
|
+
`export const RUST_RELEASE_BINARY_COMMANDS = ${JSON.stringify(binaries, null, 2)} as const;`,
|
|
253
|
+
"",
|
|
254
|
+
].join("\n");
|
|
255
|
+
}
|
|
256
|
+
|
|
233
257
|
function uniffiReleaseTaskSource(): string {
|
|
234
258
|
const sourceDirectory = dirname(fileURLToPath(import.meta.url));
|
|
235
259
|
const candidates = [
|
|
@@ -259,6 +283,23 @@ export interface RustWorkspaceMapping {
|
|
|
259
283
|
readonly root: string;
|
|
260
284
|
readonly crates: readonly string[];
|
|
261
285
|
readonly bindings: readonly RustBindingMapping[];
|
|
286
|
+
readonly binaries: readonly RustReleaseBinaryMapping[];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** One platform archive published for a Rust CLI binary. */
|
|
290
|
+
export interface RustReleaseBinaryAssetMapping {
|
|
291
|
+
readonly os: RustReleaseOs;
|
|
292
|
+
readonly cpu: RustReleaseCpu;
|
|
293
|
+
readonly name: string;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Runtime metadata for one lazily installed Rust CLI binary. */
|
|
297
|
+
export interface RustReleaseBinaryMapping {
|
|
298
|
+
readonly command: string;
|
|
299
|
+
readonly description: string;
|
|
300
|
+
readonly binaryName: string;
|
|
301
|
+
readonly repository: string;
|
|
302
|
+
readonly assets: readonly RustReleaseBinaryAssetMapping[];
|
|
262
303
|
}
|
|
263
304
|
|
|
264
305
|
export function orderRustBindings(bindings: readonly RustBindingMapping[]): RustBindingMapping[] {
|
|
@@ -474,12 +515,13 @@ function rustPackageDependencies(
|
|
|
474
515
|
);
|
|
475
516
|
}
|
|
476
517
|
|
|
477
|
-
/** Generated Rust workspace plus convention-derived UniFFI
|
|
518
|
+
/** Generated Rust workspace plus convention-derived UniFFI binding packages. */
|
|
478
519
|
export class DBXToolsRustWorkspace {
|
|
479
520
|
readonly packages: readonly DBXToolsRustProject[];
|
|
480
521
|
readonly nodePackages: readonly DBXToolsTypeScriptProject[];
|
|
481
522
|
readonly pythonPackages: readonly PythonPackageOptions[];
|
|
482
523
|
readonly bindingMappings: readonly RustBindingMapping[];
|
|
524
|
+
readonly releaseBinaries: readonly RustReleaseBinaryMapping[];
|
|
483
525
|
readonly workspaceMapping: RustWorkspaceMapping;
|
|
484
526
|
|
|
485
527
|
constructor(project: javascript.NodeProject, options: DBXToolsRustWorkspaceOptions) {
|
|
@@ -492,6 +534,7 @@ export class DBXToolsRustWorkspace {
|
|
|
492
534
|
projectRepositoryUrl(project) ??
|
|
493
535
|
coreProject.repositoryUrl(project.outdir) ??
|
|
494
536
|
"";
|
|
537
|
+
const nativeTargets = releaseTargets(options);
|
|
495
538
|
const pythonModulePrefix = options.pythonModulePrefix ?? scope.replaceAll("-", "_");
|
|
496
539
|
const packageOptions = options.packages ?? {};
|
|
497
540
|
this.packages = discoverRustCrates(resolve(project.outdir, root)).map(
|
|
@@ -501,15 +544,65 @@ export class DBXToolsRustWorkspace {
|
|
|
501
544
|
...packageOptions[directory],
|
|
502
545
|
}),
|
|
503
546
|
);
|
|
547
|
+
const commands = new Set<string>();
|
|
548
|
+
this.releaseBinaries = this.packages
|
|
549
|
+
.filter((pkg) => Boolean(pkg.packageOptions.cli))
|
|
550
|
+
.map((pkg) => {
|
|
551
|
+
if (!pkg.packageOptions.release) {
|
|
552
|
+
throw new Error(`${pkg.crateName} must set release when cli is configured`);
|
|
553
|
+
}
|
|
554
|
+
const configured = pkg.packageOptions.cli;
|
|
555
|
+
const command =
|
|
556
|
+
typeof configured === "object" && configured.command
|
|
557
|
+
? configured.command
|
|
558
|
+
: pkg.packageOptions.directory;
|
|
559
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(command)) {
|
|
560
|
+
throw new Error(`Invalid Rust CLI command: ${command}`);
|
|
561
|
+
}
|
|
562
|
+
if (commands.has(command)) {
|
|
563
|
+
throw new Error(`Duplicate Rust CLI command: ${command}`);
|
|
564
|
+
}
|
|
565
|
+
commands.add(command);
|
|
566
|
+
const description =
|
|
567
|
+
(typeof configured === "object" ? configured.description : undefined) ??
|
|
568
|
+
pkg.packageOptions.description ??
|
|
569
|
+
pkg.crateName;
|
|
570
|
+
const excludedOs = new Set(pkg.packageOptions.releaseExcludeOs ?? []);
|
|
571
|
+
const binaryName = pkg.packageOptions.binaryName ?? pkg.crateName;
|
|
572
|
+
return {
|
|
573
|
+
command,
|
|
574
|
+
description,
|
|
575
|
+
binaryName,
|
|
576
|
+
repository,
|
|
577
|
+
assets: nativeTargets
|
|
578
|
+
.filter((target) => !excludedOs.has(target.os))
|
|
579
|
+
.map((target) => ({
|
|
580
|
+
os: target.os,
|
|
581
|
+
cpu: target.cpu,
|
|
582
|
+
name: releaseBinaryAssetName(binaryName, target.node, target.os),
|
|
583
|
+
})),
|
|
584
|
+
};
|
|
585
|
+
});
|
|
586
|
+
if (this.releaseBinaries.length && !repository) {
|
|
587
|
+
throw new Error("A repository URL is required when Rust CLI commands are configured");
|
|
588
|
+
}
|
|
589
|
+
if (this.releaseBinaries.length && !options.cliRegistryPath) {
|
|
590
|
+
throw new Error("cliRegistryPath is required when Rust CLI commands are configured");
|
|
591
|
+
}
|
|
592
|
+
if (options.cliRegistryPath) {
|
|
593
|
+
new TextFile(project, options.cliRegistryPath, {
|
|
594
|
+
lines: rustCliRegistrySource(this.releaseBinaries).trimEnd().split("\n"),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
504
597
|
|
|
505
598
|
const bindings = this.packages.filter((pkg) => pkg.uniffi);
|
|
506
599
|
const packageDependencies = (pkg: DBXToolsRustProject) =>
|
|
507
600
|
rustPackageDependencies(this.packages, pkg, project.outdir, options.workspaceDependencies);
|
|
508
601
|
for (const pkg of this.packages) {
|
|
509
602
|
const excludedOs = new Set(pkg.packageOptions.releaseExcludeOs ?? []);
|
|
510
|
-
if (excludedOs.size &&
|
|
603
|
+
if (excludedOs.size && pkg.uniffi) {
|
|
511
604
|
throw new Error(
|
|
512
|
-
`${pkg.crateName} cannot set releaseExcludeOs because
|
|
605
|
+
`${pkg.crateName} cannot set releaseExcludeOs because UniFFI requires every configured target`,
|
|
513
606
|
);
|
|
514
607
|
}
|
|
515
608
|
for (const dependency of packageDependencies(pkg)) {
|
|
@@ -531,7 +624,7 @@ export class DBXToolsRustWorkspace {
|
|
|
531
624
|
this.bindingMappings = orderRustBindings(
|
|
532
625
|
bindings.map((pkg) => {
|
|
533
626
|
const targets = pkg.packageOptions.bindings ?? ["node", "python"];
|
|
534
|
-
const
|
|
627
|
+
const packageDirectory = `${string.toSlug(pkg.packageOptions.directory)}-rs`;
|
|
535
628
|
const dependencies = [
|
|
536
629
|
...new Set([...bindingDependencies(pkg, "node"), ...bindingDependencies(pkg, "python")]),
|
|
537
630
|
].map((dependency) => dependency.crateName);
|
|
@@ -541,15 +634,15 @@ export class DBXToolsRustWorkspace {
|
|
|
541
634
|
rust: `${root}/${pkg.packageOptions.directory}`,
|
|
542
635
|
...(targets.includes("node")
|
|
543
636
|
? {
|
|
544
|
-
node: `${nodeRoot}/${
|
|
545
|
-
nodePackage: `@${scope}/${
|
|
637
|
+
node: `${nodeRoot}/${packageDirectory}`,
|
|
638
|
+
nodePackage: `@${scope}/${packageDirectory}`,
|
|
546
639
|
}
|
|
547
640
|
: {}),
|
|
548
641
|
...(targets.includes("python")
|
|
549
642
|
? {
|
|
550
|
-
python: `${options.pythonRoot ?? "packages/py"}/${
|
|
551
|
-
pythonPackage:
|
|
552
|
-
pythonModule: pythonModuleName(pythonModulePrefix,
|
|
643
|
+
python: `${options.pythonRoot ?? "packages/py"}/${packageDirectory}`,
|
|
644
|
+
pythonPackage: `${scope}-${packageDirectory}`,
|
|
645
|
+
pythonModule: pythonModuleName(pythonModulePrefix, packageDirectory),
|
|
553
646
|
}
|
|
554
647
|
: {}),
|
|
555
648
|
};
|
|
@@ -567,7 +660,10 @@ export class DBXToolsRustWorkspace {
|
|
|
567
660
|
"bindings.python.external_packages": Object.fromEntries(
|
|
568
661
|
dependencies.map((dependency) => [
|
|
569
662
|
dependency.crateName.replaceAll("-", "_"),
|
|
570
|
-
`${pythonModuleName(
|
|
663
|
+
`${pythonModuleName(
|
|
664
|
+
pythonModulePrefix,
|
|
665
|
+
`${string.toSlug(dependency.packageOptions.directory)}-rs`,
|
|
666
|
+
)}.bindings`,
|
|
571
667
|
]),
|
|
572
668
|
),
|
|
573
669
|
})
|
|
@@ -583,6 +679,7 @@ export class DBXToolsRustWorkspace {
|
|
|
583
679
|
root,
|
|
584
680
|
crates: this.packages.map((pkg) => `${root}/${pkg.packageOptions.directory}`),
|
|
585
681
|
bindings: this.bindingMappings,
|
|
682
|
+
binaries: this.releaseBinaries,
|
|
586
683
|
};
|
|
587
684
|
if (dbxToolsProject) {
|
|
588
685
|
dbxToolsProject.dbxToolsConfig.rust = this.workspaceMapping;
|
|
@@ -609,23 +706,25 @@ export class DBXToolsRustWorkspace {
|
|
|
609
706
|
this.pythonPackages = bindings
|
|
610
707
|
.filter((pkg) => (pkg.packageOptions.bindings ?? ["node", "python"]).includes("python"))
|
|
611
708
|
.map((pkg) => {
|
|
612
|
-
const
|
|
709
|
+
const directory = `${string.toSlug(pkg.packageOptions.directory)}-rs`;
|
|
710
|
+
const module = pythonModuleName(pythonModulePrefix, directory);
|
|
711
|
+
const name = `${scope}-${directory}`;
|
|
613
712
|
return {
|
|
614
|
-
directory
|
|
615
|
-
name
|
|
713
|
+
directory,
|
|
714
|
+
name,
|
|
616
715
|
module,
|
|
617
716
|
description: `Python bindings for ${pkg.crateName}`,
|
|
618
717
|
uniffi: true,
|
|
619
718
|
internalDependencies: bindingDependencies(pkg, "python").map(
|
|
620
|
-
(dependency) => dependency.packageOptions.directory
|
|
719
|
+
(dependency) => `${string.toSlug(dependency.packageOptions.directory)}-rs`,
|
|
621
720
|
),
|
|
622
721
|
generatedSources: [
|
|
623
722
|
`src/${module.replaceAll(".", "/")}/bindings.py`,
|
|
624
723
|
`src/${module.replaceAll(".", "/")}/__init__.py`,
|
|
625
724
|
],
|
|
626
725
|
trustedPublisher: {
|
|
627
|
-
environment: `pypi-${
|
|
628
|
-
artifacts: `platform-specific wheels for ${
|
|
726
|
+
environment: `pypi-${name}`,
|
|
727
|
+
artifacts: `platform-specific wheels for ${nativeTargets
|
|
629
728
|
.map((target) => `${target.os}-${target.cpu}`)
|
|
630
729
|
.join(", ")}; all architectures publish to this one PyPI project`,
|
|
631
730
|
},
|
|
@@ -639,7 +738,7 @@ export class DBXToolsRustWorkspace {
|
|
|
639
738
|
for (const binding of bindings.filter((pkg) =>
|
|
640
739
|
(pkg.packageOptions.bindings ?? ["node", "python"]).includes("node"),
|
|
641
740
|
)) {
|
|
642
|
-
const directory = binding.packageOptions.directory
|
|
741
|
+
const directory = `${string.toSlug(binding.packageOptions.directory)}-rs`;
|
|
643
742
|
const memberPath = `${nodeRoot}/${directory}`;
|
|
644
743
|
const found = existing.get(memberPath);
|
|
645
744
|
const existingNode = found instanceof DBXToolsTypeScriptProject ? found : undefined;
|
|
@@ -656,10 +755,7 @@ export class DBXToolsRustWorkspace {
|
|
|
656
755
|
`@${scope}/${directory.toLowerCase().replace(/[^a-z0-9-]+/g, "-")}`,
|
|
657
756
|
);
|
|
658
757
|
node.dbxToolsConfig.uniffi = true;
|
|
659
|
-
|
|
660
|
-
node.package.addField("description", `Node bindings for ${binding.crateName}`);
|
|
661
|
-
}
|
|
662
|
-
const nativeTargets = releaseTargets(options);
|
|
758
|
+
node.package.addField("description", `Node bindings for ${binding.crateName}`);
|
|
663
759
|
if (options.release ?? true) {
|
|
664
760
|
node.package.addField(
|
|
665
761
|
"optionalDependencies",
|
|
@@ -674,16 +770,11 @@ export class DBXToolsRustWorkspace {
|
|
|
674
770
|
node.addDeps("@ubjs/core@0.31.0-5", "@ubjs/node@0.31.0-5");
|
|
675
771
|
node.addDeps(
|
|
676
772
|
...bindingDependencies(binding, "node").map(
|
|
677
|
-
(dependency) =>
|
|
773
|
+
(dependency) =>
|
|
774
|
+
`@${scope}/${string.toSlug(dependency.packageOptions.directory)}-rs@workspace:*`,
|
|
678
775
|
),
|
|
679
776
|
);
|
|
680
|
-
if (binding.packageOptions.nodeDependencies?.length) {
|
|
681
|
-
node.addDeps(...binding.packageOptions.nodeDependencies);
|
|
682
|
-
}
|
|
683
777
|
node.addDevDeps(`uniffi-bindgen-react-native@${UBRN_VERSION}`);
|
|
684
|
-
if (binding.packageOptions.nodeDevDependencies?.length) {
|
|
685
|
-
node.addDevDeps(...binding.packageOptions.nodeDevDependencies);
|
|
686
|
-
}
|
|
687
778
|
nodePackages.push(node);
|
|
688
779
|
}
|
|
689
780
|
this.nodePackages = nodePackages;
|
|
@@ -728,20 +819,9 @@ export class DBXToolsRustWorkspace {
|
|
|
728
819
|
if (this.bindingMappings.some((binding) => binding.node)) {
|
|
729
820
|
project.tasks.tryFind("pre-compile")?.spawn(bindingsTask);
|
|
730
821
|
}
|
|
731
|
-
project.
|
|
732
|
-
description: "Generate and run UniFFI Node and Python example CLIs",
|
|
733
|
-
exec: [
|
|
734
|
-
"bun run rs:bindings",
|
|
735
|
-
...this.bindingMappings.flatMap((binding) => [
|
|
736
|
-
...(binding.node ? [`bun ${binding.node}/test/cli.ts`] : []),
|
|
737
|
-
...(binding.python
|
|
738
|
-
? [`uv run --project ${binding.python} ${binding.python}/test/cli.py`]
|
|
739
|
-
: []),
|
|
740
|
-
]),
|
|
741
|
-
].join(" && "),
|
|
742
|
-
});
|
|
822
|
+
project.removeTask("rs:bindings:demo");
|
|
743
823
|
if (releaseEnabled) {
|
|
744
|
-
this.addReleaseWorkflow(project, options,
|
|
824
|
+
this.addReleaseWorkflow(project, options, nativeTargets);
|
|
745
825
|
}
|
|
746
826
|
}
|
|
747
827
|
|
|
@@ -764,6 +844,7 @@ export class DBXToolsRustWorkspace {
|
|
|
764
844
|
.map((pkg) => ({
|
|
765
845
|
crate: pkg.crateName,
|
|
766
846
|
binary: pkg.packageOptions.binaryName ?? pkg.crateName,
|
|
847
|
+
excludedOs: pkg.packageOptions.releaseExcludeOs ?? [],
|
|
767
848
|
}));
|
|
768
849
|
const publicCrates = orderRustBindings(
|
|
769
850
|
this.packages
|
|
@@ -812,6 +893,7 @@ export class DBXToolsRustWorkspace {
|
|
|
812
893
|
`--python "${binding.python}"`,
|
|
813
894
|
`--node-package "${binding.nodePackage}"`,
|
|
814
895
|
`--python-package "${binding.pythonPackage}"`,
|
|
896
|
+
`--python-module "${binding.pythonModule}"`,
|
|
815
897
|
'--cargo-target "${{ matrix.cargo }}"',
|
|
816
898
|
'--node-triple "${{ matrix.node }}"',
|
|
817
899
|
'--python-tag "${{ matrix.python }}"',
|
|
@@ -823,18 +905,40 @@ export class DBXToolsRustWorkspace {
|
|
|
823
905
|
"--skip-build",
|
|
824
906
|
].join(" \\\n "),
|
|
825
907
|
);
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
908
|
+
const releaseBinaryCondition = (excludedOs: readonly RustReleaseOs[]) =>
|
|
909
|
+
excludedOs.length
|
|
910
|
+
? `\${{ ${excludedOs.map((os) => `matrix.os != '${os}'`).join(" && ")} }}`
|
|
911
|
+
: undefined;
|
|
912
|
+
const binaryCommands = releaseBinaries.flatMap((pkg) => {
|
|
913
|
+
const windowsAsset = releaseBinaryAssetName(
|
|
914
|
+
pkg.binary,
|
|
915
|
+
"${{ matrix.node }}",
|
|
916
|
+
RustReleaseOs.WINDOWS,
|
|
917
|
+
);
|
|
918
|
+
const unixAsset = releaseBinaryAssetName(
|
|
919
|
+
pkg.binary,
|
|
920
|
+
"${{ matrix.node }}",
|
|
921
|
+
RustReleaseOs.LINUX,
|
|
922
|
+
);
|
|
923
|
+
const commands = [
|
|
924
|
+
`mkdir -p "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage"`,
|
|
925
|
+
`SOURCE="target/\${{ matrix.cargo }}/release/${pkg.binary}\${{ matrix.os == 'win32' && '.exe' || '' }}"`,
|
|
926
|
+
`DESTINATION="dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage/${pkg.binary}\${{ matrix.os == 'win32' && '.exe' || '' }}"`,
|
|
927
|
+
'cp "$SOURCE" "$DESTINATION"',
|
|
928
|
+
'if [ "${{ matrix.os }}" = "win32" ]; then',
|
|
929
|
+
` 7z a "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/${windowsAsset}" "$DESTINATION"`,
|
|
930
|
+
"else",
|
|
931
|
+
` tar -C "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage" -czf "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/${unixAsset}" "${pkg.binary}"`,
|
|
932
|
+
"fi",
|
|
933
|
+
`rm -rf "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage"`,
|
|
934
|
+
];
|
|
935
|
+
const excludedCondition = pkg.excludedOs
|
|
936
|
+
.map((os) => `[ "\${{ matrix.os }}" != "${os}" ]`)
|
|
937
|
+
.join(" && ");
|
|
938
|
+
return excludedCondition
|
|
939
|
+
? [`if ${excludedCondition}; then`, ...commands.map((command) => ` ${command}`), "fi"]
|
|
940
|
+
: commands;
|
|
941
|
+
});
|
|
838
942
|
const artifactSteps: JobStep[] = [
|
|
839
943
|
...bindings.flatMap((binding) => [
|
|
840
944
|
...(binding.node
|
|
@@ -856,7 +960,7 @@ export class DBXToolsRustWorkspace {
|
|
|
856
960
|
name: `Upload ${binding.crate} Python wheel`,
|
|
857
961
|
uses: "actions/upload-artifact@v7",
|
|
858
962
|
with: {
|
|
859
|
-
name: `${binding.
|
|
963
|
+
name: `${binding.pythonPackage}--\${{ matrix.python }}--python-wheel`,
|
|
860
964
|
path: `dist/release/${binding.crate}/\${{ matrix.node }}/python/*.whl`,
|
|
861
965
|
"retention-days": 7,
|
|
862
966
|
},
|
|
@@ -867,6 +971,9 @@ export class DBXToolsRustWorkspace {
|
|
|
867
971
|
...releaseBinaries.map((pkg) => ({
|
|
868
972
|
name: `Upload ${pkg.crate} release binary`,
|
|
869
973
|
uses: "actions/upload-artifact@v7",
|
|
974
|
+
...(releaseBinaryCondition(pkg.excludedOs)
|
|
975
|
+
? { if: releaseBinaryCondition(pkg.excludedOs) }
|
|
976
|
+
: {}),
|
|
870
977
|
with: {
|
|
871
978
|
name: `${pkg.crate}-\${{ matrix.node }}-binary`,
|
|
872
979
|
path: `dist/release/${pkg.crate}/\${{ matrix.node }}/binary/*`,
|
|
@@ -926,7 +1033,11 @@ export class DBXToolsRustWorkspace {
|
|
|
926
1033
|
{
|
|
927
1034
|
name: "Install Linux native dependencies",
|
|
928
1035
|
if: "${{ matrix.os == 'linux' }}",
|
|
929
|
-
run:
|
|
1036
|
+
run: [
|
|
1037
|
+
"sudo rm -f /etc/apt/sources.list.d/google-chrome.list",
|
|
1038
|
+
"sudo apt-get update",
|
|
1039
|
+
"sudo apt-get install --yes libdbus-1-dev pkg-config",
|
|
1040
|
+
].join("\n"),
|
|
930
1041
|
},
|
|
931
1042
|
{
|
|
932
1043
|
name: "Build Rust outputs",
|
package/src/release.ts
CHANGED
|
@@ -247,16 +247,16 @@ function addDocsJobs(
|
|
|
247
247
|
{ name: "Configure Pages", uses: "actions/configure-pages@v5" },
|
|
248
248
|
{ name: "Install dependencies", run: "bun install" },
|
|
249
249
|
{ name: "Generate docs from READMEs", run: "bun docs/scripts/sync-readmes.mjs" },
|
|
250
|
-
{
|
|
251
|
-
name: "Check generated titles",
|
|
252
|
-
run: "bun docs/scripts/check-generated-titles.mjs",
|
|
253
|
-
},
|
|
254
250
|
{ name: "Install docs dependencies", run: "bun install --cwd .docs-build/site" },
|
|
255
251
|
bunCacheSaveStep(),
|
|
256
252
|
{
|
|
257
253
|
name: "Generate TypeScript API docs",
|
|
258
254
|
run: "bun docs/scripts/generate-api-docs.mjs",
|
|
259
255
|
},
|
|
256
|
+
{
|
|
257
|
+
name: "Check generated titles",
|
|
258
|
+
run: "bun docs/scripts/check-generated-titles.mjs",
|
|
259
|
+
},
|
|
260
260
|
{ name: "Build docs", run: "bun run --cwd .docs-build/site build" },
|
|
261
261
|
{
|
|
262
262
|
name: "Check generated links",
|
package/src/tags.ts
CHANGED
|
@@ -182,6 +182,7 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
182
182
|
openapi: create(projectPredicate.hasTag("openapi"), (p) => {
|
|
183
183
|
p.addDeps("openapi-fetch@catalog:");
|
|
184
184
|
applyCompilerOptions(p, { target: "ES2022", lib: [...DOM_LIB], types: [] });
|
|
185
|
+
addPackageFiles(p, "openapi.json");
|
|
185
186
|
}),
|
|
186
187
|
} satisfies Record<string, ConstructsMixin>;
|
|
187
188
|
|
package/src/uniffi.ts
CHANGED
|
@@ -56,40 +56,3 @@ export const addExplicitInterfaceReexports = (
|
|
|
56
56
|
if (exports.length === 0) return facade;
|
|
57
57
|
return `${facade.trimEnd()}\n${exports.join("\n")}\n`;
|
|
58
58
|
};
|
|
59
|
-
|
|
60
|
-
const GENERATED_BINDINGS_HEADER = "GENERATED by UniFFI binding generation";
|
|
61
|
-
|
|
62
|
-
function pythonAllNames(source: string, file: string): string[] {
|
|
63
|
-
const body = /^__all__\s*=\s*\[([\s\S]*?)\]/m.exec(source)?.[1];
|
|
64
|
-
if (body === undefined) {
|
|
65
|
-
throw new Error(`Python exports must define a literal __all__ list: ${file}`);
|
|
66
|
-
}
|
|
67
|
-
return [...body.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]!);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Generate one package root from the binding module's explicit exports. */
|
|
71
|
-
export function mergePythonBindingExports(
|
|
72
|
-
initSource: string,
|
|
73
|
-
bindingsSource: string,
|
|
74
|
-
options: { readonly crate: string; readonly file: string },
|
|
75
|
-
): string {
|
|
76
|
-
if (initSource.trim() && !initSource.includes(GENERATED_BINDINGS_HEADER)) {
|
|
77
|
-
throw new Error(`Cannot replace non-generated Python package exports: ${options.file}`);
|
|
78
|
-
}
|
|
79
|
-
const bindingNames = pythonAllNames(bindingsSource, options.file);
|
|
80
|
-
const publicNames = [...bindingNames].sort((left, right) =>
|
|
81
|
-
left < right ? -1 : left > right ? 1 : 0,
|
|
82
|
-
);
|
|
83
|
-
return [
|
|
84
|
-
"# GENERATED by UniFFI binding generation - DO NOT EDIT.",
|
|
85
|
-
`# Regenerated from the ${options.crate} Rust exports.`,
|
|
86
|
-
"# Hand edits are overwritten on the next watch; this file is read-only.",
|
|
87
|
-
"",
|
|
88
|
-
"from .bindings import *",
|
|
89
|
-
"",
|
|
90
|
-
"__all__ = [",
|
|
91
|
-
...publicNames.map((name) => ` ${JSON.stringify(name)},`),
|
|
92
|
-
"]",
|
|
93
|
-
"",
|
|
94
|
-
].join("\n");
|
|
95
|
-
}
|
package/tasks/bump.ts
CHANGED
|
@@ -55,8 +55,8 @@ import { exec, project } from "@dbx-tools/core";
|
|
|
55
55
|
import { log, net } from "@dbx-tools/shared-core";
|
|
56
56
|
import { Command, Option } from "commander";
|
|
57
57
|
import { activePythonIndexes, resolveLocalPypi } from "./python-registry.ts";
|
|
58
|
-
import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
|
|
59
58
|
import { releasePlatformFilter } from "../src/_release-platform.ts";
|
|
59
|
+
import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
|
|
60
60
|
import {
|
|
61
61
|
type Semver,
|
|
62
62
|
compareSemver,
|
|
@@ -330,7 +330,7 @@ program
|
|
|
330
330
|
"-m",
|
|
331
331
|
"🌸 Shipped with Kanna — https://kanna.sh",
|
|
332
332
|
"-m",
|
|
333
|
-
"Co-Authored-By: Kanna <noreply@kanna.sh>\nKanna-Agent: codex/gpt",
|
|
333
|
+
"Co-Authored-By: Kanna <noreply@kanna.sh>\nKanna-Agent: codex/databricks-gpt-5-6-sol",
|
|
334
334
|
]);
|
|
335
335
|
} else {
|
|
336
336
|
logger.info("nothing to commit");
|
|
@@ -157,6 +157,8 @@ function buildAndPublish(binding: RustBindingMapping, version: string): void {
|
|
|
157
157
|
includeNode ? binding.nodePackage! : "",
|
|
158
158
|
"--python-package",
|
|
159
159
|
includePython ? binding.pythonPackage! : "",
|
|
160
|
+
"--python-module",
|
|
161
|
+
includePython ? binding.pythonModule! : "",
|
|
160
162
|
"--cargo-target",
|
|
161
163
|
rustHost(),
|
|
162
164
|
"--node-triple",
|
package/tasks/rust.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { log } from "@dbx-tools/shared-core";
|
|
7
7
|
import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
|
|
@@ -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 { root: config.root, crates, bindings };
|
|
43
|
+
return { root: config.root, crates, bindings, binaries: config.binaries ?? [] };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Whether discovered crate membership or UniFFI marker membership changed. */
|
|
@@ -70,7 +70,9 @@ function ownerBinding(
|
|
|
70
70
|
function generate(binding: RustBindingMapping): void {
|
|
71
71
|
const targets = [
|
|
72
72
|
...(binding.node ? ["--node", binding.node] : []),
|
|
73
|
-
...(binding.python
|
|
73
|
+
...(binding.python
|
|
74
|
+
? ["--python", binding.python, "--python-module", binding.pythonModule ?? ""]
|
|
75
|
+
: []),
|
|
74
76
|
];
|
|
75
77
|
const result = spawnSync(
|
|
76
78
|
process.execPath,
|
|
@@ -92,8 +94,9 @@ export function affectedRustBindings(
|
|
|
92
94
|
const affected = new Set(changed);
|
|
93
95
|
const ordered = orderRustBindings(bindings);
|
|
94
96
|
for (const binding of ordered) {
|
|
95
|
-
if (binding.dependencies?.some((dependency) => affected.has(dependency)))
|
|
97
|
+
if (binding.dependencies?.some((dependency) => affected.has(dependency))) {
|
|
96
98
|
affected.add(binding.crate);
|
|
99
|
+
}
|
|
97
100
|
}
|
|
98
101
|
return ordered.filter((binding) => affected.has(binding.crate));
|
|
99
102
|
}
|
package/tasks/uniffi-release.mjs
CHANGED
|
@@ -27,6 +27,7 @@ const parsed = parseArgs({
|
|
|
27
27
|
"node-package": { type: "string" },
|
|
28
28
|
"native-package": { type: "string" },
|
|
29
29
|
"python-package": { type: "string" },
|
|
30
|
+
"python-module": { type: "string" },
|
|
30
31
|
"cargo-target": { type: "string" },
|
|
31
32
|
"node-triple": { type: "string" },
|
|
32
33
|
"python-tag": { type: "string" },
|
|
@@ -231,11 +232,28 @@ const writable = (path) => {
|
|
|
231
232
|
if (existsSync(path)) chmodSync(path, statSync(path).mode | 0o200);
|
|
232
233
|
};
|
|
233
234
|
|
|
235
|
+
const cargoMetadata = spawnSync("cargo", ["metadata", "--format-version", "1", "--no-deps"], {
|
|
236
|
+
cwd: root,
|
|
237
|
+
encoding: "utf8",
|
|
238
|
+
});
|
|
239
|
+
if (cargoMetadata.error) {
|
|
240
|
+
throw new Error(`cargo metadata failed: ${cargoMetadata.error.message}`, {
|
|
241
|
+
cause: cargoMetadata.error,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (cargoMetadata.status !== 0) {
|
|
245
|
+
throw new Error(`cargo metadata exited with ${cargoMetadata.status}`);
|
|
246
|
+
}
|
|
247
|
+
const cargoTargetRoot = JSON.parse(cargoMetadata.stdout).target_directory;
|
|
248
|
+
if (typeof cargoTargetRoot !== "string" || !cargoTargetRoot) {
|
|
249
|
+
throw new Error("cargo metadata returned no target_directory");
|
|
250
|
+
}
|
|
251
|
+
|
|
234
252
|
const libraryPath = (crate, cargoTarget, os) => {
|
|
235
253
|
const name = crate.replaceAll("-", "_");
|
|
236
254
|
const extension = os === "darwin" ? "dylib" : os === "win32" ? "dll" : "so";
|
|
237
255
|
const prefix = os === "win32" ? "" : "lib";
|
|
238
|
-
return resolve(
|
|
256
|
+
return resolve(cargoTargetRoot, cargoTarget, "release", `${prefix}${name}.${extension}`);
|
|
239
257
|
};
|
|
240
258
|
|
|
241
259
|
const packageNode = ({
|
|
@@ -387,6 +405,7 @@ const packagePython = ({
|
|
|
387
405
|
version,
|
|
388
406
|
cargoTarget,
|
|
389
407
|
os,
|
|
408
|
+
pythonModule,
|
|
390
409
|
}) => {
|
|
391
410
|
const pythonRoot = resolve(output, "python-root");
|
|
392
411
|
cpSync(resolve(root, pythonDirectory), pythonRoot, { recursive: true });
|
|
@@ -407,12 +426,12 @@ const packagePython = ({
|
|
|
407
426
|
}
|
|
408
427
|
writeFileSync(pyproject, metadata);
|
|
409
428
|
|
|
410
|
-
const packageName =
|
|
411
|
-
|
|
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("."));
|
|
412
432
|
const generatedDirectory = mkdtempSync(join(tmpdir(), `${packageName}-python-`));
|
|
413
433
|
const generator = resolve(
|
|
414
|
-
|
|
415
|
-
"target",
|
|
434
|
+
cargoTargetRoot,
|
|
416
435
|
cargoTarget,
|
|
417
436
|
"release",
|
|
418
437
|
`${crate}-uniffi-bindgen${os === "win32" ? ".exe" : ""}`,
|
|
@@ -506,6 +525,7 @@ const build = () => {
|
|
|
506
525
|
version,
|
|
507
526
|
cargoTarget,
|
|
508
527
|
os,
|
|
528
|
+
pythonModule: required("python-module"),
|
|
509
529
|
});
|
|
510
530
|
}
|
|
511
531
|
};
|
package/tasks/uniffi.ts
CHANGED
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
addExplicitInterfaceReexports,
|
|
22
22
|
addTypeScriptExtensionsToBindingImports,
|
|
23
23
|
makeDefaultedInterfaceParametersOptional,
|
|
24
|
-
mergePythonBindingExports,
|
|
25
24
|
} from "../src/uniffi.ts";
|
|
26
25
|
|
|
27
26
|
const { values } = parseArgs({
|
|
@@ -30,6 +29,7 @@ const { values } = parseArgs({
|
|
|
30
29
|
crate: { type: "string" },
|
|
31
30
|
node: { type: "string" },
|
|
32
31
|
python: { type: "string" },
|
|
32
|
+
"python-module": { type: "string" },
|
|
33
33
|
"cargo-target": { type: "string" },
|
|
34
34
|
"node-package-base": { type: "string" },
|
|
35
35
|
ubrn: { type: "string" },
|
|
@@ -54,10 +54,6 @@ const dependencies =
|
|
|
54
54
|
const extension =
|
|
55
55
|
process.platform === "darwin" ? "dylib" : process.platform === "win32" ? "dll" : "so";
|
|
56
56
|
const prefix = process.platform === "win32" ? "" : "lib";
|
|
57
|
-
const targetDirectory = values["cargo-target"]
|
|
58
|
-
? join(root, "target", values["cargo-target"], "release")
|
|
59
|
-
: join(root, "target", "release");
|
|
60
|
-
const library = join(targetDirectory, `${prefix}${libraryName}.${extension}`);
|
|
61
57
|
const normalizedOutput = (value: string): string =>
|
|
62
58
|
value
|
|
63
59
|
.replace(/[\p{Extended_Pictographic}\uFE0F]/gu, "")
|
|
@@ -75,6 +71,28 @@ const run = (command: string, args: string[]) => {
|
|
|
75
71
|
if (result.status !== 0) throw new Error(`${command} exited with ${result.status}`);
|
|
76
72
|
};
|
|
77
73
|
|
|
74
|
+
const cargoMetadata = spawnSync("cargo", ["metadata", "--format-version", "1", "--no-deps"], {
|
|
75
|
+
cwd: root,
|
|
76
|
+
encoding: "utf8",
|
|
77
|
+
});
|
|
78
|
+
if (cargoMetadata.error) {
|
|
79
|
+
throw new Error(`cargo metadata failed: ${cargoMetadata.error.message}`, {
|
|
80
|
+
cause: cargoMetadata.error,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (cargoMetadata.status !== 0) {
|
|
84
|
+
throw new Error(`cargo metadata exited with ${cargoMetadata.status}`);
|
|
85
|
+
}
|
|
86
|
+
const cargoTargetRoot = (JSON.parse(cargoMetadata.stdout) as { target_directory?: unknown })
|
|
87
|
+
.target_directory;
|
|
88
|
+
if (typeof cargoTargetRoot !== "string" || !cargoTargetRoot) {
|
|
89
|
+
throw new Error("cargo metadata returned no target_directory");
|
|
90
|
+
}
|
|
91
|
+
const targetDirectory = values["cargo-target"]
|
|
92
|
+
? join(cargoTargetRoot, values["cargo-target"], "release")
|
|
93
|
+
: join(cargoTargetRoot, "release");
|
|
94
|
+
const library = join(targetDirectory, `${prefix}${libraryName}.${extension}`);
|
|
95
|
+
|
|
78
96
|
const replaceGenerated = (source: string, destination: string): void => {
|
|
79
97
|
const deadline = Date.now() + 5_000;
|
|
80
98
|
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -243,12 +261,10 @@ if (values.node) {
|
|
|
243
261
|
}
|
|
244
262
|
|
|
245
263
|
if (values.python) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
crate.replace(/^dbx-tools-/, "").replaceAll("-", "_"),
|
|
251
|
-
);
|
|
264
|
+
if (!values["python-module"]) {
|
|
265
|
+
throw new Error("Expected --python-module with --python");
|
|
266
|
+
}
|
|
267
|
+
const pythonPackage = resolve(root, values.python, "src", ...values["python-module"].split("."));
|
|
252
268
|
const pythonOutput = mkdtempSync(join(tmpdir(), `${libraryName}-python-`));
|
|
253
269
|
run(
|
|
254
270
|
join(targetDirectory, `${crate}-uniffi-bindgen${process.platform === "win32" ? ".exe" : ""}`),
|
|
@@ -269,14 +285,7 @@ if (values.python) {
|
|
|
269
285
|
stampGeneratedPython(pythonBindings);
|
|
270
286
|
const pythonInit = join(pythonPackage, "__init__.py");
|
|
271
287
|
makeWritable(pythonInit);
|
|
272
|
-
writeFileSync(
|
|
273
|
-
pythonInit,
|
|
274
|
-
mergePythonBindingExports(
|
|
275
|
-
existsSync(pythonInit) ? readFileSync(pythonInit, "utf8") : "",
|
|
276
|
-
readFileSync(pythonBindings, "utf8"),
|
|
277
|
-
{ crate, file: pythonInit },
|
|
278
|
-
),
|
|
279
|
-
);
|
|
288
|
+
writeFileSync(pythonInit, "");
|
|
280
289
|
makeReadonly(pythonInit);
|
|
281
290
|
const pythonLibrary = join(pythonPackage, basename(library));
|
|
282
291
|
makeWritable(pythonLibrary);
|