Skip to main content

lib/
fn_copy_dist.rs

1// src/lib/fn_copy_dist.rs
2use std::fs;
3use std::io;
4use std::path::{Path, PathBuf};
5
6/// Struktura konfiguracyjna do zarządzania dystrybucją (Wzorzec: Parameter Object).
7pub struct DistConfig<'a> {
8    pub target_dir: &'a str,
9    pub dist_dir: &'a str,
10    /// Lista nazw binarek (bez rozszerzeń). Jeśli pusta - kopiuje wszystkie odnalezione binarki.
11    pub binaries: Vec<&'a str>,
12    pub clear_dist: bool,
13    pub overwrite: bool,
14    pub dry_run: bool,
15}
16
17impl<'a> Default for DistConfig<'a> {
18    fn default() -> Self {
19        Self {
20            target_dir: "./target",
21            dist_dir: "./dist",
22            binaries: vec![],
23            clear_dist: false,
24            overwrite: true,
25            dry_run: false,
26        }
27    }
28}
29
30/// Helper: Mapuje architekturę na przyjazne nazwy systemów.
31fn parse_os_from_triple(triple: &str) -> String {
32    let t = triple.to_lowercase();
33    if t.contains("windows") {
34        "windows".to_string()
35    } else if t.contains("linux") {
36        "linux".to_string()
37    } else if t.contains("darwin") || t.contains("apple") {
38        "macos".to_string()
39    } else if t.contains("android") {
40        "android".to_string()
41    } else if t.contains("wasm") {
42        "wasm".to_string()
43    } else {
44        "unknown".to_string()
45    }
46}
47
48/// Helper: Prosta heurystyka odróżniająca prawdziwą binarkę od śmieci po kompilacji w systemach Unix/Windows.
49fn is_likely_binary(path: &Path, os_name: &str) -> bool {
50    if !path.is_file() {
51        return false;
52    }
53
54    // Ignorujemy ukryte pliki (na wszelki wypadek)
55    let file_name = path.file_name().unwrap_or_default().to_string_lossy();
56    if file_name.starts_with('.') {
57        return false;
58    }
59
60    if let Some(ext) = path.extension() {
61        let ext_str = ext.to_string_lossy().to_lowercase();
62        // Odrzucamy techniczne pliki Rusta
63        if ["d", "rlib", "rmeta", "pdb", "lib", "dll", "so", "dylib"].contains(&ext_str.as_str()) {
64            return false;
65        }
66        if os_name == "windows" {
67            return ext_str == "exe";
68        }
69        if os_name == "wasm" {
70            return ext_str == "wasm";
71        }
72    } else {
73        // Brak rozszerzenia to standard dla plików wykonywalnych na Linux/macOS
74        if os_name == "windows" {
75            return false;
76        }
77    }
78
79    true
80}
81
82/// Przeszukuje katalog kompilacji i kopiuje pliki według konfiguracji `DistConfig`.
83/// Zwraca listę przetworzonych plików: Vec<(Źródło, Cel)>
84pub fn copy_dist(config: &DistConfig) -> io::Result<Vec<(PathBuf, PathBuf)>> {
85    let target_path = Path::new(config.target_dir);
86    let dist_path = Path::new(config.dist_dir);
87
88    // Fail Fast
89    if !target_path.exists() {
90        return Err(io::Error::new(
91            io::ErrorKind::NotFound,
92            format!(
93                "Katalog '{}' nie istnieje. Uruchom najpierw `cargo build`.",
94                config.target_dir
95            ),
96        ));
97    }
98
99    // Opcja: Czyszczenie folderu dystrybucyjnego przed kopiowaniem
100    if config.clear_dist && dist_path.exists() && !config.dry_run {
101        // Używamy `let _` bo jeśli folder nie istnieje lub jest zablokowany, chcemy po prostu iść dalej
102        let _ = fs::remove_dir_all(dist_path);
103    }
104
105    let mut found_files = Vec::new(); // Lista krotek (źródło, docelowy_folder, docelowy_plik)
106    let profiles = ["debug", "release"];
107
108    // Funkcja wewnętrzna: Przeszukuje folder (np. target/release) i dopasowuje reguły
109    let mut scan_directory = |search_dir: &Path, os_name: &str, dest_base_dir: &Path| {
110        if config.binaries.is_empty() {
111            // TRYB 1: Kopiuj WSZYSTKIE odnalezione binarki
112            if let Ok(entries) = fs::read_dir(search_dir) {
113                for entry in entries.filter_map(|e| e.ok()) {
114                    let path = entry.path();
115                    if is_likely_binary(&path, os_name) {
116                        let dest_file = dest_base_dir.join(path.file_name().unwrap());
117                        found_files.push((path, dest_base_dir.to_path_buf(), dest_file));
118                    }
119                }
120            }
121        } else {
122            // TRYB 2: Kopiuj KONKRETNE binarki
123            for bin in &config.binaries {
124                let suffix = if os_name == "windows" {
125                    ".exe"
126                } else if os_name == "wasm" {
127                    ".wasm"
128                } else {
129                    ""
130                };
131                let full_name = format!("{}{}", bin, suffix);
132                let path = search_dir.join(&full_name);
133                if path.exists() {
134                    let dest_file = dest_base_dir.join(&full_name);
135                    found_files.push((path, dest_base_dir.to_path_buf(), dest_file));
136                }
137            }
138        }
139    };
140
141    // =========================================================
142    // KROK 1: Skanowanie kompilacji natywnej (Hosta)
143    // =========================================================
144    let host_os = std::env::consts::OS;
145    for profile in &profiles {
146        let search_dir = target_path.join(profile);
147        let dest_base = dist_path.join(host_os).join(profile);
148        if search_dir.exists() {
149            scan_directory(&search_dir, host_os, &dest_base);
150        }
151    }
152
153    // =========================================================
154    // KROK 2: Skanowanie cross-kompilacji (Target Triples)
155    // =========================================================
156    if let Ok(entries) = fs::read_dir(target_path) {
157        for entry in entries.filter_map(|e| e.ok()) {
158            let path = entry.path();
159            if path.is_dir() {
160                let dir_name = path.file_name().unwrap_or_default().to_string_lossy();
161                if dir_name.contains('-') {
162                    let os_name = parse_os_from_triple(&dir_name);
163                    for profile in &profiles {
164                        let search_dir = path.join(profile);
165                        let dest_base = dist_path.join(&os_name).join(profile);
166                        if search_dir.exists() {
167                            scan_directory(&search_dir, &os_name, &dest_base);
168                        }
169                    }
170                }
171            }
172        }
173    }
174
175    // =========================================================
176    // KROK 3: Fizyczne operacje (z uwzględnieniem overwrite i dry_run)
177    // =========================================================
178    let mut processed_files = Vec::new();
179
180    for (src, dest_dir, dest_file) in found_files {
181        // Obsługa nadpisywania
182        if dest_file.exists() && !config.overwrite {
183            continue; // Pomijamy ten plik
184        }
185
186        if !config.dry_run {
187            fs::create_dir_all(&dest_dir)?;
188            fs::copy(&src, &dest_file)?;
189        }
190
191        processed_files.push((src, dest_file));
192    }
193
194    Ok(processed_files)
195}