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}