Skip to content

Commit dc475cb

Browse files
committed
fix: Resolve Windows CI 'cargo not found' errors
**Windows Rust Toolchain Fixes:** - Add comprehensive Rust environment setup for Windows runners - Use bash shell consistently for all Rust commands (fixes PowerShell PATH issues) - Add fallback Rust installation via rustup for Windows if dtolnay action fails - Ensure cargo binary is properly added to GITHUB_PATH and CARGO_HOME is set - Add robust verification and debugging steps to identify toolchain issues **Specific Changes:** - All Rust commands now use 'shell: bash' instead of default PowerShell - Added Windows-specific PATH setup for both Unix-style and Windows-style paths - Added comprehensive debugging output to identify cargo location issues - Added fallback installation mechanism if primary toolchain setup fails - Enhanced verification steps with detailed environment information **Testing:** - Created Windows-specific test script (test_windows_rust.py) - Verified YAML syntax and workflow structure - Added detailed debugging output for CI troubleshooting - All Rust commands should now work on Windows runners **Expected Results:** - Windows CI jobs should no longer fail with 'cargo not found' errors - All Python versions (3.8-3.13) should work on Windows runners - Consistent behavior across Ubuntu, Windows, and macOS platforms
1 parent 37a506e commit dc475cb

2 files changed

Lines changed: 331 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,57 @@ jobs:
2727
uses: dtolnay/rust-toolchain@stable
2828
with:
2929
components: rustfmt, clippy
30+
31+
- name: Setup Rust environment (Windows fix)
32+
if: runner.os == 'Windows'
33+
shell: bash
34+
run: |
35+
echo "Setting up Rust environment for Windows..."
36+
37+
# Ensure cargo is in PATH
38+
if [ -f "$HOME/.cargo/bin/cargo" ] || [ -f "$HOME/.cargo/bin/cargo.exe" ]; then
39+
echo "Found existing Rust installation"
40+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
41+
echo "CARGO_HOME=$HOME/.cargo" >> $GITHUB_ENV
42+
else
43+
echo "Installing Rust via rustup..."
44+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --component rustfmt clippy
45+
source $HOME/.cargo/env
46+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
47+
echo "CARGO_HOME=$HOME/.cargo" >> $GITHUB_ENV
48+
fi
49+
50+
# Verify installation
51+
export PATH="$HOME/.cargo/bin:$PATH"
52+
cargo --version || echo "Cargo still not available"
53+
54+
- name: Verify Rust installation
55+
shell: bash
56+
run: |
57+
echo "Rust version:"
58+
rustc --version
59+
echo "Cargo version:"
60+
cargo --version
61+
echo "PATH: $PATH"
62+
echo "CARGO_HOME: ${CARGO_HOME:-not set}"
63+
64+
- name: Ensure Rust is in PATH (All platforms)
65+
shell: bash
66+
run: |
67+
echo "Ensuring Rust is available in PATH..."
68+
69+
# Add cargo to PATH if it exists but isn't in PATH
70+
if [ -d "$HOME/.cargo/bin" ]; then
71+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
72+
echo "CARGO_HOME=$HOME/.cargo" >> $GITHUB_ENV
73+
echo "Added $HOME/.cargo/bin to PATH"
74+
fi
75+
76+
# For Windows, also try the Windows-style path
77+
if [ "$RUNNER_OS" = "Windows" ] && [ -d "/c/Users/runneradmin/.cargo/bin" ]; then
78+
echo "/c/Users/runneradmin/.cargo/bin" >> $GITHUB_PATH
79+
echo "Added Windows cargo path to PATH"
80+
fi
3081
3182
- name: Cache Rust dependencies
3283
uses: actions/cache@v4
@@ -52,10 +103,50 @@ jobs:
52103
echo "VIRTUAL_ENV=$VIRTUAL_ENV" >> $GITHUB_ENV
53104
echo "PATH=$PATH" >> $GITHUB_ENV
54105
106+
- name: Debug and verify Rust toolchain
107+
shell: bash
108+
run: |
109+
echo "=== RUST TOOLCHAIN DEBUG ==="
110+
echo "Operating System: $RUNNER_OS"
111+
echo "PATH: $PATH"
112+
echo "CARGO_HOME: ${CARGO_HOME:-not set}"
113+
echo "HOME: $HOME"
114+
115+
echo "=== SEARCHING FOR CARGO ==="
116+
# Try to find cargo in various locations
117+
for path in \
118+
"cargo" \
119+
"$HOME/.cargo/bin/cargo" \
120+
"/c/Users/runneradmin/.cargo/bin/cargo" \
121+
"/c/Users/runneradmin/.cargo/bin/cargo.exe" \
122+
"$(which cargo 2>/dev/null || echo 'not found')"
123+
do
124+
if command -v "$path" &> /dev/null; then
125+
echo "✅ Found cargo at: $path"
126+
"$path" --version
127+
break
128+
else
129+
echo "❌ Not found: $path"
130+
fi
131+
done
132+
133+
echo "=== FINAL VERIFICATION ==="
134+
if command -v cargo &> /dev/null; then
135+
echo "✅ Cargo is available"
136+
cargo --version
137+
else
138+
echo "❌ Cargo is NOT available"
139+
echo "Available commands:"
140+
compgen -c | grep -i rust || echo "No rust-related commands found"
141+
exit 1
142+
fi
143+
55144
- name: Run Rust tests
145+
shell: bash
56146
run: cargo test
57147

