1use crate::context::Context;
6use crate::libtorch::{build, detect, download};
7use crate::util::{docker, prompt, system};
8
9#[derive(Default)]
10pub struct SetupOpts {
11 pub non_interactive: bool,
13 pub force: bool,
15}
16
17pub fn run(opts: SetupOpts) -> Result<(), String> {
18 println!();
19 println!(" floDl Setup");
20 println!(" ===========");
21 println!();
22 println!(" floDl is a Rust deep learning framework built on libtorch");
23 println!(" (PyTorch's C++ backend). This wizard will help you set up");
24 println!(" your development environment.");
25 println!();
26
27 println!(" Step 1: Detecting your system");
30 println!(" -----------------------------");
31 println!();
32
33 let cpu = system::cpu_model().unwrap_or_else(|| "Unknown".into());
34 let threads = system::cpu_threads();
35 let ram_gb = system::ram_total_gb();
36 println!(" CPU: {} ({} threads, {}GB RAM)", cpu, threads, ram_gb);
37
38 let has_docker = docker::has_docker();
39 let has_cargo = system::has_cargo();
40
41 if has_docker {
42 if let Some(v) = system::docker_version() {
43 println!(" Docker: {}", v);
44 } else {
45 println!(" Docker: available");
46 }
47 } else {
48 println!(" Docker: not found");
49 }
50
51 if has_cargo {
52 println!(" Rust: available");
53 } else {
54 println!(" Rust: not found");
55 }
56
57 let gpus = system::detect_gpus();
58 if !gpus.is_empty() {
59 println!();
60 println!(" GPUs:");
61 for g in &gpus {
62 println!(
63 " [{}] {} -- sm_{}.{}, {}GB VRAM",
64 g.index,
65 g.name,
66 g.sm_major,
67 g.sm_minor,
68 g.total_memory_mb / 1024
69 );
70 }
71 } else {
72 println!();
73 println!(" GPU: not detected (CPU-only mode)");
74 }
75
76 if !has_docker && !has_cargo {
77 println!();
78 println!(" You need at least one of these to continue:");
79 println!();
80 println!(" Rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh");
81 println!(" Docker: https://docs.docker.com/engine/install/");
82 println!();
83 println!(" Install one or both and run 'fdl setup' again.");
84 return Err("no Rust or Docker found".into());
85 }
86
87 println!();
90 println!(" Step 2: libtorch");
91 println!(" ----------------");
92 println!();
93 println!(" floDl needs libtorch, PyTorch's C++ library.");
94 println!(" This downloads pre-built binaries (~2GB for CUDA, ~200MB for CPU).");
95 println!();
96
97 let ctx = Context::resolve();
98 let root = &ctx.root;
99
100 if !ctx.is_project {
101 println!(" Not inside a floDl project.");
102 println!(" libtorch will be installed to: {}", ctx.libtorch_dir().display());
103 println!();
104 }
105
106 let existing = detect::read_active(root);
107 let mut skip_download = false;
108
109 if !opts.force {
110 if let Some(ref info) = existing {
111 let is_cuda = info.cuda_version.as_deref() != Some("none");
112 if is_cuda {
113 println!(" Found existing CUDA libtorch: {}", info.path);
114 if opts.non_interactive {
115 println!(" Keeping existing installation.");
116 skip_download = true;
117 } else if !prompt::ask_yn(" Download fresh?", false) {
118 skip_download = true;
119 }
120 println!();
121 } else {
122 println!(" Found existing CPU libtorch.");
123 }
124 }
125 }
126
127 if !skip_download {
128 let mounted_docker_project =
135 ctx.is_project && ctx.root.join("Dockerfile").exists();
136 let force_linux = cfg!(target_os = "macos") && mounted_docker_project;
137 if force_linux {
138 println!(" macOS + Docker-Mounted project: fetching Linux libtorch");
139 println!(" for the container (host arch would not load inside Linux).");
140 }
141 println!(" Downloading CPU libtorch...");
142 let cpu_opts = download::DownloadOpts {
143 variant: download::Variant::Cpu,
144 activate: false, force_linux,
146 ..Default::default()
147 };
148 download::run_with_context(cpu_opts, &ctx)?;
149
150 if !gpus.is_empty() {
152 let lo_major = gpus.iter().map(|g| g.sm_major).min().unwrap_or(0);
153 let hi_major = gpus.iter().map(|g| g.sm_major).max().unwrap_or(0);
154
155 if lo_major < 7 && hi_major >= 10 {
156 println!();
158 println!(" Your GPUs span sm_{}.x to sm_{}.x.", lo_major, hi_major);
159 println!(" No pre-built libtorch covers both architectures.");
160 println!();
161
162 let has_source_build = detect::list_variants(root)
164 .iter()
165 .any(|v| v.starts_with("builds/"));
166
167 if has_source_build {
168 println!(" Found existing source build in libtorch/builds/.");
169 } else if opts.non_interactive {
170 println!(" Downloading cu126 (broadest coverage).");
171 let cuda_opts = download::DownloadOpts {
172 variant: download::Variant::Cuda126,
173 ..Default::default()
174 };
175 download::run_with_context(cuda_opts, &ctx)?;
176 } else {
177 let choice = prompt::ask_choice(
178 " Choice",
179 &[
180 "Build libtorch from source (2-6 hours, covers all GPUs)",
181 "Download cu128 (Volta+ only, your older GPU won't work)",
182 "Download cu126 (pre-Volta only, your newer GPU won't work)",
183 "Skip for now",
184 ],
185 4,
186 );
187
188 match choice {
189 1 => {
190 println!();
191 println!(" Starting libtorch source build...");
192 println!(" This will take 2-6 hours. You can safely Ctrl-C and");
193 println!(" resume later with: fdl libtorch build");
194 println!();
195 build::run(build::BuildOpts::default())?;
196 }
197 2 => {
198 println!(" Downloading cu128...");
199 let cuda_opts = download::DownloadOpts {
200 variant: download::Variant::Cuda128,
201 ..Default::default()
202 };
203 download::run_with_context(cuda_opts, &ctx)?;
204 }
205 3 => {
206 println!(" Downloading cu126...");
207 let cuda_opts = download::DownloadOpts {
208 variant: download::Variant::Cuda126,
209 ..Default::default()
210 };
211 download::run_with_context(cuda_opts, &ctx)?;
212 }
213 _ => {
214 println!(" Skipping CUDA libtorch. You can download later with:");
215 println!(" fdl libtorch download --cuda 12.8");
216 println!(" # or build from source:");
217 println!(" fdl libtorch build");
218 }
219 }
220 }
221 } else if lo_major < 7 {
222 println!();
223 println!(" Downloading CUDA libtorch (cu126 for your pre-Volta GPU)...");
224 let cuda_opts = download::DownloadOpts {
225 variant: download::Variant::Cuda126,
226 ..Default::default()
227 };
228 download::run_with_context(cuda_opts, &ctx)?;
229 } else {
230 println!();
231 println!(" Downloading CUDA libtorch (cu128 for your Volta+ GPU)...");
232 let cuda_opts = download::DownloadOpts {
233 variant: download::Variant::Cuda128,
234 ..Default::default()
235 };
236 download::run_with_context(cuda_opts, &ctx)?;
237 }
238 }
239 }
240
241 if !ctx.is_project {
244 println!();
246 println!(" Setup complete!");
247 println!(" ===============");
248 println!();
249 if let Some(info) = detect::read_active(root) {
250 let cuda_str = if info.cuda_version.as_deref() != Some("none") { "CUDA" } else { "CPU" };
251 println!(" libtorch: {} ({})", info.path, cuda_str);
252 println!(" Location: {}", ctx.libtorch_dir().display());
253 }
254 println!();
255 println!(" Next steps:");
256 println!(" fdl init my-project # scaffold a new project");
257 println!(" fdl diagnose # verify GPU compatibility");
258 println!();
259 return Ok(());
260 }
261
262 println!();
263 println!(" Step 3: Build environment");
264 println!(" -------------------------");
265 println!();
266 println!(" floDl compiles Rust code that links against libtorch.");
267 println!(" You can build with Docker (isolated, reproducible) or");
268 println!(" natively (faster iteration, requires Rust + C++ toolchain).");
269 println!();
270
271 let build_mode = if has_docker && has_cargo {
272 if opts.non_interactive {
273 "docker"
274 } else {
275 let choice = prompt::ask_choice(
276 " Choice",
277 &[
278 "Docker (recommended) -- isolated, reproducible builds",
279 "Native -- faster iteration, requires C++ compiler on host",
280 "Both -- set up Docker and show native instructions",
281 ],
282 1,
283 );
284 match choice {
285 1 => "docker",
286 2 => "native",
287 3 => "both",
288 _ => "docker",
289 }
290 }
291 } else if has_docker {
292 if opts.non_interactive {
293 "docker"
294 } else {
295 println!(" Docker is available. Rust is not installed on this machine.");
296 println!(" Docker is the easiest way to get started (no Rust install needed).");
297 println!();
298 if prompt::ask_yn(" Set up Docker build environment?", true) {
299 "docker"
300 } else {
301 println!();
305 println!(" No worries. To build flodl natively you need Rust on the host:");
306 println!();
307 println!(" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh");
308 println!();
309 println!(" More: https://www.rust-lang.org/tools/install");
310 println!(" Then re-run `fdl setup` and the native path will be picked up.");
311 println!();
312 if prompt::ask_yn(" Or use Docker after all?", false) {
313 "docker"
314 } else {
315 "none"
316 }
317 }
318 }
319 } else {
320 println!(" Rust is available. Docker is not installed.");
321 println!(" You can build natively (requires C++ compiler on the host).");
322 println!();
323 "native"
324 };
325
326 if build_mode == "docker" || build_mode == "both" {
328 println!();
329 println!(" Building Docker images...");
330
331 let _ = std::fs::create_dir_all(".cargo-cache");
333 let _ = std::fs::create_dir_all(".cargo-git");
334
335 let status = docker::compose_run(".", &["build", "dev"])?;
336 if !status.success() {
337 println!(" Warning: CPU Docker image build failed.");
338 }
339
340 let has_cuda_lt = detect::read_active(root)
342 .is_some_and(|i| i.cuda_version.as_deref() != Some("none"));
343
344 if !gpus.is_empty() && has_cuda_lt {
345 let _ = std::fs::create_dir_all(".cargo-cache-cuda");
346 let _ = std::fs::create_dir_all(".cargo-git-cuda");
347
348 let status = docker::compose_run(".", &["build", "cuda"])?;
349 if !status.success() {
350 println!(" Warning: CUDA Docker image build failed.");
351 }
352 }
353
354 println!(" Docker images ready.");
355 }
356
357 println!();
360 println!(" Setup complete!");
361 println!(" ===============");
362 println!();
363
364 if let Some(info) = detect::read_active(root) {
366 let cuda_str = if info.cuda_version.as_deref() != Some("none") {
367 "CUDA"
368 } else {
369 "CPU"
370 };
371 println!(" libtorch: {} ({})", info.path, cuda_str);
372 }
373
374 if build_mode == "docker" || build_mode == "both" {
376 println!();
377 println!(" Build with Docker:");
378 let has_cuda_lt = detect::read_active(root)
379 .is_some_and(|i| i.cuda_version.as_deref() != Some("none"));
380 if !gpus.is_empty() && has_cuda_lt {
381 println!(" fdl cuda-test # run GPU tests");
382 println!(" fdl cuda-build # compile with CUDA");
383 println!(" fdl cuda-shell # interactive shell");
384 } else {
385 println!(" fdl test # run tests");
386 println!(" fdl build # compile");
387 println!(" fdl shell # interactive shell");
388 }
389 }
390
391 if build_mode == "native" || build_mode == "both" {
393 if let Some(info) = detect::read_active(root) {
394 let lt_path = format!("libtorch/{}", info.path);
395 println!();
396 println!(" Build natively:");
397 println!(" export LIBTORCH_PATH=\"{}\"", lt_path);
398 println!(
399 " export LD_LIBRARY_PATH=\"$LIBTORCH_PATH/lib${{LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}}\""
400 );
401 let has_cuda_lt = info.cuda_version.as_deref() != Some("none");
402 if !gpus.is_empty() && has_cuda_lt {
403 println!(" cargo test --features cuda");
404 } else {
405 println!(" cargo test");
406 }
407 }
408 }
409
410 if build_mode == "none" {
417 println!();
418 println!(" No build environment configured.");
419 println!(" Install Rust (link above) for native builds, or re-run `fdl setup`");
420 println!(" and pick Docker. libtorch is already in place either way.");
421 }
422
423 println!();
424 println!(" Other commands:");
425 println!(" fdl diagnose # verify GPU compatibility");
426 println!(" fdl init my-project # scaffold a new project");
427 println!();
428
429 if !opts.non_interactive {
430 crate::util::install_prompt::offer_global_install();
431 }
432
433 Ok(())
434}