flowey_lib_hvlite/init_cross_build.rs
1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Install dependencies and set environment variables for cross compiling
5
6use flowey::node::prelude::*;
7use std::collections::BTreeMap;
8use target_lexicon::Architecture;
9
10flowey_request! {
11 pub struct Request {
12 pub target: target_lexicon::Triple,
13 pub injected_env: WriteVar<BTreeMap<String, String>>,
14 }
15}
16
17new_flow_node!(struct Node);
18
19impl FlowNode for Node {
20 type Request = Request;
21
22 fn imports(ctx: &mut ImportCtx<'_>) {
23 ctx.import::<flowey_lib_common::install_dist_pkg::Node>();
24 }
25
26 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
27 let host_platform = ctx.platform();
28 let host_arch = ctx.arch();
29
30 let native = |target: &target_lexicon::Triple| -> bool {
31 // Check if the target matches the host platform, treat Linux distros as equivalent
32 let os_matches = matches!(
33 (host_platform, target.operating_system),
34 (
35 FlowPlatform::Linux(_),
36 target_lexicon::OperatingSystem::Linux
37 ) | (
38 FlowPlatform::Windows,
39 target_lexicon::OperatingSystem::Windows
40 ) | (
41 FlowPlatform::MacOs,
42 target_lexicon::OperatingSystem::Darwin(_)
43 )
44 );
45
46 let arch_matches = match target.architecture {
47 Architecture::X86_64 => host_arch == FlowArch::X86_64,
48 Architecture::Aarch64(_) => host_arch == FlowArch::Aarch64,
49 _ => false,
50 };
51
52 os_matches && arch_matches
53 };
54
55 for Request {
56 target,
57 injected_env: injected_env_write,
58 } in requests
59 {
60 let mut pre_build_deps = Vec::new();
61 let mut injected_env = BTreeMap::new();
62
63 if !native(&target) {
64 let platform = ctx.platform();
65
66 match (platform, target.operating_system) {
67 (FlowPlatform::Linux(_), target_lexicon::OperatingSystem::Linux) => {
68 let (packages, bin): (Vec<&str>, String) = match target.architecture {
69 Architecture::X86_64 => match platform {
70 FlowPlatform::Linux(linux_distribution) => {
71 let packages = match linux_distribution {
72 FlowPlatformLinuxDistro::Fedora => {
73 vec!["gcc-x86_64-linux-gnu"]
74 }
75 FlowPlatformLinuxDistro::Ubuntu
76 if matches!(
77 target.environment,
78 target_lexicon::Environment::Gnu
79 ) =>
80 {
81 vec!["gcc-x86-64-linux-gnu", "libc6-dev-amd64-cross"]
82 }
83 FlowPlatformLinuxDistro::Ubuntu => {
84 vec!["gcc-x86-64-linux-gnu"]
85 }
86 FlowPlatformLinuxDistro::AzureLinux => {
87 // Azure Linux doesn't have an x86_64 cross-gcc;
88 // the native `gcc` package is used on x86_64 hosts.
89 match_arch!(host_arch, FlowArch::X86_64, vec!["gcc"])
90 }
91 FlowPlatformLinuxDistro::Arch => {
92 match_arch!(host_arch, FlowArch::X86_64, vec!["gcc"])
93 }
94 FlowPlatformLinuxDistro::Nix => Vec::new(),
95 FlowPlatformLinuxDistro::OpenSuse => {
96 anyhow::bail!(
97 "Linux cross compilation on openSUSE is not supported"
98 )
99 }
100 FlowPlatformLinuxDistro::Unknown => {
101 anyhow::bail!("Unknown Linux distribution")
102 }
103 };
104 (packages, "x86_64-linux-gnu-gcc".to_string())
105 }
106 _ => anyhow::bail!("Unsupported platform"),
107 },
108 Architecture::Aarch64(_) => match platform {
109 FlowPlatform::Linux(linux_distribution) => {
110 let packages = match linux_distribution {
111 FlowPlatformLinuxDistro::Ubuntu
112 if matches!(
113 target.environment,
114 target_lexicon::Environment::Gnu
115 ) =>
116 {
117 vec!["gcc-aarch64-linux-gnu", "libc6-dev-arm64-cross"]
118 }
119 FlowPlatformLinuxDistro::Fedora
120 | FlowPlatformLinuxDistro::Ubuntu
121 | FlowPlatformLinuxDistro::AzureLinux => {
122 vec!["gcc-aarch64-linux-gnu"]
123 }
124 FlowPlatformLinuxDistro::Arch => match_arch!(
125 host_arch,
126 FlowArch::X86_64,
127 vec!["aarch64-linux-gnu-gcc"]
128 ),
129 FlowPlatformLinuxDistro::Nix => Vec::new(),
130 FlowPlatformLinuxDistro::OpenSuse => {
131 anyhow::bail!(
132 "Linux cross compilation on openSUSE is not supported"
133 )
134 }
135 FlowPlatformLinuxDistro::Unknown => {
136 anyhow::bail!("Unknown Linux distribution")
137 }
138 };
139 (packages, "aarch64-linux-gnu-gcc".to_string())
140 }
141 _ => anyhow::bail!("Unsupported platform"),
142 },
143 arch => anyhow::bail!("unsupported arch {arch}"),
144 };
145
146 // We use `gcc`'s linker for cross-compiling due to:
147 //
148 // * The special baremetal options are the same. These options
149 // don't work for the LLVM linker,
150 // * The compiler team at Microsoft has stated that `rust-lld`
151 // is not a production option,
152 // * The only Rust `aarch64` targets that produce
153 // position-independent static ELF binaries with no std are
154 // `aarch64-unknown-linux-*`.
155 //
156 // Skip package installation for Nix (shell.nix provides cross-compilers)
157 if !packages.is_empty() {
158 pre_build_deps.push(ctx.reqv(|v| {
159 flowey_lib_common::install_dist_pkg::Request::Install {
160 package_names: packages.into_iter().map(String::from).collect(),
161 done: v,
162 }
163 }));
164 }
165
166 // when cross compiling for gnu linux, explicitly set the
167 // linker being used.
168 //
169 // Note: Don't do this for musl, since for that we use the
170 // openhcl linker set in the repo's `.cargo/config.toml`
171 // This isn't ideal because it means _any_ musl code (not just
172 // code running in VTL2) will use the openhcl-specific musl
173 if matches!(target.environment, target_lexicon::Environment::Gnu) {
174 injected_env.insert(
175 format!(
176 "CARGO_TARGET_{}_LINKER",
177 target.to_string().replace('-', "_").to_uppercase()
178 ),
179 bin,
180 );
181 }
182 }
183 // Cross compiling for Windows with the MSVC toolchain
184 // relies on the appropriate Visual Studio Build Tools
185 // components being installed. The necessary libraries can
186 // be accessed from WSL, allowing for compilation of Windows
187 // applications from Linux. For now, just silently continue
188 // regardless.
189 // TODO: Detect (and potentially install) these dependencies
190 //
191 // Cross compiling for Windows with the GNU (mingw-w64)
192 // toolchain works on a native (non-WSL) Linux host without
193 // the Windows SDK, and is used to build Windows *guest*
194 // payloads (e.g. pipette). Install the mingw-w64 cross gcc,
195 // which rustc auto-detects as the linker
196 // (`x86_64-w64-mingw32-gcc`).
197 (
198 FlowPlatform::Linux(linux_distribution),
199 target_lexicon::OperatingSystem::Windows,
200 ) => {
201 if matches!(target.environment, target_lexicon::Environment::Gnu)
202 && matches!(target.architecture, Architecture::X86_64)
203 {
204 let pkg = match linux_distribution {
205 FlowPlatformLinuxDistro::Ubuntu => {
206 Some("gcc-mingw-w64-x86-64-win32")
207 }
208 FlowPlatformLinuxDistro::Fedora => Some("mingw64-gcc"),
209 _ => None,
210 };
211 if let Some(pkg) = pkg {
212 pre_build_deps.push(ctx.reqv(|v| {
213 flowey_lib_common::install_dist_pkg::Request::Install {
214 package_names: vec![pkg.into()],
215 done: v,
216 }
217 }));
218 }
219 }
220 }
221 (FlowPlatform::Windows, target_lexicon::OperatingSystem::Windows) => {}
222 (_, target_lexicon::OperatingSystem::None_) => {}
223 (_, target_lexicon::OperatingSystem::Uefi) => {}
224 (host_os, target_os) => {
225 anyhow::bail!("cannot cross compile for {target_os} on {host_os}")
226 }
227 }
228 }
229
230 ctx.emit_minor_rust_step("inject cross env", |ctx| {
231 pre_build_deps.claim(ctx);
232 let injected_env_write = injected_env_write.claim(ctx);
233 move |rt| {
234 rt.write(injected_env_write, &injected_env);
235 }
236 });
237 }
238
239 Ok(())
240 }
241}