@dbx-tools/projen 0.6.151 → 0.6.153
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 +32 -13
- package/package.json +4 -4
- package/src/_release-platform.ts +14 -0
- package/src/project-js.ts +3 -2
- package/src/project-rs.ts +266 -151
- package/tasks/bump.ts +2 -6
- package/tasks/publish-uniffi-local.ts +6 -4
- package/tasks/uniffi-release.mjs +259 -0
- package/tasks/uniffi.ts +16 -9
- package/tasks/uniffi-release.ts +0 -168
package/README.md
CHANGED
|
@@ -104,11 +104,26 @@ removing a crate or `setup_scaffolding!()` marker triggers a full synth. Repos
|
|
|
104
104
|
without Rust crates start no Rust watcher. When Rust projects are detected,
|
|
105
105
|
Cargo is required and the focused task fails immediately if it is unavailable.
|
|
106
106
|
|
|
107
|
-
The workspace also generates a `rust-release` workflow from
|
|
108
|
-
UniFFI
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
The workspace also generates a `rust-release` workflow from discovered crates,
|
|
108
|
+
UniFFI bindings, and release-enabled binaries. Its matrix has one row per
|
|
109
|
+
target. Each row installs native dependencies and restores Cargo/sccache once,
|
|
110
|
+
builds the Rust workspace once, then packages every discovered output from that
|
|
111
|
+
shared build. Bun and the workspace install are present only when a Node binding
|
|
112
|
+
needs TypeScript generation; uv is present only when a Python wheel is needed.
|
|
113
|
+
A binary-only workspace therefore installs neither.
|
|
114
|
+
|
|
115
|
+
The Node generator's Rust CLI is cached under one target directory keyed by its
|
|
116
|
+
pinned UBRN version and runner architecture, then prepared before the workspace
|
|
117
|
+
build. Python generation executes the already-built
|
|
118
|
+
`target/<triple>/release/uniffi-bindgen` directly. Artifact packaging therefore
|
|
119
|
+
does no Rust compilation after the main workspace build.
|
|
120
|
+
|
|
121
|
+
Artifacts identify their crate, target, and type. Download-only publication
|
|
122
|
+
jobs publish native npm platform archives before the facade and publish
|
|
123
|
+
platform-tagged Python wheels without checkout, Bun, or `bun install`.
|
|
124
|
+
Non-private Cargo crates publish from a source-only job with
|
|
125
|
+
`cargo publish --no-verify`; GitHub Release uploads likewise consume prebuilt
|
|
126
|
+
binary artifacts without reinstalling a toolchain.
|
|
112
127
|
|
|
113
128
|
Set the repository variable `LOCAL_REPOSITORIES=true` to enable the generated
|
|
114
129
|
self-hosted mirror job. Configure `LOCAL_NPM_REGISTRY` and
|
|
@@ -119,11 +134,13 @@ detects its OS, architecture, libc, Rust target, and Python wheel tag and builds
|
|
|
119
134
|
only that native target. Public Cargo crates also publish directly to crates.io
|
|
120
135
|
with `CARGO_REGISTRY_TOKEN`. Override `releaseTargets` only when a consumer has
|
|
121
136
|
additional native runners; ordinary projects inherit the maintained matrix
|
|
122
|
-
automatically.
|
|
137
|
+
automatically. `bun run bump` also accepts repeatable `--os` and `--arch`
|
|
138
|
+
selectors; every selected operating system is crossed with every selected
|
|
139
|
+
architecture. Omit both filters to regenerate the complete maintained matrix.
|
|
123
140
|
|
|
124
|
-
Private Python projects are marked with `[tool.dbx-tools] private =
|
|
125
|
-
|
|
126
|
-
|
|
141
|
+
Private Python binding projects are marked with `[tool.dbx-tools] private =
|
|
142
|
+
true`. They stay out of the standard uv/Python release and docs surfaces;
|
|
143
|
+
`rust-release` publishes their prebuilt native wheels directly.
|
|
127
144
|
|
|
128
145
|
## Customize Packages With Mixins
|
|
129
146
|
|
|
@@ -318,10 +335,12 @@ Use `--local-registry false` or `--local-pypi false` to disable either local
|
|
|
318
335
|
publish. An explicit `--local-pypi http://localhost:3141/user/index/` overrides
|
|
319
336
|
auto-detection; `--python-root` defaults to `packages/py`.
|
|
320
337
|
|
|
321
|
-
The pushed `v*` tag is also the public release boundary
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
338
|
+
The pushed `v*` tag is also the public release boundary. Available workflow
|
|
339
|
+
stages form the generated chain Rust -> Python -> Node -> docs: Rust builds and
|
|
340
|
+
publishes native artifacts and Cargo crates, Python publishes standard
|
|
341
|
+
distributions, Node publishes standard workspace packages, and docs deploys
|
|
342
|
+
after publication. A stage with no corresponding outputs is omitted. Ordinary
|
|
343
|
+
pushes to `main` publish none of those surfaces.
|
|
325
344
|
|
|
326
345
|
Members intentionally keep only the tasks that something OTHER than a human
|
|
327
346
|
invokes, so there is no second place to run the same thing:
|
package/package.json
CHANGED
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^1.7.0",
|
|
29
|
-
"@dbx-tools/core": "0.6.
|
|
30
|
-
"@dbx-tools/path": "0.6.
|
|
31
|
-
"@dbx-tools/shared-core": "0.6.
|
|
29
|
+
"@dbx-tools/core": "0.6.153",
|
|
30
|
+
"@dbx-tools/path": "0.6.153",
|
|
31
|
+
"@dbx-tools/shared-core": "0.6.153",
|
|
32
32
|
"commander": "^15.0.0",
|
|
33
33
|
"concurrently": "^10.0.3",
|
|
34
34
|
"constructs": "^10.6.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"main": "index.ts",
|
|
50
50
|
"license": "Apache-2.0",
|
|
51
|
-
"version": "0.6.
|
|
51
|
+
"version": "0.6.153",
|
|
52
52
|
"types": "index.ts",
|
|
53
53
|
"type": "module",
|
|
54
54
|
"exports": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Cartesian-product release target filter shared by the bump task. */
|
|
2
|
+
export function releasePlatformFilter(
|
|
3
|
+
operatingSystems: readonly string[],
|
|
4
|
+
architectures: readonly string[],
|
|
5
|
+
): string {
|
|
6
|
+
if ((operatingSystems.length === 0) !== (architectures.length === 0)) {
|
|
7
|
+
throw new Error("--os and --arch must be used together");
|
|
8
|
+
}
|
|
9
|
+
return operatingSystems
|
|
10
|
+
.flatMap((operatingSystem) =>
|
|
11
|
+
architectures.map((architecture) => `${operatingSystem}:${architecture}`),
|
|
12
|
+
)
|
|
13
|
+
.join(",");
|
|
14
|
+
}
|
package/src/project-js.ts
CHANGED
|
@@ -379,6 +379,7 @@ function defaultProjectOptions(
|
|
|
379
379
|
// provider. See {@link DBXToolsRelease}.
|
|
380
380
|
...(isRoot ? {} : { npmAccess: javascript.NpmAccess.PUBLIC }),
|
|
381
381
|
buildWorkflow: false,
|
|
382
|
+
workflowPackageCache: false,
|
|
382
383
|
release: false,
|
|
383
384
|
// The root build validates the whole workspace and must not also pack every
|
|
384
385
|
// member into unused `dist/js` tarballs. Child projects keep projen's package
|
|
@@ -518,7 +519,7 @@ export interface DBXToolsJavaScriptProjectOptions
|
|
|
518
519
|
/** Workflow that must finish successfully before the main Node release runs. */
|
|
519
520
|
readonly releaseUpstreamWorkflow?: string;
|
|
520
521
|
/** Main Node release workflow name. Defaults to `node-release`. */
|
|
521
|
-
readonly
|
|
522
|
+
readonly nodeReleaseWorkflowName?: string | false;
|
|
522
523
|
/**
|
|
523
524
|
* Extra workspace member paths (repo-relative, POSIX) to list in the workspace
|
|
524
525
|
* config ALONGSIDE the discovered `packageRoots` members - for a package that
|
|
@@ -1188,7 +1189,7 @@ function initProject(
|
|
|
1188
1189
|
tagPrefix: options.releaseTagPrefix,
|
|
1189
1190
|
standaloneReleases: options.standaloneReleases,
|
|
1190
1191
|
upstreamWorkflow: options.releaseUpstreamWorkflow,
|
|
1191
|
-
workflowName: options.releaseWorkflowName,
|
|
1192
|
+
workflowName: options.nodeReleaseWorkflowName ?? options.releaseWorkflowName,
|
|
1192
1193
|
});
|
|
1193
1194
|
}
|
|
1194
1195
|
|
package/src/project-rs.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/** Filesystem-discovered Rust workspaces and UniFFI binding package wiring. */
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
-
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { string } from "@dbx-tools/shared-core";
|
|
5
6
|
import { Project, TextFile, YamlFile, javascript } from "projen";
|
|
6
7
|
import type { DBXToolsProject } from "./project.ts";
|
|
7
8
|
import { DBXToolsTypeScriptProject } from "./project-js.ts";
|
|
8
9
|
import type { PythonPackageOptions } from "./project-py.ts";
|
|
9
10
|
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
10
|
-
import {
|
|
11
|
-
import { isDBXToolsJavaScriptProject, isDBXToolsProject } from "./project-predicate.ts";
|
|
11
|
+
import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
|
|
12
12
|
|
|
13
13
|
export interface CargoDependencyOptions {
|
|
14
14
|
readonly version?: string;
|
|
@@ -139,6 +139,7 @@ const RUST_CACHE_ENV = {
|
|
|
139
139
|
RUSTC_WRAPPER: "sccache",
|
|
140
140
|
SCCACHE_GHA_ENABLED: "true",
|
|
141
141
|
} as const;
|
|
142
|
+
const UBRN_VERSION = "0.31.0-5";
|
|
142
143
|
|
|
143
144
|
function rustCacheSteps(sharedKey: string): readonly Record<string, unknown>[] {
|
|
144
145
|
return [
|
|
@@ -172,6 +173,17 @@ function releaseTargets(options: DBXToolsRustWorkspaceOptions): readonly UniFFIR
|
|
|
172
173
|
});
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
function uniffiReleaseTaskSource(): string {
|
|
177
|
+
const sourceDirectory = dirname(fileURLToPath(import.meta.url));
|
|
178
|
+
const candidates = [
|
|
179
|
+
resolve(sourceDirectory, "../tasks/uniffi-release.mjs"),
|
|
180
|
+
resolve(sourceDirectory, "../../tasks/uniffi-release.mjs"),
|
|
181
|
+
];
|
|
182
|
+
const source = candidates.find(existsSync);
|
|
183
|
+
if (!source) throw new Error("Could not locate tasks/uniffi-release.mjs");
|
|
184
|
+
return readFileSync(source, "utf8");
|
|
185
|
+
}
|
|
186
|
+
|
|
175
187
|
/** Persisted mapping consumed by the focused Rust source watcher. */
|
|
176
188
|
export interface RustBindingMapping {
|
|
177
189
|
readonly crate: string;
|
|
@@ -462,7 +474,7 @@ export class DBXToolsRustWorkspace {
|
|
|
462
474
|
if (binding.packageOptions.nodeDependencies?.length) {
|
|
463
475
|
node.addDeps(...binding.packageOptions.nodeDependencies);
|
|
464
476
|
}
|
|
465
|
-
node.addDevDeps(
|
|
477
|
+
node.addDevDeps(`uniffi-bindgen-react-native@${UBRN_VERSION}`);
|
|
466
478
|
if (binding.packageOptions.nodeDevDependencies?.length) {
|
|
467
479
|
node.addDevDeps(...binding.packageOptions.nodeDevDependencies);
|
|
468
480
|
}
|
|
@@ -525,7 +537,8 @@ export class DBXToolsRustWorkspace {
|
|
|
525
537
|
});
|
|
526
538
|
if (
|
|
527
539
|
(options.release ?? true) &&
|
|
528
|
-
(this.bindingMappings.length > 0 ||
|
|
540
|
+
(this.bindingMappings.length > 0 ||
|
|
541
|
+
this.packages.some((pkg) => pkg.packageOptions.release || !pkg.packageOptions.private))
|
|
529
542
|
) {
|
|
530
543
|
this.addReleaseWorkflow(project, options, releaseTargets(options));
|
|
531
544
|
}
|
|
@@ -544,118 +557,269 @@ export class DBXToolsRustWorkspace {
|
|
|
544
557
|
nodePackage: binding.nodePackage ?? "",
|
|
545
558
|
pythonPackage: binding.pythonPackage ?? "",
|
|
546
559
|
}));
|
|
547
|
-
const matrix = bindings.flatMap((binding) =>
|
|
548
|
-
targets.map((target, index) => ({
|
|
549
|
-
binding,
|
|
550
|
-
target: { ...target, facade: index === 0 },
|
|
551
|
-
})),
|
|
552
|
-
);
|
|
553
560
|
const releaseBinaries = this.packages
|
|
554
561
|
.filter((pkg) => pkg.packageOptions.release)
|
|
555
562
|
.map((pkg) => ({
|
|
556
563
|
crate: pkg.crateName,
|
|
557
564
|
binary: pkg.packageOptions.binaryName ?? pkg.crateName,
|
|
558
565
|
}));
|
|
559
|
-
const
|
|
560
|
-
|
|
566
|
+
const publicCrates = this.packages
|
|
567
|
+
.filter((pkg) => !pkg.packageOptions.private)
|
|
568
|
+
.map((pkg) => pkg.crateName);
|
|
569
|
+
const targetMatrix = targets.map((target, index) => ({
|
|
570
|
+
target: { ...target, facade: index === 0 },
|
|
571
|
+
}));
|
|
572
|
+
const hasNodeBindings = bindings.some((binding) => binding.node);
|
|
573
|
+
const hasPythonBindings = bindings.some((binding) => binding.python);
|
|
574
|
+
const hasTargetOutputs = bindings.length > 0 || releaseBinaries.length > 0;
|
|
575
|
+
const releaseTask = ".projen/uniffi-release.mjs";
|
|
576
|
+
if (bindings.length) {
|
|
577
|
+
new TextFile(project, releaseTask, {
|
|
578
|
+
lines: uniffiReleaseTaskSource().trimEnd().split("\n"),
|
|
579
|
+
});
|
|
580
|
+
} else {
|
|
581
|
+
project.tryRemoveFile(releaseTask);
|
|
582
|
+
}
|
|
583
|
+
const bindingCommands = bindings.map((binding) =>
|
|
584
|
+
[
|
|
585
|
+
`node ${releaseTask} build`,
|
|
586
|
+
`--crate "${binding.crate}"`,
|
|
587
|
+
`--node "${binding.node}"`,
|
|
588
|
+
`--python "${binding.python}"`,
|
|
589
|
+
`--node-package "${binding.nodePackage}"`,
|
|
590
|
+
`--python-package "${binding.pythonPackage}"`,
|
|
591
|
+
...(binding.node
|
|
592
|
+
? ['--node-generator "node_modules/@dbx-tools/projen/tasks/uniffi.ts"']
|
|
593
|
+
: []),
|
|
594
|
+
'--cargo-target "${{ matrix.target.cargo }}"',
|
|
595
|
+
'--node-triple "${{ matrix.target.node }}"',
|
|
596
|
+
'--python-tag "${{ matrix.target.python }}"',
|
|
597
|
+
'--os "${{ matrix.target.os }}"',
|
|
598
|
+
'--cpu "${{ matrix.target.cpu }}"',
|
|
599
|
+
'--libc "${{ matrix.target.libc }}"',
|
|
600
|
+
'--facade "${{ matrix.target.facade }}"',
|
|
601
|
+
'--version "${VERSION#v}"',
|
|
602
|
+
`--output "dist/release/${binding.crate}/\${{ matrix.target.node }}"`,
|
|
603
|
+
"--skip-build",
|
|
604
|
+
].join(" \\\n "),
|
|
561
605
|
);
|
|
606
|
+
const binaryCommands = releaseBinaries.flatMap((pkg) => [
|
|
607
|
+
`mkdir -p "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage"`,
|
|
608
|
+
`SOURCE="target/\${{ matrix.target.cargo }}/release/${pkg.binary}\${{ matrix.target.os == 'win32' && '.exe' || '' }}"`,
|
|
609
|
+
`DESTINATION="dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage/${pkg.binary}\${{ matrix.target.os == 'win32' && '.exe' || '' }}"`,
|
|
610
|
+
'cp "$SOURCE" "$DESTINATION"',
|
|
611
|
+
'if [ "${{ matrix.target.os }}" = "win32" ]; then',
|
|
612
|
+
` 7z a "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/${pkg.binary}-\${{ matrix.target.node }}.zip" "$DESTINATION"`,
|
|
613
|
+
"else",
|
|
614
|
+
` tar -C "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage" -czf "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/${pkg.binary}-\${{ matrix.target.node }}.tar.gz" "${pkg.binary}"`,
|
|
615
|
+
"fi",
|
|
616
|
+
`rm -rf "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage"`,
|
|
617
|
+
]);
|
|
618
|
+
const artifactSteps = [
|
|
619
|
+
...bindings.flatMap((binding) => [
|
|
620
|
+
...(binding.node
|
|
621
|
+
? [
|
|
622
|
+
{
|
|
623
|
+
name: `Upload ${binding.crate} native npm package`,
|
|
624
|
+
uses: "actions/upload-artifact@v7",
|
|
625
|
+
with: {
|
|
626
|
+
name: `${binding.crate}-\${{ matrix.target.node }}-npm`,
|
|
627
|
+
path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/npm/*.tgz`,
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: `Upload ${binding.crate} npm facade`,
|
|
632
|
+
if: "${{ matrix.target.facade }}",
|
|
633
|
+
uses: "actions/upload-artifact@v7",
|
|
634
|
+
with: {
|
|
635
|
+
name: `${binding.crate}-npm-facade`,
|
|
636
|
+
path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/npm-facade/*.tgz`,
|
|
637
|
+
},
|
|
638
|
+
},
|
|
639
|
+
]
|
|
640
|
+
: []),
|
|
641
|
+
...(binding.python
|
|
642
|
+
? [
|
|
643
|
+
{
|
|
644
|
+
name: `Upload ${binding.crate} Python wheel`,
|
|
645
|
+
uses: "actions/upload-artifact@v7",
|
|
646
|
+
with: {
|
|
647
|
+
name: `${binding.crate}-\${{ matrix.target.python }}-python-wheel`,
|
|
648
|
+
path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/python/*.whl`,
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
]
|
|
652
|
+
: []),
|
|
653
|
+
]),
|
|
654
|
+
...releaseBinaries.map((pkg) => ({
|
|
655
|
+
name: `Upload ${pkg.crate} release binary`,
|
|
656
|
+
uses: "actions/upload-artifact@v7",
|
|
657
|
+
with: {
|
|
658
|
+
name: `${pkg.crate}-\${{ matrix.target.node }}-binary`,
|
|
659
|
+
path: `dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/*`,
|
|
660
|
+
},
|
|
661
|
+
})),
|
|
662
|
+
];
|
|
562
663
|
const buildJob = {
|
|
563
|
-
name: "${{ matrix.
|
|
664
|
+
name: "${{ matrix.target.node }}",
|
|
564
665
|
"runs-on": "${{ matrix.target.runner }}",
|
|
565
666
|
env: RUST_CACHE_ENV,
|
|
566
667
|
strategy: {
|
|
567
668
|
"fail-fast": false,
|
|
568
|
-
matrix: { include:
|
|
669
|
+
matrix: { include: targetMatrix },
|
|
569
670
|
},
|
|
570
671
|
steps: [
|
|
571
672
|
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
673
|
+
...(hasNodeBindings
|
|
674
|
+
? [
|
|
675
|
+
{
|
|
676
|
+
name: "Setup Bun",
|
|
677
|
+
uses: "oven-sh/setup-bun@v2",
|
|
678
|
+
with: { "bun-version": "1.3.14" },
|
|
679
|
+
},
|
|
680
|
+
]
|
|
681
|
+
: []),
|
|
682
|
+
...(hasPythonBindings ? [{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" }] : []),
|
|
578
683
|
{
|
|
579
684
|
name: "Setup Rust",
|
|
580
685
|
uses: "dtolnay/rust-toolchain@stable",
|
|
581
686
|
with: { targets: "${{ matrix.target.cargo }}" },
|
|
582
687
|
},
|
|
583
688
|
...rustCacheSteps("release-${{ matrix.target.cargo }}"),
|
|
689
|
+
...(hasNodeBindings
|
|
690
|
+
? [
|
|
691
|
+
{
|
|
692
|
+
name: "Cache UBRN generator",
|
|
693
|
+
uses: "actions/cache@v5",
|
|
694
|
+
with: {
|
|
695
|
+
path: "target/ubrn",
|
|
696
|
+
key: `ubrn-\${{ runner.os }}-\${{ runner.arch }}-${UBRN_VERSION}`,
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
]
|
|
700
|
+
: []),
|
|
584
701
|
{
|
|
585
702
|
name: "Install Linux native dependencies",
|
|
586
703
|
if: "${{ matrix.target.os == 'linux' }}",
|
|
587
704
|
run: "sudo apt-get update && sudo apt-get install --yes libdbus-1-dev pkg-config",
|
|
588
705
|
},
|
|
589
|
-
{ name: "Install", run: "bun install" },
|
|
706
|
+
...(hasNodeBindings ? [{ name: "Install Node tooling", run: "bun install" }] : []),
|
|
707
|
+
...(hasNodeBindings
|
|
708
|
+
? [
|
|
709
|
+
{
|
|
710
|
+
name: "Prepare UBRN generator",
|
|
711
|
+
env: {
|
|
712
|
+
CARGO_TARGET_DIR: "${{ github.workspace }}/target/ubrn",
|
|
713
|
+
},
|
|
714
|
+
run: "cargo build --manifest-path node_modules/uniffi-bindgen-react-native/crates/ubrn_cli/Cargo.toml",
|
|
715
|
+
},
|
|
716
|
+
]
|
|
717
|
+
: []),
|
|
590
718
|
{
|
|
591
|
-
name: "Build
|
|
592
|
-
|
|
593
|
-
env: {
|
|
594
|
-
VERSION: "${{ github.event_name == 'push' && github.ref_name || inputs.version }}",
|
|
595
|
-
},
|
|
596
|
-
run: 'bun node_modules/@dbx-tools/projen/tasks/uniffi-release.ts build --crate "${{ matrix.binding.crate }}" --rust "${{ matrix.binding.rust }}" --node "${{ matrix.binding.node }}" --python "${{ matrix.binding.python }}" --node-package "${{ matrix.binding.nodePackage }}" --python-package "${{ matrix.binding.pythonPackage }}" --cargo-target "${{ matrix.target.cargo }}" --node-triple "${{ matrix.target.node }}" --python-tag "${{ matrix.target.python }}" --os "${{ matrix.target.os }}" --cpu "${{ matrix.target.cpu }}" --libc "${{ matrix.target.libc }}" --facade "${{ matrix.target.facade }}" --version "${VERSION#v}"',
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
name: "Upload native packages",
|
|
600
|
-
uses: "actions/upload-artifact@v7",
|
|
601
|
-
with: {
|
|
602
|
-
name: "${{ matrix.binding.crate }}-${{ matrix.target.node }}",
|
|
603
|
-
path: [
|
|
604
|
-
"dist/uniffi/npm/*.tgz",
|
|
605
|
-
"dist/uniffi/npm-facade/*.tgz",
|
|
606
|
-
"dist/uniffi/python/*.whl",
|
|
607
|
-
].join("\n"),
|
|
608
|
-
},
|
|
719
|
+
name: "Build Rust outputs",
|
|
720
|
+
run: 'cargo build --release --workspace --target "${{ matrix.target.cargo }}"',
|
|
609
721
|
},
|
|
722
|
+
...(bindingCommands.length
|
|
723
|
+
? [
|
|
724
|
+
{
|
|
725
|
+
name: "Package UniFFI outputs",
|
|
726
|
+
shell: "bash",
|
|
727
|
+
env: {
|
|
728
|
+
VERSION:
|
|
729
|
+
"${{ github.event_name == 'push' && github.ref_name || inputs.version }}",
|
|
730
|
+
...(hasNodeBindings
|
|
731
|
+
? {
|
|
732
|
+
CARGO_TARGET_DIR: "${{ github.workspace }}/target/ubrn",
|
|
733
|
+
}
|
|
734
|
+
: {}),
|
|
735
|
+
},
|
|
736
|
+
run: bindingCommands.join("\n"),
|
|
737
|
+
},
|
|
738
|
+
]
|
|
739
|
+
: []),
|
|
740
|
+
...(binaryCommands.length
|
|
741
|
+
? [
|
|
742
|
+
{
|
|
743
|
+
name: "Package release binaries",
|
|
744
|
+
shell: "bash",
|
|
745
|
+
run: binaryCommands.join("\n"),
|
|
746
|
+
},
|
|
747
|
+
]
|
|
748
|
+
: []),
|
|
749
|
+
...artifactSteps,
|
|
610
750
|
],
|
|
611
751
|
};
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
env: RUST_CACHE_ENV,
|
|
616
|
-
strategy: { "fail-fast": false, matrix: { include: binaryMatrix } },
|
|
617
|
-
steps: [
|
|
618
|
-
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
752
|
+
const bindingPublishJobs = Object.fromEntries(
|
|
753
|
+
bindings.map((binding) => [
|
|
754
|
+
`publish-${binding.crate}`,
|
|
619
755
|
{
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
name: "Install Linux native dependencies",
|
|
627
|
-
if: "${{ matrix.target.os == 'linux' }}",
|
|
628
|
-
run: "sudo apt-get update && sudo apt-get install --yes libdbus-1-dev pkg-config",
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
name: "Build release binary",
|
|
632
|
-
shell: "bash",
|
|
633
|
-
run: [
|
|
634
|
-
'cargo build --release --package "${{ matrix.package.crate }}" --bin "${{ matrix.package.binary }}" --target "${{ matrix.target.cargo }}"',
|
|
635
|
-
"mkdir -p dist/rust-release/stage",
|
|
636
|
-
"SOURCE=\"target/${{ matrix.target.cargo }}/release/${{ matrix.package.binary }}${{ matrix.target.os == 'win32' && '.exe' || '' }}\"",
|
|
637
|
-
"DESTINATION=\"dist/rust-release/stage/${{ matrix.package.binary }}${{ matrix.target.os == 'win32' && '.exe' || '' }}\"",
|
|
638
|
-
'cp "$SOURCE" "$DESTINATION"',
|
|
639
|
-
'if [ "${{ matrix.target.os }}" = "win32" ]; then',
|
|
640
|
-
' ARCHIVE="dist/rust-release/${{ matrix.package.binary }}-${{ matrix.target.node }}.zip"',
|
|
641
|
-
' 7z a "$ARCHIVE" "$DESTINATION"',
|
|
642
|
-
"else",
|
|
643
|
-
' ARCHIVE="dist/rust-release/${{ matrix.package.binary }}-${{ matrix.target.node }}.tar.gz"',
|
|
644
|
-
' tar -C dist/rust-release/stage -czf "$ARCHIVE" "${{ matrix.package.binary }}"',
|
|
645
|
-
"fi",
|
|
646
|
-
"rm -rf dist/rust-release/stage",
|
|
647
|
-
].join("\n"),
|
|
648
|
-
},
|
|
649
|
-
{
|
|
650
|
-
name: "Upload release binary",
|
|
651
|
-
uses: "actions/upload-artifact@v7",
|
|
652
|
-
with: {
|
|
653
|
-
name: "release-${{ matrix.package.crate }}-${{ matrix.target.node }}",
|
|
654
|
-
path: "dist/rust-release/*",
|
|
756
|
+
if: "${{ github.event_name == 'push' }}",
|
|
757
|
+
needs: ["build"],
|
|
758
|
+
"runs-on": "ubuntu-latest",
|
|
759
|
+
permissions: {
|
|
760
|
+
contents: "read",
|
|
761
|
+
...(binding.python ? { "id-token": "write" } : {}),
|
|
655
762
|
},
|
|
763
|
+
...(binding.python ? { environment: { name: `pypi-${binding.crate}` } } : {}),
|
|
764
|
+
steps: [
|
|
765
|
+
...(binding.node
|
|
766
|
+
? [
|
|
767
|
+
{
|
|
768
|
+
name: "Setup Node.js",
|
|
769
|
+
uses: "actions/setup-node@v6",
|
|
770
|
+
with: { "registry-url": "https://registry.npmjs.org" },
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
name: "Download native npm packages",
|
|
774
|
+
uses: "actions/download-artifact@v8",
|
|
775
|
+
with: {
|
|
776
|
+
pattern: `${binding.crate}-*-npm`,
|
|
777
|
+
path: "dist/npm",
|
|
778
|
+
"merge-multiple": true,
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
name: "Download npm facade",
|
|
783
|
+
uses: "actions/download-artifact@v8",
|
|
784
|
+
with: {
|
|
785
|
+
name: `${binding.crate}-npm-facade`,
|
|
786
|
+
path: "dist/npm-facade",
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
name: "Publish native npm packages",
|
|
791
|
+
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
792
|
+
run: 'for package in dist/npm/*.tgz; do npm publish "$package" --access public; done',
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
name: "Publish npm facade",
|
|
796
|
+
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
797
|
+
run: 'for package in dist/npm-facade/*.tgz; do npm publish "$package" --access public; done',
|
|
798
|
+
},
|
|
799
|
+
]
|
|
800
|
+
: []),
|
|
801
|
+
...(binding.python
|
|
802
|
+
? [
|
|
803
|
+
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
804
|
+
{
|
|
805
|
+
name: "Download Python wheels",
|
|
806
|
+
uses: "actions/download-artifact@v8",
|
|
807
|
+
with: {
|
|
808
|
+
pattern: `${binding.crate}-*-python-wheel`,
|
|
809
|
+
path: "dist/python",
|
|
810
|
+
"merge-multiple": true,
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
name: "Publish Python wheels",
|
|
815
|
+
run: "uv publish --trusted-publishing always dist/python/*.whl",
|
|
816
|
+
},
|
|
817
|
+
]
|
|
818
|
+
: []),
|
|
819
|
+
],
|
|
656
820
|
},
|
|
657
|
-
],
|
|
658
|
-
|
|
821
|
+
]),
|
|
822
|
+
);
|
|
659
823
|
const workflowName = options.releaseWorkflowName ?? "rust-release";
|
|
660
824
|
new YamlFile(project, `.github/workflows/${workflowName}.yml`, {
|
|
661
825
|
obj: {
|
|
@@ -674,81 +838,32 @@ export class DBXToolsRustWorkspace {
|
|
|
674
838
|
},
|
|
675
839
|
permissions: { contents: "read" },
|
|
676
840
|
jobs: {
|
|
677
|
-
...(
|
|
678
|
-
...
|
|
679
|
-
...(
|
|
841
|
+
...(hasTargetOutputs && targetMatrix.length ? { build: buildJob } : {}),
|
|
842
|
+
...bindingPublishJobs,
|
|
843
|
+
...(publicCrates.length
|
|
680
844
|
? {
|
|
681
|
-
publish: {
|
|
845
|
+
"publish-cargo": {
|
|
682
846
|
if: "${{ github.event_name == 'push' }}",
|
|
683
|
-
needs: ["build"],
|
|
847
|
+
...(hasTargetOutputs ? { needs: ["build"] } : {}),
|
|
684
848
|
"runs-on": "ubuntu-latest",
|
|
685
|
-
|
|
686
|
-
permissions: { contents: "read", "id-token": "write" },
|
|
687
|
-
environment: { name: "pypi-${{ matrix.binding.crate }}" },
|
|
849
|
+
permissions: { contents: "read" },
|
|
688
850
|
steps: [
|
|
689
851
|
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
852
|
+
{ name: "Setup Rust", uses: "dtolnay/rust-toolchain@stable" },
|
|
690
853
|
{
|
|
691
|
-
name: "
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
},
|
|
700
|
-
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
701
|
-
{ name: "Install", run: "bun install" },
|
|
702
|
-
{
|
|
703
|
-
name: "Download packages",
|
|
704
|
-
uses: "actions/download-artifact@v8",
|
|
705
|
-
with: {
|
|
706
|
-
pattern: "${{ matrix.binding.crate }}-*",
|
|
707
|
-
path: "dist/uniffi",
|
|
708
|
-
"merge-multiple": true,
|
|
709
|
-
},
|
|
710
|
-
},
|
|
711
|
-
{
|
|
712
|
-
name: "Publish npm packages",
|
|
713
|
-
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
714
|
-
run: 'for package in dist/uniffi/npm/*.tgz; do npm publish "$package" --access public; done',
|
|
715
|
-
},
|
|
716
|
-
{
|
|
717
|
-
name: "Publish npm facades",
|
|
718
|
-
if: "${{ matrix.binding.node != '' }}",
|
|
719
|
-
env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
|
|
720
|
-
run: 'for package in dist/uniffi/npm-facade/*.tgz; do npm publish "$package" --access public; done',
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
name: "Publish Python wheels",
|
|
724
|
-
if: "${{ matrix.binding.python != '' }}",
|
|
725
|
-
run: "uv publish --trusted-publishing always dist/uniffi/python/*.whl",
|
|
854
|
+
name: "Publish public crates",
|
|
855
|
+
env: { CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}" },
|
|
856
|
+
run: publicCrates
|
|
857
|
+
.map(
|
|
858
|
+
(crate) =>
|
|
859
|
+
`cargo publish --package "${crate}" --registry crates-io --no-verify`,
|
|
860
|
+
)
|
|
861
|
+
.join("\n"),
|
|
726
862
|
},
|
|
727
863
|
],
|
|
728
864
|
},
|
|
729
865
|
}
|
|
730
866
|
: {}),
|
|
731
|
-
"publish-cargo": {
|
|
732
|
-
if: "${{ github.event_name == 'push' }}",
|
|
733
|
-
needs: [matrix.length ? "build" : "build-binaries"],
|
|
734
|
-
"runs-on": "ubuntu-latest",
|
|
735
|
-
permissions: { contents: "read" },
|
|
736
|
-
env: RUST_CACHE_ENV,
|
|
737
|
-
steps: [
|
|
738
|
-
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
739
|
-
{ name: "Setup Rust", uses: "dtolnay/rust-toolchain@stable" },
|
|
740
|
-
...rustCacheSteps("cargo-publish"),
|
|
741
|
-
{
|
|
742
|
-
name: "Install Linux native dependencies",
|
|
743
|
-
run: "sudo apt-get update && sudo apt-get install --yes libdbus-1-dev pkg-config",
|
|
744
|
-
},
|
|
745
|
-
{
|
|
746
|
-
name: "Publish public crates",
|
|
747
|
-
env: { CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}" },
|
|
748
|
-
run: "cargo publish --workspace --registry crates-io",
|
|
749
|
-
},
|
|
750
|
-
],
|
|
751
|
-
},
|
|
752
867
|
"publish-local": {
|
|
753
868
|
if: "${{ github.event_name == 'push' && vars.LOCAL_REPOSITORIES == 'true' }}",
|
|
754
869
|
"runs-on": ["self-hosted"],
|
|
@@ -780,11 +895,11 @@ export class DBXToolsRustWorkspace {
|
|
|
780
895
|
},
|
|
781
896
|
],
|
|
782
897
|
},
|
|
783
|
-
...(
|
|
898
|
+
...(releaseBinaries.length
|
|
784
899
|
? {
|
|
785
900
|
"publish-github-release": {
|
|
786
901
|
if: "${{ github.event_name == 'push' }}",
|
|
787
|
-
needs: ["build
|
|
902
|
+
needs: ["build"],
|
|
788
903
|
"runs-on": "ubuntu-latest",
|
|
789
904
|
permissions: { contents: "write" },
|
|
790
905
|
steps: [
|
|
@@ -792,7 +907,7 @@ export class DBXToolsRustWorkspace {
|
|
|
792
907
|
name: "Download release binaries",
|
|
793
908
|
uses: "actions/download-artifact@v8",
|
|
794
909
|
with: {
|
|
795
|
-
pattern: "
|
|
910
|
+
pattern: "*-binary",
|
|
796
911
|
path: "dist/rust-release",
|
|
797
912
|
"merge-multiple": true,
|
|
798
913
|
},
|
package/tasks/bump.ts
CHANGED
|
@@ -56,6 +56,7 @@ 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
58
|
import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
|
|
59
|
+
import { releasePlatformFilter } from "../src/_release-platform.ts";
|
|
59
60
|
import {
|
|
60
61
|
type Semver,
|
|
61
62
|
compareSemver,
|
|
@@ -234,9 +235,7 @@ program
|
|
|
234
235
|
}) => {
|
|
235
236
|
const pkgPath = resolve(process.cwd(), "package.json");
|
|
236
237
|
if (!existsSync(pkgPath)) throw new Error(`no package.json in ${process.cwd()}`);
|
|
237
|
-
|
|
238
|
-
throw new Error("--os and --arch must be used together");
|
|
239
|
-
}
|
|
238
|
+
const releasePlatforms = releasePlatformFilter(opts.os, opts.arch);
|
|
240
239
|
|
|
241
240
|
const siblings = opts.sibling.map((s) => ({ ...s, pkgPath: resolve(s.dir, "package.json") }));
|
|
242
241
|
for (const s of siblings) {
|
|
@@ -285,9 +284,6 @@ program
|
|
|
285
284
|
|
|
286
285
|
if (opts.synth) {
|
|
287
286
|
logger.info("synthesizing (projen)");
|
|
288
|
-
const releasePlatforms = opts.os
|
|
289
|
-
.flatMap((os) => opts.arch.map((arch) => `${os}:${arch}`))
|
|
290
|
-
.join(",");
|
|
291
287
|
exec.spawnSync("bun", [".projenrc.ts"], {
|
|
292
288
|
cwd: process.cwd(),
|
|
293
289
|
stdout: "inherit",
|
|
@@ -144,19 +144,19 @@ function buildAndPublish(binding: RustBindingMapping, version: string): void {
|
|
|
144
144
|
|
|
145
145
|
const target = nativeTarget();
|
|
146
146
|
const output = resolve(repoRoot, "dist/uniffi");
|
|
147
|
-
run("
|
|
148
|
-
resolve(dirname(fileURLToPath(import.meta.url)), "uniffi-release.
|
|
147
|
+
run("node", [
|
|
148
|
+
resolve(dirname(fileURLToPath(import.meta.url)), "uniffi-release.mjs"),
|
|
149
149
|
"build",
|
|
150
150
|
"--crate",
|
|
151
151
|
binding.crate,
|
|
152
|
-
"--rust",
|
|
153
|
-
binding.rust,
|
|
154
152
|
"--node",
|
|
155
153
|
includeNode ? binding.node! : "",
|
|
156
154
|
"--python",
|
|
157
155
|
includePython ? binding.python! : "",
|
|
158
156
|
"--node-package",
|
|
159
157
|
includeNode ? binding.nodePackage! : "",
|
|
158
|
+
"--node-generator",
|
|
159
|
+
"projen/tasks/uniffi.ts",
|
|
160
160
|
"--python-package",
|
|
161
161
|
includePython ? binding.pythonPackage! : "",
|
|
162
162
|
"--cargo-target",
|
|
@@ -175,6 +175,8 @@ function buildAndPublish(binding: RustBindingMapping, version: string): void {
|
|
|
175
175
|
"true",
|
|
176
176
|
"--version",
|
|
177
177
|
version,
|
|
178
|
+
"--output",
|
|
179
|
+
"dist/uniffi",
|
|
178
180
|
]);
|
|
179
181
|
|
|
180
182
|
if (includeNode) {
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
cpSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { parseArgs } from "node:util";
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
|
|
20
|
+
const parsed = parseArgs({
|
|
21
|
+
allowPositionals: true,
|
|
22
|
+
options: {
|
|
23
|
+
root: { type: "string" },
|
|
24
|
+
crate: { type: "string" },
|
|
25
|
+
node: { type: "string" },
|
|
26
|
+
python: { type: "string" },
|
|
27
|
+
"node-package": { type: "string" },
|
|
28
|
+
"node-generator": { type: "string" },
|
|
29
|
+
"python-package": { type: "string" },
|
|
30
|
+
"cargo-target": { type: "string" },
|
|
31
|
+
"node-triple": { type: "string" },
|
|
32
|
+
"python-tag": { type: "string" },
|
|
33
|
+
os: { type: "string" },
|
|
34
|
+
cpu: { type: "string" },
|
|
35
|
+
libc: { type: "string" },
|
|
36
|
+
facade: { type: "string" },
|
|
37
|
+
version: { type: "string" },
|
|
38
|
+
output: { type: "string" },
|
|
39
|
+
"skip-build": { type: "boolean" },
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const required = (name) => {
|
|
44
|
+
const value = parsed.values[name];
|
|
45
|
+
if (!value) throw new Error(`Missing --${name}`);
|
|
46
|
+
return value;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const root = resolve(parsed.values.root ?? process.cwd());
|
|
50
|
+
const run = (command, args, cwd = root) => {
|
|
51
|
+
const result = spawnSync(command, args, { cwd, stdio: "inherit" });
|
|
52
|
+
if (result.status !== 0) throw new Error(`${command} exited with ${result.status}`);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const replaceVersion = (source, version) =>
|
|
56
|
+
source.replace(/^version = "[^"]+"$/m, `version = "${version}"`);
|
|
57
|
+
|
|
58
|
+
const writable = (path) => {
|
|
59
|
+
if (existsSync(path)) chmodSync(path, statSync(path).mode | 0o200);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const libraryPath = (crate, cargoTarget, os) => {
|
|
63
|
+
const name = crate.replaceAll("-", "_");
|
|
64
|
+
const extension = os === "darwin" ? "dylib" : os === "win32" ? "dll" : "so";
|
|
65
|
+
const prefix = os === "win32" ? "" : "lib";
|
|
66
|
+
return resolve(root, "target", cargoTarget, "release", `${prefix}${name}.${extension}`);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const packageNode = ({
|
|
70
|
+
crate,
|
|
71
|
+
library,
|
|
72
|
+
output,
|
|
73
|
+
nodeDirectory,
|
|
74
|
+
nodePackage,
|
|
75
|
+
nodeTriple,
|
|
76
|
+
os,
|
|
77
|
+
cpu,
|
|
78
|
+
version,
|
|
79
|
+
facade,
|
|
80
|
+
nodeGenerator,
|
|
81
|
+
}) => {
|
|
82
|
+
const libraryFile = basename(library);
|
|
83
|
+
const nativePackage = resolve(output, "native-node");
|
|
84
|
+
mkdirSync(nativePackage, { recursive: true });
|
|
85
|
+
cpSync(library, join(nativePackage, libraryFile));
|
|
86
|
+
writeFileSync(
|
|
87
|
+
join(nativePackage, "package.json"),
|
|
88
|
+
`${JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
name: `${nodePackage}-${nodeTriple}`,
|
|
91
|
+
version,
|
|
92
|
+
description: `Native ${nodeTriple} library for ${nodePackage}`,
|
|
93
|
+
license: "Apache-2.0",
|
|
94
|
+
os: [os],
|
|
95
|
+
cpu: [cpu],
|
|
96
|
+
...(parsed.values.libc ? { libc: [parsed.values.libc] } : {}),
|
|
97
|
+
files: [libraryFile],
|
|
98
|
+
},
|
|
99
|
+
null,
|
|
100
|
+
2,
|
|
101
|
+
)}\n`,
|
|
102
|
+
);
|
|
103
|
+
mkdirSync(resolve(output, "npm"), { recursive: true });
|
|
104
|
+
run("npm", ["pack", "--pack-destination", resolve(output, "npm")], nativePackage);
|
|
105
|
+
|
|
106
|
+
if (!facade) return;
|
|
107
|
+
const facadeDirectory = resolve(output, "facade-node");
|
|
108
|
+
cpSync(resolve(root, nodeDirectory), facadeDirectory, { recursive: true });
|
|
109
|
+
rmSync(join(facadeDirectory, "src", libraryFile), { force: true });
|
|
110
|
+
const manifestPath = join(facadeDirectory, "package.json");
|
|
111
|
+
writable(manifestPath);
|
|
112
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
113
|
+
manifest.version = version;
|
|
114
|
+
manifest.private = false;
|
|
115
|
+
manifest.license = manifest.license === "UNLICENSED" ? "Apache-2.0" : manifest.license;
|
|
116
|
+
manifest.optionalDependencies = Object.fromEntries(
|
|
117
|
+
Object.keys(manifest.optionalDependencies ?? {}).map((name) => [name, version]),
|
|
118
|
+
);
|
|
119
|
+
manifest.dependencies = Object.fromEntries(
|
|
120
|
+
Object.entries(manifest.dependencies ?? {}).map(([name, dependency]) => [
|
|
121
|
+
name,
|
|
122
|
+
typeof dependency === "string" && dependency.startsWith("workspace:") ? version : dependency,
|
|
123
|
+
]),
|
|
124
|
+
);
|
|
125
|
+
delete manifest.scripts;
|
|
126
|
+
delete manifest.devDependencies;
|
|
127
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
128
|
+
run("bun", [
|
|
129
|
+
resolve(root, nodeGenerator),
|
|
130
|
+
"--root",
|
|
131
|
+
root,
|
|
132
|
+
"--crate",
|
|
133
|
+
crate,
|
|
134
|
+
"--node",
|
|
135
|
+
facadeDirectory,
|
|
136
|
+
"--cargo-target",
|
|
137
|
+
required("cargo-target"),
|
|
138
|
+
"--node-package-base",
|
|
139
|
+
`${nodePackage}-`,
|
|
140
|
+
"--skip-build",
|
|
141
|
+
]);
|
|
142
|
+
mkdirSync(resolve(output, "npm-facade"), { recursive: true });
|
|
143
|
+
run("npm", ["pack", "--pack-destination", resolve(output, "npm-facade")], facadeDirectory);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const packagePython = ({
|
|
147
|
+
crate,
|
|
148
|
+
library,
|
|
149
|
+
output,
|
|
150
|
+
pythonDirectory,
|
|
151
|
+
pythonTag,
|
|
152
|
+
version,
|
|
153
|
+
cargoTarget,
|
|
154
|
+
os,
|
|
155
|
+
}) => {
|
|
156
|
+
const pythonRoot = resolve(output, "python-root");
|
|
157
|
+
cpSync(resolve(root, pythonDirectory), pythonRoot, { recursive: true });
|
|
158
|
+
const pyproject = join(pythonRoot, "pyproject.toml");
|
|
159
|
+
writable(pyproject);
|
|
160
|
+
writeFileSync(pyproject, replaceVersion(readFileSync(pyproject, "utf8"), version));
|
|
161
|
+
|
|
162
|
+
const packageName = crate.replace(/^dbx-tools-/, "").replaceAll("-", "_");
|
|
163
|
+
const packageDirectory = resolve(pythonRoot, "src", "dbx_tools", packageName);
|
|
164
|
+
const generatedDirectory = mkdtempSync(join(tmpdir(), `${packageName}-python-`));
|
|
165
|
+
const generator = resolve(
|
|
166
|
+
root,
|
|
167
|
+
"target",
|
|
168
|
+
cargoTarget,
|
|
169
|
+
"release",
|
|
170
|
+
`uniffi-bindgen${os === "win32" ? ".exe" : ""}`,
|
|
171
|
+
);
|
|
172
|
+
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
173
|
+
run(generator, ["generate", "--language", "python", "--out-dir", generatedDirectory, library]);
|
|
174
|
+
const bindings = join(packageDirectory, "bindings.py");
|
|
175
|
+
writable(bindings);
|
|
176
|
+
const body = readFileSync(join(generatedDirectory, `${crate.replaceAll("-", "_")}.py`), "utf8");
|
|
177
|
+
writeFileSync(
|
|
178
|
+
bindings,
|
|
179
|
+
[
|
|
180
|
+
"# GENERATED by UniFFI binding generation - DO NOT EDIT.",
|
|
181
|
+
`# Regenerated from the ${crate} Rust exports.`,
|
|
182
|
+
"# Hand edits are overwritten on the next watch; this file is read-only.",
|
|
183
|
+
"",
|
|
184
|
+
body,
|
|
185
|
+
].join("\n"),
|
|
186
|
+
);
|
|
187
|
+
cpSync(library, join(packageDirectory, basename(library)));
|
|
188
|
+
rmSync(generatedDirectory, { recursive: true, force: true });
|
|
189
|
+
|
|
190
|
+
const wheelDirectory = resolve(output, "python");
|
|
191
|
+
mkdirSync(wheelDirectory, { recursive: true });
|
|
192
|
+
run("uv", ["build", "--wheel", "--out-dir", wheelDirectory], pythonRoot);
|
|
193
|
+
const wheels = readdirSync(wheelDirectory).filter((file) => file.endsWith(".whl"));
|
|
194
|
+
if (wheels.length !== 1) throw new Error(`Expected one Python wheel, found ${wheels.length}`);
|
|
195
|
+
run("uvx", [
|
|
196
|
+
"--from",
|
|
197
|
+
"wheel",
|
|
198
|
+
"wheel",
|
|
199
|
+
"tags",
|
|
200
|
+
"--remove",
|
|
201
|
+
"--platform-tag",
|
|
202
|
+
pythonTag,
|
|
203
|
+
join(wheelDirectory, wheels[0]),
|
|
204
|
+
]);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const build = () => {
|
|
208
|
+
const crate = required("crate");
|
|
209
|
+
const cargoTarget = required("cargo-target");
|
|
210
|
+
const nodeTriple = required("node-triple");
|
|
211
|
+
const pythonTag = required("python-tag");
|
|
212
|
+
const version = required("version");
|
|
213
|
+
const os = required("os");
|
|
214
|
+
const cpu = required("cpu");
|
|
215
|
+
const output = resolve(root, required("output"));
|
|
216
|
+
const library = libraryPath(crate, cargoTarget, os);
|
|
217
|
+
|
|
218
|
+
rmSync(output, { recursive: true, force: true });
|
|
219
|
+
mkdirSync(output, { recursive: true });
|
|
220
|
+
if (!parsed.values["skip-build"]) {
|
|
221
|
+
run("cargo", ["build", "--release", "--package", crate, "--target", cargoTarget]);
|
|
222
|
+
}
|
|
223
|
+
if (!existsSync(library)) throw new Error(`Missing native library ${library}`);
|
|
224
|
+
|
|
225
|
+
const nodeDirectory = parsed.values.node;
|
|
226
|
+
const nodePackage = parsed.values["node-package"];
|
|
227
|
+
if (nodeDirectory && nodePackage) {
|
|
228
|
+
packageNode({
|
|
229
|
+
crate,
|
|
230
|
+
library,
|
|
231
|
+
output,
|
|
232
|
+
nodeDirectory,
|
|
233
|
+
nodePackage,
|
|
234
|
+
nodeTriple,
|
|
235
|
+
os,
|
|
236
|
+
cpu,
|
|
237
|
+
version,
|
|
238
|
+
facade: parsed.values.facade === "true",
|
|
239
|
+
nodeGenerator: required("node-generator"),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const pythonDirectory = parsed.values.python;
|
|
244
|
+
if (pythonDirectory) {
|
|
245
|
+
packagePython({
|
|
246
|
+
crate,
|
|
247
|
+
library,
|
|
248
|
+
output,
|
|
249
|
+
pythonDirectory,
|
|
250
|
+
pythonTag,
|
|
251
|
+
version,
|
|
252
|
+
cargoTarget,
|
|
253
|
+
os,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if (parsed.positionals[0] !== "build") throw new Error("Expected build command");
|
|
259
|
+
build();
|
package/tasks/uniffi.ts
CHANGED
|
@@ -23,18 +23,22 @@ import {
|
|
|
23
23
|
|
|
24
24
|
const { values } = parseArgs({
|
|
25
25
|
options: {
|
|
26
|
+
root: { type: "string" },
|
|
26
27
|
crate: { type: "string" },
|
|
27
28
|
node: { type: "string" },
|
|
28
29
|
python: { type: "string" },
|
|
29
30
|
"cargo-target": { type: "string" },
|
|
30
31
|
"node-package-base": { type: "string" },
|
|
32
|
+
"skip-build": { type: "boolean" },
|
|
31
33
|
},
|
|
32
34
|
});
|
|
33
35
|
if (!values.crate || (!values.node && !values.python)) {
|
|
34
36
|
throw new Error("Expected --crate and at least one of --node or --python");
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
const root =
|
|
39
|
+
const root = values.root
|
|
40
|
+
? resolve(values.root)
|
|
41
|
+
: resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
38
42
|
const crate = values.crate;
|
|
39
43
|
const libraryName = crate.replaceAll("-", "_");
|
|
40
44
|
const extension =
|
|
@@ -60,7 +64,8 @@ const run = (command: string, args: string[]) => {
|
|
|
60
64
|
|
|
61
65
|
const replaceGenerated = (source: string, destination: string): void => {
|
|
62
66
|
const deadline = Date.now() + 5_000;
|
|
63
|
-
|
|
67
|
+
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
68
|
+
while (!existsSync(source) && Date.now() < deadline) Atomics.wait(signal, 0, 0, 25);
|
|
64
69
|
if (!existsSync(source)) throw new Error(`Missing generated binding: ${source}`);
|
|
65
70
|
mkdirSync(dirname(destination), { recursive: true });
|
|
66
71
|
makeWritable(destination);
|
|
@@ -90,13 +95,15 @@ const stampGeneratedPython = (file: string): void => {
|
|
|
90
95
|
makeReadonly(file);
|
|
91
96
|
};
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
])
|
|
98
|
+
if (!values["skip-build"]) {
|
|
99
|
+
run("cargo", [
|
|
100
|
+
"build",
|
|
101
|
+
"--release",
|
|
102
|
+
"--package",
|
|
103
|
+
crate,
|
|
104
|
+
...(values["cargo-target"] ? ["--target", values["cargo-target"]] : []),
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
100
107
|
if (!existsSync(library)) throw new Error(`Missing compiled UniFFI library: ${library}`);
|
|
101
108
|
|
|
102
109
|
if (values.node) {
|
package/tasks/uniffi-release.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S bun
|
|
2
|
-
import {
|
|
3
|
-
chmodSync,
|
|
4
|
-
cpSync,
|
|
5
|
-
existsSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readFileSync,
|
|
8
|
-
rmSync,
|
|
9
|
-
statSync,
|
|
10
|
-
writeFileSync,
|
|
11
|
-
} from "node:fs";
|
|
12
|
-
import { dirname, join, resolve } from "node:path";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
-
import { parseArgs } from "node:util";
|
|
15
|
-
import { spawnSync } from "node:child_process";
|
|
16
|
-
|
|
17
|
-
const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
18
|
-
const parsed = parseArgs({
|
|
19
|
-
allowPositionals: true,
|
|
20
|
-
options: {
|
|
21
|
-
crate: { type: "string" },
|
|
22
|
-
rust: { type: "string" },
|
|
23
|
-
node: { type: "string" },
|
|
24
|
-
python: { type: "string" },
|
|
25
|
-
"node-package": { type: "string" },
|
|
26
|
-
"python-package": { type: "string" },
|
|
27
|
-
"cargo-target": { type: "string" },
|
|
28
|
-
"node-triple": { type: "string" },
|
|
29
|
-
"python-tag": { type: "string" },
|
|
30
|
-
os: { type: "string" },
|
|
31
|
-
cpu: { type: "string" },
|
|
32
|
-
libc: { type: "string" },
|
|
33
|
-
facade: { type: "string" },
|
|
34
|
-
version: { type: "string" },
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const required = (name: keyof typeof parsed.values): string => {
|
|
39
|
-
const value = parsed.values[name];
|
|
40
|
-
if (!value) throw new Error(`Missing --${name}`);
|
|
41
|
-
return value;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const run = (command: string, args: string[], cwd = root): void => {
|
|
45
|
-
const result = spawnSync(command, args, { cwd, stdio: "inherit" });
|
|
46
|
-
if (result.status !== 0) throw new Error(`${command} exited with ${result.status}`);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const replaceVersion = (source: string, version: string): string =>
|
|
50
|
-
source.replace(/^version = "[^"]+"$/m, `version = "${version}"`);
|
|
51
|
-
|
|
52
|
-
function build(): void {
|
|
53
|
-
const crate = required("crate");
|
|
54
|
-
required("rust");
|
|
55
|
-
const cargoTarget = required("cargo-target");
|
|
56
|
-
const nodeTriple = required("node-triple");
|
|
57
|
-
const pythonTag = required("python-tag");
|
|
58
|
-
const version = required("version");
|
|
59
|
-
const os = required("os");
|
|
60
|
-
const cpu = required("cpu");
|
|
61
|
-
const nodeDirectory = parsed.values.node;
|
|
62
|
-
const pythonDirectory = parsed.values.python;
|
|
63
|
-
const nodePackage = parsed.values["node-package"];
|
|
64
|
-
const pythonPackage = parsed.values["python-package"];
|
|
65
|
-
const libraryName = crate.replaceAll("-", "_");
|
|
66
|
-
const extension = os === "darwin" ? "dylib" : os === "win32" ? "dll" : "so";
|
|
67
|
-
const prefix = os === "win32" ? "" : "lib";
|
|
68
|
-
const libraryFile = `${prefix}${libraryName}.${extension}`;
|
|
69
|
-
const library = resolve(root, "target", cargoTarget, "release", libraryFile);
|
|
70
|
-
const output = resolve(root, "dist/uniffi");
|
|
71
|
-
|
|
72
|
-
rmSync(output, { recursive: true, force: true });
|
|
73
|
-
mkdirSync(join(output, "npm"), { recursive: true });
|
|
74
|
-
mkdirSync(join(output, "npm-facade"), { recursive: true });
|
|
75
|
-
mkdirSync(join(output, "python"), { recursive: true });
|
|
76
|
-
run("cargo", ["build", "--release", "--package", crate, "--target", cargoTarget]);
|
|
77
|
-
if (!existsSync(library)) throw new Error(`Missing native library ${library}`);
|
|
78
|
-
|
|
79
|
-
if (nodeDirectory && nodePackage) {
|
|
80
|
-
const nativePackage = resolve(output, "native-node");
|
|
81
|
-
mkdirSync(nativePackage, { recursive: true });
|
|
82
|
-
cpSync(library, join(nativePackage, libraryFile));
|
|
83
|
-
writeFileSync(
|
|
84
|
-
join(nativePackage, "package.json"),
|
|
85
|
-
`${JSON.stringify(
|
|
86
|
-
{
|
|
87
|
-
name: `${nodePackage}-${nodeTriple}`,
|
|
88
|
-
version,
|
|
89
|
-
description: `Native ${nodeTriple} library for ${nodePackage}`,
|
|
90
|
-
license: "Apache-2.0",
|
|
91
|
-
os: [os],
|
|
92
|
-
cpu: [cpu],
|
|
93
|
-
...(parsed.values.libc ? { libc: [parsed.values.libc] } : {}),
|
|
94
|
-
files: [libraryFile],
|
|
95
|
-
},
|
|
96
|
-
null,
|
|
97
|
-
2,
|
|
98
|
-
)}\n`,
|
|
99
|
-
);
|
|
100
|
-
run("npm", ["pack", "--pack-destination", resolve(output, "npm")], nativePackage);
|
|
101
|
-
|
|
102
|
-
if (parsed.values.facade === "true") {
|
|
103
|
-
const facade = resolve(output, "facade-node");
|
|
104
|
-
cpSync(resolve(root, nodeDirectory), facade, { recursive: true });
|
|
105
|
-
rmSync(join(facade, "src", libraryFile), { force: true });
|
|
106
|
-
const manifestPath = join(facade, "package.json");
|
|
107
|
-
const manifestMode = statSync(manifestPath).mode;
|
|
108
|
-
chmodSync(manifestPath, manifestMode | 0o200);
|
|
109
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
110
|
-
manifest.version = version;
|
|
111
|
-
manifest.private = false;
|
|
112
|
-
manifest.license = manifest.license === "UNLICENSED" ? "Apache-2.0" : manifest.license;
|
|
113
|
-
manifest.optionalDependencies = Object.fromEntries(
|
|
114
|
-
Object.keys(manifest.optionalDependencies ?? {}).map((name) => [name, version]),
|
|
115
|
-
);
|
|
116
|
-
manifest.dependencies = Object.fromEntries(
|
|
117
|
-
Object.entries(manifest.dependencies ?? {}).map(([name, dependency]) => [
|
|
118
|
-
name,
|
|
119
|
-
typeof dependency === "string" && dependency.startsWith("workspace:")
|
|
120
|
-
? version
|
|
121
|
-
: dependency,
|
|
122
|
-
]),
|
|
123
|
-
);
|
|
124
|
-
delete manifest.scripts;
|
|
125
|
-
delete manifest.devDependencies;
|
|
126
|
-
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
127
|
-
const generator = resolve(root, "node_modules/@dbx-tools/projen/tasks/uniffi.ts");
|
|
128
|
-
run("bun", [
|
|
129
|
-
generator,
|
|
130
|
-
"--crate",
|
|
131
|
-
crate,
|
|
132
|
-
"--node",
|
|
133
|
-
facade,
|
|
134
|
-
"--cargo-target",
|
|
135
|
-
cargoTarget,
|
|
136
|
-
"--node-package-base",
|
|
137
|
-
`${nodePackage}-`,
|
|
138
|
-
]);
|
|
139
|
-
run("npm", ["pack", "--pack-destination", resolve(output, "npm-facade")], facade);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (pythonDirectory && pythonPackage) {
|
|
144
|
-
const pythonRoot = resolve(output, "python-root");
|
|
145
|
-
cpSync(resolve(root, pythonDirectory), pythonRoot, { recursive: true });
|
|
146
|
-
const pyproject = join(pythonRoot, "pyproject.toml");
|
|
147
|
-
const mode = statSync(pyproject).mode;
|
|
148
|
-
chmodSync(pyproject, mode | 0o200);
|
|
149
|
-
writeFileSync(pyproject, replaceVersion(readFileSync(pyproject, "utf8"), version));
|
|
150
|
-
const generator = resolve(root, "node_modules/@dbx-tools/projen/tasks/uniffi.ts");
|
|
151
|
-
run("bun", [
|
|
152
|
-
generator,
|
|
153
|
-
"--crate",
|
|
154
|
-
crate,
|
|
155
|
-
"--python",
|
|
156
|
-
pythonRoot,
|
|
157
|
-
"--cargo-target",
|
|
158
|
-
cargoTarget,
|
|
159
|
-
]);
|
|
160
|
-
run("uv", ["build", "--wheel", "--out-dir", resolve(output, "python")], pythonRoot);
|
|
161
|
-
const wheels = [...new Bun.Glob("*.whl").scanSync(join(output, "python"))];
|
|
162
|
-
if (wheels.length !== 1) throw new Error(`Expected one Python wheel, found ${wheels.length}`);
|
|
163
|
-
run("uvx", ["--from", "wheel", "wheel", "tags", "--remove", "--platform-tag", pythonTag, join(output, "python", wheels[0]!)]);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (parsed.positionals[0] !== "build") throw new Error("Expected build command");
|
|
168
|
-
build();
|