1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// Build script to auto-generate Rust code from Windjammer (.wj) source
// This runs automatically when developers run: cargo build
// Zero Rust knowledge needed - just edit .wj files!
use std::env;
use std::path::PathBuf;
use std::process::Command;
fn main() {
// Tell Cargo to rerun this build script if .wj files change
println!("cargo:rerun-if-changed=src/components_wj");
// Get project root
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let project_root = PathBuf::from(&manifest_dir);
// Paths
let src_dir = project_root.join("src/components_wj");
let out_dir = project_root.join("src/components/generated");
// Check if we're in a cargo package/publish verification context
// During verification, Cargo extracts the package to target/package/crate-name-version/
let is_package_verification = manifest_dir.contains("/target/package/");
// If we're in package verification and generated files already exist, skip generation
// This prevents "Source directory was modified by build.rs" errors during cargo publish
if is_package_verification {
let mod_file = out_dir.join("mod.rs");
if mod_file.exists() {
println!("cargo:warning=📦 Skipping generation (package verification, files exist)");
return;
}
// If files don't exist in package verification, we have a problem
// but let generation proceed - it will fail with a clear error
}
// Try to find wj CLI - first check local build, then system PATH
let local_wj = project_root.join("../windjammer/target/release/wj");
let wj_cli = if local_wj.exists() {
local_wj.to_str().unwrap().to_string()
} else {
// Try to use wj from PATH (installed via cargo install)
"wj".to_string()
};
// Check if wj CLI is available and version
let wj_check = Command::new(&wj_cli).arg("--version").output();
if wj_check.is_err() {
eprintln!("⚠️ Warning: wj CLI not found!");
eprintln!(" Skipping .wj transpilation. To fix:");
eprintln!(" Option 1: cargo install windjammer --version ^0.38.3");
eprintln!(" Option 2: cd ../windjammer && cargo build --release");
eprintln!();
eprintln!(" Note: windjammer-ui v0.3.0 requires Windjammer v0.38.3+");
eprintln!(" (for trait implementation visibility fixes)");
return;
}
// Parse version and check minimum requirement
if let Ok(output) = wj_check {
let version_str = String::from_utf8_lossy(&output.stdout);
if let Some(version_line) = version_str.lines().next() {
// Extract version number (format: "windjammer 0.38.3")
if let Some(version) = version_line.split_whitespace().nth(1) {
let parts: Vec<&str> = version.split('.').collect();
if parts.len() >= 2 {
if let (Ok(major), Ok(minor)) =
(parts[0].parse::<u32>(), parts[1].parse::<u32>())
{
let patch = parts
.get(2)
.and_then(|p| p.parse::<u32>().ok())
.unwrap_or(0);
// Require v0.38.3+
if major == 0 && (minor < 38 || (minor == 38 && patch < 3)) {
eprintln!("⚠️ Warning: Windjammer version {} is too old!", version);
eprintln!(" windjammer-ui v0.3.0 requires Windjammer v0.38.3+");
eprintln!(
" Please upgrade: cargo install windjammer --version ^0.38.3"
);
eprintln!();
eprintln!(" Skipping .wj transpilation to avoid compilation errors.");
return;
}
}
}
}
}
}
// Check if source directory exists
if !src_dir.exists() {
eprintln!("⚠️ Warning: No .wj source found at {:?}", src_dir);
return;
}
println!(
"cargo:warning=🔨 Transpiling Windjammer components from {:?}",
src_dir
);
// Create output directory
std::fs::create_dir_all(&out_dir).expect("Failed to create output directory");
// Run wj build with --library and --module-file flags for automated library generation
let status = Command::new(&wj_cli)
.arg("build")
.arg(&src_dir)
.arg("-o")
.arg(&out_dir)
.arg("--target")
.arg("rust")
.arg("--library") // Auto-strip main() functions
.arg("--module-file") // Auto-generate mod.rs
.arg("--no-cargo") // Skip cargo build (we'll do it ourselves)
.status()
.expect("Failed to execute wj build");
if !status.success() {
panic!("wj build failed! Check .wj source for errors.");
}
println!("cargo:warning=✅ Successfully transpiled Windjammer components!");
// Remove the generated Cargo.toml to prevent cargo from treating it as a separate package
let cargo_toml = out_dir.join("Cargo.toml");
if cargo_toml.exists() {
let _ = std::fs::remove_file(&cargo_toml);
println!("cargo:warning=🗑️ Removed generated Cargo.toml (not needed for library)");
}
// Format the generated Rust code and add clippy allow directives
println!("cargo:warning=🎨 Formatting generated Rust code...");
// Find all .rs files in the output directory
if let Ok(entries) = std::fs::read_dir(&out_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("rs") {
// Add allow directives to the top of each generated file
if let Ok(content) = std::fs::read_to_string(&path) {
let new_content = format!(
"#![allow(clippy::all)]\n#![allow(noop_method_call)]\n{}",
content
);
let _ = std::fs::write(&path, new_content);
}
// Format the file
let _ = Command::new("rustfmt")
.arg("--edition")
.arg("2021")
.arg(&path)
.status();
}
}
println!("cargo:warning=✅ Generated code formatted!");
}
}