flowey_lib_common/
nuget_install_package.rs1use crate::download_nuget_exe::NugetInstallPlatform;
7use flowey::node::prelude::*;
8use std::collections::BTreeMap;
9use std::fmt::Write as _;
10
11#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
12pub struct NugetPackage {
13 pub id: String,
14 pub version: String,
15}
16
17flowey_request! {
18 pub enum Request {
19 Install {
21 nuget_config_file: ReadVar<PathBuf>,
23 packages: Vec<(ReadVar<NugetPackage>, WriteVar<PathBuf>)>,
26 install_dir: ReadVar<PathBuf>,
28 pre_install_side_effects: Vec<ReadVar<SideEffect>>,
32 },
33 LocalOnlyInteractive(bool),
35 }
36}
37
38struct InstallRequest {
39 nuget_config_file: ReadVar<PathBuf>,
40 packages: Vec<(ReadVar<NugetPackage>, WriteVar<PathBuf>)>,
41 install_dir: ReadVar<PathBuf>,
42 pre_install_side_effects: Vec<ReadVar<SideEffect>>,
43}
44
45new_flow_node!(struct Node);
46
47impl FlowNode for Node {
48 type Request = Request;
49
50 fn imports(ctx: &mut ImportCtx<'_>) {
51 ctx.import::<super::install_nuget_azure_credential_provider::Node>();
52 ctx.import::<super::download_nuget_exe::Node>();
53 }
54
55 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
56 let mut interactive = None;
57 let mut install = Vec::new();
58
59 for request in requests {
60 match request {
61 Request::LocalOnlyInteractive(v) => {
62 same_across_all_reqs("LocalOnlyInteractive", &mut interactive, v)?
63 }
64
65 Request::Install {
66 packages,
67 nuget_config_file,
68 install_dir,
69 pre_install_side_effects,
70 } => install.push(InstallRequest {
71 packages,
72 nuget_config_file,
73 install_dir,
74 pre_install_side_effects,
75 }),
76 }
77 }
78
79 let interactive = if matches!(ctx.backend(), FlowBackend::Ado | FlowBackend::Github) {
80 if interactive.is_some() {
81 anyhow::bail!("can only use `LocalOnlyInteractive` when using the Local backend");
82 }
83 false
84 } else if matches!(ctx.backend(), FlowBackend::Local) {
85 interactive.ok_or(anyhow::anyhow!(
86 "Missing essential request: LocalOnlyInteractive",
87 ))?
88 } else {
89 anyhow::bail!("unsupported backend")
90 };
91
92 if install.is_empty() {
95 return Ok(());
96 }
97
98 let nuget_bin = ctx.reqv(super::download_nuget_exe::Request::NugetBin);
100
101 let nuget_config_platform =
102 ctx.reqv(super::download_nuget_exe::Request::NugetInstallPlatform);
103
104 for InstallRequest {
105 packages,
106 nuget_config_file,
107 install_dir,
108 pre_install_side_effects,
109 } in install
110 {
111 ctx.emit_rust_step("restore nuget packages", |ctx| {
112 let nuget_bin = nuget_bin.clone().claim(ctx);
113 let nuget_config_platform = nuget_config_platform.clone().claim(ctx);
114 let install_dir = install_dir.claim(ctx);
115 pre_install_side_effects.claim(ctx);
116
117 let packages = packages
118 .into_iter()
119 .map(|(a, b)| (a.claim(ctx), b.claim(ctx)))
120 .collect::<Vec<_>>();
121 let nuget_config_file = nuget_config_file.claim(ctx);
122
123 move |rt| {
124 let nuget_bin = rt.read(nuget_bin);
125 let nuget_config_platform = rt.read(nuget_config_platform);
126 let nuget_config_file = rt.read(nuget_config_file);
127 let install_dir = rt.read(install_dir);
128
129 let packages = {
130 let mut pkgmap: BTreeMap<_, Vec<_>> = BTreeMap::new();
131 for (package, var) in packages {
132 pkgmap.entry(rt.read(package)).or_default().push(var);
133 }
134 pkgmap
135 };
136
137 let packages_config = {
146 let mut packages_config = String::new();
147 let _ =
148 writeln!(packages_config, r#"<?xml version="1.0" encoding="utf-8"?>"#);
149 let _ = writeln!(packages_config, r#"<packages>"#);
150 for NugetPackage { id, version } in packages.keys() {
151 let _ = writeln!(
152 packages_config,
153 r#" <package id="{}" version="{}" />"#,
154 id, version
155 );
156 }
157 let _ = writeln!(packages_config, r#"</packages>"#);
158 packages_config
159 };
160
161 log::debug!("generated package.config:\n{}", packages_config);
162
163 let packages_config_filepath = PathBuf::from("./packages.config");
164
165 fs_err::write(&packages_config_filepath, packages_config)?;
166
167 let (packages_config_filepath, config_filepath) =
169 if crate::_util::running_in_wsl(rt)
170 && matches!(nuget_config_platform, NugetInstallPlatform::Windows)
171 {
172 (
173 crate::_util::wslpath::linux_to_win(&packages_config_filepath),
174 crate::_util::wslpath::linux_to_win(&nuget_config_file),
175 )
176 } else {
177 (packages_config_filepath, nuget_config_file)
178 };
179
180 let non_interactive = (!interactive).then_some("-NonInteractive");
182
183 let sh = xshell::Shell::new()?;
187 xshell::cmd!(
188 sh,
189 "{nuget_bin}
190 install
191 {non_interactive...}
192 -ExcludeVersion
193 -OutputDirectory {install_dir}
194 -ConfigFile {config_filepath}
195 {packages_config_filepath}
196 "
197 )
198 .run()?;
199
200 for (package, package_out_dir) in packages {
201 for var in package_out_dir {
202 rt.write(var, &install_dir.join(&package.id).absolute()?);
203 }
204 }
205
206 Ok(())
207 }
208 });
209 }
210
211 Ok(())
212 }
213}