@dbx-tools/projen 0.6.163 → 0.6.174

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/src/project-rs.ts CHANGED
@@ -5,14 +5,14 @@ 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";
9
- import type { DBXToolsProject } from "./project.ts";
10
8
  import {
11
9
  DBXToolsTypeScriptProject,
12
10
  projectReleaseBranch,
13
11
  projectRepositoryUrl,
14
12
  } from "./project-js.ts";
13
+ import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
15
14
  import { pythonModuleName, type PythonPackageOptions } from "./project-py.ts";
15
+ import type { DBXToolsProject } from "./project.ts";
16
16
  import {
17
17
  DOWNSTREAM_RELEASE_EVENT,
18
18
  RELEASE_SHA,
@@ -21,13 +21,13 @@ import {
21
21
  releaseSourceSteps,
22
22
  } from "./release-dispatch.ts";
23
23
  import { readWorkspaceVersion } from "./workspace-version.ts";
24
- import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
25
24
 
26
25
  export interface CargoDependencyOptions {
27
26
  readonly version?: string;
28
27
  readonly workspace?: boolean;
29
28
  readonly path?: string;
30
29
  readonly optional?: boolean;
30
+ readonly package?: string;
31
31
  readonly defaultFeatures?: boolean;
32
32
  readonly features?: readonly string[];
33
33
  }
@@ -61,8 +61,6 @@ export interface DBXToolsRustWorkspaceOptions {
61
61
  readonly rustVersion?: string;
62
62
  /** Rust toolchain used by release builds. Defaults to `stable`. */
63
63
  readonly releaseRustVersion?: string;
64
- /** Rust toolchain used to compile the host-side UBRN generator. Defaults to releaseRustVersion. */
65
- readonly ubrnRustVersion?: string;
66
64
  readonly license?: string;
67
65
  readonly repository?: string;
68
66
  readonly workspaceDependencies?: Readonly<Record<string, CargoDependency>>;
@@ -79,6 +77,8 @@ export interface DBXToolsRustWorkspaceOptions {
79
77
  readonly releasePlatforms?: readonly RustReleasePlatform[];
80
78
  /** Workflow name used by downstream release stages. Defaults to `rust-release`. */
81
79
  readonly releaseWorkflowName?: string;
80
+ /** Workflow that publishes generated Python wheels. Defaults to `python-release`. */
81
+ readonly pythonReleaseWorkflowName?: string;
82
82
  /** Tag prefix dispatched into the branch-scoped release workflow. Defaults to `v`. */
83
83
  readonly releaseTagPrefix?: string;
84
84
  }
@@ -156,6 +156,8 @@ export const UNIFFI_RELEASE_TARGETS: readonly UniFFIReleaseTarget[] = [
156
156
  ] as const;
157
157
 
158
158
  const RUST_CACHE_ENV = {
159
+ CARGO_INCREMENTAL: "0",
160
+ CARGO_TERM_COLOR: "always",
159
161
  RUSTC_WRAPPER: "sccache",
160
162
  SCCACHE_GHA_ENABLED: "true",
161
163
  } as const;
@@ -203,8 +205,9 @@ function releaseTargets(options: DBXToolsRustWorkspaceOptions): readonly UniFFIR
203
205
  const target = UNIFFI_RELEASE_TARGETS.find(
204
206
  (candidate) => candidate.os === platform.os && candidate.cpu === platform.cpu,
205
207
  );
206
- if (!target)
208
+ if (!target) {
207
209
  throw new Error(`Unsupported Rust release platform: ${platform.os}-${platform.cpu}`);
210
+ }
208
211
  return target;
209
212
  });
210
213
  }
@@ -247,6 +250,7 @@ export interface RustBindingMapping {
247
250
  readonly pythonPackage?: string;
248
251
  readonly pythonModule?: string;
249
252
  readonly facadeTarget?: boolean;
253
+ readonly dependencies?: readonly string[];
250
254
  }
251
255
 
252
256
  /** Persisted Rust workspace state consumed by `sync --watch`. */
@@ -257,6 +261,29 @@ export interface RustWorkspaceMapping {
257
261
  readonly releaseWorkflow?: string;
258
262
  }
