1use std::{fmt::Display, process::Command};
2
3use execute::command;
4use nonempty::{nonempty, NonEmpty};
5
6use crate::lib_type::LibType;
7use crate::metadata::{metadata, MetadataExt};
8use crate::package::FeatureOptions;
9
10pub trait TargetInfo {
11 fn target(&self) -> Target;
12 fn is_tier_3(&self) -> bool;
16}
17
18#[derive(Debug, Clone)]
19pub enum Target {
20 Single {
21 architecture: &'static str,
22 display_name: &'static str,
23 platform: ApplePlatform,
24 },
25 Universal {
26 universal_name: &'static str,
27 architectures: NonEmpty<&'static str>,
28 display_name: &'static str,
29 platform: ApplePlatform,
30 },
31}
32
33#[derive(Debug, Clone, Copy)]
34pub enum Mode {
35 Debug,
36 Release,
37}
38
39impl Display for Mode {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Mode::Debug => write!(f, "debug"),
43 Mode::Release => write!(f, "release"),
44 }
45 }
46}
47
48impl Target {
49 fn cargo_build_commands(&self, mode: Mode, features: &FeatureOptions) -> Vec<Command> {
50 self.architectures()
51 .into_iter()
52 .map(|arch| {
53 let mut cmd = if self.platform().is_tier_3() {
55 command("cargo +nightly build -Z build-std")
56 } else {
57 command("cargo build")
58 };
59 cmd.arg("--target").arg(arch);
60
61 match mode {
62 Mode::Debug => {}
63 Mode::Release => {
64 cmd.arg("--release");
65 }
66 }
67
68 if let Some(features) = &features.features {
69 cmd.arg("--features").arg(features.join(","));
70 }
71 if features.all_features {
72 cmd.arg("--all-features");
73 }
74 if features.no_default_features {
75 cmd.arg("--no-default-features");
76 }
77
78 cmd
79 })
80 .collect()
81 }
82
83 fn lipo_commands(&self, lib_name: &str, mode: Mode, lib_type: LibType) -> Vec<Command> {
84 match self {
85 Target::Single { .. } => vec![],
86 Target::Universal { architectures, .. } => {
87 let path = self.library_directory(mode);
88
89 let target = metadata().target_dir();
90 let target_name = library_file_name(lib_name, lib_type);
91 let component_paths: Vec<_> = architectures
92 .iter()
93 .map(|arch| format!("{target}/{arch}/{mode}/{target_name}"))
94 .collect();
95 let args = component_paths.join(" ");
96 let target_path = self.library_path(lib_name, mode, lib_type);
97
98 let make_dir = command(format!("mkdir -p {path}"));
99 let lipo = command(format!("lipo {args} -create -output {target_path}"));
100 vec![make_dir, lipo]
101 }
102 }
103 }
104
105 fn rpath_install_id_commands(
106 &self,
107 lib_name: &str,
108 mode: Mode,
109 lib_type: LibType,
110 ) -> Vec<Command> {
111 if matches!(lib_type, LibType::Dynamic) {
112 vec![command(format!(
113 "install_name_tool -id @rpath/{} {}",
114 library_file_name(lib_name, lib_type),
115 self.library_path(lib_name, mode, lib_type)
116 ))]
117 } else {
118 vec![]
119 }
120 }
121
122 pub fn commands(
127 &self,
128 lib_name: &str,
129 mode: Mode,
130 lib_type: LibType,
131 features: &FeatureOptions,
132 ) -> Vec<Command> {
133 self.cargo_build_commands(mode, features)
134 .into_iter()
135 .chain(self.lipo_commands(lib_name, mode, lib_type))
136 .chain(self.rpath_install_id_commands(lib_name, mode, lib_type))
137 .collect()
138 }
139
140 pub fn architectures(&self) -> NonEmpty<&'static str> {
145 match self {
146 Target::Single { architecture, .. } => nonempty![architecture],
147 Target::Universal { architectures, .. } => architectures.to_owned(),
148 }
149 }
150
151 pub fn display_name(&self) -> &'static str {
152 match self {
153 Target::Single { display_name, .. } => display_name,
154 Target::Universal { display_name, .. } => display_name,
155 }
156 }
157
158 pub fn platform(&self) -> ApplePlatform {
159 match self {
160 Target::Single { platform, .. } => *platform,
161 Target::Universal { platform, .. } => *platform,
162 }
163 }
164
165 pub fn library_directory(&self, mode: Mode) -> String {
166 let mode = match mode {
167 Mode::Debug => "debug",
168 Mode::Release => "release",
169 };
170
171 let target = metadata().target_dir();
172
173 match self {
174 Target::Single { architecture, .. } => format!("{target}/{architecture}/{mode}"),
175 Target::Universal { universal_name, .. } => format!("{target}/{universal_name}/{mode}"),
176 }
177 }
178
179 pub fn library_path(&self, lib_name: &str, mode: Mode, lib_type: LibType) -> String {
180 format!(
181 "{}/{}",
182 self.library_directory(mode),
183 library_file_name(lib_name, lib_type)
184 )
185 }
186}
187
188pub fn library_file_name(lib_name: &str, lib_type: LibType) -> String {
189 format!("lib{}.{}", lib_name, lib_type.file_extension())
190}
191
192#[derive(Clone, Copy, Debug)]
193pub enum ApplePlatform {
194 IOS,
195 IOSSimulator,
196 MacOS,
197 TvOS,
199 TvOSSimulator,
200 WatchOS,
201 WatchOSSimulator,
202 VisionOS,
203 VisionOSSimulator,
204}
205
206impl TargetInfo for ApplePlatform {
207 fn target(&self) -> Target {
208 use ApplePlatform::*;
209 match self {
210 IOS => Target::Single {
211 architecture: "aarch64-apple-ios",
212 display_name: "iOS",
213 platform: *self,
214 },
215 IOSSimulator => Target::Universal {
216 universal_name: "universal-ios",
217 architectures: nonempty!["x86_64-apple-ios", "aarch64-apple-ios-sim"],
218 display_name: "iOS Simulator",
219 platform: *self,
220 },
221 MacOS => Target::Universal {
222 universal_name: "universal-macos",
223 architectures: nonempty!["x86_64-apple-darwin", "aarch64-apple-darwin"],
224 display_name: "macOS",
225 platform: *self,
226 },
227 TvOS => Target::Single {
228 architecture: "aarch64-apple-tvos",
229 display_name: "tvOS",
230 platform: *self,
231 },
232 TvOSSimulator => Target::Universal {
233 universal_name: "universal-tvos-simulator",
234 architectures: nonempty!["aarch64-apple-tvos-sim", "x86_64-apple-tvos"],
235 display_name: "tvOS Simulator",
236 platform: *self,
237 },
238 WatchOS => Target::Universal {
239 universal_name: "universal-watchos",
240 architectures: nonempty![
241 "aarch64-apple-watchos",
242 "arm64_32-apple-watchos",
243 "armv7k-apple-watchos"
244 ],
245 display_name: "watchOS",
246 platform: *self,
247 },
248 WatchOSSimulator => Target::Universal {
249 universal_name: "universal-watchos-sim",
250 architectures: nonempty!["aarch64-apple-watchos-sim", "x86_64-apple-watchos-sim"],
251 display_name: "watchOS Simulator",
252 platform: *self,
253 },
254 VisionOS => Target::Single {
255 architecture: "aarch64-apple-visionos",
256 display_name: "visionOS",
257 platform: *self,
258 },
259 VisionOSSimulator => Target::Single {
260 architecture: "aarch64-apple-visionos-sim",
261 display_name: "visionOS Simulator",
262 platform: *self,
263 },
264 }
265 }
266
267 fn is_tier_3(&self) -> bool {
268 match self {
269 ApplePlatform::IOS | ApplePlatform::IOSSimulator => false,
270 ApplePlatform::MacOS => false,
271 ApplePlatform::TvOS | ApplePlatform::TvOSSimulator => true,
272 ApplePlatform::WatchOS | ApplePlatform::WatchOSSimulator => true,
273 ApplePlatform::VisionOS | ApplePlatform::VisionOSSimulator => true,
274 }
275 }
276}