@dbx-tools/projen 0.6.161 → 0.6.168
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 +34 -4
- package/index.ts +4 -1
- package/package.json +4 -4
- package/src/bun-workflow.ts +145 -0
- package/src/project-js.ts +40 -22
- package/src/project-py.ts +10 -7
- package/src/project-rs.ts +167 -46
- package/src/release.ts +6 -4
- package/src/uniffi.ts +8 -0
- package/tasks/rust.ts +17 -3
- package/tasks/uniffi-release.mjs +139 -9
- package/tasks/uniffi.ts +83 -53
package/src/project-rs.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { project as coreProject } from "@dbx-tools/core";
|
|
6
6
|
import { string } from "@dbx-tools/shared-core";
|
|
7
7
|
import { Project, TextFile, YamlFile, javascript } from "projen";
|
|
8
|
+
import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
|
|
8
9
|
import type { DBXToolsProject } from "./project.ts";
|
|
9
10
|
import {
|
|
10
11
|
DBXToolsTypeScriptProject,
|
|
@@ -27,6 +28,7 @@ export interface CargoDependencyOptions {
|
|
|
27
28
|
readonly workspace?: boolean;
|
|
28
29
|
readonly path?: string;
|
|
29
30
|
readonly optional?: boolean;
|
|
31
|
+
readonly package?: string;
|
|
30
32
|
readonly defaultFeatures?: boolean;
|
|
31
33
|
readonly features?: readonly string[];
|
|
32
34
|
}
|
|
@@ -155,6 +157,8 @@ export const UNIFFI_RELEASE_TARGETS: readonly UniFFIReleaseTarget[] = [
|
|
|
155
157
|
] as const;
|
|
156
158
|
|
|
157
159
|
const RUST_CACHE_ENV = {
|
|
160
|
+
CARGO_INCREMENTAL: "0",
|
|
161
|
+
CARGO_TERM_COLOR: "always",
|
|
158
162
|
RUSTC_WRAPPER: "sccache",
|
|
159
163
|
SCCACHE_GHA_ENABLED: "true",
|
|
160
164
|
} as const;
|
|
@@ -202,8 +206,9 @@ function releaseTargets(options: DBXToolsRustWorkspaceOptions): readonly UniFFIR
|
|
|
202
206
|
const target = UNIFFI_RELEASE_TARGETS.find(
|
|
203
207
|
(candidate) => candidate.os === platform.os && candidate.cpu === platform.cpu,
|
|
204
208
|
);
|
|
205
|
-
if (!target)
|
|
209
|
+
if (!target) {
|
|
206
210
|
throw new Error(`Unsupported Rust release platform: ${platform.os}-${platform.cpu}`);
|
|
211
|
+
}
|
|
207
212
|
return target;
|
|
208
213
|
});
|
|
209
214
|
}
|
|
@@ -246,6 +251,7 @@ export interface RustBindingMapping {
|
|
|
246
251
|
readonly pythonPackage?: string;
|
|
247
252
|
readonly pythonModule?: string;
|
|
248
253
|
readonly facadeTarget?: boolean;
|
|
254
|
+
readonly dependencies?: readonly string[];
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
/** Persisted Rust workspace state consumed by `sync --watch`. */
|
|
@@ -256,6 +262,28 @@ export interface RustWorkspaceMapping {
|
|
|
256
262
|
readonly releaseWorkflow?: string;
|
|
257
263
|
}
|
|
258
264
|
|
|
265
|
+
export function orderRustBindings(bindings: readonly RustBindingMapping[]): RustBindingMapping[] {
|
|
266
|
+
const ordered: RustBindingMapping[] = [];
|
|
267
|
+
const visiting = new Set<string>();
|
|
268
|
+
const completed = new Set<string>();
|
|
269
|
+
const visit = (binding: RustBindingMapping): void => {
|
|
270
|
+
if (completed.has(binding.crate)) return;
|
|
271
|
+
if (visiting.has(binding.crate))
|
|
272
|
+
throw new Error(`Cyclic Rust binding dependency: ${binding.crate}`);
|
|
273
|
+
visiting.add(binding.crate);
|
|
274
|
+
for (const name of binding.dependencies ?? []) {
|
|
275
|
+
const dependency = bindings.find((candidate) => candidate.crate === name);
|
|
276
|
+
if (!dependency) throw new Error(`Missing Rust binding dependency: ${name}`);
|
|
277
|
+
visit(dependency);
|
|
278
|
+
}
|
|
279
|
+
visiting.delete(binding.crate);
|
|
280
|
+
completed.add(binding.crate);
|
|
281
|
+
ordered.push(binding);
|
|
282
|
+
};
|
|
283
|
+
for (const binding of bindings) visit(binding);
|
|
284
|
+
return ordered;
|
|
285
|
+
}
|
|
286
|
+
|
|
259
287
|
function rustSources(directory: string): string[] {
|
|
260
288
|
if (!existsSync(directory)) return [];
|
|
261
289
|
const files: string[] = [];
|
|
@@ -293,6 +321,7 @@ function cargoDependency(
|
|
|
293
321
|
...(!value.version && value.path && workspaceVersion ? { version: workspaceVersion } : {}),
|
|
294
322
|
...(value.workspace ? { workspace: true } : {}),
|
|
295
323
|
...(value.path ? { path: value.path } : {}),
|
|
324
|
+
...(value.package ? { package: value.package } : {}),
|
|
296
325
|
...(value.optional ? { optional: true } : {}),
|
|
297
326
|
...(value.defaultFeatures === false ? { "default-features": false } : {}),
|
|
298
327
|
...(value.features?.length ? { features: [...value.features] } : {}),
|
|
@@ -365,7 +394,7 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
|
|
|
365
394
|
...(this.uniffi || binary
|
|
366
395
|
? {
|
|
367
396
|
bin: this.uniffi
|
|
368
|
-
? { name:
|
|
397
|
+
? { name: `${crateName}-uniffi-bindgen`, path: "uniffi-bindgen.rs" }
|
|
369
398
|
: { name: binaryName, path: "src/main.rs" },
|
|
370
399
|
}
|
|
371
400
|
: {}),
|
|
@@ -446,27 +475,75 @@ export class DBXToolsRustWorkspace {
|
|
|
446
475
|
);
|
|
447
476
|
|
|
448
477
|
const bindings = this.packages.filter((pkg) => pkg.uniffi);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
478
|
+
const bindingDependencies = (pkg: DBXToolsRustProject, language: "node" | "python") =>
|
|
479
|
+
bindings.filter(
|
|
480
|
+
(dependency) =>
|
|
481
|
+
dependency !== pkg &&
|
|
482
|
+
(dependency.packageOptions.bindings ?? ["node", "python"]).includes(language) &&
|
|
483
|
+
Object.entries(pkg.packageOptions.dependencies ?? {}).some(([name, value]) => {
|
|
484
|
+
const resolved =
|
|
485
|
+
typeof value === "object" && value.workspace
|
|
486
|
+
? (options.workspaceDependencies?.[name] ?? value)
|
|
487
|
+
: value;
|
|
488
|
+
return (
|
|
489
|
+
name === dependency.crateName ||
|
|
490
|
+
(typeof resolved === "object" &&
|
|
491
|
+
(resolved.package === dependency.crateName ||
|
|
492
|
+
(resolved.path !== undefined &&
|
|
493
|
+
resolve(
|
|
494
|
+
typeof value === "object" && value.workspace ? project.outdir : pkg.outdir,
|
|
495
|
+
resolved.path,
|
|
496
|
+
) === dependency.outdir)))
|
|
497
|
+
);
|
|
498
|
+
}),
|
|
499
|
+
);
|
|
500
|
+
this.bindingMappings = orderRustBindings(
|
|
501
|
+
bindings.map((pkg) => {
|
|
502
|
+
const targets = pkg.packageOptions.bindings ?? ["node", "python"];
|
|
503
|
+
const packageName = pkg.packageOptions.directory.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
504
|
+
const dependencies = [
|
|
505
|
+
...new Set([...bindingDependencies(pkg, "node"), ...bindingDependencies(pkg, "python")]),
|
|
506
|
+
].map((dependency) => dependency.crateName);
|
|
507
|
+
return {
|
|
508
|
+
crate: pkg.crateName,
|
|
509
|
+
...(dependencies.length ? { dependencies } : {}),
|
|
510
|
+
rust: `${root}/${pkg.packageOptions.directory}`,
|
|
511
|
+
...(targets.includes("node")
|
|
512
|
+
? {
|
|
513
|
+
node: `${nodeRoot}/${pkg.packageOptions.directory}`,
|
|
514
|
+
nodePackage: `@${scope}/${packageName}`,
|
|
515
|
+
}
|
|
516
|
+
: {}),
|
|
517
|
+
...(targets.includes("python")
|
|
518
|
+
? {
|
|
519
|
+
python: `${options.pythonRoot ?? "packages/py"}/${pkg.packageOptions.directory}`,
|
|
520
|
+
pythonPackage: pkg.crateName,
|
|
521
|
+
pythonModule: pythonModuleName(pythonModulePrefix, pkg.packageOptions.directory),
|
|
522
|
+
}
|
|
523
|
+
: {}),
|
|
524
|
+
};
|
|
525
|
+
}),
|
|
526
|
+
);
|
|
527
|
+
for (const pkg of bindings) {
|
|
528
|
+
const dependencies = bindingDependencies(pkg, "python");
|
|
529
|
+
if (dependencies.length === 0) continue;
|
|
530
|
+
pkg.tryRemoveFile("uniffi.toml");
|
|
531
|
+
new TextFile(pkg, "uniffi.toml", {
|
|
532
|
+
lines: renderToml({
|
|
533
|
+
"bindings.python": { cdylib_name: pkg.crateName.replaceAll("-", "_") },
|
|
534
|
+
"bindings.typescript": { strictTypeChecking: true },
|
|
535
|
+
...pkg.packageOptions.uniffiConfig,
|
|
536
|
+
"bindings.python.external_packages": Object.fromEntries(
|
|
537
|
+
dependencies.map((dependency) => [
|
|
538
|
+
dependency.crateName.replaceAll("-", "_"),
|
|
539
|
+
`${pythonModuleName(pythonModulePrefix, dependency.packageOptions.directory)}.bindings`,
|
|
540
|
+
]),
|
|
541
|
+
),
|
|
542
|
+
})
|
|
543
|
+
.trimEnd()
|
|
544
|
+
.split("\n"),
|
|
545
|
+
});
|
|
546
|
+
}
|
|
470
547
|
const releaseEnabled =
|
|
471
548
|
(options.release ?? true) &&
|
|
472
549
|
(this.bindingMappings.length > 0 ||
|
|
@@ -483,13 +560,7 @@ export class DBXToolsRustWorkspace {
|
|
|
483
560
|
project.gitignore.addPatterns(
|
|
484
561
|
"target/",
|
|
485
562
|
...this.bindingMappings.flatMap((binding) => [
|
|
486
|
-
...(binding.node
|
|
487
|
-
? [
|
|
488
|
-
`${binding.node}/src/bindings.ts`,
|
|
489
|
-
`${binding.node}/src/_bindings*.ts`,
|
|
490
|
-
`${binding.node}/src/*${binding.crate.replaceAll("-", "_")}.*`,
|
|
491
|
-
]
|
|
492
|
-
: []),
|
|
563
|
+
...(binding.node ? [`${binding.node}/src/*${binding.crate.replaceAll("-", "_")}.*`] : []),
|
|
493
564
|
...(binding.python && binding.pythonModule
|
|
494
565
|
? [
|
|
495
566
|
`${binding.python}/src/${binding.pythonModule.replaceAll(".", "/")}/bindings.py`,
|
|
@@ -498,6 +569,13 @@ export class DBXToolsRustWorkspace {
|
|
|
498
569
|
: []),
|
|
499
570
|
]),
|
|
500
571
|
);
|
|
572
|
+
for (const binding of this.bindingMappings) {
|
|
573
|
+
if (!binding.node) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
project.prettier?.addIgnorePattern(`${binding.node}/src/bindings.ts`);
|
|
577
|
+
project.prettier?.addIgnorePattern(`${binding.node}/src/_bindings*.ts`);
|
|
578
|
+
}
|
|
501
579
|
this.pythonPackages = bindings
|
|
502
580
|
.filter((pkg) => (pkg.packageOptions.bindings ?? ["node", "python"]).includes("python"))
|
|
503
581
|
.map((pkg) => {
|
|
@@ -508,6 +586,9 @@ export class DBXToolsRustWorkspace {
|
|
|
508
586
|
module,
|
|
509
587
|
description: `Python bindings for ${pkg.crateName}`,
|
|
510
588
|
private: true,
|
|
589
|
+
internalDependencies: bindingDependencies(pkg, "python").map(
|
|
590
|
+
(dependency) => dependency.packageOptions.directory,
|
|
591
|
+
),
|
|
511
592
|
generatedSources: [`src/${module.replaceAll(".", "/")}/bindings.py`],
|
|
512
593
|
trustedPublisher: {
|
|
513
594
|
workflowName: options.releaseWorkflowName ?? "rust-release",
|
|
@@ -543,7 +624,6 @@ export class DBXToolsRustWorkspace {
|
|
|
543
624
|
`@${scope}/${directory.toLowerCase().replace(/[^a-z0-9-]+/g, "-")}`,
|
|
544
625
|
);
|
|
545
626
|
node.package.addField("private", true);
|
|
546
|
-
node.package.file.addDeletionOverride("publishConfig");
|
|
547
627
|
node.package.addField("description", `Node bindings for ${binding.crateName}`);
|
|
548
628
|
const nativeTargets = releaseTargets(options);
|
|
549
629
|
if (options.release ?? true) {
|
|
@@ -558,6 +638,11 @@ export class DBXToolsRustWorkspace {
|
|
|
558
638
|
);
|
|
559
639
|
}
|
|
560
640
|
node.addDeps("@ubjs/core@0.31.0-5", "@ubjs/node@0.31.0-5");
|
|
641
|
+
node.addDeps(
|
|
642
|
+
...bindingDependencies(binding, "node").map(
|
|
643
|
+
(dependency) => `@${scope}/${dependency.packageOptions.directory}@workspace:*`,
|
|
644
|
+
),
|
|
645
|
+
);
|
|
561
646
|
if (binding.packageOptions.nodeDependencies?.length) {
|
|
562
647
|
node.addDeps(...binding.packageOptions.nodeDependencies);
|
|
563
648
|
}
|
|
@@ -565,7 +650,6 @@ export class DBXToolsRustWorkspace {
|
|
|
565
650
|
if (binding.packageOptions.nodeDevDependencies?.length) {
|
|
566
651
|
node.addDevDeps(...binding.packageOptions.nodeDevDependencies);
|
|
567
652
|
}
|
|
568
|
-
node.compileTask.reset();
|
|
569
653
|
new TextFile(node, "exports.ts", {
|
|
570
654
|
lines: ['export * from "./src/bindings.ts";', ""],
|
|
571
655
|
});
|
|
@@ -606,10 +690,13 @@ export class DBXToolsRustWorkspace {
|
|
|
606
690
|
project.addTask("rs:lint", { exec: "cargo clippy --workspace --all-targets --all-features" });
|
|
607
691
|
project.addTask("rs:test", { exec: "cargo test --workspace" });
|
|
608
692
|
project.addTask("rs:build", { exec: "cargo build --workspace" });
|
|
609
|
-
project.addTask("rs:bindings", {
|
|
693
|
+
const bindingsTask = project.addTask("rs:bindings", {
|
|
610
694
|
exec: "bun node_modules/@dbx-tools/projen/tasks/rust.ts",
|
|
611
695
|
description: "Generate language bindings for UniFFI-enabled Rust crates",
|
|
612
696
|
});
|
|
697
|
+
if (this.bindingMappings.some((binding) => binding.node)) {
|
|
698
|
+
project.tasks.tryFind("pre-compile")?.spawn(bindingsTask);
|
|
699
|
+
}
|
|
613
700
|
project.addTask("rs:bindings:demo", {
|
|
614
701
|
description: "Generate and run UniFFI Node and Python example CLIs",
|
|
615
702
|
exec: [
|
|
@@ -661,12 +748,20 @@ export class DBXToolsRustWorkspace {
|
|
|
661
748
|
}));
|
|
662
749
|
const publicCrates = this.packages
|
|
663
750
|
.filter((pkg) => !pkg.packageOptions.private)
|
|
751
|
+
.sort((first, second) => {
|
|
752
|
+
const order = this.bindingMappings.map((binding) => binding.crate);
|
|
753
|
+
return order.indexOf(first.crateName) - order.indexOf(second.crateName);
|
|
754
|
+
})
|
|
664
755
|
.map((pkg) => pkg.crateName);
|
|
665
756
|
const targetMatrix = targets.map((target, index) => ({
|
|
666
757
|
target: { ...target, facade: index === 0 },
|
|
667
758
|
}));
|
|
668
759
|
const hasNodeBindings = bindings.some((binding) => binding.node);
|
|
669
760
|
const hasPythonBindings = bindings.some((binding) => binding.python);
|
|
761
|
+
const facadeCondition = "matrix.target.facade";
|
|
762
|
+
const facadeCacheMissCondition =
|
|
763
|
+
"matrix.target.facade && steps.ubrn_cache.outputs.cache-hit != 'true'";
|
|
764
|
+
const usePreinstalledWindowsRust = releaseRustVersion === "stable";
|
|
670
765
|
const hasTargetOutputs =
|
|
671
766
|
bindings.length > 0 || releaseBinaries.length > 0 || publicCrates.length > 0;
|
|
672
767
|
if (hasTargetOutputs && targetMatrix.length === 0) {
|
|
@@ -766,6 +861,7 @@ export class DBXToolsRustWorkspace {
|
|
|
766
861
|
"runs-on": "${{ matrix.target.runner }}",
|
|
767
862
|
env: {
|
|
768
863
|
...RUST_CACHE_ENV,
|
|
864
|
+
BUN_VERSION,
|
|
769
865
|
SCCACHE_GHA_VERSION: `release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`,
|
|
770
866
|
},
|
|
771
867
|
strategy: {
|
|
@@ -774,27 +870,35 @@ export class DBXToolsRustWorkspace {
|
|
|
774
870
|
},
|
|
775
871
|
steps: [
|
|
776
872
|
...releaseSourceSteps(),
|
|
777
|
-
...(hasNodeBindings
|
|
778
|
-
? [
|
|
779
|
-
{
|
|
780
|
-
name: "Setup Bun",
|
|
781
|
-
uses: "oven-sh/setup-bun@v2",
|
|
782
|
-
with: { "bun-version": "1.3.14" },
|
|
783
|
-
},
|
|
784
|
-
]
|
|
785
|
-
: []),
|
|
786
873
|
...(hasPythonBindings ? [{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" }] : []),
|
|
787
874
|
{
|
|
788
875
|
name: "Setup Rust",
|
|
876
|
+
...(usePreinstalledWindowsRust ? { if: "${{ matrix.target.os != 'win32' }}" } : {}),
|
|
789
877
|
uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
|
|
790
878
|
with: { targets: "${{ matrix.target.cargo }}" },
|
|
791
879
|
},
|
|
880
|
+
...(usePreinstalledWindowsRust
|
|
881
|
+
? [
|
|
882
|
+
{
|
|
883
|
+
name: "Verify preinstalled Windows Rust",
|
|
884
|
+
if: "${{ matrix.target.os == 'win32' }}",
|
|
885
|
+
shell: "bash",
|
|
886
|
+
run: [
|
|
887
|
+
"rustc --version --verbose",
|
|
888
|
+
"cargo --version",
|
|
889
|
+
'rustup target list --installed | grep -Fx "${{ matrix.target.cargo }}"',
|
|
890
|
+
'test -f "$(rustc --print sysroot)/lib/rustlib/${{ matrix.target.cargo }}/bin/rust-lld.exe"',
|
|
891
|
+
].join("\n"),
|
|
892
|
+
},
|
|
893
|
+
]
|
|
894
|
+
: []),
|
|
792
895
|
...rustCacheSteps(`release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`),
|
|
793
896
|
...(hasNodeBindings
|
|
794
897
|
? [
|
|
795
898
|
{
|
|
796
899
|
name: "Restore UBRN executable",
|
|
797
900
|
id: "ubrn_cache",
|
|
901
|
+
if: "${{ matrix.target.facade }}",
|
|
798
902
|
uses: "actions/cache/restore@v5",
|
|
799
903
|
with: {
|
|
800
904
|
path: ".cache/ubrn",
|
|
@@ -803,6 +907,12 @@ export class DBXToolsRustWorkspace {
|
|
|
803
907
|
},
|
|
804
908
|
]
|
|
805
909
|
: []),
|
|
910
|
+
...(hasNodeBindings
|
|
911
|
+
? bunCacheRestoreSteps(project, {
|
|
912
|
+
setupCondition: facadeCondition,
|
|
913
|
+
condition: facadeCacheMissCondition,
|
|
914
|
+
})
|
|
915
|
+
: []),
|
|
806
916
|
{
|
|
807
917
|
name: "Log cache configuration",
|
|
808
918
|
shell: "bash",
|
|
@@ -829,17 +939,20 @@ export class DBXToolsRustWorkspace {
|
|
|
829
939
|
? [
|
|
830
940
|
{
|
|
831
941
|
name: "Install Node tooling",
|
|
832
|
-
if: "${{ steps.ubrn_cache.outputs.cache-hit != 'true' }}",
|
|
942
|
+
if: "${{ matrix.target.facade && steps.ubrn_cache.outputs.cache-hit != 'true' }}",
|
|
833
943
|
shell: "bash",
|
|
834
944
|
run: timedBash("node_tooling", "bun install"),
|
|
835
945
|
},
|
|
946
|
+
bunCacheSaveStep({
|
|
947
|
+
condition: facadeCacheMissCondition,
|
|
948
|
+
}),
|
|
836
949
|
]
|
|
837
950
|
: []),
|
|
838
951
|
...(hasNodeBindings
|
|
839
952
|
? [
|
|
840
953
|
{
|
|
841
954
|
name: "Prepare UBRN generator",
|
|
842
|
-
if: "${{ steps.ubrn_cache.outputs.cache-hit != 'true' }}",
|
|
955
|
+
if: "${{ matrix.target.facade && steps.ubrn_cache.outputs.cache-hit != 'true' }}",
|
|
843
956
|
env: {
|
|
844
957
|
CARGO_TARGET_DIR: "${{ github.workspace }}/target/ubrn",
|
|
845
958
|
UBRN_EXECUTABLE: ubrnExecutable,
|
|
@@ -857,13 +970,14 @@ export class DBXToolsRustWorkspace {
|
|
|
857
970
|
},
|
|
858
971
|
{
|
|
859
972
|
name: "Verify UBRN executable",
|
|
973
|
+
if: "${{ matrix.target.facade }}",
|
|
860
974
|
env: { UBRN_EXECUTABLE: ubrnExecutable },
|
|
861
975
|
shell: "bash",
|
|
862
976
|
run: 'test -f "$UBRN_EXECUTABLE"',
|
|
863
977
|
},
|
|
864
978
|
{
|
|
865
979
|
name: "Save UBRN executable",
|
|
866
|
-
if: "${{ steps.ubrn_cache.outputs.cache-hit != 'true' }}",
|
|
980
|
+
if: "${{ matrix.target.facade && steps.ubrn_cache.outputs.cache-hit != 'true' }}",
|
|
867
981
|
uses: "actions/cache/save@v5",
|
|
868
982
|
with: {
|
|
869
983
|
path: ".cache/ubrn",
|
|
@@ -875,6 +989,10 @@ export class DBXToolsRustWorkspace {
|
|
|
875
989
|
{
|
|
876
990
|
name: "Build Rust outputs",
|
|
877
991
|
shell: "bash",
|
|
992
|
+
env: {
|
|
993
|
+
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER:
|
|
994
|
+
"${{ matrix.target.os == 'win32' && 'rust-lld' || '' }}",
|
|
995
|
+
},
|
|
878
996
|
run: timedBash(
|
|
879
997
|
"rust_workspace",
|
|
880
998
|
'cargo build --release --workspace --target "${{ matrix.target.cargo }}"',
|
|
@@ -920,7 +1038,10 @@ export class DBXToolsRustWorkspace {
|
|
|
920
1038
|
`publish-${binding.crate}`,
|
|
921
1039
|
{
|
|
922
1040
|
if: "${{ github.event_name == 'repository_dispatch' }}",
|
|
923
|
-
needs: [
|
|
1041
|
+
needs: [
|
|
1042
|
+
"build",
|
|
1043
|
+
...(binding.dependencies ?? []).map((dependency) => `publish-${dependency}`),
|
|
1044
|
+
],
|
|
924
1045
|
"runs-on": "ubuntu-latest",
|
|
925
1046
|
permissions: {
|
|
926
1047
|
contents: "read",
|
package/src/release.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { Component, YamlFile } from "projen";
|
|
|
10
10
|
import { GithubWorkflow } from "projen/lib/github";
|
|
11
11
|
import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
|
|
12
12
|
import { object } from "@dbx-tools/shared-core";
|
|
13
|
+
import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
|
|
13
14
|
import { applyTasks, taskScript, type DBXToolsNodeProject } from "./project.ts";
|
|
14
15
|
import {
|
|
15
16
|
DOWNSTREAM_RELEASE_EVENT,
|
|
@@ -21,7 +22,6 @@ import {
|
|
|
21
22
|
|
|
22
23
|
const NODE_VERSION = "lts/*";
|
|
23
24
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
24
|
-
const BUN_VERSION = "1.3.14";
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* The `release` workflow's version-stamp + publish step, as a shell script.
|
|
@@ -73,10 +73,10 @@ interface PublishWorkflow {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/** Shared checkout and toolchain setup for every npm publish workflow. */
|
|
76
|
-
function publishSetupSteps(): JobStep[] {
|
|
76
|
+
function publishSetupSteps(project: DBXToolsNodeProject): JobStep[] {
|
|
77
77
|
return [
|
|
78
78
|
{ name: "Checkout", uses: "actions/checkout@v6", with: { "fetch-depth": 0 } },
|
|
79
|
-
|
|
79
|
+
...bunCacheRestoreSteps(project),
|
|
80
80
|
{
|
|
81
81
|
name: "Setup Node.js",
|
|
82
82
|
uses: "actions/setup-node@v6",
|
|
@@ -88,6 +88,7 @@ function publishSetupSteps(): JobStep[] {
|
|
|
88
88
|
},
|
|
89
89
|
// Bun's install; the lockfile may be absent or stale in CI so it is not frozen.
|
|
90
90
|
{ name: "Install", run: "bun install" },
|
|
91
|
+
bunCacheSaveStep(),
|
|
91
92
|
];
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -301,7 +302,7 @@ export class DBXToolsRelease extends Component {
|
|
|
301
302
|
project: DBXToolsNodeProject,
|
|
302
303
|
{ name, tagPrefix, steps, workingDirectory, upstreamWorkflow }: PublishWorkflow,
|
|
303
304
|
): void {
|
|
304
|
-
const setupSteps = publishSetupSteps();
|
|
305
|
+
const setupSteps = publishSetupSteps(project);
|
|
305
306
|
const branchDispatch = upstreamWorkflow === undefined;
|
|
306
307
|
const workflow = new GithubWorkflow(project.github!, name, {
|
|
307
308
|
// A newer release supersedes an older run even when publication has
|
|
@@ -369,6 +370,7 @@ export class DBXToolsRelease extends Component {
|
|
|
369
370
|
// `DRY_RUN_INPUT` is `--dry-run` when the dispatch input is true, else empty;
|
|
370
371
|
// the publish script also FORCES it on any `workflow_dispatch` run.
|
|
371
372
|
env: {
|
|
373
|
+
BUN_VERSION,
|
|
372
374
|
CI: "true",
|
|
373
375
|
DRY_RUN_INPUT: "${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}",
|
|
374
376
|
},
|
package/src/uniffi.ts
CHANGED
|
@@ -36,6 +36,14 @@ export interface TypeScriptBindingModule {
|
|
|
36
36
|
readonly source: string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Add source extensions to UBRN's generated relative binding imports. */
|
|
40
|
+
export const addTypeScriptExtensionsToBindingImports = (source: string): string =>
|
|
41
|
+
source.replace(
|
|
42
|
+
/(["'])\.\/_bindings(-ffi)?\1/g,
|
|
43
|
+
(_specifier, quote: string, suffix: string | undefined) =>
|
|
44
|
+
`${quote}./_bindings${suffix ?? ""}.ts${quote}`,
|
|
45
|
+
);
|
|
46
|
+
|
|
39
47
|
/** Add explicit type exports for interfaces that TypeScript misses through UBRN's star exports. */
|
|
40
48
|
export const addExplicitInterfaceReexports = (
|
|
41
49
|
facade: string,
|
package/tasks/rust.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
|
|
|
8
8
|
import {
|
|
9
9
|
discoverRustCrates,
|
|
10
10
|
hasUniFFIBindings,
|
|
11
|
+
orderRustBindings,
|
|
11
12
|
type RustBindingMapping,
|
|
12
13
|
type RustWorkspaceMapping,
|
|
13
14
|
} from "../src/project-rs.ts";
|
|
@@ -84,6 +85,19 @@ function generate(binding: RustBindingMapping): void {
|
|
|
84
85
|
if (result.status !== 0) throw new Error(`binding generation exited with ${result.status}`);
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
export function affectedRustBindings(
|
|
89
|
+
bindings: readonly RustBindingMapping[],
|
|
90
|
+
changed: ReadonlySet<string>,
|
|
91
|
+
): RustBindingMapping[] {
|
|
92
|
+
const affected = new Set(changed);
|
|
93
|
+
const ordered = orderRustBindings(bindings);
|
|
94
|
+
for (const binding of ordered) {
|
|
95
|
+
if (binding.dependencies?.some((dependency) => affected.has(dependency)))
|
|
96
|
+
affected.add(binding.crate);
|
|
97
|
+
}
|
|
98
|
+
return ordered.filter((binding) => affected.has(binding.crate));
|
|
99
|
+
}
|
|
100
|
+
|
|
87
101
|
const config = rustConfig();
|
|
88
102
|
|
|
89
103
|
async function main(): Promise<void> {
|
|
@@ -92,7 +106,7 @@ async function main(): Promise<void> {
|
|
|
92
106
|
throw new Error("Cargo is required because Rust projects were detected");
|
|
93
107
|
}
|
|
94
108
|
if (!process.argv.includes("--watch")) {
|
|
95
|
-
for (const binding of config.bindings) generate(binding);
|
|
109
|
+
for (const binding of orderRustBindings(config.bindings)) generate(binding);
|
|
96
110
|
return;
|
|
97
111
|
}
|
|
98
112
|
|
|
@@ -109,7 +123,7 @@ async function main(): Promise<void> {
|
|
|
109
123
|
const binding = ownerBinding(path, refreshed.bindings);
|
|
110
124
|
if (binding) targets.set(binding.crate, binding);
|
|
111
125
|
}
|
|
112
|
-
for (const binding of targets.
|
|
126
|
+
for (const binding of affectedRustBindings(refreshed.bindings, new Set(targets.keys()))) {
|
|
113
127
|
logger.start(`generating ${binding.crate} bindings`);
|
|
114
128
|
generate(binding);
|
|
115
129
|
logger.success(`generated ${binding.crate} bindings`);
|
|
@@ -121,7 +135,7 @@ async function main(): Promise<void> {
|
|
|
121
135
|
const binding = ownerBinding(path, latest.bindings);
|
|
122
136
|
if (binding) targets.set(binding.crate, binding);
|
|
123
137
|
}
|
|
124
|
-
for (const binding of targets.
|
|
138
|
+
for (const binding of affectedRustBindings(latest.bindings, new Set(targets.keys()))) {
|
|
125
139
|
logger.start(`generating ${binding.crate} bindings`);
|
|
126
140
|
generate(binding);
|
|
127
141
|
logger.success(`generated ${binding.crate} bindings`);
|