Skip to content

Commit 492c1f7

Browse files
committed
ci: add build using unified tool
1 parent a980d1b commit 492c1f7

File tree

2 files changed

+243
-2
lines changed

2 files changed

+243
-2
lines changed

.github/workflows/ci-unified.yml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
name: CI Unified - All ESP32 Projects
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
paths:
7+
- "esp32-**/**"
8+
- "esope-**/**"
9+
- "m5stack-**/**"
10+
- "waveshare-**/**"
11+
- "xtask/**"
12+
- ".github/workflows/ci-unified.yml"
13+
pull_request:
14+
branches: [ main ]
15+
paths:
16+
- "esp32-**/**"
17+
- "esope-**/**"
18+
- "m5stack-**/**"
19+
- "waveshare-**/**"
20+
- "xtask/**"
21+
- ".github/workflows/ci-unified.yml"
22+
workflow_dispatch:
23+
inputs:
24+
verbose:
25+
description: 'Enable verbose output'
26+
required: false
27+
default: 'false'
28+
type: boolean
29+
keep_going:
30+
description: 'Continue building even if some projects fail'
31+
required: false
32+
default: 'true'
33+
type: boolean
34+
35+
env:
36+
CARGO_TERM_COLOR: always
37+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
39+
jobs:
40+
unified-build:
41+
name: Build All ESP32 Projects
42+
runs-on: ubuntu-latest
43+
44+
steps:
45+
- name: Checkout Repository
46+
uses: actions/checkout@v4
47+
48+
- name: Install espup for ESP toolchain management
49+
run: |
50+
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
51+
cargo binstall -y espflash espup
52+
espup install --toolchain-version 1.88.0.0
53+
source ~/export-esp.sh
54+
55+
- name: Setup Rust stable toolchain
56+
uses: actions-rs/toolchain@v1
57+
with:
58+
toolchain: stable
59+
components: rustfmt, clippy
60+
targets: |
61+
riscv32imac-unknown-none-elf
62+
riscv32imc-unknown-none-elf
63+
64+
- name: Setup ESP Xtensa toolchain
65+
uses: esp-rs/[email protected]
66+
with:
67+
default: false
68+
buildtargets: esp32s3
69+
ldproxy: false
70+
version: "1.88.0"
71+
72+
- name: Verify toolchain setup
73+
run: |
74+
rustup show
75+
rustup toolchain list
76+
77+
- name: Build xtask maintenance tool
78+
run: |
79+
cargo build --manifest-path xtask/Cargo.toml
80+
81+
- name: List discovered projects
82+
run: |
83+
cargo xtask list
84+
85+
- name: Format all projects
86+
run: |
87+
cargo xtask format ${{ github.event.inputs.verbose == 'true' && '--verbose' || '' }}
88+
89+
- name: Build all ESP32 projects
90+
run: |
91+
cargo xtask build \
92+
${{ github.event.inputs.keep_going == 'true' && '--keep-going' || '' }} \
93+
${{ github.event.inputs.verbose == 'true' && '--verbose' || '' }}
94+
95+
- name: Run clippy on all projects
96+
run: |
97+
cargo xtask clippy ${{ github.event.inputs.verbose == 'true' && '--verbose' || '' }}
98+
99+
- name: Collect build artifacts
100+
run: |
101+
mkdir -p artifacts
102+
find . -name "*.bin" -path "*/target/*/release/*" -exec cp {} artifacts/ \; || true
103+
find . -name "bootloader.bin" -path "*/target/*" -exec cp {} artifacts/ \; || true
104+
find . -name "partition-table.bin" -path "*/target/*" -exec cp {} artifacts/ \; || true
105+
ls -la artifacts/ || echo "No artifacts found"
106+
107+
- name: Upload build artifacts
108+
if: always()
109+
uses: actions/upload-artifact@v4
110+
with:
111+
name: esp32-build-artifacts
112+
path: artifacts/
113+
retention-days: 30

