1use std::fs;
3use std::io;
4use std::path::{Path, PathBuf};
5
6pub struct DistConfig<'a> {
8 pub target_dir: &'a str,
9 pub dist_dir: &'a str,
10 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
30fn 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
48fn is_likely_binary(path: &Path, os_name: &str) -> bool {
50 if !path.is_file() {
51 return false;
52 }
53
54 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 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 if os_name == "windows" {
75 return false;
76 }
77 }
78
79 true
80}
81
82pub 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 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 if config.clear_dist && dist_path.exists() && !config.dry_run {
101 let _ = fs::remove_dir_all(dist_path);
103 }
104
105 let mut found_files = Vec::new(); let profiles = ["debug", "release"];
107
108 let mut scan_directory = |search_dir: &Path, os_name: &str, dest_base_dir: &Path| {
110 if config.binaries.is_empty() {
111 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 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 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 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 let mut processed_files = Vec::new();
179
180 for (src, dest_dir, dest_file) in found_files {
181 if dest_file.exists() && !config.overwrite {
183 continue; }
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}