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 install_dir = rt.read(install_dir);
165
166                let nuget_exe_path = install_dir.join("nuget.exe");
167
168                // download nuget if none was previously downloaded
169                if !nuget_exe_path.exists() {
170                    let nuget_install_latest_url =
171                        "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe";
172                    flowey::shell_cmd!(
173                        rt,
174                        "curl --fail -o {nuget_exe_path} {nuget_install_latest_url}"
175                    )
176                    .run()?;
177                }
178
179                let write_mono_shim = |rt: &mut RustRuntimeServices<'_>| {
180                    fs::write(
181                        "./nuget-shim.sh",
182                        format!("#!/bin/sh\nmono {}/nuget.exe \"$@\"", install_dir.display()),
183                    )?;
184                    flowey::shell_cmd!(rt, "chmod +x ./nuget-shim.sh").run()?;
185                    anyhow::Ok(rt.sh.current_dir().join("nuget-shim.sh").absolute()?)
186                };
187
188                let nuget_exec_path = match rt.platform() {
189                    FlowPlatform::Windows => nuget_exe_path,
190                    FlowPlatform::Linux(_) if crate::_util::running_in_wsl(rt) => {
191                        // allow reusing the windows config directory from wsl2, if available
192                        {
193                            let windows_userprofile =
194                                flowey::shell_cmd!(rt, "cmd.exe /c echo %UserProfile%").read()?;
195
196                            let windows_dot_nuget_path =
197                                crate::_util::wslpath::win_to_linux(rt, windows_userprofile)
198                                    .join(".nuget");
199
200                            let linux_dot_nuget_path =
201                                dirs::home_dir().unwrap_or_default().join(".nuget");
202
203                            // Only symlink if the user doesn't already have an
204                            // existing .nuget folder / symlink
205                            if windows_dot_nuget_path.exists()
206                                && fs_err::symlink_metadata(&linux_dot_nuget_path).is_err()
207                            {
208                                flowey::shell_cmd!(
209                                    rt,
210                                    "ln -s {windows_dot_nuget_path} {linux_dot_nuget_path}"
211                                )
212                                .run()?;
213                            }
214                        }
215
216                        if force_mono_nuget_exe_wsl2 {
217                            write_mono_shim(rt)?
218                        } else {
219                            // rely on magical wsl2 interop
220
221                            // WORKARDOUND: seems like on some folk's machines,
222                            // nuget.exe will only work correctly when launched
223                            // from a windows filesystem.
224                            let windows_tempdir = crate::_util::wslpath::win_to_linux(
225                                rt,
226                                flowey::shell_cmd!(rt, "cmd.exe /c echo %Temp%").read()?,
227                            );
228                            let flowey_nuget = windows_tempdir.join("flowey_nuget.exe");
229                            if !flowey_nuget.exists() {
230                                fs_err::copy(nuget_exe_path, &flowey_nuget)?;
231                            }
232                            flowey::shell_cmd!(rt, "chmod +x {flowey_nuget}").run()?;
233                            flowey_nuget
234                        }
235                    }
236                    FlowPlatform::Linux(_) => write_mono_shim(rt)?,
237                    platform => anyhow::bail!("unsupported platform {platform}"),
238                };
239
240                rt.write_all(broadcast_nuget_tool_kind, &nuget_exec_path);
241
242                Ok(())
243            }
244        });
245
246        Ok(())
247    }
248}