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            let platform = match target.operating_system {
32                target_lexicon::OperatingSystem::Windows => FlowPlatform::Windows,
33                target_lexicon::OperatingSystem::Linux => {
34                    FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu)
35                }
36                target_lexicon::OperatingSystem::Darwin(_) => FlowPlatform::MacOs,
37                _ => return false,
38            };
39            let arch = match target.architecture {
40                Architecture::X86_64 => FlowArch::X86_64,
41                Architecture::Aarch64(_) => FlowArch::Aarch64,
42                _ => return false,
43            };
44            host_platform == platform && host_arch == arch
45        };
46
47        for Request {
48            target,
49            injected_env: injected_env_write,
50        } in requests
51        {
52            let mut pre_build_deps = Vec::new();
53            let mut injected_env = BTreeMap::new();
54
55            if !native(&target) {
56                let platform = ctx.platform();
57                let gcc_arch_str = match target.architecture {
58                    Architecture::X86_64 => match platform {
59                        FlowPlatform::Linux(linux_distribution) => match linux_distribution {
60                            FlowPlatformLinuxDistro::Fedora => "x86_64",
61                            FlowPlatformLinuxDistro::Ubuntu => "x86-64",
62                            FlowPlatformLinuxDistro::Unknown => {
63                                anyhow::bail!("Unknown Linux distribution")
64                            }
65                        },
66                        _ => anyhow::bail!("Unsupported platform"),
67                    },
68                    Architecture::Aarch64(_) => "aarch64",
69                    arch => anyhow::bail!("unsupported arch {arch}"),
70                };
71
72                match (platform, target.operating_system) {
73                    (FlowPlatform::Linux(_), target_lexicon::OperatingSystem::Linux) => {
74                        let (gcc_pkg, bin) = match target.architecture {
75                            Architecture::Aarch64(_) => (
76                                format!("gcc-{gcc_arch_str}-linux-gnu"),
77                                "aarch64-linux-gnu-gcc".to_string(),
78                            ),
79                            Architecture::X86_64 => (
80                                format!("gcc-{gcc_arch_str}-linux-gnu"),
81                                "x86_64-linux-gnu-gcc".to_string(),
82                            ),
83                            arch => anyhow::bail!("unsupported arch {arch}"),
84                        };
85
86                        // We use `gcc`'s linker for cross-compiling due to:
87                        //
88                        // * The special baremetal options are the same. These options
89                        //   don't work for the LLVM linker,
90                        // * The compiler team at Microsoft has stated that `rust-lld`
91                        //   is not a production option,
92                        // * The only Rust `aarch64` targets that produce
93                        //   position-independent static ELF binaries with no std are
94                        //   `aarch64-unknown-linux-*`.
95                        pre_build_deps.push(ctx.reqv(|v| {
96                            flowey_lib_common::install_dist_pkg::Request::Install {
97                                package_names: vec![gcc_pkg],
98                                done: v,
99                            }
100                        }));
101
102                        // when cross compiling for gnu linux, explicitly set the
103                        // linker being used.
104                        //
105                        // Note: Don't do this for musl, since for that we use the
106                        // openhcl linker set in the repo's `.cargo/config.toml`
107                        // This isn't ideal because it means _any_ musl code (not just
108                        // code running in VTL2) will use the openhcl-specific musl
109                        if matches!(target.environment, target_lexicon::Environment::Gnu) {
110                            injected_env.insert(
111                                format!(
112                                    "CARGO_TARGET_{}_LINKER",
113                                    target.to_string().replace('-', "_").to_uppercase()
114                                ),
115                                bin,
116                            );
117                        }
118                    }
119                    // Cross compiling for Windows relies on the appropriate
120                    // Visual Studio Build Tools components being installed.
121                    // The necessary libraries can be accessed from WSL,
122                    // allowing for compilation of Windows applications from Linux.
123                    // For now, just silently continue regardless.
124                    // TODO: Detect (and potentially install) these dependencies
125                    (FlowPlatform::Linux(_), target_lexicon::OperatingSystem::Windows) => {}
126                    (FlowPlatform::Windows, target_lexicon::OperatingSystem::Windows) => {}
127                    (_, target_lexicon::OperatingSystem::None_) => {}
128                    (_, target_lexicon::OperatingSystem::Uefi) => {}
129                    (host_os, target_os) => {
130                        anyhow::bail!("cannot cross compile for {target_os} on {host_os}")
131                    }
132                }
133            }
134
135            ctx.emit_minor_rust_step("inject cross env", |ctx| {
136                pre_build_deps.claim(ctx);
137                let injected_env_write = injected_env_write.claim(ctx);
138                move |rt| {
139                    rt.write(injected_env_write, &injected_env);
140                }
141            });
142        }
143
144        Ok(())
145    }
146}