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 (gcc_pkg, bin) = match target.architecture {
69 Architecture::X86_64 => match platform {
70 FlowPlatform::Linux(linux_distribution) => {
71 let pkg = match linux_distribution {
72 FlowPlatformLinuxDistro::Fedora => "gcc-x86_64-linux-gnu",
73 FlowPlatformLinuxDistro::Ubuntu => "gcc-x86-64-linux-gnu",
74 FlowPlatformLinuxDistro::Arch => {
75 match_arch!(host_arch, FlowArch::X86_64, "gcc")
76 }
77 FlowPlatformLinuxDistro::Nix => {
78 anyhow::bail!(
79 "Cross-compilation for Linux is not yet supported in Nix environments"
80 )
81 }
82 FlowPlatformLinuxDistro::Unknown => {
83 anyhow::bail!("Unknown Linux distribution")
84 }
85 };
86 (pkg.to_string(), "x86_64-linux-gnu-gcc".to_string())
87 }
88 _ => anyhow::bail!("Unsupported platform"),
89 },
90 Architecture::Aarch64(_) => match platform {
91 FlowPlatform::Linux(linux_distribution) => {
92 let pkg = match linux_distribution {
93 FlowPlatformLinuxDistro::Fedora
94 | FlowPlatformLinuxDistro::Ubuntu => {
95 "gcc-aarch64-linux-gnu"
96 }
97 FlowPlatformLinuxDistro::Arch => match_arch!(
98 host_arch,
99 FlowArch::X86_64,
100 "aarch64-linux-gnu-gcc"
101 ),
102 FlowPlatformLinuxDistro::Nix => {
103 anyhow::bail!(
104 "Cross-compilation for Linux is not yet supported in Nix environments"
105 )
106 }
107 FlowPlatformLinuxDistro::Unknown => {
108 anyhow::bail!("Unknown Linux distribution")
109 }
110 };
111 (pkg.to_string(), "aarch64-linux-gnu-gcc".to_string())
112 }
113 _ => anyhow::bail!("Unsupported platform"),
114 },
115 arch => anyhow::bail!("unsupported arch {arch}"),
116 };
117
118 // We use `gcc`'s linker for cross-compiling due to:
119 //
120 // * The special baremetal options are the same. These options
121 // don't work for the LLVM linker,
122 // * The compiler team at Microsoft has stated that `rust-lld`
123 // is not a production option,
124 // * The only Rust `aarch64` targets that produce
125 // position-independent static ELF binaries with no std are
126 // `aarch64-unknown-linux-*`.
127 pre_build_deps.push(ctx.reqv(|v| {
128 flowey_lib_common::install_dist_pkg::Request::Install {
129 package_names: vec![gcc_pkg],
130 done: v,
131 }
132 }));
133
134 // when cross compiling for gnu linux, explicitly set the
135 // linker being used.
136 //
137 // Note: Don't do this for musl, since for that we use the
138 // openhcl linker set in the repo's `.cargo/config.toml`
139 // This isn't ideal because it means _any_ musl code (not just
140 // code running in VTL2) will use the openhcl-specific musl
141 if matches!(target.environment, target_lexicon::Environment::Gnu) {
142 injected_env.insert(
143 format!(
144 "CARGO_TARGET_{}_LINKER",
145 target.to_string().replace('-', "_").to_uppercase()
146 ),
147 bin,
148 );
149 }
150 }
151 // Cross compiling for Windows relies on the appropriate
152 // Visual Studio Build Tools components being installed.
153 // The necessary libraries can be accessed from WSL,
154 // allowing for compilation of Windows applications from Linux.
155 // For now, just silently continue regardless.
156 // TODO: Detect (and potentially install) these dependencies
157 (FlowPlatform::Linux(_), target_lexicon::OperatingSystem::Windows) => {}
158 (FlowPlatform::Windows, target_lexicon::OperatingSystem::Windows) => {}
159 (_, target_lexicon::OperatingSystem::None_) => {}
160 (_, target_lexicon::OperatingSystem::Uefi) => {}
161 (host_os, target_os) => {
162 anyhow::bail!("cannot cross compile for {target_os} on {host_os}")
163 }
164 }
165 }
166
167 ctx.emit_minor_rust_step("inject cross env", |ctx| {
168 pre_build_deps.claim(ctx);
169 let injected_env_write = injected_env_write.claim(ctx);
170 move |rt| {
171 rt.write(injected_env_write, &injected_env);
172 }
173 });
174 }
175
176 Ok(())
177 }
178}