From b7bebf89b8eb3c009fb870b393fc514158259a7b Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 11:59:50 -0700 Subject: [PATCH 01/19] [bfops/translate-script]: WIP --- .cargo/config.toml | 2 + .github/workflows/ci.yml | 15 ++-- Cargo.lock | 19 +++++ Cargo.toml | 2 + sdks/csharp/DEVELOP.md | 3 +- .../regression-tests/client/Program.cs | 2 +- .../procedure-client/Program.cs | 2 +- .../procedure-client/README.md | 2 +- .../republishing/client/Program.cs | 2 +- sdks/csharp/tests~/README.md | 4 +- sdks/csharp/tools~/gen-regression-tests.sh | 12 --- sdks/csharp/tools~/run-regression-tests.sh | 42 ---------- sdks/csharp/tools~/update-against-stdb.sh | 5 +- sdks/csharp/tools~/write-nuget-config.sh | 84 ------------------- tools/ci/README.md | 17 +--- 15 files changed, 39 insertions(+), 174 deletions(-) delete mode 100755 sdks/csharp/tools~/gen-regression-tests.sh delete mode 100644 sdks/csharp/tools~/run-regression-tests.sh delete mode 100755 sdks/csharp/tools~/write-nuget-config.sh diff --git a/.cargo/config.toml b/.cargo/config.toml index 48647676517..9fb5ae97e8a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,6 +5,8 @@ rustflags = ["--cfg", "tokio_unstable"] bump-versions = "run -p upgrade-version --" llm = "run --package xtask-llm-benchmark --bin llm_benchmark --" ci = "run -p ci --" +csharp = "run -p csharp-tools --" +regen = "run -p regen --" smoketest = "ci smoketests --" smoketests = "smoketest" lint = "ci lint --" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bb00eb6f12..1b2d7c472cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,8 +132,7 @@ jobs: run: | dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime dotnet pack -c Release crates/bindings-csharp/Runtime - cd sdks/csharp - ./tools~/write-nuget-config.sh ../.. + cargo csharp write-nuget-config # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. # ChatGPT suspects that this could be due to different build invocations using the same target dir, @@ -626,8 +625,7 @@ jobs: # This means that (if version numbers match) we will test the local versions of the C# packages, even # if they're not pushed to NuGet. # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. - cd sdks/csharp - ./tools~/write-nuget-config.sh ../.. + cargo csharp write-nuget-config - name: Restore .NET solution working-directory: sdks/csharp @@ -689,7 +687,7 @@ jobs: } - name: Hydrate Unity SDK DLLs - run: cargo ci dlls + run: cargo regen csharp dlls - name: Check Unity meta files uses: DeNA/unity-meta-check@v3 @@ -771,8 +769,7 @@ jobs: # This means that (if version numbers match) we will test the local versions of the C# packages, even # if they're not pushed to NuGet. # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. - cd sdks/csharp - ./tools~/write-nuget-config.sh ../.. + cargo csharp write-nuget-config - name: Restore .NET solution working-directory: sdks/csharp @@ -855,9 +852,9 @@ jobs: - name: Run regression tests run: | - bash sdks/csharp/tools~/run-regression-tests.sh + cargo csharp run-regression-tests tools/check-diff.sh sdks/csharp/examples~/regression-tests || { - echo 'Error: Bindings are dirty. Please run `sdks/csharp/tools~/gen-regression-tests.sh`.' + echo 'Error: Bindings are dirty. Please run `cargo regen csharp regression-tests`.' exit 1 } diff --git a/Cargo.lock b/Cargo.lock index 9cc97fceadc..d76ea80d997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1549,6 +1549,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "csharp-tools" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.5.50", + "duct", + "tempfile", +] + [[package]] name = "css-module-lexer" version = "0.0.15" @@ -6237,6 +6247,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "regen" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.5.50", + "csharp-tools", +] + [[package]] name = "regex" version = "1.12.2" diff --git a/Cargo.toml b/Cargo.toml index 120ec24c2cf..2512a8b7b6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,11 +60,13 @@ members = [ "sdks/rust/tests/view-pk-client", "sdks/rust/tests/event-table-client", "tools/ci", + "tools/csharp-tools", "tools/upgrade-version", "tools/license-check", "tools/replace-spacetimedb", "tools/generate-client-api", "tools/gen-bindings", + "tools/regen", "tools/xtask-llm-benchmark", "crates/bindings-typescript/test-app/server", "crates/bindings-typescript/test-react-router-app/server", diff --git a/sdks/csharp/DEVELOP.md b/sdks/csharp/DEVELOP.md index ff56a1baf82..1fd5396d411 100644 --- a/sdks/csharp/DEVELOP.md +++ b/sdks/csharp/DEVELOP.md @@ -12,7 +12,7 @@ When developing against a local clone of SpacetimeDB, you'll need to ensure that To develop against a local clone of SpacetimeDB at `../SpacetimeDB`, run the following command: ```sh -dotnet pack ../SpacetimeDB/crates/bindings-csharp/BSATN.Runtime && ./tools~/write-nuget-config.sh ../SpacetimeDB +dotnet pack ../SpacetimeDB/crates/bindings-csharp/BSATN.Runtime && cargo csharp write-nuget-config ../SpacetimeDB ``` This will create a (`.gitignore`d) `nuget.config` file that uses the local build of the package, instead of the package on NuGet. @@ -99,4 +99,3 @@ We could deduplicate multiply-subscribed rows server-side, but this represents a There is also a class `MultiDictionaryDelta`. This represents a pre-processed batch of changes to a `MultiDictionary`. We prepare `MultiDictionaryDelta`s on a background thread and `Apply` them on the main thread. This allows us to do at least some work without blocking the main thread. Note that if multiple subscriptions are subscribed to a row, when a server-side transaction updates that row, exactly the right number of updates will be sent over the network, in a single `ServerMessage`. `MultiDictionary` and `MultiDictionaryDelta` rely on this guarantee for correct operation, and will throw exceptions in debug mode if it is not met. - diff --git a/sdks/csharp/examples~/regression-tests/client/Program.cs b/sdks/csharp/examples~/regression-tests/client/Program.cs index c3b88d1b05a..65b84a05a3e 100644 --- a/sdks/csharp/examples~/regression-tests/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/client/Program.cs @@ -1,6 +1,6 @@ /// tests run with a live server. /// To run these, run a local SpacetimeDB via `spacetime start`, -/// then in a separate terminal run `tools~/run-regression-tests.sh PATH_TO_SPACETIMEDB_REPO_CHECKOUT`. +/// then in a separate terminal run `cargo csharp run-regression-tests`. /// This is done on CI in .github/workflows/test.yml. using System; using System.Collections.Generic; diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs b/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs index 2345af3af15..deeebeec654 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs @@ -1,6 +1,6 @@ /// Procedure tests run with a live server. /// To run these, run a local SpacetimeDB via `spacetime start`, -/// then in a separate terminal run `tools~/run-regression-tests.sh PATH_TO_SPACETIMEDB_REPO_CHECKOUT`. +/// then in a separate terminal run `cargo csharp run-regression-tests`. /// This is done on CI in .github/workflows/test.yml. using System.Diagnostics; diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/README.md b/sdks/csharp/examples~/regression-tests/procedure-client/README.md index 9895da77413..7820d489810 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/README.md +++ b/sdks/csharp/examples~/regression-tests/procedure-client/README.md @@ -3,4 +3,4 @@ Mirrored from regression-tests/client. Currently separated from regression-tests as of 2025-11-14 the C# module bindings does not support procedures. -This relies on /modules/sdk-test-procedure as the server bindings, and is baked into the ../tools~/run-regression-tests.sh. +This relies on /modules/sdk-test-procedure as the server bindings, and is baked into `cargo csharp run-regression-tests`. diff --git a/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs b/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs index 41fc80bb8b3..50d615600a1 100644 --- a/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs @@ -1,6 +1,6 @@ /// Regression tests run with a live server. /// To run these, run a local SpacetimeDB via `spacetime start`, -/// then in a separate terminal run `tools~/run-regression-tests.sh PATH_TO_SPACETIMEDB_REPO_CHECKOUT`. +/// then in a separate terminal run `cargo csharp run-regression-tests`. /// This is done on CI in .github/workflows/test.yml. using System.Diagnostics; diff --git a/sdks/csharp/tests~/README.md b/sdks/csharp/tests~/README.md index 95f9a400393..232d4105d48 100644 --- a/sdks/csharp/tests~/README.md +++ b/sdks/csharp/tests~/README.md @@ -4,7 +4,7 @@ You can use `dotnet test` (either in this directory or in the project root direc # Using a different SpacetimeDB version To run tests using a local version of the `SpacetimeDB` repo, you can add a `nuget.config` file in the **root** of this repository. -The `tools/write-nuget-config.sh` script can generate the `nuget.config`. It takes one parameter: the path to the root SpacetimeDB repository (relative or absolute). +The `cargo csharp write-nuget-config` command can generate the `nuget.config`. It takes one optional parameter: the path to the root SpacetimeDB repository (relative or absolute). Then, you need to `dotnet pack` the `BSATN.Runtime` package in the `SpacetimeDB` repo. @@ -13,7 +13,7 @@ Lastly, before running `dotnet test`, you should `dotnet nuget locals all --clea Example: ```bash $ export SPACETIMEDB_REPO_PATH="../SpacetimeDB" -$ tools/write-nuget-config.sh "${SPACETIMEDB_REPO_PATH}" +$ cargo csharp write-nuget-config "${SPACETIMEDB_REPO_PATH}" $ ( cd "${SPACETIMEDB_REPO_PATH}"/crates/bindings-csharp/BSATN.Runtime && dotnet pack ) $ dotnet nuget locals all --clear $ dotnet test diff --git a/sdks/csharp/tools~/gen-regression-tests.sh b/sdks/csharp/tools~/gen-regression-tests.sh deleted file mode 100755 index 8936971cd5a..00000000000 --- a/sdks/csharp/tools~/gen-regression-tests.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -ueo pipefail - -SDK_PATH="$(dirname "$0")/.." -SDK_PATH="$(realpath "$SDK_PATH")" -STDB_PATH="$SDK_PATH/../.." - -cargo build --manifest-path "$STDB_PATH/crates/standalone/Cargo.toml" -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- generate -y -l csharp -o "$SDK_PATH/examples~/regression-tests/client/module_bindings" --module-path "$SDK_PATH/examples~/regression-tests/server" -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- generate -y -l csharp -o "$SDK_PATH/examples~/regression-tests/republishing/client/module_bindings" --module-path "$SDK_PATH/examples~/regression-tests/republishing/server-republish" -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- generate -y -l csharp -o "$SDK_PATH/examples~/regression-tests/procedure-client/module_bindings" --module-path "$STDB_PATH/modules/sdk-test-procedure" diff --git a/sdks/csharp/tools~/run-regression-tests.sh b/sdks/csharp/tools~/run-regression-tests.sh deleted file mode 100644 index 168eeab668a..00000000000 --- a/sdks/csharp/tools~/run-regression-tests.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -# This script requires a running local SpacetimeDB instance. - -set -ueo pipefail - -SDK_PATH="$(dirname "$0")/.." -SDK_PATH="$(realpath "$SDK_PATH")" -STDB_PATH="$SDK_PATH/../.." -SPACETIMEDB_SERVER_URL="${SPACETIMEDB_SERVER_URL:-local}" - -# Regenerate Bindings -"$SDK_PATH/tools~/gen-regression-tests.sh" - -# Build and run SpacetimeDB server -cargo build --manifest-path "$STDB_PATH/crates/standalone/Cargo.toml" - -# Publish module for btree test -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/server" btree-repro - -# Publish module for republishing module test -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/republishing/server-initial" republish-test -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server "$SPACETIMEDB_SERVER_URL" republish-test insert 1 -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/republishing/server-republish" --break-clients republish-test -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server "$SPACETIMEDB_SERVER_URL" republish-test insert 2 - -echo "Cleanup obj~ folders generated in $SDK_PATH/examples~/regression-tests/procedure-client" -# There is a bug in the code generator that creates obj~ folders in the output directory using a Rust project. -rm -rf "$SDK_PATH/examples~/regression-tests/procedure-client"/*/obj~ -rm -rf "$SDK_PATH/examples~/regression-tests/procedure-client/module_bindings"/*/obj~ - -# Publish module for procedure tests -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$STDB_PATH/modules/sdk-test-procedure" procedure-tests - -# Run client for btree test -cd "$SDK_PATH/examples~/regression-tests/client" && dotnet run -c Debug - -# Run client for republishing module test -cd "$SDK_PATH/examples~/regression-tests/republishing/client" && dotnet run -c Debug - -# Run client for procedure test -cd "$SDK_PATH/examples~/regression-tests/procedure-client" && dotnet run -c Debug diff --git a/sdks/csharp/tools~/update-against-stdb.sh b/sdks/csharp/tools~/update-against-stdb.sh index 58c0b8b649a..d90f90f1f4b 100644 --- a/sdks/csharp/tools~/update-against-stdb.sh +++ b/sdks/csharp/tools~/update-against-stdb.sh @@ -8,10 +8,10 @@ STDB_PATH="$1" SDK_PATH="$(dirname "$0")/.." SDK_PATH="$(realpath "$SDK_PATH")" -"$SDK_PATH/tools~/write-nuget-config.sh" "$STDB_PATH" +cargo csharp write-nuget-config "$STDB_PATH" "$SDK_PATH/tools~/gen-client-api.sh" "$SDK_PATH/tools~/gen-quickstart.sh" -"$SDK_PATH/tools~/gen-regression-tests.sh" +cargo regen csharp regression-tests dotnet nuget locals all --clear dotnet pack "$STDB_PATH/crates/bindings-csharp" rm -rf "$SDK_PATH/packages" @@ -19,4 +19,3 @@ dotnet pack dotnet test pushd "$SDK_PATH"; git checkout -- 'packages/*.meta' 'packages/**/*.meta' packages/.gitignore; popd - diff --git a/sdks/csharp/tools~/write-nuget-config.sh b/sdks/csharp/tools~/write-nuget-config.sh deleted file mode 100755 index 69286284ad8..00000000000 --- a/sdks/csharp/tools~/write-nuget-config.sh +++ /dev/null @@ -1,84 +0,0 @@ -set -ueo pipefail - -SPACETIMEDB_REPO_PATH="$(readlink -f "$1")" - -cd "$(dirname "$(readlink -f "$0")")" -cd .. - -# Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository -# to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if -# available. -# See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file, -# and https://tldp.org/LDP/abs/html/here-docs.html for more info on this bash feature. -cat >NuGet.Config < - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -EOF - -cat >"${SPACETIMEDB_REPO_PATH}/NuGet.Config" < - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -EOF - -echo "Wrote sdks/csharp/NuGet.Config contents:" -cat NuGet.Config diff --git a/tools/ci/README.md b/tools/ci/README.md index 3530ee24b5e..7e4cb6186ba 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -70,21 +70,6 @@ Usage: wasm-bindings - `--help`: Print help (see a summary with '-h') -### `dlls` - -Builds and packs C# DLLs and NuGet packages for local Unity workflows - -Packs the in-repo C# NuGet packages and restores the C# SDK to populate `sdks/csharp/packages/**`. Then overlays Unity `.meta` skeleton files from `sdks/csharp/unity-meta-skeleton~/**` onto the restored versioned package directory, so Unity can associate stable meta files with the most recently built package. - -**Usage:** -```bash -Usage: dlls -``` - -**Options:** - -- `--help`: Print help (see a summary with '-h') - ### `smoketests` Runs smoketests @@ -257,4 +242,4 @@ This document is auto-generated by running: ```bash cargo ci self-docs -``` \ No newline at end of file +``` From 4397c9e1a635e3b72fdefd20115af8dcf4afd008 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 12:09:18 -0700 Subject: [PATCH 02/19] [bfops/translate-script]: wip --- Cargo.lock | 4 ++-- tools/csharp-tools/Cargo.toml | 9 +++++++++ tools/regen/Cargo.toml | 10 ++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tools/csharp-tools/Cargo.toml create mode 100644 tools/regen/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index d76ea80d997..517c561b21f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1556,7 +1556,6 @@ dependencies = [ "anyhow", "clap 4.5.50", "duct", - "tempfile", ] [[package]] @@ -6253,7 +6252,8 @@ version = "0.1.0" dependencies = [ "anyhow", "clap 4.5.50", - "csharp-tools", + "duct", + "tempfile", ] [[package]] diff --git a/tools/csharp-tools/Cargo.toml b/tools/csharp-tools/Cargo.toml new file mode 100644 index 00000000000..e50c68179c0 --- /dev/null +++ b/tools/csharp-tools/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "csharp-tools" +version = "0.1.0" +edition.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +duct.workspace = true diff --git a/tools/regen/Cargo.toml b/tools/regen/Cargo.toml new file mode 100644 index 00000000000..cd3613f267b --- /dev/null +++ b/tools/regen/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "regen" +version = "0.1.0" +edition.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +duct.workspace = true +tempfile.workspace = true From 2861ba3e0dca088ac6abfd11189f332dff5eff23 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 12:09:26 -0700 Subject: [PATCH 03/19] [bfops/translate-script]: WIP --- tools/ci/src/main.rs | 205 ---------------------- tools/csharp-tools/src/lib.rs | 245 ++++++++++++++++++++++++++ tools/csharp-tools/src/main.rs | 36 ++++ tools/regen/src/csharp.rs | 303 +++++++++++++++++++++++++++++++++ tools/regen/src/main.rs | 41 +++++ 5 files changed, 625 insertions(+), 205 deletions(-) create mode 100644 tools/csharp-tools/src/lib.rs create mode 100644 tools/csharp-tools/src/main.rs create mode 100644 tools/regen/src/csharp.rs create mode 100644 tools/regen/src/main.rs diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 146651c1053..29709f55517 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -91,123 +91,6 @@ fn check_global_json_policy() -> Result<()> { Ok(()) } -fn overlay_unity_meta_skeleton(pkg_id: &str) -> Result<()> { - let skeleton_base = Path::new("sdks/csharp/unity-meta-skeleton~"); - let skeleton_root = skeleton_base.join(pkg_id); - if !skeleton_root.exists() { - return Ok(()); - } - - let pkg_root = Path::new("sdks/csharp/packages").join(pkg_id); - if !pkg_root.exists() { - return Ok(()); - } - - // Copy spacetimedb..meta - let pkg_root_meta = skeleton_base.join(format!("{pkg_id}.meta")); - if pkg_root_meta.exists() - && let Some(parent) = pkg_root.parent() - { - let pkg_meta_dst = parent.join(format!("{pkg_id}.meta")); - fs::copy(&pkg_root_meta, &pkg_meta_dst)?; - } - - let versioned_dir = match find_only_subdir(&pkg_root) { - Ok(dir) => dir, - Err(err) => { - log::info!("Skipping Unity meta overlay for {pkg_id}: could not locate restored version dir: {err}"); - return Ok(()); - } - }; - - // If version.meta exists under the skeleton package, rename it to match the restored version dir. - let version_meta_template = skeleton_root.join("version.meta"); - if version_meta_template.exists() - && let Some(parent) = versioned_dir.parent() - { - let version_name = versioned_dir - .file_name() - .expect("versioned directory should have a file name"); - let version_meta_dst = parent.join(format!("{}.meta", version_name.to_string_lossy())); - fs::copy(&version_meta_template, &version_meta_dst)?; - } - - copy_overlay_dir(&skeleton_root, &versioned_dir) -} - -fn clear_restored_package_dirs(pkg_id: &str) -> Result<()> { - let pkg_root = Path::new("sdks/csharp/packages").join(pkg_id); - if !pkg_root.exists() { - return Ok(()); - } - - fs::remove_dir_all(&pkg_root)?; - - Ok(()) -} - -fn find_only_subdir(dir: &Path) -> Result { - let mut subdirs: Vec = vec![]; - - for entry in fs::read_dir(dir)? { - let entry = entry?; - if entry.file_type()?.is_dir() { - subdirs.push(entry.path()); - } - } - - match subdirs.as_slice() { - [] => Err(anyhow::anyhow!( - "Could not find a restored versioned directory under {}", - dir.display() - )), - [only] => Ok(only.clone()), - _ => Err(anyhow::anyhow!( - "Expected exactly one restored versioned directory under {}, found {}", - dir.display(), - subdirs.len() - )), - } -} - -fn copy_overlay_dir(src: &Path, dst: &Path) -> Result<()> { - if !src.exists() { - bail!("Skeleton directory does not exist: {}", src.display()); - } - if !dst.exists() { - bail!("Destination directory does not exist: {}", dst.display()); - } - - for entry in fs::read_dir(src)? { - let entry = entry?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); - if entry.file_type()?.is_dir() { - if dst_path.exists() { - copy_overlay_dir(&src_path, &dst_path)?; - } - } else { - if src_path.extension() == Some(OsStr::new("meta")) { - let asset_path = dst_path - .parent() - .expect("dst_path should have a parent") - .join(dst_path.file_stem().expect(".meta file should have a file stem")); - - if asset_path.exists() { - fs::copy(&src_path, &dst_path)?; - } else if dst_path.exists() { - fs::remove_file(&dst_path)?; - } - continue; - } - - fs::copy(&src_path, &dst_path)?; - } - } - - Ok(()) -} - #[derive(Subcommand)] enum CiCmd { /// Runs tests @@ -225,12 +108,6 @@ enum CiCmd { /// /// Runs tests for the codegen crate and builds a test module with the wasm bindings. WasmBindings, - /// Builds and packs C# DLLs and NuGet packages for local Unity workflows - /// - /// Packs the in-repo C# NuGet packages and restores the C# SDK to populate `sdks/csharp/packages/**`. - /// Then overlays Unity `.meta` skeleton files from `sdks/csharp/unity-meta-skeleton~/**` onto the restored - /// versioned package directory, so Unity can associate stable meta files with the most recently built package. - Dlls, /// Runs smoketests /// /// Executes the smoketests suite with some default exclusions. @@ -308,84 +185,6 @@ fn tracked_rs_files_under(path: &str) -> Result> { .collect()) } -fn run_dlls() -> Result<()> { - ensure_repo_root()?; - - cmd!( - "dotnet", - "pack", - "crates/bindings-csharp/BSATN.Runtime", - "-c", - "Release" - ) - .run()?; - cmd!("dotnet", "pack", "crates/bindings-csharp/Runtime", "-c", "Release").run()?; - - let repo_root = env::current_dir()?; - let bsatn_source = repo_root.join("crates/bindings-csharp/BSATN.Runtime/bin/Release"); - let runtime_source = repo_root.join("crates/bindings-csharp/Runtime/bin/Release"); - - let nuget_config_dir = tempfile::tempdir()?; - let nuget_config_path = nuget_config_dir.path().join("nuget.config"); - let nuget_config_contents = format!( - r#" - - - - - - - - - - - - - - - - - - - - "#, - bsatn_source.display(), - runtime_source.display(), - ); - fs::write(&nuget_config_path, nuget_config_contents)?; - - let nuget_config_path_str = nuget_config_path.to_string_lossy().to_string(); - - clear_restored_package_dirs("spacetimedb.bsatn.runtime")?; - clear_restored_package_dirs("spacetimedb.runtime")?; - - cmd!( - "dotnet", - "restore", - "SpacetimeDB.ClientSDK.csproj", - "--configfile", - &nuget_config_path_str, - ) - .dir("sdks/csharp") - .run()?; - - overlay_unity_meta_skeleton("spacetimedb.bsatn.runtime")?; - overlay_unity_meta_skeleton("spacetimedb.runtime")?; - - cmd!( - "dotnet", - "pack", - "SpacetimeDB.ClientSDK.csproj", - "-c", - "Release", - "--no-restore" - ) - .dir("sdks/csharp") - .run()?; - - Ok(()) -} - fn run_publish_checks() -> Result<()> { cmd!("bash", "-lc", "test -d venv || python3 -m venv venv").run()?; cmd!("venv/bin/pip3", "install", "argparse", "toml").run()?; @@ -622,10 +421,6 @@ fn main() -> Result<()> { cmd!(cli_path, "build", "--module-path", "modules/module-test",).run()?; } - Some(CiCmd::Dlls) => { - run_dlls()?; - } - Some(CiCmd::Smoketests(args)) => { ensure_repo_root()?; smoketest::run(args)?; diff --git a/tools/csharp-tools/src/lib.rs b/tools/csharp-tools/src/lib.rs new file mode 100644 index 00000000000..a3ebf24fb16 --- /dev/null +++ b/tools/csharp-tools/src/lib.rs @@ -0,0 +1,245 @@ +#![allow(clippy::disallowed_macros)] + +use anyhow::{Context, Result}; +use duct::cmd; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +pub fn workspace_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .expect("tools/csharp-tools should be two levels below the workspace root") + .to_path_buf() +} + +pub fn sdk_dir() -> PathBuf { + workspace_dir().join("sdks/csharp") +} + +fn cli_manifest() -> PathBuf { + workspace_dir().join("crates/cli/Cargo.toml") +} + +fn standalone_manifest() -> PathBuf { + workspace_dir().join("crates/standalone/Cargo.toml") +} + +fn path_arg(path: &Path) -> String { + path.to_string_lossy().into_owned() +} + +fn canonicalize_existing(path: &Path) -> Result { + path.canonicalize() + .with_context(|| format!("failed to canonicalize {}", path.display())) +} + +fn render_nuget_config(bsatn_source: &Path, runtime_source: &Path) -> String { + format!( + r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"#, + bsatn_source.display(), + runtime_source.display(), + ) +} + +pub fn write_persistent_nuget_configs(spacetimedb_repo_path: Option<&Path>) -> Result<()> { + let spacetimedb_repo_path = match spacetimedb_repo_path { + Some(path) => canonicalize_existing(path)?, + None => workspace_dir(), + }; + + let sdk_config = sdk_dir().join("NuGet.Config"); + let sdk_config_contents = render_nuget_config( + &spacetimedb_repo_path.join("crates/bindings-csharp/BSATN.Runtime/bin/Release"), + &spacetimedb_repo_path.join("crates/bindings-csharp/Runtime/bin/Release"), + ); + fs::write(&sdk_config, sdk_config_contents).with_context(|| format!("failed to write {}", sdk_config.display()))?; + + let repo_config = spacetimedb_repo_path.join("NuGet.Config"); + let repo_config_contents = render_nuget_config( + Path::new("crates/bindings-csharp/BSATN.Runtime/bin/Release"), + Path::new("crates/bindings-csharp/Runtime/bin/Release"), + ); + fs::write(&repo_config, repo_config_contents) + .with_context(|| format!("failed to write {}", repo_config.display()))?; + + println!("Wrote {} contents:", sdk_config.display()); + print!("{}", fs::read_to_string(&sdk_config)?); + + Ok(()) +} + +fn remove_obj_tilde_children(parent: &Path) -> Result<()> { + if !parent.exists() { + return Ok(()); + } + + for entry in fs::read_dir(parent)? { + let entry = entry?; + if entry.file_type()?.is_dir() { + let obj_tilde = entry.path().join("obj~"); + if obj_tilde.exists() { + fs::remove_dir_all(&obj_tilde).with_context(|| format!("failed to remove {}", obj_tilde.display()))?; + } + } + } + Ok(()) +} + +fn clean_procedure_obj_tilde_dirs() -> Result<()> { + let procedure_client = sdk_dir().join("examples~/regression-tests/procedure-client"); + println!("Cleanup obj~ folders generated in {}", procedure_client.display()); + remove_obj_tilde_children(&procedure_client)?; + remove_obj_tilde_children(&procedure_client.join("module_bindings"))?; + Ok(()) +} + +pub fn run_regression_tests() -> Result<()> { + let sdk = sdk_dir(); + let workspace = workspace_dir(); + let server_url = env::var("SPACETIMEDB_SERVER_URL").unwrap_or_else(|_| "local".to_string()); + + cmd!("cargo", "regen", "csharp", "regression-tests").run()?; + + cmd!("cargo", "build", "--manifest-path", path_arg(&standalone_manifest())).run()?; + + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "publish", + "-c", + "-y", + "--server", + &server_url, + "-p", + path_arg(&sdk.join("examples~/regression-tests/server")), + "btree-repro", + ) + .run()?; + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "publish", + "-c", + "-y", + "--server", + &server_url, + "-p", + path_arg(&sdk.join("examples~/regression-tests/republishing/server-initial")), + "republish-test", + ) + .run()?; + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "call", + "--server", + &server_url, + "republish-test", + "insert", + "1", + ) + .run()?; + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "publish", + "--server", + &server_url, + "-p", + path_arg(&sdk.join("examples~/regression-tests/republishing/server-republish")), + "--break-clients", + "republish-test", + ) + .run()?; + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "call", + "--server", + &server_url, + "republish-test", + "insert", + "2", + ) + .run()?; + + clean_procedure_obj_tilde_dirs()?; + + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "publish", + "-c", + "-y", + "--server", + &server_url, + "-p", + path_arg(&workspace.join("modules/sdk-test-procedure")), + "procedure-tests", + ) + .run()?; + + cmd!("dotnet", "run", "-c", "Debug") + .dir(sdk.join("examples~/regression-tests/client")) + .run()?; + cmd!("dotnet", "run", "-c", "Debug") + .dir(sdk.join("examples~/regression-tests/republishing/client")) + .run()?; + cmd!("dotnet", "run", "-c", "Debug") + .dir(sdk.join("examples~/regression-tests/procedure-client")) + .run()?; + + Ok(()) +} diff --git a/tools/csharp-tools/src/main.rs b/tools/csharp-tools/src/main.rs new file mode 100644 index 00000000000..8088ff0a45f --- /dev/null +++ b/tools/csharp-tools/src/main.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +#[derive(Parser)] +#[command(name = "csharp-tools", bin_name = "cargo csharp", about = "C# SDK maintenance tasks")] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Write NuGet.Config files that point at local SpacetimeDB C# packages. + WriteNugetConfig { + /// Path to the SpacetimeDB repository whose C# packages should be used. + spacetimedb_repo_path: Option, + }, + /// Run the C# regression test workflow against a running local SpacetimeDB instance. + RunRegressionTests, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Command::WriteNugetConfig { spacetimedb_repo_path } => { + csharp_tools::write_persistent_nuget_configs(spacetimedb_repo_path.as_deref())?; + } + Command::RunRegressionTests => { + csharp_tools::run_regression_tests()?; + } + } + + Ok(()) +} diff --git a/tools/regen/src/csharp.rs b/tools/regen/src/csharp.rs new file mode 100644 index 00000000000..18cc50bb3f0 --- /dev/null +++ b/tools/regen/src/csharp.rs @@ -0,0 +1,303 @@ +#![allow(clippy::disallowed_macros)] + +use anyhow::{bail, Result}; +use duct::cmd; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +const BSATN_PACKAGE_ID: &str = "spacetimedb.bsatn.runtime"; +const RUNTIME_PACKAGE_ID: &str = "spacetimedb.runtime"; + +fn workspace_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .expect("tools/regen should be two levels below the workspace root") + .to_path_buf() +} + +fn sdk_dir() -> PathBuf { + workspace_dir().join("sdks/csharp") +} + +fn cli_manifest() -> PathBuf { + workspace_dir().join("crates/cli/Cargo.toml") +} + +fn standalone_manifest() -> PathBuf { + workspace_dir().join("crates/standalone/Cargo.toml") +} + +fn path_arg(path: &Path) -> String { + path.to_string_lossy().into_owned() +} + +fn render_nuget_config(bsatn_source: &Path, runtime_source: &Path) -> String { + format!( + r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"#, + bsatn_source.display(), + runtime_source.display(), + ) +} + +pub fn regen_regression_tests() -> Result<()> { + let sdk = sdk_dir(); + let workspace = workspace_dir(); + + cmd!("cargo", "build", "--manifest-path", path_arg(&standalone_manifest())).run()?; + + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "generate", + "-y", + "-l", + "csharp", + "-o", + path_arg(&sdk.join("examples~/regression-tests/client/module_bindings")), + "--module-path", + path_arg(&sdk.join("examples~/regression-tests/server")), + ) + .run()?; + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "generate", + "-y", + "-l", + "csharp", + "-o", + path_arg(&sdk.join("examples~/regression-tests/republishing/client/module_bindings")), + "--module-path", + path_arg(&sdk.join("examples~/regression-tests/republishing/server-republish")), + ) + .run()?; + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "generate", + "-y", + "-l", + "csharp", + "-o", + path_arg(&sdk.join("examples~/regression-tests/procedure-client/module_bindings")), + "--module-path", + path_arg(&workspace.join("modules/sdk-test-procedure")), + ) + .run()?; + + Ok(()) +} + +fn overlay_unity_meta_skeleton(pkg_id: &str) -> Result<()> { + let sdk = sdk_dir(); + let skeleton_base = sdk.join("unity-meta-skeleton~"); + let skeleton_root = skeleton_base.join(pkg_id); + if !skeleton_root.exists() { + return Ok(()); + } + + let pkg_root = sdk.join("packages").join(pkg_id); + if !pkg_root.exists() { + return Ok(()); + } + + let pkg_root_meta = skeleton_base.join(format!("{pkg_id}.meta")); + if pkg_root_meta.exists() + && let Some(parent) = pkg_root.parent() + { + let pkg_meta_dst = parent.join(format!("{pkg_id}.meta")); + fs::copy(&pkg_root_meta, &pkg_meta_dst)?; + } + + let versioned_dir = match find_only_subdir(&pkg_root) { + Ok(dir) => dir, + Err(err) => { + eprintln!("Skipping Unity meta overlay for {pkg_id}: could not locate restored version dir: {err}"); + return Ok(()); + } + }; + + let version_meta_template = skeleton_root.join("version.meta"); + if version_meta_template.exists() + && let Some(parent) = versioned_dir.parent() + { + let version_name = versioned_dir + .file_name() + .expect("versioned directory should have a file name"); + let version_meta_dst = parent.join(format!("{}.meta", version_name.to_string_lossy())); + fs::copy(&version_meta_template, &version_meta_dst)?; + } + + copy_overlay_dir(&skeleton_root, &versioned_dir) +} + +fn clear_restored_package_dirs(pkg_id: &str) -> Result<()> { + let pkg_root = sdk_dir().join("packages").join(pkg_id); + if pkg_root.exists() { + fs::remove_dir_all(&pkg_root)?; + } + Ok(()) +} + +fn find_only_subdir(dir: &Path) -> Result { + let mut subdirs = vec![]; + + for entry in fs::read_dir(dir)? { + let entry = entry?; + if entry.file_type()?.is_dir() { + subdirs.push(entry.path()); + } + } + + match subdirs.as_slice() { + [] => bail!("Could not find a restored versioned directory under {}", dir.display()), + [only] => Ok(only.clone()), + _ => bail!( + "Expected exactly one restored versioned directory under {}, found {}", + dir.display(), + subdirs.len() + ), + } +} + +fn copy_overlay_dir(src: &Path, dst: &Path) -> Result<()> { + if !src.exists() { + bail!("Skeleton directory does not exist: {}", src.display()); + } + if !dst.exists() { + bail!("Destination directory does not exist: {}", dst.display()); + } + + for entry in fs::read_dir(src)? { + let entry = entry?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + if entry.file_type()?.is_dir() { + if dst_path.exists() { + copy_overlay_dir(&src_path, &dst_path)?; + } + } else { + if src_path.extension().is_some_and(|ext| ext == "meta") { + let asset_path = dst_path + .parent() + .expect("dst_path should have a parent") + .join(dst_path.file_stem().expect(".meta file should have a file stem")); + + if asset_path.exists() { + fs::copy(&src_path, &dst_path)?; + } else if dst_path.exists() { + fs::remove_file(&dst_path)?; + } + continue; + } + + fs::copy(&src_path, &dst_path)?; + } + } + + Ok(()) +} + +pub fn regen_dlls() -> Result<()> { + let workspace = workspace_dir(); + let sdk = sdk_dir(); + + cmd!( + "dotnet", + "pack", + workspace.join("crates/bindings-csharp/BSATN.Runtime"), + "-c", + "Release" + ) + .run()?; + cmd!( + "dotnet", + "pack", + workspace.join("crates/bindings-csharp/Runtime"), + "-c", + "Release" + ) + .run()?; + + let nuget_config_dir = tempfile::tempdir()?; + let nuget_config_path = nuget_config_dir.path().join("nuget.config"); + fs::write( + &nuget_config_path, + render_nuget_config( + &workspace.join("crates/bindings-csharp/BSATN.Runtime/bin/Release"), + &workspace.join("crates/bindings-csharp/Runtime/bin/Release"), + ), + )?; + + clear_restored_package_dirs(BSATN_PACKAGE_ID)?; + clear_restored_package_dirs(RUNTIME_PACKAGE_ID)?; + + cmd!( + "dotnet", + "restore", + "SpacetimeDB.ClientSDK.csproj", + "--configfile", + path_arg(&nuget_config_path), + ) + .dir(&sdk) + .run()?; + + overlay_unity_meta_skeleton(BSATN_PACKAGE_ID)?; + overlay_unity_meta_skeleton(RUNTIME_PACKAGE_ID)?; + + cmd!( + "dotnet", + "pack", + "SpacetimeDB.ClientSDK.csproj", + "-c", + "Release", + "--no-restore" + ) + .dir(&sdk) + .run()?; + + Ok(()) +} diff --git a/tools/regen/src/main.rs b/tools/regen/src/main.rs new file mode 100644 index 00000000000..c8e07dc676d --- /dev/null +++ b/tools/regen/src/main.rs @@ -0,0 +1,41 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; + +mod csharp; + +#[derive(Parser)] +#[command(name = "regen", bin_name = "cargo regen", about = "Regenerate checked-in artifacts")] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Regenerate C# SDK artifacts. + Csharp { + #[command(subcommand)] + command: CsharpCommand, + }, +} + +#[derive(Subcommand)] +enum CsharpCommand { + /// Regenerate C# regression test bindings. + RegressionTests, + /// Regenerate C# DLL and NuGet package artifacts for Unity workflows. + Dlls, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Command::Csharp { command } => match command { + CsharpCommand::RegressionTests => csharp::regen_regression_tests()?, + CsharpCommand::Dlls => csharp::regen_dlls()?, + }, + } + + Ok(()) +} From e4874fcf6f2c5d3ae0532128e21385a53f93406f Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 14:00:36 -0700 Subject: [PATCH 04/19] [bfops/translate-script]: update docs --- tools/ci/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/README.md b/tools/ci/README.md index 7e4cb6186ba..34d2534f703 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -242,4 +242,4 @@ This document is auto-generated by running: ```bash cargo ci self-docs -``` +``` \ No newline at end of file From 2b3071b25ac066dc9bc137c38225e1d441699147 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 14:03:21 -0700 Subject: [PATCH 05/19] [bfops/translate-script]: fix order? --- .github/workflows/ci.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b2d7c472cd..cfbb357efdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -614,6 +614,11 @@ jobs: with: global-json-file: global.json + - name: Install Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + - name: Override NuGet packages run: | dotnet pack crates/bindings-csharp/BSATN.Runtime @@ -638,11 +643,6 @@ jobs: sed -i "s|spacetimedb *=.*|spacetimedb = \{ path = \"../../../crates/bindings\" \}|" Cargo.toml cat Cargo.toml - - name: Install Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: @@ -758,6 +758,11 @@ jobs: with: global-json-file: global.json + - name: Install Rust toolchain + uses: dsherret/rust-toolchain-file@v1 + - name: Set default rust toolchain + run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) + - name: Override NuGet packages run: | dotnet pack crates/bindings-csharp/BSATN.Runtime @@ -783,11 +788,6 @@ jobs: working-directory: sdks/csharp run: dotnet format --no-restore --verify-no-changes SpacetimeDB.ClientSDK.sln - - name: Install Rust toolchain - uses: dsherret/rust-toolchain-file@v1 - - name: Set default rust toolchain - run: rustup default $(rustup show active-toolchain | cut -d' ' -f1) - - name: Cache Rust dependencies uses: Swatinem/rust-cache@v2 with: From e5f3e0aa5c162573c530bef0757953efd45d2e35 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 14:35:29 -0700 Subject: [PATCH 06/19] [bfops/translate-script]: update readme --- tools/ci/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/ci/README.md b/tools/ci/README.md index 34d2534f703..577a87a89e9 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -70,6 +70,21 @@ Usage: wasm-bindings - `--help`: Print help (see a summary with '-h') +### `dlls` + +Deprecated; use `cargo regen csharp dlls`. + +Builds and packs C# DLLs and NuGet packages for local Unity workflows. + +**Usage:** +```bash +Usage: dlls +``` + +**Options:** + +- `--help`: Print help (see a summary with '-h') + ### `smoketests` Runs smoketests @@ -242,4 +257,4 @@ This document is auto-generated by running: ```bash cargo ci self-docs -``` \ No newline at end of file +``` From 14aae3cd2c598314e26dc3393c8fece605c6fa31 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 14:35:38 -0700 Subject: [PATCH 07/19] [bfops/translate-script]: add back cargo ci dlls --- tools/ci/src/main.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 29709f55517..2454ea3349f 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -108,6 +108,10 @@ enum CiCmd { /// /// Runs tests for the codegen crate and builds a test module with the wasm bindings. WasmBindings, + /// Deprecated; use `cargo regen csharp dlls`. + /// + /// Builds and packs C# DLLs and NuGet packages for local Unity workflows. + Dlls, /// Runs smoketests /// /// Executes the smoketests suite with some default exclusions. @@ -421,6 +425,11 @@ fn main() -> Result<()> { cmd!(cli_path, "build", "--module-path", "modules/module-test",).run()?; } + Some(CiCmd::Dlls) => { + eprintln!("warning: `cargo ci dlls` is deprecated; use `cargo regen csharp dlls` instead"); + cmd!("cargo", "regen", "csharp", "dlls").run()?; + } + Some(CiCmd::Smoketests(args)) => { ensure_repo_root()?; smoketest::run(args)?; From ce0520744d487aa77e25cee184811d316fa5b34e Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 14:44:41 -0700 Subject: [PATCH 08/19] [bfops/translate-script]: update --- tools/ci/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/README.md b/tools/ci/README.md index 577a87a89e9..9b71b406fef 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -257,4 +257,4 @@ This document is auto-generated by running: ```bash cargo ci self-docs -``` +``` \ No newline at end of file From 151895b24b5107803164d1fbcb37308cfc6da862 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 16:50:48 -0700 Subject: [PATCH 09/19] [bfops/regen-dlls]: migrate changes --- .cargo/config.toml | 1 + .github/workflows/ci.yml | 2 +- Cargo.toml | 1 + tools/ci/README.md | 4 +- tools/ci/src/main.rs | 204 +------------------------------ tools/regen/Cargo.toml | 10 ++ tools/regen/src/csharp.rs | 245 ++++++++++++++++++++++++++++++++++++++ tools/regen/src/main.rs | 38 ++++++ 8 files changed, 302 insertions(+), 203 deletions(-) create mode 100644 tools/regen/Cargo.toml create mode 100644 tools/regen/src/csharp.rs create mode 100644 tools/regen/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 48647676517..cd65da0b165 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,6 +5,7 @@ rustflags = ["--cfg", "tokio_unstable"] bump-versions = "run -p upgrade-version --" llm = "run --package xtask-llm-benchmark --bin llm_benchmark --" ci = "run -p ci --" +regen = "run -p regen --" smoketest = "ci smoketests --" smoketests = "smoketest" lint = "ci lint --" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9bb00eb6f12..227e995d80b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -689,7 +689,7 @@ jobs: } - name: Hydrate Unity SDK DLLs - run: cargo ci dlls + run: cargo regen csharp dlls - name: Check Unity meta files uses: DeNA/unity-meta-check@v3 diff --git a/Cargo.toml b/Cargo.toml index 120ec24c2cf..ef15a8e2a31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ members = [ "tools/replace-spacetimedb", "tools/generate-client-api", "tools/gen-bindings", + "tools/regen", "tools/xtask-llm-benchmark", "crates/bindings-typescript/test-app/server", "crates/bindings-typescript/test-react-router-app/server", diff --git a/tools/ci/README.md b/tools/ci/README.md index 3530ee24b5e..9b71b406fef 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -72,9 +72,9 @@ Usage: wasm-bindings ### `dlls` -Builds and packs C# DLLs and NuGet packages for local Unity workflows +Deprecated; use `cargo regen csharp dlls`. -Packs the in-repo C# NuGet packages and restores the C# SDK to populate `sdks/csharp/packages/**`. Then overlays Unity `.meta` skeleton files from `sdks/csharp/unity-meta-skeleton~/**` onto the restored versioned package directory, so Unity can associate stable meta files with the most recently built package. +Builds and packs C# DLLs and NuGet packages for local Unity workflows. **Usage:** ```bash diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 146651c1053..2454ea3349f 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -91,123 +91,6 @@ fn check_global_json_policy() -> Result<()> { Ok(()) } -fn overlay_unity_meta_skeleton(pkg_id: &str) -> Result<()> { - let skeleton_base = Path::new("sdks/csharp/unity-meta-skeleton~"); - let skeleton_root = skeleton_base.join(pkg_id); - if !skeleton_root.exists() { - return Ok(()); - } - - let pkg_root = Path::new("sdks/csharp/packages").join(pkg_id); - if !pkg_root.exists() { - return Ok(()); - } - - // Copy spacetimedb..meta - let pkg_root_meta = skeleton_base.join(format!("{pkg_id}.meta")); - if pkg_root_meta.exists() - && let Some(parent) = pkg_root.parent() - { - let pkg_meta_dst = parent.join(format!("{pkg_id}.meta")); - fs::copy(&pkg_root_meta, &pkg_meta_dst)?; - } - - let versioned_dir = match find_only_subdir(&pkg_root) { - Ok(dir) => dir, - Err(err) => { - log::info!("Skipping Unity meta overlay for {pkg_id}: could not locate restored version dir: {err}"); - return Ok(()); - } - }; - - // If version.meta exists under the skeleton package, rename it to match the restored version dir. - let version_meta_template = skeleton_root.join("version.meta"); - if version_meta_template.exists() - && let Some(parent) = versioned_dir.parent() - { - let version_name = versioned_dir - .file_name() - .expect("versioned directory should have a file name"); - let version_meta_dst = parent.join(format!("{}.meta", version_name.to_string_lossy())); - fs::copy(&version_meta_template, &version_meta_dst)?; - } - - copy_overlay_dir(&skeleton_root, &versioned_dir) -} - -fn clear_restored_package_dirs(pkg_id: &str) -> Result<()> { - let pkg_root = Path::new("sdks/csharp/packages").join(pkg_id); - if !pkg_root.exists() { - return Ok(()); - } - - fs::remove_dir_all(&pkg_root)?; - - Ok(()) -} - -fn find_only_subdir(dir: &Path) -> Result { - let mut subdirs: Vec = vec![]; - - for entry in fs::read_dir(dir)? { - let entry = entry?; - if entry.file_type()?.is_dir() { - subdirs.push(entry.path()); - } - } - - match subdirs.as_slice() { - [] => Err(anyhow::anyhow!( - "Could not find a restored versioned directory under {}", - dir.display() - )), - [only] => Ok(only.clone()), - _ => Err(anyhow::anyhow!( - "Expected exactly one restored versioned directory under {}, found {}", - dir.display(), - subdirs.len() - )), - } -} - -fn copy_overlay_dir(src: &Path, dst: &Path) -> Result<()> { - if !src.exists() { - bail!("Skeleton directory does not exist: {}", src.display()); - } - if !dst.exists() { - bail!("Destination directory does not exist: {}", dst.display()); - } - - for entry in fs::read_dir(src)? { - let entry = entry?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); - if entry.file_type()?.is_dir() { - if dst_path.exists() { - copy_overlay_dir(&src_path, &dst_path)?; - } - } else { - if src_path.extension() == Some(OsStr::new("meta")) { - let asset_path = dst_path - .parent() - .expect("dst_path should have a parent") - .join(dst_path.file_stem().expect(".meta file should have a file stem")); - - if asset_path.exists() { - fs::copy(&src_path, &dst_path)?; - } else if dst_path.exists() { - fs::remove_file(&dst_path)?; - } - continue; - } - - fs::copy(&src_path, &dst_path)?; - } - } - - Ok(()) -} - #[derive(Subcommand)] enum CiCmd { /// Runs tests @@ -225,11 +108,9 @@ enum CiCmd { /// /// Runs tests for the codegen crate and builds a test module with the wasm bindings. WasmBindings, - /// Builds and packs C# DLLs and NuGet packages for local Unity workflows + /// Deprecated; use `cargo regen csharp dlls`. /// - /// Packs the in-repo C# NuGet packages and restores the C# SDK to populate `sdks/csharp/packages/**`. - /// Then overlays Unity `.meta` skeleton files from `sdks/csharp/unity-meta-skeleton~/**` onto the restored - /// versioned package directory, so Unity can associate stable meta files with the most recently built package. + /// Builds and packs C# DLLs and NuGet packages for local Unity workflows. Dlls, /// Runs smoketests /// @@ -308,84 +189,6 @@ fn tracked_rs_files_under(path: &str) -> Result> { .collect()) } -fn run_dlls() -> Result<()> { - ensure_repo_root()?; - - cmd!( - "dotnet", - "pack", - "crates/bindings-csharp/BSATN.Runtime", - "-c", - "Release" - ) - .run()?; - cmd!("dotnet", "pack", "crates/bindings-csharp/Runtime", "-c", "Release").run()?; - - let repo_root = env::current_dir()?; - let bsatn_source = repo_root.join("crates/bindings-csharp/BSATN.Runtime/bin/Release"); - let runtime_source = repo_root.join("crates/bindings-csharp/Runtime/bin/Release"); - - let nuget_config_dir = tempfile::tempdir()?; - let nuget_config_path = nuget_config_dir.path().join("nuget.config"); - let nuget_config_contents = format!( - r#" - - - - - - - - - - - - - - - - - - - - "#, - bsatn_source.display(), - runtime_source.display(), - ); - fs::write(&nuget_config_path, nuget_config_contents)?; - - let nuget_config_path_str = nuget_config_path.to_string_lossy().to_string(); - - clear_restored_package_dirs("spacetimedb.bsatn.runtime")?; - clear_restored_package_dirs("spacetimedb.runtime")?; - - cmd!( - "dotnet", - "restore", - "SpacetimeDB.ClientSDK.csproj", - "--configfile", - &nuget_config_path_str, - ) - .dir("sdks/csharp") - .run()?; - - overlay_unity_meta_skeleton("spacetimedb.bsatn.runtime")?; - overlay_unity_meta_skeleton("spacetimedb.runtime")?; - - cmd!( - "dotnet", - "pack", - "SpacetimeDB.ClientSDK.csproj", - "-c", - "Release", - "--no-restore" - ) - .dir("sdks/csharp") - .run()?; - - Ok(()) -} - fn run_publish_checks() -> Result<()> { cmd!("bash", "-lc", "test -d venv || python3 -m venv venv").run()?; cmd!("venv/bin/pip3", "install", "argparse", "toml").run()?; @@ -623,7 +426,8 @@ fn main() -> Result<()> { } Some(CiCmd::Dlls) => { - run_dlls()?; + eprintln!("warning: `cargo ci dlls` is deprecated; use `cargo regen csharp dlls` instead"); + cmd!("cargo", "regen", "csharp", "dlls").run()?; } Some(CiCmd::Smoketests(args)) => { diff --git a/tools/regen/Cargo.toml b/tools/regen/Cargo.toml new file mode 100644 index 00000000000..cd3613f267b --- /dev/null +++ b/tools/regen/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "regen" +version = "0.1.0" +edition.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +duct.workspace = true +tempfile.workspace = true diff --git a/tools/regen/src/csharp.rs b/tools/regen/src/csharp.rs new file mode 100644 index 00000000000..6092c56ca9c --- /dev/null +++ b/tools/regen/src/csharp.rs @@ -0,0 +1,245 @@ +#![allow(clippy::disallowed_macros)] + +use anyhow::{bail, Result}; +use duct::cmd; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +const BSATN_PACKAGE_ID: &str = "spacetimedb.bsatn.runtime"; +const RUNTIME_PACKAGE_ID: &str = "spacetimedb.runtime"; + +fn workspace_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .expect("tools/regen should be two levels below the workspace root") + .to_path_buf() +} + +fn sdk_dir() -> PathBuf { + workspace_dir().join("sdks/csharp") +} + +fn cli_manifest() -> PathBuf { + workspace_dir().join("crates/cli/Cargo.toml") +} + +fn standalone_manifest() -> PathBuf { + workspace_dir().join("crates/standalone/Cargo.toml") +} + +fn path_arg(path: &Path) -> String { + path.to_string_lossy().into_owned() +} + +fn render_nuget_config(bsatn_source: &Path, runtime_source: &Path) -> String { + format!( + r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"#, + bsatn_source.display(), + runtime_source.display(), + ) +} + +fn overlay_unity_meta_skeleton(pkg_id: &str) -> Result<()> { + let sdk = sdk_dir(); + let skeleton_base = sdk.join("unity-meta-skeleton~"); + let skeleton_root = skeleton_base.join(pkg_id); + if !skeleton_root.exists() { + return Ok(()); + } + + let pkg_root = sdk.join("packages").join(pkg_id); + if !pkg_root.exists() { + return Ok(()); + } + + let pkg_root_meta = skeleton_base.join(format!("{pkg_id}.meta")); + if pkg_root_meta.exists() + && let Some(parent) = pkg_root.parent() + { + let pkg_meta_dst = parent.join(format!("{pkg_id}.meta")); + fs::copy(&pkg_root_meta, &pkg_meta_dst)?; + } + + let versioned_dir = match find_only_subdir(&pkg_root) { + Ok(dir) => dir, + Err(err) => { + eprintln!("Skipping Unity meta overlay for {pkg_id}: could not locate restored version dir: {err}"); + return Ok(()); + } + }; + + let version_meta_template = skeleton_root.join("version.meta"); + if version_meta_template.exists() + && let Some(parent) = versioned_dir.parent() + { + let version_name = versioned_dir + .file_name() + .expect("versioned directory should have a file name"); + let version_meta_dst = parent.join(format!("{}.meta", version_name.to_string_lossy())); + fs::copy(&version_meta_template, &version_meta_dst)?; + } + + copy_overlay_dir(&skeleton_root, &versioned_dir) +} + +fn clear_restored_package_dirs(pkg_id: &str) -> Result<()> { + let pkg_root = sdk_dir().join("packages").join(pkg_id); + if pkg_root.exists() { + fs::remove_dir_all(&pkg_root)?; + } + Ok(()) +} + +fn find_only_subdir(dir: &Path) -> Result { + let mut subdirs = vec![]; + + for entry in fs::read_dir(dir)? { + let entry = entry?; + if entry.file_type()?.is_dir() { + subdirs.push(entry.path()); + } + } + + match subdirs.as_slice() { + [] => bail!("Could not find a restored versioned directory under {}", dir.display()), + [only] => Ok(only.clone()), + _ => bail!( + "Expected exactly one restored versioned directory under {}, found {}", + dir.display(), + subdirs.len() + ), + } +} + +fn copy_overlay_dir(src: &Path, dst: &Path) -> Result<()> { + if !src.exists() { + bail!("Skeleton directory does not exist: {}", src.display()); + } + if !dst.exists() { + bail!("Destination directory does not exist: {}", dst.display()); + } + + for entry in fs::read_dir(src)? { + let entry = entry?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + if entry.file_type()?.is_dir() { + if dst_path.exists() { + copy_overlay_dir(&src_path, &dst_path)?; + } + } else { + if src_path.extension().is_some_and(|ext| ext == "meta") { + let asset_path = dst_path + .parent() + .expect("dst_path should have a parent") + .join(dst_path.file_stem().expect(".meta file should have a file stem")); + + if asset_path.exists() { + fs::copy(&src_path, &dst_path)?; + } else if dst_path.exists() { + fs::remove_file(&dst_path)?; + } + continue; + } + + fs::copy(&src_path, &dst_path)?; + } + } + + Ok(()) +} + +pub fn regen_dlls() -> Result<()> { + let workspace = workspace_dir(); + let sdk = sdk_dir(); + + cmd!( + "dotnet", + "pack", + workspace.join("crates/bindings-csharp/BSATN.Runtime"), + "-c", + "Release" + ) + .run()?; + cmd!( + "dotnet", + "pack", + workspace.join("crates/bindings-csharp/Runtime"), + "-c", + "Release" + ) + .run()?; + + let nuget_config_dir = tempfile::tempdir()?; + let nuget_config_path = nuget_config_dir.path().join("nuget.config"); + fs::write( + &nuget_config_path, + render_nuget_config( + &workspace.join("crates/bindings-csharp/BSATN.Runtime/bin/Release"), + &workspace.join("crates/bindings-csharp/Runtime/bin/Release"), + ), + )?; + + clear_restored_package_dirs(BSATN_PACKAGE_ID)?; + clear_restored_package_dirs(RUNTIME_PACKAGE_ID)?; + + cmd!( + "dotnet", + "restore", + "SpacetimeDB.ClientSDK.csproj", + "--configfile", + path_arg(&nuget_config_path), + ) + .dir(&sdk) + .run()?; + + overlay_unity_meta_skeleton(BSATN_PACKAGE_ID)?; + overlay_unity_meta_skeleton(RUNTIME_PACKAGE_ID)?; + + cmd!( + "dotnet", + "pack", + "SpacetimeDB.ClientSDK.csproj", + "-c", + "Release", + "--no-restore" + ) + .dir(&sdk) + .run()?; + + Ok(()) +} diff --git a/tools/regen/src/main.rs b/tools/regen/src/main.rs new file mode 100644 index 00000000000..48aa55a305c --- /dev/null +++ b/tools/regen/src/main.rs @@ -0,0 +1,38 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; + +mod csharp; + +#[derive(Parser)] +#[command(name = "regen", bin_name = "cargo regen", about = "Regenerate checked-in artifacts")] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Regenerate C# SDK artifacts. + Csharp { + #[command(subcommand)] + command: CsharpCommand, + }, +} + +#[derive(Subcommand)] +enum CsharpCommand { + /// Regenerate C# DLL and NuGet package artifacts for Unity workflows. + Dlls, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Command::Csharp { command } => match command { + CsharpCommand::Dlls => csharp::regen_dlls()?, + }, + } + + Ok(()) +} From 5552adbe49a6075c27ddbdec5b14a22959748269 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 16:54:02 -0700 Subject: [PATCH 10/19] [bfops/regen-dlls]: lints --- Cargo.lock | 10 ++++++++++ tools/regen/src/csharp.rs | 8 -------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cc97fceadc..14344b10a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6237,6 +6237,16 @@ dependencies = [ "smallvec", ] +[[package]] +name = "regen" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.5.50", + "duct", + "tempfile", +] + [[package]] name = "regex" version = "1.12.2" diff --git a/tools/regen/src/csharp.rs b/tools/regen/src/csharp.rs index 6092c56ca9c..f8ac31f0335 100644 --- a/tools/regen/src/csharp.rs +++ b/tools/regen/src/csharp.rs @@ -21,14 +21,6 @@ fn sdk_dir() -> PathBuf { workspace_dir().join("sdks/csharp") } -fn cli_manifest() -> PathBuf { - workspace_dir().join("crates/cli/Cargo.toml") -} - -fn standalone_manifest() -> PathBuf { - workspace_dir().join("crates/standalone/Cargo.toml") -} - fn path_arg(path: &Path) -> String { path.to_string_lossy().into_owned() } From 3518b332ef258b393b94c47435428c2a67fbc864 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 17:22:28 -0700 Subject: [PATCH 11/19] [bfops/translate-script]: updates --- .github/workflows/ci.yml | 6 +-- sdks/csharp/DEVELOP.md | 2 +- sdks/csharp/tests~/README.md | 4 +- sdks/csharp/tools~/update-against-stdb.sh | 3 +- tools/csharp-tools/src/lib.rs | 25 +++++----- tools/csharp-tools/src/main.rs | 17 +++++-- tools/regen/src/csharp.rs | 57 ++++------------------- 7 files changed, 42 insertions(+), 72 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfbb357efdd..1cea505b16f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,7 +132,7 @@ jobs: run: | dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime dotnet pack -c Release crates/bindings-csharp/Runtime - cargo csharp write-nuget-config + cargo csharp write-nuget-config sdks/csharp . # This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a. # ChatGPT suspects that this could be due to different build invocations using the same target dir, @@ -630,7 +630,7 @@ jobs: # This means that (if version numbers match) we will test the local versions of the C# packages, even # if they're not pushed to NuGet. # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. - cargo csharp write-nuget-config + cargo csharp write-nuget-config sdks/csharp . - name: Restore .NET solution working-directory: sdks/csharp @@ -774,7 +774,7 @@ jobs: # This means that (if version numbers match) we will test the local versions of the C# packages, even # if they're not pushed to NuGet. # See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file. - cargo csharp write-nuget-config + cargo csharp write-nuget-config sdks/csharp . - name: Restore .NET solution working-directory: sdks/csharp diff --git a/sdks/csharp/DEVELOP.md b/sdks/csharp/DEVELOP.md index 1fd5396d411..a12d3524155 100644 --- a/sdks/csharp/DEVELOP.md +++ b/sdks/csharp/DEVELOP.md @@ -12,7 +12,7 @@ When developing against a local clone of SpacetimeDB, you'll need to ensure that To develop against a local clone of SpacetimeDB at `../SpacetimeDB`, run the following command: ```sh -dotnet pack ../SpacetimeDB/crates/bindings-csharp/BSATN.Runtime && cargo csharp write-nuget-config ../SpacetimeDB +dotnet pack ../SpacetimeDB/crates/bindings-csharp/BSATN.Runtime && cargo csharp write-nuget-config . --stdb-path ../SpacetimeDB ``` This will create a (`.gitignore`d) `nuget.config` file that uses the local build of the package, instead of the package on NuGet. diff --git a/sdks/csharp/tests~/README.md b/sdks/csharp/tests~/README.md index 232d4105d48..05a1edb4318 100644 --- a/sdks/csharp/tests~/README.md +++ b/sdks/csharp/tests~/README.md @@ -4,7 +4,7 @@ You can use `dotnet test` (either in this directory or in the project root direc # Using a different SpacetimeDB version To run tests using a local version of the `SpacetimeDB` repo, you can add a `nuget.config` file in the **root** of this repository. -The `cargo csharp write-nuget-config` command can generate the `nuget.config`. It takes one optional parameter: the path to the root SpacetimeDB repository (relative or absolute). +The `cargo csharp write-nuget-config` command can generate `NuGet.Config`. It takes one or more directories where `NuGet.Config` should be written. Use `--stdb-path` to point at a different SpacetimeDB repository. Then, you need to `dotnet pack` the `BSATN.Runtime` package in the `SpacetimeDB` repo. @@ -13,7 +13,7 @@ Lastly, before running `dotnet test`, you should `dotnet nuget locals all --clea Example: ```bash $ export SPACETIMEDB_REPO_PATH="../SpacetimeDB" -$ cargo csharp write-nuget-config "${SPACETIMEDB_REPO_PATH}" +$ cargo csharp write-nuget-config . --stdb-path "${SPACETIMEDB_REPO_PATH}" $ ( cd "${SPACETIMEDB_REPO_PATH}"/crates/bindings-csharp/BSATN.Runtime && dotnet pack ) $ dotnet nuget locals all --clear $ dotnet test diff --git a/sdks/csharp/tools~/update-against-stdb.sh b/sdks/csharp/tools~/update-against-stdb.sh index d90f90f1f4b..f17ce6c12b5 100644 --- a/sdks/csharp/tools~/update-against-stdb.sh +++ b/sdks/csharp/tools~/update-against-stdb.sh @@ -8,7 +8,7 @@ STDB_PATH="$1" SDK_PATH="$(dirname "$0")/.." SDK_PATH="$(realpath "$SDK_PATH")" -cargo csharp write-nuget-config "$STDB_PATH" +cargo csharp write-nuget-config "$SDK_PATH" "$STDB_PATH" --stdb-path "$STDB_PATH" "$SDK_PATH/tools~/gen-client-api.sh" "$SDK_PATH/tools~/gen-quickstart.sh" cargo regen csharp regression-tests @@ -18,4 +18,3 @@ rm -rf "$SDK_PATH/packages" dotnet pack dotnet test pushd "$SDK_PATH"; git checkout -- 'packages/*.meta' 'packages/**/*.meta' packages/.gitignore; popd - diff --git a/tools/csharp-tools/src/lib.rs b/tools/csharp-tools/src/lib.rs index a3ebf24fb16..2e8c460bd27 100644 --- a/tools/csharp-tools/src/lib.rs +++ b/tools/csharp-tools/src/lib.rs @@ -75,29 +75,28 @@ fn render_nuget_config(bsatn_source: &Path, runtime_source: &Path) -> String { ) } -pub fn write_persistent_nuget_configs(spacetimedb_repo_path: Option<&Path>) -> Result<()> { +pub fn write_nuget_configs(target_dirs: &[PathBuf], spacetimedb_repo_path: Option<&Path>, quiet: bool) -> Result<()> { let spacetimedb_repo_path = match spacetimedb_repo_path { Some(path) => canonicalize_existing(path)?, None => workspace_dir(), }; - let sdk_config = sdk_dir().join("NuGet.Config"); - let sdk_config_contents = render_nuget_config( + let config_contents = render_nuget_config( &spacetimedb_repo_path.join("crates/bindings-csharp/BSATN.Runtime/bin/Release"), &spacetimedb_repo_path.join("crates/bindings-csharp/Runtime/bin/Release"), ); - fs::write(&sdk_config, sdk_config_contents).with_context(|| format!("failed to write {}", sdk_config.display()))?; - let repo_config = spacetimedb_repo_path.join("NuGet.Config"); - let repo_config_contents = render_nuget_config( - Path::new("crates/bindings-csharp/BSATN.Runtime/bin/Release"), - Path::new("crates/bindings-csharp/Runtime/bin/Release"), - ); - fs::write(&repo_config, repo_config_contents) - .with_context(|| format!("failed to write {}", repo_config.display()))?; + for target_dir in target_dirs { + let target_dir = canonicalize_existing(target_dir)?; + let config_path = target_dir.join("NuGet.Config"); + fs::write(&config_path, &config_contents) + .with_context(|| format!("failed to write {}", config_path.display()))?; - println!("Wrote {} contents:", sdk_config.display()); - print!("{}", fs::read_to_string(&sdk_config)?); + if !quiet { + println!("Wrote {} contents:", config_path.display()); + print!("{}", fs::read_to_string(&config_path)?); + } + } Ok(()) } diff --git a/tools/csharp-tools/src/main.rs b/tools/csharp-tools/src/main.rs index 8088ff0a45f..8db53376d9f 100644 --- a/tools/csharp-tools/src/main.rs +++ b/tools/csharp-tools/src/main.rs @@ -13,8 +13,15 @@ struct Cli { enum Command { /// Write NuGet.Config files that point at local SpacetimeDB C# packages. WriteNugetConfig { + /// Directories where NuGet.Config should be written. + #[arg(required = true)] + target_dirs: Vec, /// Path to the SpacetimeDB repository whose C# packages should be used. - spacetimedb_repo_path: Option, + #[arg(long, alias = "spacetimedb-repo-path")] + stdb_path: Option, + /// Do not print the generated SDK NuGet.Config contents. + #[arg(long)] + quiet: bool, }, /// Run the C# regression test workflow against a running local SpacetimeDB instance. RunRegressionTests, @@ -24,8 +31,12 @@ fn main() -> Result<()> { let cli = Cli::parse(); match cli.command { - Command::WriteNugetConfig { spacetimedb_repo_path } => { - csharp_tools::write_persistent_nuget_configs(spacetimedb_repo_path.as_deref())?; + Command::WriteNugetConfig { + target_dirs, + stdb_path, + quiet, + } => { + csharp_tools::write_nuget_configs(&target_dirs, stdb_path.as_deref(), quiet)?; } Command::RunRegressionTests => { csharp_tools::run_regression_tests()?; diff --git a/tools/regen/src/csharp.rs b/tools/regen/src/csharp.rs index 18cc50bb3f0..9a92e90b189 100644 --- a/tools/regen/src/csharp.rs +++ b/tools/regen/src/csharp.rs @@ -33,46 +33,6 @@ fn path_arg(path: &Path) -> String { path.to_string_lossy().into_owned() } -fn render_nuget_config(bsatn_source: &Path, runtime_source: &Path) -> String { - format!( - r#" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"#, - bsatn_source.display(), - runtime_source.display(), - ) -} - pub fn regen_regression_tests() -> Result<()> { let sdk = sdk_dir(); let workspace = workspace_dir(); @@ -263,14 +223,15 @@ pub fn regen_dlls() -> Result<()> { .run()?; let nuget_config_dir = tempfile::tempdir()?; - let nuget_config_path = nuget_config_dir.path().join("nuget.config"); - fs::write( - &nuget_config_path, - render_nuget_config( - &workspace.join("crates/bindings-csharp/BSATN.Runtime/bin/Release"), - &workspace.join("crates/bindings-csharp/Runtime/bin/Release"), - ), - )?; + let nuget_config_path = nuget_config_dir.path().join("NuGet.Config"); + cmd!( + "cargo", + "csharp", + "write-nuget-config", + path_arg(nuget_config_dir.path()), + "--quiet", + ) + .run()?; clear_restored_package_dirs(BSATN_PACKAGE_ID)?; clear_restored_package_dirs(RUNTIME_PACKAGE_ID)?; From b46558f925153b56f23f1182943753d7cea22459 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 17:40:30 -0700 Subject: [PATCH 12/19] [bfops/translate-script]: more scripts --- tools/ci/README.md | 12 ++++++++++++ tools/ci/src/main.rs | 33 +++++++++++++++++++++++---------- tools/ci/src/smoketest.rs | 3 +-- tools/ci/src/util.rs | 16 +++++++++++++++- tools/regen/src/csharp.rs | 25 +++++++++++++++++++++++++ tools/regen/src/main.rs | 3 +++ 6 files changed, 79 insertions(+), 13 deletions(-) diff --git a/tools/ci/README.md b/tools/ci/README.md index 9b71b406fef..562b66d91ba 100644 --- a/tools/ci/README.md +++ b/tools/ci/README.md @@ -228,6 +228,18 @@ Usage: version-upgrade-check - `--help`: Print help +### `check-diff` + +**Usage:** +```bash +Usage: check-diff [SUBDIR] +``` + +**Options:** + +- `subdir`: Subdirectory to check. Defaults to the whole repository +- `--help`: Print help + ### `docs` **Usage:** diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 2454ea3349f..a78a0e78362 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -15,7 +15,7 @@ mod ci_docs; mod smoketest; mod util; -use util::ensure_repo_root; +use util::{check_diff, ensure_repo_root}; /// SpacetimeDB CI tasks /// @@ -159,6 +159,12 @@ enum CiCmd { TypescriptTest, /// Verifies that the repository version upgrade tool still works. VersionUpgradeCheck, + /// Checks for uncommitted diffs, ignoring generated SpacetimeDB CLI version comments. + CheckDiff { + /// Subdirectory to check. Defaults to the whole repository. + #[arg(default_value = ".")] + subdir: PathBuf, + }, /// Builds the docs site. Docs, } @@ -190,7 +196,9 @@ fn tracked_rs_files_under(path: &str) -> Result> { } fn run_publish_checks() -> Result<()> { - cmd!("bash", "-lc", "test -d venv || python3 -m venv venv").run()?; + if !Path::new("venv").is_dir() { + cmd!("python3", "-m", "venv", "venv").run()?; + } cmd!("venv/bin/pip3", "install", "argparse", "toml").run()?; let crates = cmd!( @@ -223,13 +231,7 @@ fn run_typescript_tests() -> Result<()> { cmd!("pnpm", "build").dir("crates/bindings-typescript").run()?; cmd!("pnpm", "test").dir("crates/bindings-typescript").run()?; cmd!("pnpm", "generate").dir("templates/chat-react-ts").run()?; - let diff_status = cmd!( - "bash", - "tools/check-diff.sh", - "templates/chat-react-ts/src/module_bindings" - ) - .run()?; - if !diff_status.status.success() { + if !check_diff(Path::new("templates/chat-react-ts/src/module_bindings"))? { bail!("Bindings are dirty. Please generate bindings again and commit them to this branch."); } cmd!("pnpm", "build").dir("templates/chat-react-ts").run()?; @@ -334,7 +336,9 @@ fn main() -> Result<()> { "--test-threads=1", ) .run()?; - cmd!("bash", "tools/check-diff.sh").run()?; + if !check_diff(Path::new("."))? { + bail!("Repository has uncommitted changes."); + } cmd!( "cargo", "run", @@ -345,6 +349,9 @@ fn main() -> Result<()> { ) .run()?; cmd!("bash", "tools/check-diff.sh", "crates/bindings-csharp").run()?; + if !check_diff(Path::new("crates/bindings-csharp"))? { + bail!("Repository has uncommitted changes."); + } cmd!("dotnet", "test", "-warnaserror") .dir("crates/bindings-csharp") .run()?; @@ -536,6 +543,12 @@ fn main() -> Result<()> { run_version_upgrade_check()?; } + Some(CiCmd::CheckDiff { subdir }) => { + if !check_diff(&subdir)? { + bail!("{} has uncommitted changes.", subdir.display()); + } + } + Some(CiCmd::Docs) => { run_docs_build()?; } diff --git a/tools/ci/src/smoketest.rs b/tools/ci/src/smoketest.rs index 456c13caa32..79a0cc11fcb 100644 --- a/tools/ci/src/smoketest.rs +++ b/tools/ci/src/smoketest.rs @@ -179,9 +179,8 @@ fn run_smoketest(server: Option, dotnet: bool, args: Vec) -> Res let status = cmd.args(&args).status()?; ensure!(status.success(), "Tests failed"); - let diff_status = cmd!("bash", "tools/check-diff.sh", "crates/smoketests").run()?; ensure!( - diff_status.status.success(), + util::check_diff(Path::new("crates/smoketests"))?, "There is a diff in the smoketests directory." ); Ok(()) diff --git a/tools/ci/src/util.rs b/tools/ci/src/util.rs index ac35a7a465b..c65c97ac470 100644 --- a/tools/ci/src/util.rs +++ b/tools/ci/src/util.rs @@ -1,7 +1,8 @@ #![allow(clippy::disallowed_macros)] -use anyhow::{bail, Result}; +use anyhow::{bail, Context, Result}; use std::path::Path; +use std::process::Command; pub fn ensure_repo_root() -> Result<()> { if !Path::new("Cargo.toml").exists() { @@ -9,3 +10,16 @@ pub fn ensure_repo_root() -> Result<()> { } Ok(()) } + +pub fn check_diff(subdir: &Path) -> Result { + let pattern = r"^// This was generated using spacetimedb cli version.*"; + let status = Command::new("git") + .args(["diff", "--exit-code"]) + .arg(format!("--ignore-matching-lines={pattern}")) + .arg("--") + .arg(subdir) + .status() + .with_context(|| format!("failed to spawn `git diff` for {}", subdir.display()))?; + + Ok(status.success()) +} diff --git a/tools/regen/src/csharp.rs b/tools/regen/src/csharp.rs index 9a92e90b189..9ea5c283d92 100644 --- a/tools/regen/src/csharp.rs +++ b/tools/regen/src/csharp.rs @@ -91,6 +91,31 @@ pub fn regen_regression_tests() -> Result<()> { Ok(()) } +pub fn regen_quickstart() -> Result<()> { + let workspace = workspace_dir(); + + cmd!("cargo", "build", "--manifest-path", path_arg(&standalone_manifest())).run()?; + + cmd!( + "cargo", + "run", + "--manifest-path", + path_arg(&cli_manifest()), + "--", + "generate", + "-y", + "-l", + "csharp", + "-o", + path_arg(&workspace.join("templates/chat-console-cs/module_bindings")), + "--module-path", + path_arg(&workspace.join("templates/chat-console-cs/spacetimedb")), + ) + .run()?; + + Ok(()) +} + fn overlay_unity_meta_skeleton(pkg_id: &str) -> Result<()> { let sdk = sdk_dir(); let skeleton_base = sdk.join("unity-meta-skeleton~"); diff --git a/tools/regen/src/main.rs b/tools/regen/src/main.rs index c8e07dc676d..9151adb1992 100644 --- a/tools/regen/src/main.rs +++ b/tools/regen/src/main.rs @@ -23,6 +23,8 @@ enum Command { enum CsharpCommand { /// Regenerate C# regression test bindings. RegressionTests, + /// Regenerate C# quickstart bindings. + Quickstart, /// Regenerate C# DLL and NuGet package artifacts for Unity workflows. Dlls, } @@ -33,6 +35,7 @@ fn main() -> Result<()> { match cli.command { Command::Csharp { command } => match command { CsharpCommand::RegressionTests => csharp::regen_regression_tests()?, + CsharpCommand::Quickstart => csharp::regen_quickstart()?, CsharpCommand::Dlls => csharp::regen_dlls()?, }, } From 80f4714dabd0aa16035f0a3ec5e4c29fa766db9f Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Wed, 6 May 2026 17:42:17 -0700 Subject: [PATCH 13/19] Apply suggestion from @bfops Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com> --- tools/ci/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index a78a0e78362..0bcb0fe3b69 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -348,7 +348,6 @@ fn main() -> Result<()> { "regen-csharp-moduledef", ) .run()?; - cmd!("bash", "tools/check-diff.sh", "crates/bindings-csharp").run()?; if !check_diff(Path::new("crates/bindings-csharp"))? { bail!("Repository has uncommitted changes."); } From edb6e948ae455a19fe385e99fa9443abd5c36dde Mon Sep 17 00:00:00 2001 From: Zeke Foppa <196249+bfops@users.noreply.github.com> Date: Wed, 6 May 2026 17:43:17 -0700 Subject: [PATCH 14/19] Apply suggestion from @bfops Signed-off-by: Zeke Foppa <196249+bfops@users.noreply.github.com> --- tools/regen/src/csharp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/regen/src/csharp.rs b/tools/regen/src/csharp.rs index 9ea5c283d92..965406fd896 100644 --- a/tools/regen/src/csharp.rs +++ b/tools/regen/src/csharp.rs @@ -248,7 +248,7 @@ pub fn regen_dlls() -> Result<()> { .run()?; let nuget_config_dir = tempfile::tempdir()?; - let nuget_config_path = nuget_config_dir.path().join("NuGet.Config"); + let nuget_config_path = nuget_config_dir.path().join("nuget.config"); cmd!( "cargo", "csharp", From e7973b934c1f1427346d372c2fcd4a5e63141fcc Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 17:46:05 -0700 Subject: [PATCH 15/19] [bfops/translate-script]: remove --- sdks/csharp/tools~/gen-quickstart.sh | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100755 sdks/csharp/tools~/gen-quickstart.sh diff --git a/sdks/csharp/tools~/gen-quickstart.sh b/sdks/csharp/tools~/gen-quickstart.sh deleted file mode 100755 index 1e216fcf556..00000000000 --- a/sdks/csharp/tools~/gen-quickstart.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -ueo pipefail - -SDK_PATH="$(dirname "$0")/.." -SDK_PATH="$(realpath "$SDK_PATH")" -STDB_PATH="$SDK_PATH/../.." - -cargo build --manifest-path "$STDB_PATH/crates/standalone/Cargo.toml" -cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- generate -y -l csharp -o "$STDB_PATH/templates/chat-console-cs/module_bindings" --module-path "$STDB_PATH/templates/chat-console-cs/spacetimedb" From 70ca70868702ddb6adf276422588fdd405e44ec3 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 17:47:51 -0700 Subject: [PATCH 16/19] [bfops/translate-script]: fixes --- tools/ci/src/main.rs | 14 ++++---------- tools/ci/src/util.rs | 7 +++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 0bcb0fe3b69..2b20df2f5d5 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -15,7 +15,7 @@ mod ci_docs; mod smoketest; mod util; -use util::{check_diff, ensure_repo_root}; +use util::{check_diff, check_diff_or_bail, ensure_repo_root}; /// SpacetimeDB CI tasks /// @@ -336,9 +336,7 @@ fn main() -> Result<()> { "--test-threads=1", ) .run()?; - if !check_diff(Path::new("."))? { - bail!("Repository has uncommitted changes."); - } + check_diff_or_bail(Path::new("."))?; cmd!( "cargo", "run", @@ -348,9 +346,7 @@ fn main() -> Result<()> { "regen-csharp-moduledef", ) .run()?; - if !check_diff(Path::new("crates/bindings-csharp"))? { - bail!("Repository has uncommitted changes."); - } + check_diff_or_bail(Path::new("crates/bindings-csharp"))?; cmd!("dotnet", "test", "-warnaserror") .dir("crates/bindings-csharp") .run()?; @@ -543,9 +539,7 @@ fn main() -> Result<()> { } Some(CiCmd::CheckDiff { subdir }) => { - if !check_diff(&subdir)? { - bail!("{} has uncommitted changes.", subdir.display()); - } + check_diff_or_bail(&subdir)?; } Some(CiCmd::Docs) => { diff --git a/tools/ci/src/util.rs b/tools/ci/src/util.rs index c65c97ac470..08052827346 100644 --- a/tools/ci/src/util.rs +++ b/tools/ci/src/util.rs @@ -23,3 +23,10 @@ pub fn check_diff(subdir: &Path) -> Result { Ok(status.success()) } + +pub fn check_diff_or_bail(subdir: &Path) -> Result<()> { + if !check_diff(subdir)? { + bail!("{} is dirty.", subdir.display()); + } + Ok(()) +} From d5e973031cc73720688f29f776034353f4c550ac Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Wed, 6 May 2026 18:05:52 -0700 Subject: [PATCH 17/19] [bfops/translate-script]: review --- tools/ci/src/main.rs | 10 +++++----- tools/ci/src/smoketest.rs | 2 +- tools/ci/src/util.rs | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 2b20df2f5d5..312d80021ac 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -15,7 +15,7 @@ mod ci_docs; mod smoketest; mod util; -use util::{check_diff, check_diff_or_bail, ensure_repo_root}; +use util::{bail_if_diff, ensure_repo_root, has_git_diff}; /// SpacetimeDB CI tasks /// @@ -231,7 +231,7 @@ fn run_typescript_tests() -> Result<()> { cmd!("pnpm", "build").dir("crates/bindings-typescript").run()?; cmd!("pnpm", "test").dir("crates/bindings-typescript").run()?; cmd!("pnpm", "generate").dir("templates/chat-react-ts").run()?; - if !check_diff(Path::new("templates/chat-react-ts/src/module_bindings"))? { + if has_git_diff(Path::new("templates/chat-react-ts/src/module_bindings"))? { bail!("Bindings are dirty. Please generate bindings again and commit them to this branch."); } cmd!("pnpm", "build").dir("templates/chat-react-ts").run()?; @@ -336,7 +336,7 @@ fn main() -> Result<()> { "--test-threads=1", ) .run()?; - check_diff_or_bail(Path::new("."))?; + bail_if_diff(Path::new("."))?; cmd!( "cargo", "run", @@ -346,7 +346,7 @@ fn main() -> Result<()> { "regen-csharp-moduledef", ) .run()?; - check_diff_or_bail(Path::new("crates/bindings-csharp"))?; + bail_if_diff(Path::new("crates/bindings-csharp"))?; cmd!("dotnet", "test", "-warnaserror") .dir("crates/bindings-csharp") .run()?; @@ -539,7 +539,7 @@ fn main() -> Result<()> { } Some(CiCmd::CheckDiff { subdir }) => { - check_diff_or_bail(&subdir)?; + bail_if_diff(&subdir)?; } Some(CiCmd::Docs) => { diff --git a/tools/ci/src/smoketest.rs b/tools/ci/src/smoketest.rs index 79a0cc11fcb..7a23876df2e 100644 --- a/tools/ci/src/smoketest.rs +++ b/tools/ci/src/smoketest.rs @@ -180,7 +180,7 @@ fn run_smoketest(server: Option, dotnet: bool, args: Vec) -> Res ensure!(status.success(), "Tests failed"); ensure!( - util::check_diff(Path::new("crates/smoketests"))?, + !util::has_git_diff(Path::new("crates/smoketests"))?, "There is a diff in the smoketests directory." ); Ok(()) diff --git a/tools/ci/src/util.rs b/tools/ci/src/util.rs index 08052827346..4e2ee0157ab 100644 --- a/tools/ci/src/util.rs +++ b/tools/ci/src/util.rs @@ -11,7 +11,7 @@ pub fn ensure_repo_root() -> Result<()> { Ok(()) } -pub fn check_diff(subdir: &Path) -> Result { +pub fn has_git_diff(subdir: &Path) -> Result { let pattern = r"^// This was generated using spacetimedb cli version.*"; let status = Command::new("git") .args(["diff", "--exit-code"]) @@ -21,11 +21,11 @@ pub fn check_diff(subdir: &Path) -> Result { .status() .with_context(|| format!("failed to spawn `git diff` for {}", subdir.display()))?; - Ok(status.success()) + Ok(!status.success()) } -pub fn check_diff_or_bail(subdir: &Path) -> Result<()> { - if !check_diff(subdir)? { +pub fn bail_if_diff(subdir: &Path) -> Result<()> { + if has_git_diff(subdir)? { bail!("{} is dirty.", subdir.display()); } Ok(()) From bf83a284f3dc45523093e57b3d03f4db5865566d Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Thu, 7 May 2026 09:29:20 -0700 Subject: [PATCH 18/19] [bfops/translate-script]: fixes --- .github/workflows/ci.yml | 13 ++++++------- sdks/csharp/tools~/update-against-stdb.sh | 2 +- tools/check-diff.sh | 12 ------------ tools/regen/src/csharp.rs | 2 +- 4 files changed, 8 insertions(+), 21 deletions(-) delete mode 100755 tools/check-diff.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1cea505b16f..547dec2ca18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -681,7 +681,7 @@ jobs: - name: Check for changes run: | - tools/check-diff.sh demo/Blackholio/client-unity/Assets/Scripts/autogen || { + cargo ci check-diff demo/Blackholio/client-unity/Assets/Scripts/autogen || { echo 'Error: Bindings are dirty. Please run `demo/Blackholio/server-rust/generate.sh`.' exit 1 } @@ -827,11 +827,10 @@ jobs: ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime - name: Check quickstart-chat bindings are up to date - working-directory: sdks/csharp run: | - bash tools~/gen-quickstart.sh - "${GITHUB_WORKSPACE}"/tools/check-diff.sh examples~/quickstart-chat || { - echo 'Error: quickstart-chat bindings have changed. Please run `sdks/csharp/tools~/gen-quickstart.sh`.' + cargo regen csharp quickstart + cargo ci check-diff templates/chat-console-cs/module_bindings || { + echo 'Error: quickstart-chat bindings have changed. Please run `cargo regen csharp quickstart`.' exit 1 } @@ -840,7 +839,7 @@ jobs: # working-directory: sdks/csharp # run: | # bash tools~/gen-client-api.sh - # "${GITHUB_WORKSPACE}"/tools/check-diff.sh src/SpacetimeDB/ClientApi || { + # cargo ci check-diff sdks/csharp/src/SpacetimeDB/ClientApi || { # echo 'Error: Client API bindings are dirty. Please run `sdks/csharp/tools~/gen-client-api.sh`.' # exit 1 # } @@ -853,7 +852,7 @@ jobs: - name: Run regression tests run: | cargo csharp run-regression-tests - tools/check-diff.sh sdks/csharp/examples~/regression-tests || { + cargo ci check-diff sdks/csharp/examples~/regression-tests || { echo 'Error: Bindings are dirty. Please run `cargo regen csharp regression-tests`.' exit 1 } diff --git a/sdks/csharp/tools~/update-against-stdb.sh b/sdks/csharp/tools~/update-against-stdb.sh index f17ce6c12b5..e4ae41a6f4b 100644 --- a/sdks/csharp/tools~/update-against-stdb.sh +++ b/sdks/csharp/tools~/update-against-stdb.sh @@ -10,7 +10,7 @@ SDK_PATH="$(realpath "$SDK_PATH")" cargo csharp write-nuget-config "$SDK_PATH" "$STDB_PATH" --stdb-path "$STDB_PATH" "$SDK_PATH/tools~/gen-client-api.sh" -"$SDK_PATH/tools~/gen-quickstart.sh" +cargo regen csharp quickstart cargo regen csharp regression-tests dotnet nuget locals all --clear dotnet pack "$STDB_PATH/crates/bindings-csharp" diff --git a/tools/check-diff.sh b/tools/check-diff.sh deleted file mode 100755 index eafd78f41b1..00000000000 --- a/tools/check-diff.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Keep in mind that this is also used from the private repo. - -SUBDIR="${1:-.}" # Default to '.' (the whole repo) if no argument given - -# We have a comment in every generated file that has the version and git hash, so these would change with every commit. -# We ignore them to avoid having to regen files for every commit unrelated to code gen. - -PATTERN='^// This was generated using spacetimedb cli version.*' - -git diff --exit-code --ignore-matching-lines="$PATTERN" -- "$SUBDIR" diff --git a/tools/regen/src/csharp.rs b/tools/regen/src/csharp.rs index 965406fd896..9ea5c283d92 100644 --- a/tools/regen/src/csharp.rs +++ b/tools/regen/src/csharp.rs @@ -248,7 +248,7 @@ pub fn regen_dlls() -> Result<()> { .run()?; let nuget_config_dir = tempfile::tempdir()?; - let nuget_config_path = nuget_config_dir.path().join("nuget.config"); + let nuget_config_path = nuget_config_dir.path().join("NuGet.Config"); cmd!( "cargo", "csharp", From a887da50fb1262db4bd47b0a45214ab4490b655f Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Thu, 7 May 2026 16:21:43 -0700 Subject: [PATCH 19/19] [bfops/translate-script]: revert --- .github/workflows/ci.yml | 2 +- .../regression-tests/client/Program.cs | 2 +- .../procedure-client/Program.cs | 2 +- .../procedure-client/README.md | 2 +- .../republishing/client/Program.cs | 2 +- sdks/csharp/tools~/run-regression-tests.sh | 42 +++++ tools/csharp-tools/src/lib.rs | 155 ------------------ tools/csharp-tools/src/main.rs | 5 - 8 files changed, 47 insertions(+), 165 deletions(-) create mode 100644 sdks/csharp/tools~/run-regression-tests.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 547dec2ca18..e6d732238c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -851,7 +851,7 @@ jobs: - name: Run regression tests run: | - cargo csharp run-regression-tests + bash sdks/csharp/tools~/run-regression-tests.sh cargo ci check-diff sdks/csharp/examples~/regression-tests || { echo 'Error: Bindings are dirty. Please run `cargo regen csharp regression-tests`.' exit 1 diff --git a/sdks/csharp/examples~/regression-tests/client/Program.cs b/sdks/csharp/examples~/regression-tests/client/Program.cs index 65b84a05a3e..c3b88d1b05a 100644 --- a/sdks/csharp/examples~/regression-tests/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/client/Program.cs @@ -1,6 +1,6 @@ /// tests run with a live server. /// To run these, run a local SpacetimeDB via `spacetime start`, -/// then in a separate terminal run `cargo csharp run-regression-tests`. +/// then in a separate terminal run `tools~/run-regression-tests.sh PATH_TO_SPACETIMEDB_REPO_CHECKOUT`. /// This is done on CI in .github/workflows/test.yml. using System; using System.Collections.Generic; diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs b/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs index deeebeec654..2345af3af15 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/procedure-client/Program.cs @@ -1,6 +1,6 @@ /// Procedure tests run with a live server. /// To run these, run a local SpacetimeDB via `spacetime start`, -/// then in a separate terminal run `cargo csharp run-regression-tests`. +/// then in a separate terminal run `tools~/run-regression-tests.sh PATH_TO_SPACETIMEDB_REPO_CHECKOUT`. /// This is done on CI in .github/workflows/test.yml. using System.Diagnostics; diff --git a/sdks/csharp/examples~/regression-tests/procedure-client/README.md b/sdks/csharp/examples~/regression-tests/procedure-client/README.md index 7820d489810..9895da77413 100644 --- a/sdks/csharp/examples~/regression-tests/procedure-client/README.md +++ b/sdks/csharp/examples~/regression-tests/procedure-client/README.md @@ -3,4 +3,4 @@ Mirrored from regression-tests/client. Currently separated from regression-tests as of 2025-11-14 the C# module bindings does not support procedures. -This relies on /modules/sdk-test-procedure as the server bindings, and is baked into `cargo csharp run-regression-tests`. +This relies on /modules/sdk-test-procedure as the server bindings, and is baked into the ../tools~/run-regression-tests.sh. diff --git a/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs b/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs index 50d615600a1..41fc80bb8b3 100644 --- a/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/republishing/client/Program.cs @@ -1,6 +1,6 @@ /// Regression tests run with a live server. /// To run these, run a local SpacetimeDB via `spacetime start`, -/// then in a separate terminal run `cargo csharp run-regression-tests`. +/// then in a separate terminal run `tools~/run-regression-tests.sh PATH_TO_SPACETIMEDB_REPO_CHECKOUT`. /// This is done on CI in .github/workflows/test.yml. using System.Diagnostics; diff --git a/sdks/csharp/tools~/run-regression-tests.sh b/sdks/csharp/tools~/run-regression-tests.sh new file mode 100644 index 00000000000..2784403bf1f --- /dev/null +++ b/sdks/csharp/tools~/run-regression-tests.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# This script requires a running local SpacetimeDB instance. + +set -ueo pipefail + +SDK_PATH="$(dirname "$0")/.." +SDK_PATH="$(realpath "$SDK_PATH")" +STDB_PATH="$SDK_PATH/../.." +SPACETIMEDB_SERVER_URL="${SPACETIMEDB_SERVER_URL:-local}" + +# Regenerate Bindings +cargo regen csharp regression-tests + +# Build and run SpacetimeDB server +cargo build --manifest-path "$STDB_PATH/crates/standalone/Cargo.toml" + +# Publish module for btree test +cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/server" btree-repro + +# Publish module for republishing module test +cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/republishing/server-initial" republish-test +cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server "$SPACETIMEDB_SERVER_URL" republish-test insert 1 +cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/republishing/server-republish" --break-clients republish-test +cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server "$SPACETIMEDB_SERVER_URL" republish-test insert 2 + +echo "Cleanup obj~ folders generated in $SDK_PATH/examples~/regression-tests/procedure-client" +# There is a bug in the code generator that creates obj~ folders in the output directory using a Rust project. +rm -rf "$SDK_PATH/examples~/regression-tests/procedure-client"/*/obj~ +rm -rf "$SDK_PATH/examples~/regression-tests/procedure-client/module_bindings"/*/obj~ + +# Publish module for procedure tests +cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$STDB_PATH/modules/sdk-test-procedure" procedure-tests + +# Run client for btree test +cd "$SDK_PATH/examples~/regression-tests/client" && dotnet run -c Debug + +# Run client for republishing module test +cd "$SDK_PATH/examples~/regression-tests/republishing/client" && dotnet run -c Debug + +# Run client for procedure test +cd "$SDK_PATH/examples~/regression-tests/procedure-client" && dotnet run -c Debug diff --git a/tools/csharp-tools/src/lib.rs b/tools/csharp-tools/src/lib.rs index 2e8c460bd27..8a65d8040a3 100644 --- a/tools/csharp-tools/src/lib.rs +++ b/tools/csharp-tools/src/lib.rs @@ -1,7 +1,6 @@ #![allow(clippy::disallowed_macros)] use anyhow::{Context, Result}; -use duct::cmd; use std::env; use std::fs; use std::path::{Path, PathBuf}; @@ -18,18 +17,6 @@ pub fn sdk_dir() -> PathBuf { workspace_dir().join("sdks/csharp") } -fn cli_manifest() -> PathBuf { - workspace_dir().join("crates/cli/Cargo.toml") -} - -fn standalone_manifest() -> PathBuf { - workspace_dir().join("crates/standalone/Cargo.toml") -} - -fn path_arg(path: &Path) -> String { - path.to_string_lossy().into_owned() -} - fn canonicalize_existing(path: &Path) -> Result { path.canonicalize() .with_context(|| format!("failed to canonicalize {}", path.display())) @@ -100,145 +87,3 @@ pub fn write_nuget_configs(target_dirs: &[PathBuf], spacetimedb_repo_path: Optio Ok(()) } - -fn remove_obj_tilde_children(parent: &Path) -> Result<()> { - if !parent.exists() { - return Ok(()); - } - - for entry in fs::read_dir(parent)? { - let entry = entry?; - if entry.file_type()?.is_dir() { - let obj_tilde = entry.path().join("obj~"); - if obj_tilde.exists() { - fs::remove_dir_all(&obj_tilde).with_context(|| format!("failed to remove {}", obj_tilde.display()))?; - } - } - } - Ok(()) -} - -fn clean_procedure_obj_tilde_dirs() -> Result<()> { - let procedure_client = sdk_dir().join("examples~/regression-tests/procedure-client"); - println!("Cleanup obj~ folders generated in {}", procedure_client.display()); - remove_obj_tilde_children(&procedure_client)?; - remove_obj_tilde_children(&procedure_client.join("module_bindings"))?; - Ok(()) -} - -pub fn run_regression_tests() -> Result<()> { - let sdk = sdk_dir(); - let workspace = workspace_dir(); - let server_url = env::var("SPACETIMEDB_SERVER_URL").unwrap_or_else(|_| "local".to_string()); - - cmd!("cargo", "regen", "csharp", "regression-tests").run()?; - - cmd!("cargo", "build", "--manifest-path", path_arg(&standalone_manifest())).run()?; - - cmd!( - "cargo", - "run", - "--manifest-path", - path_arg(&cli_manifest()), - "--", - "publish", - "-c", - "-y", - "--server", - &server_url, - "-p", - path_arg(&sdk.join("examples~/regression-tests/server")), - "btree-repro", - ) - .run()?; - cmd!( - "cargo", - "run", - "--manifest-path", - path_arg(&cli_manifest()), - "--", - "publish", - "-c", - "-y", - "--server", - &server_url, - "-p", - path_arg(&sdk.join("examples~/regression-tests/republishing/server-initial")), - "republish-test", - ) - .run()?; - cmd!( - "cargo", - "run", - "--manifest-path", - path_arg(&cli_manifest()), - "--", - "call", - "--server", - &server_url, - "republish-test", - "insert", - "1", - ) - .run()?; - cmd!( - "cargo", - "run", - "--manifest-path", - path_arg(&cli_manifest()), - "--", - "publish", - "--server", - &server_url, - "-p", - path_arg(&sdk.join("examples~/regression-tests/republishing/server-republish")), - "--break-clients", - "republish-test", - ) - .run()?; - cmd!( - "cargo", - "run", - "--manifest-path", - path_arg(&cli_manifest()), - "--", - "call", - "--server", - &server_url, - "republish-test", - "insert", - "2", - ) - .run()?; - - clean_procedure_obj_tilde_dirs()?; - - cmd!( - "cargo", - "run", - "--manifest-path", - path_arg(&cli_manifest()), - "--", - "publish", - "-c", - "-y", - "--server", - &server_url, - "-p", - path_arg(&workspace.join("modules/sdk-test-procedure")), - "procedure-tests", - ) - .run()?; - - cmd!("dotnet", "run", "-c", "Debug") - .dir(sdk.join("examples~/regression-tests/client")) - .run()?; - cmd!("dotnet", "run", "-c", "Debug") - .dir(sdk.join("examples~/regression-tests/republishing/client")) - .run()?; - cmd!("dotnet", "run", "-c", "Debug") - .dir(sdk.join("examples~/regression-tests/procedure-client")) - .run()?; - - Ok(()) -} diff --git a/tools/csharp-tools/src/main.rs b/tools/csharp-tools/src/main.rs index 8db53376d9f..4b2b3f50d13 100644 --- a/tools/csharp-tools/src/main.rs +++ b/tools/csharp-tools/src/main.rs @@ -23,8 +23,6 @@ enum Command { #[arg(long)] quiet: bool, }, - /// Run the C# regression test workflow against a running local SpacetimeDB instance. - RunRegressionTests, } fn main() -> Result<()> { @@ -38,9 +36,6 @@ fn main() -> Result<()> { } => { csharp_tools::write_nuget_configs(&target_dirs, stdb_path.as_deref(), quiet)?; } - Command::RunRegressionTests => { - csharp_tools::run_regression_tests()?; - } } Ok(())