cargo_swift/
targets.rs

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    /// Marks whether a pre-built std-lib is provided for this target (Tier 1 and Tier 2) via rustup or target needs to
13    /// be build (Tier 3)
14    /// See: https://doc.rust-lang.org/nightly/rustc/platform-support.html
15    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                // FIXME: Remove nightly for Tier 3 targets here once build-std is stabilized
54                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    /// Generates all commands necessary to build this target
123    ///
124    /// This function returns a list of commands that should be executed in their given
125    /// order to build this target (and bundle architecture targets with lipo if it is a universal target).
126    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    /// Returns the names of all target architectures for this target
141    ///
142    /// If this target is a single target, the returned vector will always contain exactly one element.
143    /// The names returned here exactly match the identifiers of the respective official Rust targets.
144    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    // MacCatalyst,
198    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}