flowey_lib_common/
download_nuget_exe.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Download `nuget.exe`
5
6use flowey::node::prelude::*;
7use std::fs;
8
9#[derive(Serialize, Deserialize)]
10pub enum NugetInstallPlatform {
11    Windows,
12    Linux,
13    MacOs,
14}
15
16flowey_request! {
17    pub enum Request {
18        NugetBin(WriteVar<PathBuf>),
19        NugetInstallPlatform(WriteVar<NugetInstallPlatform>),
20        /// When running using WSL2, use `mono` to run the `nuget.exe` inside
21        /// WSL2 directly, instead of running `nuget.exe` via WSL2 interop.
22        ///
23        /// This is sometimes required to work around windows defender bugs when
24        /// restoring.
25        LocalOnlyForceWsl2MonoNugetExe(bool),
26    }
27}
28
29new_flow_node!(struct Node);
30
31impl FlowNode for Node {
32    type Request = Request;
33
34    fn imports(ctx: &mut ImportCtx<'_>) {
35        ctx.import::<crate::ado_task_nuget_tool_installer::Node>();
36        ctx.import::<crate::install_dist_pkg::Node>();
37    }
38
39    fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
40        let mut broadcast_nuget_tool_kind = Vec::new();
41        let mut broadcast_nuget_config_platform = Vec::new();
42        let mut force_mono_nuget_exe_wsl2 = None;
43
44        for req in requests {
45            match req {
46                Request::LocalOnlyForceWsl2MonoNugetExe(v) => same_across_all_reqs(
47                    "LocalOnlyForceWsl2MonoNugetExe",
48                    &mut force_mono_nuget_exe_wsl2,
49                    v,
50                )?,
51                Request::NugetBin(outvar) => broadcast_nuget_tool_kind.push(outvar),
52                Request::NugetInstallPlatform(outvar) => {
53                    broadcast_nuget_config_platform.push(outvar)
54                }
55            };
56        }
57
58        let broadcast_nuget_tool_kind = broadcast_nuget_tool_kind;
59        let broadcast_nuget_config_platform = broadcast_nuget_config_platform;
60
61        let force_mono_nuget_exe_wsl2 = if matches!(ctx.backend(), FlowBackend::Local) {
62            force_mono_nuget_exe_wsl2.ok_or(anyhow::anyhow!(
63                "Missing essential request: LocalOnlyForceWsl2MonoNugetExe"
64            ))?
65        } else {
66            if force_mono_nuget_exe_wsl2.is_some() {
67                anyhow::bail!(
68                    "can only use `LocalOnlyForceWsl2MonoNugetExe` when using the Local backend"
69                );
70            }
71            false
72        };
73
74        // -- end of req processing -- //
75
76        if !broadcast_nuget_config_platform.is_empty() {
77            ctx.emit_rust_step("report nuget install platform", |ctx| {
78                let broadcast_nuget_config_platform = broadcast_nuget_config_platform.claim(ctx);
79                move |rt| {
80                    let nuget_config_platform = match rt.platform() {
81                        FlowPlatform::Windows => NugetInstallPlatform::Windows,
82                        FlowPlatform::Linux(_) if crate::_util::running_in_wsl(rt) => {
83                            if force_mono_nuget_exe_wsl2 {
84                                NugetInstallPlatform::Linux
85                            } else {
86                                NugetInstallPlatform::Windows
87                            }
88                        }
89                        FlowPlatform::Linux(_) => NugetInstallPlatform::Linux,
90                        FlowPlatform::MacOs => NugetInstallPlatform::MacOs,
91                        platform => anyhow::bail!("unsupported platform {platform}"),
92                    };
93
94                    rt.write_all(broadcast_nuget_config_platform, &nuget_config_platform);
95
96                    Ok(())
97                }
98            });
99        }
100
101        match ctx.backend() {
102            FlowBackend::Ado => Self::emit_ado(ctx, broadcast_nuget_tool_kind),
103            FlowBackend::Local => {
104                Self::emit_local(ctx, broadcast_nuget_tool_kind, force_mono_nuget_exe_wsl2)
105            }
106            FlowBackend::Github => {
107                anyhow::bail!("nuget installation not yet implemented for the Github backend")
108            }
109        }
110    }
111}
112
113impl Node {
114    fn emit_ado(
115        ctx: &mut NodeCtx<'_>,
116        broadcast_nuget_tool_kind: Vec<WriteVar<PathBuf>>,
117    ) -> anyhow::Result<()> {
118        let nuget_tool_installed = ctx.reqv(crate::ado_task_nuget_tool_installer::Request);
119
120        ctx.emit_rust_step("report nuget install", move |ctx| {
121            nuget_tool_installed.claim(ctx);
122            let broadcast_nuget_tool_kind = broadcast_nuget_tool_kind.claim(ctx);
123            move |rt| {
124                // trust that the ADO nuget install task works correctly
125                rt.write_all(
126                    broadcast_nuget_tool_kind,
127                    &which::which(rt.platform().binary("nuget"))?,
128                );
129
130                Ok(())
131            }
132        });
133
134        Ok(())
135    }
136
137    fn emit_local(
138        ctx: &mut NodeCtx<'_>,
139        broadcast_nuget_tool_kind: Vec<WriteVar<PathBuf>>,
140        force_mono_nuget_exe_wsl2: bool,
141    ) -> anyhow::Result<()> {
142        if broadcast_nuget_tool_kind.is_empty() {
143            return Ok(());
144        }
145
146        let install_dir = ctx
147            .persistent_dir()
148            .ok_or(anyhow::anyhow!("No persistent dir for nuget installation"))?;
149
150        // let install_mono = if matches!(ctx.platform(), FlowPlatform::Linux(_)) {
151        //     Some(ctx.reqv(|v| crate::install_dist_pkg::Request::Install {
152        //         package_names: vec!["mono-devel".to_string()],
153        //         done: v,
154        //     }))
155        // } else {
156        //     None
157        // }; // !!! TEMP !!! installing mono is breaking WSL2 interop!
158
159        ctx.emit_rust_step("Install nuget", |ctx| {
160            // install_mono.claim(ctx);
161            let install_dir = install_dir.clone().claim(ctx);
162            let broadcast_nuget_tool_kind = broadcast_nuget_tool_kind.claim(ctx);
163            move |rt| {
164                let sh = xshell::Shell::new()?;
165
166                let install_dir = rt.read(install_dir);
167
168                let nuget_exe_path = install_dir.join("nuget.exe");
169
170                // download nuget if none was previously downloaded
171                if !nuget_exe_path.exists() {
172                    let nuget_install_latest_url =
173                        "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe";
174                    xshell::cmd!(
175                        sh,
176                        "curl --fail -o {nuget_exe_path} {nuget_install_latest_url}"
177                    )
178                    .run()?;
179                }
180
181                let write_mono_shim = || {
182                    let sh = xshell::Shell::new()?;
183                    fs::write(
184                        "./nuget-shim.sh",
185                        format!("#!/bin/sh\nmono {}/nuget.exe \"$@\"", install_dir.display()),
186                    )?;
187                    xshell::cmd!(sh, "chmod +x ./nuget-shim.sh").run()?;
188                    anyhow::Ok(sh.current_dir().join("nuget-shim.sh").absolute()?)
189                };
190
191                let nuget_exec_path = match rt.platform() {
192                    FlowPlatform::Windows => nuget_exe_path,
193                    FlowPlatform::Linux(_) if crate::_util::running_in_wsl(rt) => {
194                        // allow reusing the windows config directory from wsl2, if available
195                        {
196                            let windows_userprofile =
197                                xshell::cmd!(sh, "cmd.exe /c echo %UserProfile%").read()?;
198
199                            let windows_dot_nuget_path =
200                                crate::_util::wslpath::win_to_linux(windows_userprofile)
201                                    .join(".nuget");
202
203                            let linux_dot_nuget_path =
204                                dirs::home_dir().unwrap_or_default().join(".nuget");
205
206                            // Only symlink if the user doesn't already have an
207                            // existing .nuget folder / symlink
208                            if windows_dot_nuget_path.exists()
209                                && fs_err::symlink_metadata(&linux_dot_nuget_path).is_err()
210                            {
211                                xshell::cmd!(
212                                    sh,
213                                    "ln -s {windows_dot_nuget_path} {linux_dot_nuget_path}"
214                                )
215                                .run()?;
216                            }
217                        }
218
219                        if force_mono_nuget_exe_wsl2 {
220                            write_mono_shim()?
221                        } else {
222                            // rely on magical wsl2 interop
223
224                            // WORKARDOUND: seems like on some folk's machines,
225                            // nuget.exe will only work correctly when launched
226                            // from a windows filesystem.
227                            let windows_tempdir = crate::_util::wslpath::win_to_linux(
228                                xshell::cmd!(sh, "cmd.exe /c echo %Temp%").read()?,
229                            );
230                            let flowey_nuget = windows_tempdir.join("flowey_nuget.exe");
231                            if !flowey_nuget.exists() {
232                                fs_err::copy(nuget_exe_path, &flowey_nuget)?;
233                            }
234                            xshell::cmd!(sh, "chmod +x {flowey_nuget}").run()?;
235                            flowey_nuget
236                        }
237                    }
238                    FlowPlatform::Linux(_) => write_mono_shim()?,
239                    platform => anyhow::bail!("unsupported platform {platform}"),
240                };
241
242                rt.write_all(broadcast_nuget_tool_kind, &nuget_exec_path);
243
244                Ok(())
245            }
246        });
247
248        Ok(())
249    }
250}