259
263
 
264
+ export function orderRustBindings(bindings: readonly RustBindingMapping[]): RustBindingMapping[] {
265
+ const ordered: RustBindingMapping[] = [];
266
+ const visiting = new Set<string>();
267
+ const completed = new Set<string>();
268
+ const visit = (binding: RustBindingMapping): void => {
269
+ if (completed.has(binding.crate)) return;
270
+ if (visiting.has(binding.crate)) {
271
+ throw new Error(`Cyclic Rust binding dependency: ${binding.crate}`);
272
+ }
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
+
260
287
  function rustSources(directory: string): string[] {
261
288
  if (!existsSync(directory)) return [];
262
289
  const files: string[] = [];
@@ -294,6 +321,7 @@ function cargoDependency(
294
321
  ...(!value.version && value.path && workspaceVersion ? { version: workspaceVersion } : {}),
295
322
  ...(value.workspace ? { workspace: true } : {}),
296
323
  ...(value.path ? { path: value.path } : {}),
324
+ ...(value.package ? { package: value.package } : {}),
297
325
  ...(value.optional ? { optional: true } : {}),
298
326
  ...(value.defaultFeatures === false ? { "default-features": false } : {}),
299
327
  ...(value.features?.length ? { features: [...value.features] } : {}),
@@ -366,7 +394,7 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
366
394
  ...(this.uniffi || binary
367
395
  ? {
368
396
  bin: this.uniffi
369
- ? { name: "uniffi-bindgen", path: "uniffi-bindgen.rs" }
397
+ ? { name: `${crateName}-uniffi-bindgen`, path: "uniffi-bindgen.rs" }
370
398
  : { name: binaryName, path: "src/main.rs" },
371
399
  }
372
400
  : {}),
@@ -418,7 +446,7 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
418
446
  }
419
447
  }
420
448
 
421
- /** Generated Rust workspace plus convention-derived private UniFFI packages. */
449
+ /** Generated Rust workspace plus convention-derived UniFFI facade packages. */
422
450
  export class DBXToolsRustWorkspace {
423
451
  readonly packages: readonly DBXToolsRustProject[];
424
452
  readonly nodePackages: readonly DBXToolsTypeScriptProject[];
@@ -447,27 +475,75 @@ export class DBXToolsRustWorkspace {
447
475
  );
448
476
 
449
477
  const bindings = this.packages.filter((pkg) => pkg.uniffi);
450
- this.bindingMappings = bindings.map((pkg) => {
451
- const targets = pkg.packageOptions.bindings ?? ["node", "python"];
452
- const packageName = pkg.packageOptions.directory.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
453
- return {
454
- crate: pkg.crateName,
455
- rust: `${root}/${pkg.packageOptions.directory}`,
456
- ...(targets.includes("node")
457
- ? {
458
- node: `${nodeRoot}/${pkg.packageOptions.directory}`,
459
- nodePackage: `@${scope}/${packageName}`,
460
- }
461
- : {}),
462
- ...(targets.includes("python")
463
- ? {
464
- python: `${options.pythonRoot ?? "packages/py"}/${pkg.packageOptions.directory}`,
465
- pythonPackage: pkg.crateName,
466
- pythonModule: pythonModuleName(pythonModulePrefix, pkg.packageOptions.directory),
467
- }
468
- : {}),
469
- };
470
- });
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
+ }
471
547
  const releaseEnabled =
472
548
  (options.release ?? true) &&
473
549
  (this.bindingMappings.length > 0 ||
@@ -484,13 +560,7 @@ export class DBXToolsRustWorkspace {
484
560
  project.gitignore.addPatterns(
485
561
  "target/",
486
562
  ...this.bindingMappings.flatMap((binding) => [
487
- ...(binding.node
488
- ? [
489
- `${binding.node}/src/bindings.ts`,
490
- `${binding.node}/src/_bindings*.ts`,
491
- `${binding.node}/src/*${binding.crate.replaceAll("-", "_")}.*`,
492
- ]
493
- : []),
563
+ ...(binding.node ? [`${binding.node}/src/*${binding.crate.replaceAll("-", "_")}.*`] : []),
494
564
  ...(binding.python && binding.pythonModule
495
565
  ? [
496
566
  `${binding.python}/src/${binding.pythonModule.replaceAll(".", "/")}/bindings.py`,
@@ -499,6 +569,13 @@ export class DBXToolsRustWorkspace {
499
569
  : []),
500
570
  ]),
501
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
+ }
502
579
  this.pythonPackages = bindings
503
580
  .filter((pkg) => (pkg.packageOptions.bindings ?? ["node", "python"]).includes("python"))
504
581
  .map((pkg) => {
@@ -508,10 +585,16 @@ export class DBXToolsRustWorkspace {
508
585
  name: pkg.crateName,
509
586
  module,
510
587
  description: `Python bindings for ${pkg.crateName}`,
511
- private: true,
512
- generatedSources: [`src/${module.replaceAll(".", "/")}/bindings.py`],
588
+ uniffi: true,
589
+ internalDependencies: bindingDependencies(pkg, "python").map(
590
+ (dependency) => dependency.packageOptions.directory,
591
+ ),
592
+ generatedSources: [
593
+ `src/${module.replaceAll(".", "/")}/bindings.py`,
594
+ `src/${module.replaceAll(".", "/")}/__init__.py`,
595
+ ],
513
596
  trustedPublisher: {
514
- workflowName: options.releaseWorkflowName ?? "rust-release",
597
+ workflowName: options.pythonReleaseWorkflowName ?? "python-release",
515
598
  environment: `pypi-${pkg.crateName}`,
516
599
  artifacts: `platform-specific wheels for ${releaseTargets(options)
517
600
  .map((target) => `${target.os}-${target.cpu}`)
@@ -530,22 +613,23 @@ export class DBXToolsRustWorkspace {
530
613
  const directory = binding.packageOptions.directory;
531
614
  const memberPath = `${nodeRoot}/${directory}`;
532
615
  const found = existing.get(memberPath);
533
- const node =
534
- found instanceof DBXToolsTypeScriptProject
535
- ? found
536
- : new DBXToolsTypeScriptProject({
537
- parent: project,
538
- outdir: memberPath,
539
- name: `@${scope}/${directory.toLowerCase().replace(/[^a-z0-9-]+/g, "-")}`,
540
- tags: ["node"],
541
- });
616
+ const existingNode = found instanceof DBXToolsTypeScriptProject ? found : undefined;
617
+ const node = existingNode
618
+ ? existingNode
619
+ : new DBXToolsTypeScriptProject({
620
+ parent: project,
621
+ outdir: memberPath,
622
+ name: `@${scope}/${directory.toLowerCase().replace(/[^a-z0-9-]+/g, "-")}`,
623
+ tags: ["node"],
624
+ });
542
625
  node.package.addField(
543
626
  "name",
544
627
  `@${scope}/${directory.toLowerCase().replace(/[^a-z0-9-]+/g, "-")}`,
545
628
  );
546
- node.package.addField("private", true);
547
- node.package.file.addDeletionOverride("publishConfig");
548
- node.package.addField("description", `Node bindings for ${binding.crateName}`);
629
+ node.dbxToolsConfig.uniffi = true;
630
+ if (!existingNode) {
631
+ node.package.addField("description", `Node bindings for ${binding.crateName}`);
632
+ }
549
633
  const nativeTargets = releaseTargets(options);
550
634
  if (options.release ?? true) {
551
635
  node.package.addField(
@@ -559,6 +643,11 @@ export class DBXToolsRustWorkspace {
559
643
  );
560
644
  }
561
645
  node.addDeps("@ubjs/core@0.31.0-5", "@ubjs/node@0.31.0-5");
646
+ node.addDeps(
647
+ ...bindingDependencies(binding, "node").map(
648
+ (dependency) => `@${scope}/${dependency.packageOptions.directory}@workspace:*`,
649
+ ),
650
+ );
562
651
  if (binding.packageOptions.nodeDependencies?.length) {
563
652
  node.addDeps(...binding.packageOptions.nodeDependencies);
564
653
  }
@@ -566,10 +655,6 @@ export class DBXToolsRustWorkspace {
566
655
  if (binding.packageOptions.nodeDevDependencies?.length) {
567
656
  node.addDevDeps(...binding.packageOptions.nodeDevDependencies);
568
657
  }
569
- node.compileTask.reset();
570
- new TextFile(node, "exports.ts", {
571
- lines: ['export * from "./src/bindings.ts";', ""],
572
- });
573
658
  nodePackages.push(node);
574
659
  }
575
660
  this.nodePackages = nodePackages;
@@ -607,10 +692,13 @@ export class DBXToolsRustWorkspace {
607
692
  project.addTask("rs:lint", { exec: "cargo clippy --workspace --all-targets --all-features" });
608
693
  project.addTask("rs:test", { exec: "cargo test --workspace" });
609
694
  project.addTask("rs:build", { exec: "cargo build --workspace" });
610
- project.addTask("rs:bindings", {
695
+ const bindingsTask = project.addTask("rs:bindings", {
611
696
  exec: "bun node_modules/@dbx-tools/projen/tasks/rust.ts",
612
697
  description: "Generate language bindings for UniFFI-enabled Rust crates",
613
698
  });
699
+ if (this.bindingMappings.some((binding) => binding.node)) {
700
+ project.tasks.tryFind("pre-compile")?.spawn(bindingsTask);
701
+ }
614
702
  project.addTask("rs:bindings:demo", {
615
703
  description: "Generate and run UniFFI Node and Python example CLIs",
616
704
  exec: [
@@ -637,15 +725,6 @@ export class DBXToolsRustWorkspace {
637
725
  const workflowName = options.releaseWorkflowName ?? "rust-release";
638
726
  const releaseTagPrefix = options.releaseTagPrefix ?? "v";
639
727
  const releaseRustVersion = options.releaseRustVersion ?? "stable";
640
- const ubrnRustVersion = options.ubrnRustVersion ?? releaseRustVersion;
641
- const ubrnToolchainInstall =
642
- ubrnRustVersion === releaseRustVersion
643
- ? ""
644
- : `rustup toolchain install ${ubrnRustVersion} --profile minimal\n`;
645
- const ubrnExecutable =
646
- "${{ github.workspace }}/.cache/ubrn/uniffi-bindgen-react-native${{ matrix.target.os == 'win32' && '.exe' || '' }}";
647
- const builtUbrnExecutable =
648
- "target/ubrn/debug/uniffi-bindgen-react-native${{ matrix.target.os == 'win32' && '.exe' || '' }}";
649
728
  const releaseBranch = projectReleaseBranch(project);
650
729
  const bindings = this.bindingMappings.map((binding) => ({
651
730
  ...binding,
@@ -662,12 +741,14 @@ export class DBXToolsRustWorkspace {
662
741
  }));
663
742
  const publicCrates = this.packages
664
743
  .filter((pkg) => !pkg.packageOptions.private)
744
+ .sort((first, second) => {
745
+ const order = this.bindingMappings.map((binding) => binding.crate);
746
+ return order.indexOf(first.crateName) - order.indexOf(second.crateName);
747
+ })
665
748
  .map((pkg) => pkg.crateName);
666
- const targetMatrix = targets.map((target, index) => ({
667
- target: { ...target, facade: index === 0 },
668
- }));
669
- const hasNodeBindings = bindings.some((binding) => binding.node);
749
+ const targetMatrix = targets.map((target) => ({ target }));
670
750
  const hasPythonBindings = bindings.some((binding) => binding.python);
751
+ const usePreinstalledWindowsRust = releaseRustVersion === "stable";
671
752
  const hasTargetOutputs =
672
753
  bindings.length > 0 || releaseBinaries.length > 0 || publicCrates.length > 0;
673
754
  if (hasTargetOutputs && targetMatrix.length === 0) {
@@ -689,16 +770,12 @@ export class DBXToolsRustWorkspace {
689
770
  `--python "${binding.python}"`,
690
771
  `--node-package "${binding.nodePackage}"`,
691
772
  `--python-package "${binding.pythonPackage}"`,
692
- ...(binding.node
693
- ? ['--node-generator "projen/tasks/uniffi.ts"', '--ubrn "$UBRN_EXECUTABLE"']
694
- : []),
695
773
  '--cargo-target "${{ matrix.target.cargo }}"',
696
774
  '--node-triple "${{ matrix.target.node }}"',
697
775
  '--python-tag "${{ matrix.target.python }}"',
698
776
  '--os "${{ matrix.target.os }}"',
699
777
  '--cpu "${{ matrix.target.cpu }}"',
700
778
  '--libc "${{ matrix.target.libc }}"',
701
- '--facade "${{ matrix.target.facade }}"',
702
779
  `--version "\${VERSION#${releaseTagPrefix}}"`,
703
780
  `--output "dist/release/${binding.crate}/\${{ matrix.target.node }}"`,
704
781
  "--skip-build",
@@ -726,15 +803,7 @@ export class DBXToolsRustWorkspace {
726
803
  with: {
727
804
  name: `${binding.crate}-\${{ matrix.target.node }}-npm`,
728
805
  path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/npm/*.tgz`,
729
- },
730
- },
731
- {
732
- name: `Upload ${binding.crate} npm facade`,
733
- if: "${{ matrix.target.facade }}",
734
- uses: "actions/upload-artifact@v7",
735
- with: {
736
- name: `${binding.crate}-npm-facade`,
737
- path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/npm-facade/*.tgz`,
806
+ "retention-days": 7,
738
807
  },
739
808
  },
740
809
  ]
@@ -745,8 +814,9 @@ export class DBXToolsRustWorkspace {
745
814
  name: `Upload ${binding.crate} Python wheel`,
746
815
  uses: "actions/upload-artifact@v7",
747
816
  with: {
748
- name: `${binding.crate}-\${{ matrix.target.python }}-python-wheel`,
817
+ name: `${binding.crate}--\${{ matrix.target.python }}--python-wheel`,
749
818
  path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/python/*.whl`,
819
+ "retention-days": 7,
750
820
  },
751
821
  },
752
822
  ]
@@ -758,6 +828,7 @@ export class DBXToolsRustWorkspace {
758
828
  with: {
759
829
  name: `${pkg.crate}-\${{ matrix.target.node }}-binary`,
760
830
  path: `dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/*`,
831
+ "retention-days": 7,
761
832
  },
762
833
  })),
763
834
  ];
@@ -767,7 +838,6 @@ export class DBXToolsRustWorkspace {
767
838
  "runs-on": "${{ matrix.target.runner }}",
768
839
  env: {
769
840
  ...RUST_CACHE_ENV,
770
- BUN_VERSION,
771
841
  SCCACHE_GHA_VERSION: `release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`,
772
842
  },
773
843
  strategy: {
@@ -779,28 +849,26 @@ export class DBXToolsRustWorkspace {
779
849
  ...(hasPythonBindings ? [{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" }] : []),
780
850
  {
781
851
  name: "Setup Rust",
852
+ ...(usePreinstalledWindowsRust ? { if: "${{ matrix.target.os != 'win32' }}" } : {}),
782
853
  uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
783
854
  with: { targets: "${{ matrix.target.cargo }}" },
784
855
  },
785
- ...rustCacheSteps(`release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`),
786
- ...(hasNodeBindings
856
+ ...(usePreinstalledWindowsRust
787
857
  ? [
788
858
  {
789
- name: "Restore UBRN executable",
790
- id: "ubrn_cache",
791
- uses: "actions/cache/restore@v5",
792
- with: {
793
- path: ".cache/ubrn",
794
- key: `ubrn-executable-\${{ runner.os }}-\${{ runner.arch }}-rust-${ubrnRustVersion}-${UBRN_VERSION}`,
795
- },
859
+ name: "Verify preinstalled Windows Rust",
860
+ if: "${{ matrix.target.os == 'win32' }}",
861
+ shell: "bash",
862
+ run: [
863
+ "rustc --version --verbose",
864
+ "cargo --version",
865
+ 'rustup target list --installed | grep -Fx "${{ matrix.target.cargo }}"',
866
+ 'test -f "$(rustc --print sysroot)/lib/rustlib/${{ matrix.target.cargo }}/bin/rust-lld.exe"',
867
+ ].join("\n"),
796
868
  },
797
869
  ]
798
870
  : []),
799
- ...(hasNodeBindings
800
- ? bunCacheRestoreSteps(project, {
801
- condition: "steps.ubrn_cache.outputs.cache-hit != 'true'",
802
- })
803
- : []),
871
+ ...rustCacheSteps(`release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`),
804
872
  {
805
873
  name: "Log cache configuration",
806
874
  shell: "bash",
@@ -809,13 +877,6 @@ export class DBXToolsRustWorkspace {
809
877
  'echo "cargo_cache_hit=${{ steps.cargo_cache.outputs.cache-hit }}"',
810
878
  'echo "sccache_scope=${{ github.ref }}"',
811
879
  'echo "sccache_namespace=${SCCACHE_GHA_VERSION}"',
812
- `echo "ubrn_rust_toolchain=${ubrnRustVersion}"`,
813
- ...(hasNodeBindings
814
- ? [
815
- `echo "ubrn_executable_cache_key=ubrn-executable-\${{ runner.os }}-\${{ runner.arch }}-rust-${ubrnRustVersion}-${UBRN_VERSION}"`,
816
- 'echo "ubrn_executable_cache_hit=${{ steps.ubrn_cache.outputs.cache-hit }}"',
817
- ]
818
- : []),
819
880
  ].join("\n"),
820
881
  },
821
882
  {
@@ -823,59 +884,13 @@ export class DBXToolsRustWorkspace {
823
884
  if: "${{ matrix.target.os == 'linux' }}",
824
885
  run: "sudo apt-get update && sudo apt-get install --yes libdbus-1-dev pkg-config",
825
886
  },
826
- ...(hasNodeBindings
827
- ? [
828
- {
829
- name: "Install Node tooling",
830
- if: "${{ steps.ubrn_cache.outputs.cache-hit != 'true' }}",
831
- shell: "bash",
832
- run: timedBash("node_tooling", "bun install"),
833
- },
834
- bunCacheSaveStep({
835
- condition: "steps.ubrn_cache.outputs.cache-hit != 'true'",
836
- }),
837
- ]
838
- : []),
839
- ...(hasNodeBindings
840
- ? [
841
- {
842
- name: "Prepare UBRN generator",
843
- if: "${{ steps.ubrn_cache.outputs.cache-hit != 'true' }}",
844
- env: {
845
- CARGO_TARGET_DIR: "${{ github.workspace }}/target/ubrn",
846
- UBRN_EXECUTABLE: ubrnExecutable,
847
- },
848
- shell: "bash",
849
- run: timedBash(
850
- "ubrn_generator",
851
- [
852
- `${ubrnToolchainInstall}cargo +${ubrnRustVersion} build --manifest-path node_modules/uniffi-bindgen-react-native/crates/ubrn_cli/Cargo.toml`,
853
- 'mkdir -p "$(dirname "$UBRN_EXECUTABLE")"',
854
- `cp "${builtUbrnExecutable}" "$UBRN_EXECUTABLE"`,
855
- 'chmod +x "$UBRN_EXECUTABLE"',
856
- ].join("\n"),
857
- ),
858
- },
859
- {
860
- name: "Verify UBRN executable",
861
- env: { UBRN_EXECUTABLE: ubrnExecutable },
862
- shell: "bash",
863
- run: 'test -f "$UBRN_EXECUTABLE"',
864
- },
865
- {
866
- name: "Save UBRN executable",
867
- if: "${{ steps.ubrn_cache.outputs.cache-hit != 'true' }}",
868
- uses: "actions/cache/save@v5",
869
- with: {
870
- path: ".cache/ubrn",
871
- key: "${{ steps.ubrn_cache.outputs.cache-primary-key }}",
872
- },
873
- },
874
- ]
875
- : []),
876
887
  {
877
888
  name: "Build Rust outputs",
878
889
  shell: "bash",
890
+ env: {
891
+ CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER:
892
+ "${{ matrix.target.os == 'win32' && 'rust-lld' || '' }}",
893
+ },
879
894
  run: timedBash(
880
895
  "rust_workspace",
881
896
  'cargo build --release --workspace --target "${{ matrix.target.cargo }}"',
@@ -886,14 +901,7 @@ export class DBXToolsRustWorkspace {
886
901
  {
887
902
  name: "Package UniFFI outputs",
888
903
  shell: "bash",
889
- env: {
890
- VERSION: RELEASE_TAG,
891
- ...(hasNodeBindings
892
- ? {
893
- UBRN_EXECUTABLE: ubrnExecutable,
894
- }
895
- : {}),
896
- },
904
+ env: { VERSION: RELEASE_TAG },
897
905
  run: timedBash("uniffi_packaging", bindingCommands.join("\n")),
898
906
  },
899
907
  ]
@@ -916,80 +924,8 @@ export class DBXToolsRustWorkspace {
916
924
  },
917
925
  ],
918
926
  };
919
- const bindingPublishJobs = Object.fromEntries(
920
- bindings.map((binding) => [
921
- `publish-${binding.crate}`,
922
- {
923
- if: "${{ github.event_name == 'repository_dispatch' }}",
924
- needs: ["build"],
925
- "runs-on": "ubuntu-latest",
926
- permissions: {
927
- contents: "read",
928
- ...(binding.python ? { "id-token": "write" } : {}),
929
- },
930
- ...(binding.python ? { environment: { name: `pypi-${binding.crate}` } } : {}),
931
- steps: [
932
- ...(binding.node
933
- ? [
934
- {
935
- name: "Setup Node.js",
936
- uses: "actions/setup-node@v6",
937
- with: { "registry-url": "https://registry.npmjs.org" },
938
- },
939
- {
940
- name: "Download native npm packages",
941
- uses: "actions/download-artifact@v8",
942
- with: {
943
- pattern: `${binding.crate}-*-npm`,
944
- path: "dist/npm",
945
- "merge-multiple": true,
946
- },
947
- },
948
- {
949
- name: "Download npm facade",
950
- uses: "actions/download-artifact@v8",
951
- with: {
952
- name: `${binding.crate}-npm-facade`,
953
- path: "dist/npm-facade",
954
- },
955
- },
956
- {
957
- name: "Publish native npm packages",
958
- env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
959
- run: 'for package in dist/npm/*.tgz; do npm publish "$package" --access public; done',
960
- },
961
- {
962
- name: "Publish npm facade",
963
- env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
964
- run: 'for package in dist/npm-facade/*.tgz; do npm publish "$package" --access public; done',
965
- },
966
- ]
967
- : []),
968
- ...(binding.python
969
- ? [
970
- { name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
971
- {
972
- name: "Download Python wheels",
973
- uses: "actions/download-artifact@v8",
974
- with: {
975
- pattern: `${binding.crate}-*-python-wheel`,
976
- path: "dist/python",
977
- "merge-multiple": true,
978
- },
979
- },
980
- {
981
- name: "Publish Python wheels",
982
- run: "uv publish --trusted-publishing always dist/python/*.whl",
983
- },
984
- ]
985
- : []),
986
- ],
987
- },
988
- ]),
989
- );
990
927
  const releaseCompletionJobs = [
991
928
  "build",
992
- ...bindings.map((binding) => `publish-${binding.crate}`),
993
929
  ...(publicCrates.length ? ["publish-cargo"] : []),
994
930
  ...(releaseBinaries.length ? ["publish-github-release"] : []),
995
931
  ];
@@ -1057,7 +993,6 @@ export class DBXToolsRustWorkspace {
1057
993
  ],
1058
994
  },
1059
995
  ...(hasTargetOutputs && targetMatrix.length ? { build: buildJob } : {}),
1060
- ...bindingPublishJobs,
1061
996
  ...(publicCrates.length
1062
997
  ? {
1063
998
  "publish-cargo": {
@@ -1085,79 +1020,6 @@ export class DBXToolsRustWorkspace {
1085
1020
  },
1086
1021
  }
1087
1022
  : {}),
1088
- ...(bindings.length
1089
- ? {
1090
- "publish-local-bindings": {
1091
- if: "${{ github.event_name == 'repository_dispatch' && vars.LOCAL_REPOSITORIES == 'true' }}",
1092
- needs: ["build"],
1093
- "runs-on": ["self-hosted"],
1094
- permissions: { contents: "read" },
1095
- steps: [
1096
- ...(hasNodeBindings
1097
- ? [
1098
- {
1099
- name: "Setup Node.js",
1100
- uses: "actions/setup-node@v6",
1101
- with: {
1102
- "registry-url": "${{ vars.LOCAL_NPM_REGISTRY }}",
1103
- },
1104
- },
1105
- {
1106
- name: "Download native npm packages",
1107
- uses: "actions/download-artifact@v8",
1108
- with: {
1109
- pattern: "*-npm",
1110
- path: "dist/npm",
1111
- "merge-multiple": true,
1112
- },
1113
- },
1114
- {
1115
- name: "Download npm facades",
1116
- uses: "actions/download-artifact@v8",
1117
- with: {
1118
- pattern: "*-npm-facade",
1119
- path: "dist/npm-facade",
1120
- "merge-multiple": true,
1121
- },
1122
- },
1123
- {
1124
- name: "Publish npm packages locally",
1125
- env: {
1126
- NODE_AUTH_TOKEN: "${{ secrets.LOCAL_NPM_TOKEN }}",
1127
- },
1128
- run: [
1129
- 'for package in dist/npm/*.tgz; do npm publish "$package" --access public; done',
1130
- 'for package in dist/npm-facade/*.tgz; do npm publish "$package" --access public; done',
1131
- ].join("\n"),
1132
- },
1133
- ]
1134
- : []),
1135
- ...(hasPythonBindings
1136
- ? [
1137
- { name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
1138
- {
1139
- name: "Download Python wheels",
1140
- uses: "actions/download-artifact@v8",
1141
- with: {
1142
- pattern: "*-python-wheel",
1143
- path: "dist/python",
1144
- "merge-multiple": true,
1145
- },
1146
- },
1147
- {
1148
- name: "Publish Python wheels locally",
1149
- env: {
1150
- UV_PUBLISH_USERNAME: "${{ secrets.LOCAL_PYPI_USERNAME }}",
1151
- UV_PUBLISH_PASSWORD: "${{ secrets.LOCAL_PYPI_PASSWORD }}",
1152
- },
1153
- run: 'uv publish --publish-url "${{ vars.LOCAL_PYPI_PUBLISH_URL }}" dist/python/*.whl',
1154
- },
1155
- ]
1156
- : []),
1157
- ],
1158
- },
1159
- }
1160
- : {}),
1161
1023
  ...(publicCrates.length
1162
1024
  ? {
1163
1025
  "publish-local-cargo": {
@@ -1232,6 +1094,8 @@ export class DBXToolsRustWorkspace {
1232
1094
  RELEASE_TAG,
1233
1095
  EXPECTED_SHA: RELEASE_SHA,
1234
1096
  RELEASE_EVENT: DOWNSTREAM_RELEASE_EVENT,
1097
+ RUST_RUN_ID: "${{ github.run_id }}",
1098
+ RUST_RUN_ATTEMPT: "${{ github.run_attempt }}",
1235
1099
  },
1236
1100
  run: [
1237
1101
  [
@@ -1239,6 +1103,8 @@ export class DBXToolsRustWorkspace {
1239
1103
  '--raw-field event_type="$RELEASE_EVENT"',
1240
1104
  '--raw-field "client_payload[release_tag]=$RELEASE_TAG"',
1241
1105
  '--raw-field "client_payload[expected_sha]=$EXPECTED_SHA"',
1106
+ '--raw-field "client_payload[rust_run_id]=$RUST_RUN_ID"',
1107
+ '--raw-field "client_payload[rust_run_attempt]=$RUST_RUN_ATTEMPT"',
1242
1108
  ].join(" \\\n "),
1243
1109
  ].join("\n"),
1244
1110
  },