Skip to main content

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}