|
| 1 | +use std::{ |
| 2 | + collections::HashSet, |
| 3 | + fs::{self, read_link}, |
| 4 | + io::ErrorKind, |
| 5 | + os::unix::fs::symlink, |
| 6 | + path::{Component, Path}, |
| 7 | +}; |
| 8 | + |
| 9 | +#[derive(Debug, Clone)] |
| 10 | +struct SDKEntry { |
| 11 | + names: HashSet<String>, |
| 12 | + values: Vec<SDKEntry>, |
| 13 | +} |
| 14 | + |
| 15 | +impl SDKEntry { |
| 16 | + // empty = wildcard |
| 17 | + fn new(names: HashSet<String>, values: Vec<SDKEntry>) -> Self { |
| 18 | + SDKEntry { names, values } |
| 19 | + } |
| 20 | + |
| 21 | + fn from_name(name: &str, values: Vec<SDKEntry>) -> Self { |
| 22 | + let mut set = HashSet::new(); |
| 23 | + set.insert(name.to_string()); |
| 24 | + SDKEntry::new(set, values) |
| 25 | + } |
| 26 | + |
| 27 | + fn matches<'a, I>(&self, path: I) -> bool |
| 28 | + where |
| 29 | + I: Iterator<Item = &'a str> + Clone, |
| 30 | + { |
| 31 | + let mut path_clone = path.clone(); |
| 32 | + let first = path_clone.next(); |
| 33 | + if first.is_none() { |
| 34 | + return true; |
| 35 | + } |
| 36 | + let first = first.unwrap(); |
| 37 | + if !self.names.is_empty() && !self.names.contains(first) { |
| 38 | + return false; |
| 39 | + } |
| 40 | + if self.values.is_empty() { |
| 41 | + return true; |
| 42 | + } |
| 43 | + let after_name = path_clone; |
| 44 | + for value in &self.values { |
| 45 | + if value.matches(after_name.clone()) { |
| 46 | + return true; |
| 47 | + } |
| 48 | + } |
| 49 | + false |
| 50 | + } |
| 51 | + |
| 52 | + fn e(name: Option<&str>, values: Vec<SDKEntry>) -> SDKEntry { |
| 53 | + if let Some(name) = name { |
| 54 | + let parts: Vec<&str> = name.split('/').collect(); |
| 55 | + let mut entry = SDKEntry::from_name(parts.last().unwrap(), values); |
| 56 | + for part in parts.iter().rev().skip(1) { |
| 57 | + entry = SDKEntry::from_name(part, vec![entry]); |
| 58 | + } |
| 59 | + entry |
| 60 | + } else { |
| 61 | + SDKEntry::new(HashSet::new(), values) |
| 62 | + } |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +// Build the wanted tree |
| 67 | +fn wanted_sdk_entry() -> SDKEntry { |
| 68 | + SDKEntry::e( |
| 69 | + Some("Contents/Developer"), |
| 70 | + vec![ |
| 71 | + SDKEntry::e( |
| 72 | + Some("Toolchains/XcodeDefault.xctoolchain/usr/lib"), |
| 73 | + vec![ |
| 74 | + SDKEntry::e(Some("swift"), vec![]), |
| 75 | + SDKEntry::e(Some("swift_static"), vec![]), |
| 76 | + SDKEntry::e(Some("clang"), vec![]), |
| 77 | + ], |
| 78 | + ), |
| 79 | + SDKEntry::e( |
| 80 | + Some("Platforms"), |
| 81 | + ["iPhoneOS", "MacOSX", "iPhoneSimulator"] |
| 82 | + .iter() |
| 83 | + .map(|plat| { |
| 84 | + SDKEntry::e( |
| 85 | + Some(&format!("{}.platform/Developer", plat)), |
| 86 | + vec![ |
| 87 | + SDKEntry::e(Some("SDKs"), vec![]), |
| 88 | + SDKEntry::e( |
| 89 | + Some("Library"), |
| 90 | + vec![ |
| 91 | + SDKEntry::e(Some("Frameworks"), vec![]), |
| 92 | + SDKEntry::e(Some("PrivateFrameworks"), vec![]), |
| 93 | + ], |
| 94 | + ), |
| 95 | + SDKEntry::e(Some("usr/lib"), vec![]), |
| 96 | + ], |
| 97 | + ) |
| 98 | + }) |
| 99 | + .collect(), |
| 100 | + ), |
| 101 | + ], |
| 102 | + ) |
| 103 | +} |
| 104 | + |
| 105 | +fn is_wanted(path: &Path) -> bool { |
| 106 | + let mut components: Vec<String> = path |
| 107 | + .components() |
| 108 | + .filter_map(|c| match c { |
| 109 | + Component::Normal(os) => Some(os.to_string_lossy().to_string()), |
| 110 | + _ => None, |
| 111 | + }) |
| 112 | + .collect(); |
| 113 | + |
| 114 | + if let Some(first) = components.first() |
| 115 | + && first == "." |
| 116 | + { |
| 117 | + components.remove(0); |
| 118 | + } |
| 119 | + if let Some(first) = components.first() |
| 120 | + && first.ends_with(".app") |
| 121 | + { |
| 122 | + components.remove(0); |
| 123 | + } |
| 124 | + |
| 125 | + if !wanted_sdk_entry().matches(components.iter().map(|s| s.as_str())) { |
| 126 | + return false; |
| 127 | + } |
| 128 | + |
| 129 | + if components.len() >= 10 |
| 130 | + && components[9] == "prebuilt-modules" |
| 131 | + && components.starts_with( |
| 132 | + &[ |
| 133 | + "Contents", |
| 134 | + "Developer", |
| 135 | + "Toolchains", |
| 136 | + "XcodeDefault.xctoolchain", |
| 137 | + "usr", |
| 138 | + "lib", |
| 139 | + "swift", |
| 140 | + ] |
| 141 | + .iter() |
| 142 | + .map(|s| s.to_string()) |
| 143 | + .collect::<Vec<_>>(), |
| 144 | + ) |
| 145 | + { |
| 146 | + return false; |
| 147 | + } |
| 148 | + |
| 149 | + true |
| 150 | +} |
| 151 | + |
| 152 | +pub fn copy_developer( |
| 153 | + src: &Path, |
| 154 | + dst: &Path, |
| 155 | + rel: &Path, |
| 156 | + crosses_devices: bool, |
| 157 | +) -> Result<(), String> { |
| 158 | + let mut has_crossed_device = crosses_devices; |
| 159 | + for entry in fs::read_dir(src).map_err(|e| format!("Failed to read dir: {}", e))? { |
| 160 | + let entry = entry.map_err(|e| format!("Failed to read entry: {}", e))?; |
| 161 | + let file_name = entry.file_name(); |
| 162 | + let rel_path = rel.join(&file_name); |
| 163 | + if !is_wanted(&rel_path) { |
| 164 | + continue; |
| 165 | + } |
| 166 | + let src_path = entry.path(); |
| 167 | + |
| 168 | + let mut rel_components = rel_path.components(); |
| 169 | + if let Some(c) = rel_components.next() |
| 170 | + && c.as_os_str() != "Contents" |
| 171 | + { |
| 172 | + rel_components = rel_path.components(); |
| 173 | + } |
| 174 | + if let Some(c) = rel_components.next() |
| 175 | + && c.as_os_str() != "Developer" |
| 176 | + { |
| 177 | + rel_components = rel_path.components(); |
| 178 | + } |
| 179 | + let dst_path = dst.join(rel_components.as_path()); |
| 180 | + |
| 181 | + let metadata = fs::symlink_metadata(&src_path) |
| 182 | + .map_err(|e| format!("Failed to get metadata: {}", e))?; |
| 183 | + |
| 184 | + if metadata.file_type().is_symlink() { |
| 185 | + let target = |
| 186 | + read_link(&src_path).map_err(|e| format!("Failed to read symlink: {}", e))?; |
| 187 | + if let Some(parent) = dst_path.parent() { |
| 188 | + fs::create_dir_all(parent) |
| 189 | + .map_err(|e| format!("Failed to create parent dir: {}", e))?; |
| 190 | + } |
| 191 | + symlink(&target, &dst_path).map_err(|e| format!("Failed to create symlink: {}", e))?; |
| 192 | + } else if metadata.is_dir() { |
| 193 | + fs::create_dir_all(&dst_path).map_err(|e| format!("Failed to create dir: {}", e))?; |
| 194 | + copy_developer(&src_path, dst, &rel_path, has_crossed_device)?; |
| 195 | + } else if metadata.is_file() { |
| 196 | + if let Some(parent) = dst_path.parent() { |
| 197 | + fs::create_dir_all(parent) |
| 198 | + .map_err(|e| format!("Failed to create parent dir: {}", e))?; |
| 199 | + } |
| 200 | + if has_crossed_device { |
| 201 | + fs::copy(&src_path, &dst_path) |
| 202 | + .map_err(|e| format!("Failed to copy file across devices: {}", e))?; |
| 203 | + continue; |
| 204 | + } |
| 205 | + match fs::rename(&src_path, &dst_path) { |
| 206 | + Ok(_) => {} |
| 207 | + Err(e) => { |
| 208 | + if e.kind() == ErrorKind::CrossesDevices { |
| 209 | + has_crossed_device = true; |
| 210 | + fs::copy(&src_path, &dst_path) |
| 211 | + .map_err(|e2| format!("Failed to copy file across devices: {}", e2))?; |
| 212 | + } else { |
| 213 | + return Err(format!("Failed to move file: {}", e)); |
| 214 | + } |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | + } |
| 219 | + Ok(()) |
| 220 | +} |
0 commit comments