58148
- name: Run Rust linting
149+
shell: bash
59150
run: |
60151
cargo fmt --all -- --check
61152
cargo clippy -- -D warnings

test_windows_rust.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Windows Rust toolchain test script.
4+
5+
This script simulates the Windows CI environment to test Rust toolchain availability.
6+
"""
7+
8+
import subprocess
9+
import sys
10+
import os
11+
import platform
12+
from pathlib import Path
13+
14+
15+
def run_command(cmd, description, shell=True, check_cargo=False):
16+
"""Run a command and return success status."""
17+
print(f"🔄 {description}...")
18+
19+
# If we're testing cargo availability, try different approaches
20+
if check_cargo and platform.system() == "Windows":
21+
# Try different ways to find cargo on Windows
22+
cargo_paths = [
23+
"cargo",
24+
"cargo.exe",
25+
str(Path.home() / ".cargo" / "bin" / "cargo.exe"),
26+
"C:\\Users\\runneradmin\\.cargo\\bin\\cargo.exe"
27+
]
28+
29+
for cargo_path in cargo_paths:
30+
try:
31+
result = subprocess.run(
32+
f"{cargo_path} --version",
33+
shell=True,
34+
check=True,
35+
capture_output=True,
36+
text=True
37+
)
38+
print(f"✅ Found cargo at: {cargo_path}")
39+
print(f" Version: {result.stdout.strip()}")
40+
return True, result.stdout
41+
except (subprocess.CalledProcessError, FileNotFoundError):
42+
continue
43+
44+
print(f"❌ Cargo not found in any of: {cargo_paths}")
45+
return False, "Cargo not found"
46+
47+
try:
48+
result = subprocess.run(
49+
cmd,
50+
shell=shell,
51+
check=True,
52+
capture_output=True,
53+
text=True
54+
)
55+
print(f"✅ {description} - SUCCESS")
56+
if result.stdout.strip():
57+
print(f" Output: {result.stdout.strip()}")
58+
return True, result.stdout
59+
except subprocess.CalledProcessError as e:
60+
print(f"❌ {description} - FAILED")
61+
print(f" Error: {e.stderr.strip() if e.stderr else str(e)}")
62+
if e.stdout:
63+
print(f" Stdout: {e.stdout.strip()}")
64+
return False, e.stderr
65+
except FileNotFoundError as e:
66+
print(f"❌ {description} - COMMAND NOT FOUND")
67+
print(f" Error: {str(e)}")
68+
return False, str(e)
69+
70+
71+
def check_environment():
72+
"""Check the current environment setup."""
73+
print("\n" + "="*50)
74+
print("🔍 ENVIRONMENT CHECK")
75+
print("="*50)
76+
77+
print(f"Platform: {platform.system()} {platform.release()}")
78+
print(f"Python: {sys.version}")
79+
80+
# Check PATH
81+
path_env = os.environ.get('PATH', '')
82+
print(f"PATH contains {len(path_env.split(os.pathsep))} entries")
83+
84+
# Look for Rust-related paths
85+
rust_paths = [p for p in path_env.split(os.pathsep) if 'cargo' in p.lower() or 'rust' in p.lower()]
86+
if rust_paths:
87+
print("Rust-related PATH entries:")
88+
for path in rust_paths:
89+
print(f" - {path}")
90+
else:
91+
print("No Rust-related PATH entries found")
92+
93+
# Check CARGO_HOME
94+
cargo_home = os.environ.get('CARGO_HOME')
95+
if cargo_home:
96+
print(f"CARGO_HOME: {cargo_home}")
97+
if Path(cargo_home).exists():
98+
print(" ✅ CARGO_HOME directory exists")
99+
else:
100+
print(" ❌ CARGO_HOME directory does not exist")
101+
else:
102+
print("CARGO_HOME: not set")
103+
104+
# Check default cargo location
105+
default_cargo = Path.home() / ".cargo"
106+
if default_cargo.exists():
107+
print(f"Default cargo directory exists: {default_cargo}")
108+
cargo_bin = default_cargo / "bin"
109+
if cargo_bin.exists():
110+
print(f" Cargo bin directory exists: {cargo_bin}")
111+
cargo_exe = cargo_bin / "cargo.exe"
112+
if cargo_exe.exists():
113+
print(f" ✅ cargo.exe found: {cargo_exe}")
114+
else:
115+
print(f" ❌ cargo.exe not found in {cargo_bin}")
116+
else:
117+
print(f" ❌ Cargo bin directory not found")
118+
else:
119+
print(f"Default cargo directory not found: {default_cargo}")
120+
121+
122+
def test_rust_commands():
123+
"""Test Rust command availability."""
124+
print("\n" + "="*50)
125+
print("🦀 RUST COMMAND TESTS")
126+
print("="*50)
127+
128+
# Test rustc
129+
success, _ = run_command("rustc --version", "Check rustc")
130+
if not success:
131+
return False
132+
133+
# Test cargo with special handling
134+
success, _ = run_command("cargo --version", "Check cargo", check_cargo=True)
135+
if not success:
136+
return False
137+
138+
# Test cargo commands
139+
if Path("Cargo.toml").exists():
140+
success, _ = run_command("cargo check", "Cargo check", check_cargo=True)
141+
if not success:
142+
print("⚠️ Cargo check failed, but this might be expected in some environments")
143+
144+
return True
145+
146+
147+
def simulate_ci_steps():
148+
"""Simulate the CI workflow steps."""
149+
print("\n" + "="*50)
150+
print("🔄 SIMULATING CI WORKFLOW")
151+
print("="*50)
152+
153+
# Step 1: Check if we're in the right directory
154+
if not Path("Cargo.toml").exists():
155+
print("❌ Not in project root (Cargo.toml not found)")
156+
return False
157+
158+
# Step 2: Simulate environment setup
159+
print("📝 Simulating environment variable setup...")
160+
161+
# Add cargo to PATH if not already there
162+
cargo_bin = Path.home() / ".cargo" / "bin"
163+
if cargo_bin.exists():
164+
current_path = os.environ.get('PATH', '')
165+
if str(cargo_bin) not in current_path:
166+
print(f"Adding {cargo_bin} to PATH")
167+
os.environ['PATH'] = str(cargo_bin) + os.pathsep + current_path
168+
169+
if 'CARGO_HOME' not in os.environ:
170+
os.environ['CARGO_HOME'] = str(Path.home() / ".cargo")
171+
print(f"Set CARGO_HOME to {os.environ['CARGO_HOME']}")
172+
173+
# Step 3: Test the commands that fail in CI
174+
print("\n🧪 Testing CI commands...")
175+
176+
commands = [
177+
("cargo --version", "Cargo version check"),
178+
("cargo test", "Cargo test"),
179+
("cargo fmt --all -- --check", "Cargo format check"),
180+
("cargo clippy -- -D warnings", "Cargo clippy")
181+
]
182+
183+
results = {}
184+
for cmd, desc in commands:
185+
success, output = run_command(cmd, desc, check_cargo=True)
186+
results[desc] = success
187+
188+
return all(results.values())
189+
190+
191+
def main():
192+
"""Main test function."""
193+
print("🚀 Windows Rust Toolchain Test")
194+
print("This script tests Rust availability in Windows-like environment")
195+
print("="*60)
196+
197+
# Run tests
198+
tests = [
199+
("Environment Check", check_environment),
200+
("Rust Commands", test_rust_commands),
201+
("CI Simulation", simulate_ci_steps),
202+
]
203+
204+
results = {}
205+
for test_name, test_func in tests:
206+
try:
207+
if test_name == "Environment Check":
208+
test_func() # This one doesn't return a boolean
209+
results[test_name] = True
210+
else:
211+
results[test_name] = test_func()
212+
except Exception as e:
213+
print(f"❌ {test_name} failed with exception: {e}")
214+
results[test_name] = False
215+
216+
# Summary
217+
print("\n" + "="*60)
218+
print("📊 TEST SUMMARY")
219+
print("="*60)
220+
221+
all_passed = True
222+
for test_name, passed in results.items():
223+
status = "✅ PASSED" if passed else "❌ FAILED"
224+
print(f"{test_name}: {status}")
225+
if not passed:
226+
all_passed = False
227+
228+
if all_passed:
229+
print("\n🎉 ALL TESTS PASSED!")
230+
print("✅ Rust toolchain should work in CI")
231+
else:
232+
print("\n⚠️ SOME TESTS FAILED")
233+
print("❌ May need additional fixes for Windows CI")
234+
235+
return all_passed
236+
237+
238+
if __name__ == "__main__":
239+
success = main()
240+
sys.exit(0 if success else 1)

0 commit comments

Comments
 (0)