Skip to main content

lib/
fn_copy_dist.rs

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