Skip to content

Commit fe6a3ba

Browse files
committed
fix(ci): harden platform compatibility gates
1 parent 9eaace6 commit fe6a3ba

4 files changed

Lines changed: 72 additions & 16 deletions

File tree

.github/actions/setup/action.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ runs:
199199
}
200200
201201
$envOutput = cmd /s /c "`"$vsDevCmd`" -arch=x64 -host_arch=x64 >nul && set"
202+
$captured = @{}
202203
$names = @(
203204
"PATH",
204205
"INCLUDE",
@@ -215,9 +216,25 @@ runs:
215216
$name = $line.Substring(0, $index)
216217
if ($names -notcontains $name) { continue }
217218
$value = $line.Substring($index + 1)
219+
$captured[$name] = $value
218220
Add-Content -Path $env:GITHUB_ENV -Value "$name=$value"
219221
}
220222
223+
$toolsDir = $captured["VCToolsInstallDir"]
224+
if (!$toolsDir) {
225+
Write-Error "VCToolsInstallDir was not produced by VsDevCmd.bat."
226+
exit 1
227+
}
228+
229+
$msvcLinker = Join-Path $toolsDir "bin\Hostx64\x64\link.exe"
230+
if (!(Test-Path $msvcLinker)) {
231+
Write-Error "MSVC linker not found at $msvcLinker"
232+
exit 1
233+
}
234+
235+
Add-Content -Path $env:GITHUB_ENV -Value "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=$msvcLinker"
236+
Write-Host "Using explicit MSVC linker: $msvcLinker"
237+
221238
- name: Setup Rust toolchain
222239
if: ${{ inputs.rust-target != '' }}
223240
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable

.github/workflows/test.yml

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
ci_changes: ${{ steps.detect.outputs.ci_changes }}
3232
code_changes: ${{ steps.detect.outputs.code_changes }}
3333
tractor_gates: ${{ steps.detect.outputs.tractor_gates }}
34+
tractor_heavy_gates: ${{ steps.detect.outputs.tractor_heavy_gates }}
3435
run_build: ${{ steps.detect.outputs.run_build }}
3536
run_e2e: ${{ steps.detect.outputs.run_e2e }}
3637
run_task_smoke: ${{ steps.detect.outputs.run_task_smoke }}
@@ -74,6 +75,7 @@ jobs:
7475
echo "ci_changes=false" >> "$GITHUB_OUTPUT"
7576
echo "code_changes=false" >> "$GITHUB_OUTPUT"
7677
echo "tractor_gates=false" >> "$GITHUB_OUTPUT"
78+
echo "tractor_heavy_gates=false" >> "$GITHUB_OUTPUT"
7779
echo "run_build=false" >> "$GITHUB_OUTPUT"
7880
echo "run_e2e=false" >> "$GITHUB_OUTPUT"
7981
echo "run_task_smoke=false" >> "$GITHUB_OUTPUT"
@@ -111,6 +113,7 @@ jobs:
111113
echo "ci_changes=true" >> "$GITHUB_OUTPUT"
112114
echo "code_changes=true" >> "$GITHUB_OUTPUT"
113115
echo "tractor_gates=true" >> "$GITHUB_OUTPUT"
116+
echo "tractor_heavy_gates=true" >> "$GITHUB_OUTPUT"
114117
echo "run_build=false" >> "$GITHUB_OUTPUT"
115118
echo "run_e2e=true" >> "$GITHUB_OUTPUT"
116119
echo "run_task_smoke=true" >> "$GITHUB_OUTPUT"
@@ -144,6 +147,11 @@ jobs:
144147
tractor_gates=true
145148
fi
146149
150+
tractor_heavy_gates=false
151+
if has_match '^(packages/tractor/|packages/tractor-ts/|packages/pi-agent/|packages/barn/|packages/plugin-manifest/|packages/storage-sqlite/|packages/storage-rest/|packages/sync-loro/|turbo\.json$|pnpm-lock\.yaml$|scripts/ci/smoke-runtime-descriptor-release-path\.mjs)'; then
152+
tractor_heavy_gates=true
153+
fi
154+
147155
# Standalone build artifact job is retired for normal PR validation.
148156
# General build correctness is covered inside quality; E2E runs through
149157
# Turbo and owns any build dependencies it needs in the E2E runner.
@@ -178,13 +186,14 @@ jobs:
178186
echo "ci_changes=$ci_changes" >> "$GITHUB_OUTPUT"
179187
echo "code_changes=$code_changes" >> "$GITHUB_OUTPUT"
180188
echo "tractor_gates=$tractor_gates" >> "$GITHUB_OUTPUT"
189+
echo "tractor_heavy_gates=$tractor_heavy_gates" >> "$GITHUB_OUTPUT"
181190
echo "run_build=$run_build" >> "$GITHUB_OUTPUT"
182191
echo "run_e2e=$run_e2e" >> "$GITHUB_OUTPUT"
183192
echo "run_task_smoke=$run_task_smoke" >> "$GITHUB_OUTPUT"
184193
echo "run_audit=$run_audit" >> "$GITHUB_OUTPUT"
185194
echo "run_deep=$run_deep" >> "$GITHUB_OUTPUT"
186195
187-
echo "::notice::changes: ci=$ci_changes code=$code_changes tractor_gates=$tractor_gates build=$run_build e2e=$run_e2e task_smoke=$run_task_smoke audit=$run_audit deep=$run_deep filter=$turbo_filter"
196+
echo "::notice::changes: ci=$ci_changes code=$code_changes tractor_gates=$tractor_gates tractor_heavy_gates=$tractor_heavy_gates build=$run_build e2e=$run_e2e task_smoke=$run_task_smoke audit=$run_audit deep=$run_deep filter=$turbo_filter"
188197
189198
- name: Compute validation signatures
190199
id: signatures
@@ -285,7 +294,7 @@ jobs:
285294
turbo-cache-token: ${{ secrets.TURBO_CACHE_TOKEN }}
286295
is-pr: ${{ github.event_name == 'pull_request' }}
287296
pr-number: ${{ github.event.pull_request.number }}
288-
rust-coverage: ${{ needs.changes.outputs.tractor_gates }}
297+
rust-coverage: ${{ needs.changes.outputs.tractor_heavy_gates }}
289298

290299
# Turbo Cache is now handled in ./.github/actions/setup
291300
# - name: Cache Turbo
@@ -394,7 +403,7 @@ jobs:
394403
# These remain separate to use the GHA script logic but are fast due to cache hits above
395404
- name: Benchmark Quality Gate (Tractor)
396405
id: benchmark
397-
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_gates == 'true'
406+
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_heavy_gates == 'true'
398407
run: |
399408
# Capture current branch explicitly for detached HEAD states
400409
CURRENT_BRANCH=$(git rev-parse HEAD)
@@ -430,7 +439,7 @@ jobs:
430439
fi
431440
432441
- name: Comment PR on Benchmark Increase
433-
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_gates == 'true' && github.event_name == 'pull_request' && steps.benchmark.outputs.improved == 'true'
442+
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_heavy_gates == 'true' && github.event_name == 'pull_request' && steps.benchmark.outputs.improved == 'true'
434443
uses: ./.github/actions/github-script
435444
with:
436445
script: |
@@ -481,7 +490,7 @@ jobs:
481490
482491
- name: Coverage Quality Gate (Tractor)
483492
id: coverage
484-
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_gates == 'true'
493+
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_heavy_gates == 'true'
485494
run: |
486495
cd packages/tractor
487496
pnpm run coverage:check
@@ -499,7 +508,7 @@ jobs:
499508
fi
500509
501510
- name: Comment PR on Coverage Increase
502-
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_gates == 'true' && github.event_name == 'pull_request' && steps.coverage.outputs.improved == 'true'
511+
if: steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_heavy_gates == 'true' && github.event_name == 'pull_request' && steps.coverage.outputs.improved == 'true'
503512
uses: ./.github/actions/github-script
504513
with:
505514
script: |
@@ -555,7 +564,7 @@ jobs:
555564
}
556565
557566
- name: Upload coverage
558-
if: always() && steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_gates == 'true'
567+
if: always() && steps.quality_cache.outputs.cache-hit != 'true' && needs.changes.outputs.tractor_heavy_gates == 'true'
559568
uses: ./.github/actions/codecov-upload
560569
with:
561570
files: ./packages/tractor/coverage/lcov.info
@@ -837,6 +846,7 @@ jobs:
837846
CODE_CHANGES: ${{ needs.changes.outputs.code_changes }}
838847
RUN_TASK_SMOKE: ${{ needs.changes.outputs.run_task_smoke }}
839848
TRACTOR_GATES: ${{ needs.changes.outputs.tractor_gates }}
849+
TRACTOR_HEAVY_GATES: ${{ needs.changes.outputs.tractor_heavy_gates }}
840850
RUN_AUDIT: ${{ needs.changes.outputs.run_audit }}
841851
RUN_BUILD: ${{ needs.changes.outputs.run_build }}
842852
RUN_E2E: ${{ needs.changes.outputs.run_e2e }}
@@ -846,7 +856,7 @@ jobs:
846856

847857
- name: Check all checks
848858
run: |
849-
echo "changes: skip_duplicate_push=${{ needs.changes.outputs.skip_duplicate_push }} ci=${{ needs.changes.outputs.ci_changes }} code=${{ needs.changes.outputs.code_changes }} build=${{ needs.changes.outputs.run_build }} e2e=${{ needs.changes.outputs.run_e2e }} tractor_gates=${{ needs.changes.outputs.tractor_gates }} audit=${{ needs.changes.outputs.run_audit }} deep=${{ needs.changes.outputs.run_deep }} base=${{ needs.changes.outputs.base_sha }} filter=${{ needs.changes.outputs.turbo_filter }}"
859+
echo "changes: skip_duplicate_push=${{ needs.changes.outputs.skip_duplicate_push }} ci=${{ needs.changes.outputs.ci_changes }} code=${{ needs.changes.outputs.code_changes }} build=${{ needs.changes.outputs.run_build }} e2e=${{ needs.changes.outputs.run_e2e }} tractor_gates=${{ needs.changes.outputs.tractor_gates }} tractor_heavy_gates=${{ needs.changes.outputs.tractor_heavy_gates }} audit=${{ needs.changes.outputs.run_audit }} deep=${{ needs.changes.outputs.run_deep }} base=${{ needs.changes.outputs.base_sha }} filter=${{ needs.changes.outputs.turbo_filter }}"
850860
851861
if [ "${{ needs.changes.outputs.skip_duplicate_push }}" = "true" ]; then
852862
echo "::notice::Heavy push validation skipped because the open PR run is canonical for this develop head."

packages/cli/src/rust-substrate.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export interface RustSubstrateCheck {
2727
}
2828

2929
const RUST_SUBSTRATE_BUILD_TOOLS_COMMAND = "Install Visual Studio Build Tools with the C++ build tools workload.";
30-
const RUST_SUBSTRATE_DEVELOPER_SHELL_COMMAND = "Open a Developer PowerShell for VS or put the MSVC linker before Git usr/bin in PATH.";
30+
const RUST_SUBSTRATE_DEVELOPER_SHELL_COMMAND = "Open a Developer PowerShell for VS, set CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER, or put the MSVC linker before Git usr/bin in PATH.";
3131
const RUST_SUBSTRATE_CARGO_COMPONENT_COMMAND = "cargo install cargo-component --locked";
3232
const RUST_SUBSTRATE_WASI_TARGET_COMMAND = "rustup target add wasm32-wasip1";
3333
const RUST_SUBSTRATE_RETRY_CHECK_COMMAND = applicationCommand("refarm", [
@@ -131,15 +131,28 @@ export async function runRustSubstrateCheck(
131131

132132
const compiler = platform === "win32" ? commandSource("cl.exe") : null;
133133
const linker = platform === "win32" ? commandSource("link.exe") : null;
134+
const explicitMsvcLinker =
135+
platform === "win32" ? process.env.CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER ?? null : null;
136+
const explicitMsvcLinkerOk =
137+
typeof explicitMsvcLinker === "string" &&
138+
/\\link\.exe$/i.test(explicitMsvcLinker) &&
139+
(await exists(explicitMsvcLinker));
140+
const effectiveLinker = explicitMsvcLinkerOk ? explicitMsvcLinker : linker;
134141
if (platform === "win32" && rustcHost?.endsWith("-msvc")) {
135142
if (!compiler) missing.push("msvc_cl");
136-
if (!linker || /\\Git\\usr\\bin\\link\.exe$/i.test(linker)) missing.push("msvc_link");
143+
if (
144+
!explicitMsvcLinkerOk &&
145+
(!linker || /\\Git\\usr\\bin\\link\.exe$/i.test(linker))
146+
) {
147+
missing.push("msvc_link");
148+
}
137149
}
138150

139151
const recommendations = buildRustSubstrateRecommendations({
140152
missing,
141153
rustcHost,
142154
linker,
155+
explicitMsvcLinkerOk,
143156
});
144157
return {
145158
command: "rust-substrate",
@@ -149,7 +162,7 @@ export async function runRustSubstrateCheck(
149162
platform,
150163
rustcHost,
151164
missing,
152-
linker,
165+
linker: effectiveLinker,
153166
compiler,
154167
recommendations,
155168
};
@@ -159,6 +172,7 @@ function buildRustSubstrateRecommendations(input: {
159172
missing: string[];
160173
rustcHost: string | null;
161174
linker: string | null;
175+
explicitMsvcLinkerOk?: boolean;
162176
}): RustSubstrateRecommendation[] {
163177
const recommendations: RustSubstrateRecommendation[] = [];
164178
const missingMsvcPrerequisite =
@@ -186,6 +200,7 @@ function buildRustSubstrateRecommendations(input: {
186200
if (
187201
input.rustcHost?.endsWith("-msvc") &&
188202
input.missing.includes("msvc_link") &&
203+
!input.explicitMsvcLinkerOk &&
189204
input.linker?.includes("\\Git\\usr\\bin\\link.exe")
190205
) {
191206
recommendations.push({

scripts/ci/check-rust-substrate.mjs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env node
22
import { execFileSync } from "node:child_process";
3+
import { existsSync } from "node:fs";
34

45
function usage() {
56
console.error("Usage: node scripts/ci/check-rust-substrate.mjs [--json]");
@@ -61,6 +62,14 @@ const checks = [
6162

6263
const linker = process.platform === "win32" ? commandSource("link.exe") : null;
6364
const compiler = process.platform === "win32" ? commandSource("cl.exe") : null;
65+
const explicitMsvcLinker =
66+
process.platform === "win32"
67+
? (process.env.CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER ?? null)
68+
: null;
69+
const explicitMsvcLinkerOk =
70+
Boolean(explicitMsvcLinker) &&
71+
/\\link\.exe$/i.test(explicitMsvcLinker) &&
72+
existsSync(explicitMsvcLinker);
6473
if (process.platform === "win32" && rustcHost?.endsWith("-msvc")) {
6574
checks.push({
6675
id: "msvc_cl",
@@ -69,16 +78,16 @@ if (process.platform === "win32" && rustcHost?.endsWith("-msvc")) {
6978
});
7079
checks.push({
7180
id: "msvc_link",
72-
ok: Boolean(linker) && !/\\Git\\usr\\bin\\link\.exe$/i.test(linker),
73-
path: linker,
81+
ok: explicitMsvcLinkerOk || (Boolean(linker) && !/\\Git\\usr\\bin\\link\.exe$/i.test(linker)),
82+
path: explicitMsvcLinkerOk ? explicitMsvcLinker : linker,
7483
});
7584
}
7685

7786
const missing = checks.filter((check) => !check.ok);
7887
const needsMsvc =
7988
process.platform === "win32" &&
8089
rustcHost?.endsWith("-msvc") &&
81-
(!compiler || linker?.includes("\\Git\\usr\\bin\\link.exe"));
90+
(!compiler || (!explicitMsvcLinkerOk && linker?.includes("\\Git\\usr\\bin\\link.exe")));
8291
const recommendations = [];
8392
if (!hasWasiTarget) {
8493
recommendations.push({
@@ -99,12 +108,17 @@ if (process.platform === "win32" && rustcHost?.endsWith("-msvc") && !compiler) {
99108
target: "cl.exe",
100109
});
101110
}
102-
if (process.platform === "win32" && rustcHost?.endsWith("-msvc") && linker?.includes("\\Git\\usr\\bin\\link.exe")) {
111+
if (
112+
process.platform === "win32" &&
113+
rustcHost?.endsWith("-msvc") &&
114+
!explicitMsvcLinkerOk &&
115+
linker?.includes("\\Git\\usr\\bin\\link.exe")
116+
) {
103117
recommendations.push({
104118
diagnostic: "rust-substrate:wrong-msvc-linker",
105119
severity: "failure",
106120
summary: "The Rust MSVC linker resolves to Git's Unix-style link.exe instead of the MSVC linker.",
107-
action: "Open a Developer PowerShell for VS or put the MSVC linker before Git usr/bin in PATH.",
121+
action: "Open a Developer PowerShell for VS, set CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER, or put the MSVC linker before Git usr/bin in PATH.",
108122
target: linker,
109123
});
110124
}

0 commit comments

Comments
 (0)