1
name: Tests
2
3
-# Runs on every push.
3
+# Runs on every branch push (not on tag pushes: a release tag re-tests a SHA the
4
+# branch push already covered, and the release workflows handle tags).
5
# Covers:
6
# - All Rust workspace members (fmt, clippy, tests)
7
# - smbcloud-cli + xcrs clippy on Windows and macOS (cfg-gated code)
8
# - smbcloud-auth-sdk-wasm (wasm32-unknown-unknown cargo check)
9
# - sdk/python → maturin wheel build (smbcloud-sdk-auth)
10
# - sdk/npm → wasm-pack + prepare-package.mjs (@smbcloud/sdk-auth)
10
-# - sdk/gems → rb_sys native-extension compile (smbcloud-auth gem)
11
+# - sdk/gems → smbcloud-auth and smbcloud-model, cross-built for
12
+# x86_64-linux, aarch64-linux, arm64-darwin and x86_64-darwin
13
+# against the workspace crates (oldest and newest shipped
14
+# Ruby), then installed and required on native runners; the
15
+# source gem is built, its file list checked and installed
16
# - nuget → .NET tool launcher build (SmbCloud.Cli)
17
13
-on: push
18
+on:
19
+ push:
20
+ branches: ["**"] # tag pushes (v*) re-test a SHA the branch push already covers
21
+
22
+# Superseded pushes to a feature branch are cancelled. development and main are
23
+# never cancelled, so every commit there keeps a result.
24
+concurrency:
25
+ group: ${{ github.workflow }}-${{ github.ref }}
26
+ cancel-in-progress: ${{ github.ref != 'refs/heads/development' && github.ref != 'refs/heads/main' }}
27
28
env:
29
CLI_CLIENT_SECRET: ${{ secrets.CLI_CLIENT_SECRET }}
239
- name: Build .NET tool launcher
240
run: dotnet build nuget/smbcloud-cli/SmbCloud.Cli.csproj --configuration Release
241
229
- # ── Ruby Gem ────────────────────────────────────────────────────────────────
230
- check-ruby-gem:
231
- name: SDK — Ruby gem (smbcloud-auth)
242
+ # ── Ruby Gems ───────────────────────────────────────────────────────────────
243
+ # Skip the gem jobs when nothing they build from changed.
244
+ changes:
245
+ name: Changes — detect gem inputs
246
runs-on: ubuntu-latest
247
+ outputs:
248
+ gems: ${{ steps.filter.outputs.gems }}
249
+ steps:
250
+ - name: Checkout
251
+ uses: actions/checkout@v6
252
+
253
+ - name: Filter changed paths
254
+ id: filter
255
+ uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
256
+ with:
257
+ filters: |
258
+ gems:
259
+ - "sdk/gems/auth/**"
260
+ - "sdk/gems/model/**"
261
+ - "crates/**"
262
+ - "Cargo.toml"
263
+ - "Cargo.lock"
264
+ - "rust-toolchain.toml"
265
+ - ".ruby-version"
266
+ - ".github/workflows/ci.yml"
267
+
268
+ # Cross-builds each gem like release-sdk-gem.yml, but against the workspace
269
+ # crates (not the published ones) and only for the oldest and newest Ruby the
270
+ # release ships. Those bracket the C-API differences.
271
+ check-ruby-gems:
272
+ name: SDK — ${{ matrix.gem }} gem (${{ matrix.platform }})
273
+ needs: changes
274
+ if: needs.changes.outputs.gems == 'true'
275
+ runs-on: ubuntu-latest
276
+ strategy:
277
+ fail-fast: false
278
+ matrix:
279
+ gem: [auth, model]
280
+ platform: [x86_64-linux, aarch64-linux, arm64-darwin, x86_64-darwin]
281
steps:
282
- name: Checkout
283
uses: actions/checkout@v6
297
with:
298
toolchain: ${{ env.RUST_TOOLCHAIN }}
299
252
- # The gem's ext Cargo workspace (sdk/gems/auth) is separate from the main
253
- # workspace and pulls smbcloud-* crates from crates.io. Its transitive
254
- # deps may link against system OpenSSL, so we install libssl-dev to be safe.
255
- - name: Install system dependencies
256
- run: sudo apt-get update -q && sudo apt-get install -y libssl-dev pkg-config
300
+ # CI validates the unreleased workspace version. Derive the local crate
301
+ # patches from the extension manifest; release builds deliberately
302
+ # resolve published crates after the release workflow has run. The
303
+ # whole checkout is mounted into the rb-sys-dock container, so the
304
+ # relative paths resolve there too.
305
+ - name: Use workspace Rust crates for the gem check
306
+ shell: bash
307
+ run: |
308
+ mkdir -p sdk/gems/${{ matrix.gem }}/.cargo
309
+ {
310
+ echo "[patch.crates-io]"
311
+ awk -F '=' '/^smbcloud-[[:alnum:]-]+[[:space:]]*=/ {
312
+ name = $1
313
+ gsub(/[[:space:]]/, "", name)
314
+ print name " = { path = \"../../../crates/" name "\" }"
315
+ }' sdk/gems/${{ matrix.gem }}/ext/${{ matrix.gem }}/Cargo.toml
316
+ } > sdk/gems/${{ matrix.gem }}/.cargo/config.toml
317
+ cargo generate-lockfile --manifest-path sdk/gems/${{ matrix.gem }}/Cargo.toml
318
+
319
+ - name: Setup Ruby
320
+ uses: ruby/setup-ruby@v1
321
+ with:
322
+ ruby-version: .ruby-version
323
+ bundler-cache: true
324
+ working-directory: sdk/gems/${{ matrix.gem }}
325
258
- # Swatinem/rust-cache scoped to the gem's own Cargo workspace so its
259
- # registry deps are cached independently from the main workspace.
260
- - name: Setup Rust cache (gem workspace)
261
- uses: Swatinem/rust-cache@v2
326
+ - name: Cross-compile native gem
327
+ id: cross-gem
328
+ uses: oxidize-rb/actions/cross-gem@v1
329
with:
263
- key: check-ruby-gem
264
- workspaces: sdk/gems/auth
330
+ platform: ${{ matrix.platform }}
331
+ ruby-versions: "3.1,4.0"
332
+ working-directory: sdk/gems/${{ matrix.gem }}
333
+ cache-version: ci-${{ matrix.gem }}
334
+
335
+ - name: Upload gem artifact
336
+ uses: actions/upload-artifact@v7
337
+ with:
338
+ name: ci-gem-${{ matrix.gem }}-${{ matrix.platform }}
339
+ path: ${{ steps.cross-gem.outputs.gem-path }}
340
+ retention-days: 1
341
+
342
+ # Install each prebuilt gem on a native runner and require it, which takes
343
+ # the real load path (the per-Ruby lib/<gem>/<x.y>/ bundle first). Catches
344
+ # load-time breakage such as a dyld-rejected install name.
345
+ smoke-test-ruby-gems:
346
+ name: Smoke — ${{ matrix.gem }} ${{ matrix.platform }} (Ruby ${{ matrix.ruby }})
347
+ needs: check-ruby-gems
348
+ runs-on: ${{ matrix.runner }}
349
+ strategy:
350
+ fail-fast: false
351
+ matrix:
352
+ gem: [auth, model]
353
+ ruby: ["3.1", "4.0"]
354
+ platform: [x86_64-linux, aarch64-linux, arm64-darwin]
355
+ include:
356
+ - platform: x86_64-linux
357
+ runner: ubuntu-latest
358
+ - platform: aarch64-linux
359
+ runner: ubuntu-24.04-arm
360
+ - platform: arm64-darwin
361
+ runner: macos-latest
362
+ - gem: auth
363
+ constant: SmbCloud::Auth
364
+ - gem: model
365
+ constant: Model
366
+ steps:
367
+ - name: Setup Ruby
368
+ uses: ruby/setup-ruby@v1
369
+ with:
370
+ ruby-version: ${{ matrix.ruby }}
371
+
372
+ - name: Download gem artifact
373
+ uses: actions/download-artifact@v7
374
+ with:
375
+ name: ci-gem-${{ matrix.gem }}-${{ matrix.platform }}
376
+ path: gem
377
+
378
+ - name: Install the gem
379
+ shell: bash
380
+ run: gem install --local gem/*.gem
381
+
382
+ # No checkout in this job, so only the installed gem can satisfy the require.
383
+ - name: Require the gem
384
+ shell: bash
385
+ run: ruby -e 'require "${{ matrix.gem }}"; puts ${{ matrix.constant }}'
386
+
387
+ # Builds the source gem from git ls-files and installs it, compiling from
388
+ # source once. Catches spec.files problems (symlinks, missing Cargo files)
389
+ # that only surface on a user's machine.
390
+ check-ruby-source-gems:
391
+ name: SDK — ${{ matrix.gem }} source gem
392
+ needs: changes
393
+ if: needs.changes.outputs.gems == 'true'
394
+ runs-on: ubuntu-latest
395
+ strategy:
396
+ fail-fast: false
397
+ matrix:
398
+ gem: [auth, model]
399
+ steps:
400
+ - name: Checkout
401
+ uses: actions/checkout@v6
402
+
403
+ - name: Read Rust toolchain
404
+ shell: bash
405
+ run: |
406
+ rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)"
407
+ if [ -z "$rust_toolchain" ]; then
408
+ echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2
409
+ exit 1
410
+ fi
411
+ echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV"
412
+
413
+ - name: Install toolkit
414
+ uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
415
+ with:
416
+ toolchain: ${{ env.RUST_TOOLCHAIN }}
417
266
- # CI validates the unreleased workspace version. Derive the local crate
267
- # patches from the extension manifest; release builds deliberately
268
- # resolve published crates after the release workflow has run.
418
- name: Use workspace Rust crates for the gem check
419
shell: bash
420
run: |
272
- mkdir -p sdk/gems/auth/.cargo
421
+ mkdir -p sdk/gems/${{ matrix.gem }}/.cargo
422
{
423
echo "[patch.crates-io]"
424
awk -F '=' '/^smbcloud-[[:alnum:]-]+[[:space:]]*=/ {
425
name = $1
426
gsub(/[[:space:]]/, "", name)
427
print name " = { path = \"../../../crates/" name "\" }"
279
- }' sdk/gems/auth/ext/auth/Cargo.toml
280
- } > sdk/gems/auth/.cargo/config.toml
428
+ }' sdk/gems/${{ matrix.gem }}/ext/${{ matrix.gem }}/Cargo.toml
429
+ } > sdk/gems/${{ matrix.gem }}/.cargo/config.toml
430
431
- name: Setup Ruby
432
uses: ruby/setup-ruby@v1
433
with:
434
ruby-version: .ruby-version
435
bundler-cache: true
287
- working-directory: sdk/gems/auth
436
+ working-directory: sdk/gems/${{ matrix.gem }}
437
289
- - name: Compile native extension
290
- working-directory: sdk/gems/auth
291
- run: bundle exec rake compile
438
+ # `gem build`, not `rake build`: the latter depends on :compile.
439
+ - name: Build the source gem
440
+ working-directory: sdk/gems/${{ matrix.gem }}
441
+ run: |
442
+ mkdir -p pkg
443
+ gem build ${{ matrix.gem }}.gemspec --output pkg/${{ matrix.gem }}-source.gem
444
+
445
+ - name: Check the source gem file list
446
+ working-directory: sdk/gems/${{ matrix.gem }}
447
+ shell: bash
448
+ run: |
449
+ files="$(gem spec pkg/${{ matrix.gem }}-source.gem files | sed -n 's/^- //p')"
450
+ echo "$files"
451
+ fail=0
452
+ require() {
453
+ if ! grep -Eq "$1" <<<"$files"; then
454
+ echo "::error::source gem is missing $2" >&2
455
+ fail=1
456
+ fi
457
+ }
458
+ require "^ext/${{ matrix.gem }}/extconf\.rb$" "ext/${{ matrix.gem }}/extconf.rb"
459
+ require '^ext/.*\.rs$' "ext/**/*.rs"
460
+ require '(^|/)Cargo\.toml$' "Cargo.toml"
461
+ require '(^|/)Cargo\.lock$' "Cargo.lock"
462
+ if grep -Eq '(^|/)(target|tmp)/' <<<"$files"; then
463
+ echo "::error::source gem contains target/ or tmp/ paths" >&2
464
+ fail=1
465
+ fi
466
+ while IFS= read -r f; do
467
+ if [ -L "$f" ]; then
468
+ echo "::error::source gem contains the symlink $f" >&2
469
+ fail=1
470
+ fi
471
+ done <<<"$files"
472
+ exit "$fail"
473
+
474
+ - name: Install the source gem
475
+ working-directory: sdk/gems/${{ matrix.gem }}
476
+ run: gem install --local pkg/${{ matrix.gem }}-source.gem