xtask/src/main.rs

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ enum Commands {
4242
#[arg(long, short)]
4343
verbose: bool,
4444
},
45+
/// Run clippy on all projects
46+
Clippy {
47+
/// Show verbose output
48+
#[arg(long, short)]
49+
verbose: bool,
50+
},
4551
/// Run all maintenance tasks: format, update (compatible only), and build
4652
All {
4753
/// Continue with other tasks even if one fails
@@ -131,6 +137,7 @@ async fn main() -> Result<()> {
131137
verbose,
132138
} => update_dependencies(&projects, dry_run, incompatible, verbose).await,
133139
Commands::Format { verbose } => format_all_projects(&projects, verbose).await,
140+
Commands::Clippy { verbose } => clippy_all_projects(&projects, verbose).await,
134141
Commands::All {
135142
keep_going,
136143
verbose,
@@ -237,8 +244,13 @@ async fn build_all_projects(
237244
}
238245
} else {
239246
println!("❌ Build failed: {}", project.name);
240-
if verbose || !keep_going {
241-
println!(" Error: {}", result.message);
247+
// Always show error details for failures, regardless of verbose flag
248+
println!(" Error details:");
249+
for line in result.message.lines().take(20) {
250+
println!(" {}", line);
251+
}
252+
if result.message.lines().count() > 20 {
253+
println!(" ... (truncated, use --verbose for full output)");
242254
}
243255
if !keep_going {
244256
return Err(anyhow::anyhow!("Build failed for {}", project.name));
@@ -516,6 +528,122 @@ async fn format_project(project: &ProjectInfo, verbose: bool) -> Result<TaskResu
516528
})
517529
}
518530

531+
async fn clippy_all_projects(projects: &[ProjectInfo], verbose: bool) -> Result<()> {
532+
println!("\n🔍 Running clippy on all ESP32 projects (--release mode)");
533+
println!("════════════════════════════════════════════════════");
534+
535+
let mut summary = TaskSummary::new();
536+
537+
for project in projects.iter().filter(|p| p.has_cargo_toml) {
538+
println!("\n🔍 Clippy: {}", project.name);
539+
540+
let result = clippy_project(project, verbose).await?;
541+
542+
if result.success {
543+
println!("✅ Clippy passed: {}", project.name);
544+
if !result.warnings.is_empty() {
545+
println!("⚠️ {} clippy warnings found:", result.warnings.len());
546+
for warning in &result.warnings[..std::cmp::min(3, result.warnings.len())] {
547+
println!(" {}", warning);
548+
}
549+
if result.warnings.len() > 3 {
550+
println!(" ... and {} more warnings", result.warnings.len() - 3);
551+
}
552+
}
553+
} else {
554+
println!("❌ Clippy failed: {}", project.name);
555+
// Always show error details for failures, regardless of verbose flag
556+
println!(" Error details:");
557+
for line in result.message.lines().take(20) {
558+
println!(" {}", line);
559+
}
560+
if result.message.lines().count() > 20 {
561+
println!(" ... (truncated, use --verbose for full output)");
562+
}
563+
}
564+
565+
summary.add_result(&result);
566+
}
567+
568+
println!("\n═══════════════════════════════════════");
569+
println!("📊 Clippy Summary:");
570+
println!("✅ Clippy passed: {} projects", summary.success);
571+
if summary.failed > 0 {
572+
println!("❌ Clippy failed: {} projects", summary.failed);
573+
}
574+
if summary.warnings > 0 {
575+
println!("⚠️ Total clippy warnings: {}", summary.warnings);
576+
}
577+
578+
Ok(())
579+
}
580+
581+
async fn clippy_project(project: &ProjectInfo, verbose: bool) -> Result<TaskResult> {
582+
// Read the rust-toolchain.toml to determine which toolchain to use
583+
let toolchain_path = project.path.join("rust-toolchain.toml");
584+
let toolchain = if toolchain_path.exists() {
585+
let content =
586+
fs::read_to_string(&toolchain_path).context("Failed to read rust-toolchain.toml")?;
587+
588+
// Parse the toolchain channel from TOML
589+
if content.contains("channel = \"esp\"") {
590+
"esp"
591+
} else if content.contains("channel = \"stable\"") {
592+
"stable"
593+
} else {
594+
"stable" // fallback
595+
}
596+
} else {
597+
"stable" // fallback if no toolchain file
598+
};
599+
600+
let output = TokioCommand::new("rustup")
601+
.arg("run")
602+
.arg(toolchain)
603+
.arg("cargo")
604+
.arg("clippy")
605+
.arg("--release")
606+
.arg("--all-features")
607+
.arg("--workspace")
608+
.arg("--")
609+
.arg("-D")
610+
.arg("warnings")
611+
.current_dir(&project.path)
612+
.output()
613+
.await
614+
.context("Failed to run cargo clippy")?;
615+
616+
let success = output.status.success();
617+
let stdout = String::from_utf8_lossy(&output.stdout);
618+
let stderr = String::from_utf8_lossy(&output.stderr);
619+
620+
let mut warnings = Vec::new();
621+
let combined_output = format!("{}{}", stdout, stderr);
622+
623+
// Extract warnings from output
624+
for line in combined_output.lines() {
625+
if verbose {
626+
println!(" {}", line);
627+
}
628+
if line.contains("warning:") || line.contains("help:") {
629+
warnings.push(line.to_string());
630+
}
631+
}
632+
633+
let message = if success {
634+
"Clippy passed".to_string()
635+
} else {
636+
format!("{}{}", stdout, stderr)
637+
};
638+
639+
Ok(TaskResult {
640+
project: project.name.clone(),
641+
success,
642+
message,
643+
warnings,
644+
})
645+
}
646+
519647
async fn run_all_tasks(projects: &[ProjectInfo], keep_going: bool, verbose: bool) -> Result<()> {
520648
println!("\n🚀 Running ALL maintenance tasks");
521649
println!("═══════════════════════════════");

0 commit comments

Comments
 (0)