1use anyhow::Error;
2use crosup_installers::{
3 apk::ApkInstaller, apt::AptInstaller, brew::BrewInstaller, curl::CurlInstaller,
4 dnf::DnfInstaller, emerge::EmergeInstaller, fleek::FleekInstaller, git::GitInstaller,
5 home_manager::HomeManagerInstaller, nix::NixInstaller, pacman::PacmanInstaller,
6 slackpkg::SlackpkgInstaller, yum::YumInstaller, zypper::ZypperInstaller, Installer,
7};
8use crosup_macros::{
9 add_vertex, add_vertex_with_condition, convert_generic_installer, downcast_installer,
10};
11use crosup_types::{
12 configuration::Configuration,
13 curl::{default_brew_installer, default_nix_installer},
14};
15use os_release::OsRelease;
16use owo_colors::OwoColorize;
17use ssh2::Session;
18
19#[derive(Clone)]
20pub struct Vertex {
21 name: String,
22 dependencies: Vec<String>,
23 provider: String,
24 apt: Option<AptInstaller>,
25 brew: Option<BrewInstaller>,
26 curl: Option<CurlInstaller>,
27 git: Option<GitInstaller>,
28 nix: Option<NixInstaller>,
29 yum: Option<YumInstaller>,
30 dnf: Option<DnfInstaller>,
31 zypper: Option<ZypperInstaller>,
32 apk: Option<ApkInstaller>,
33 pacman: Option<PacmanInstaller>,
34 emerge: Option<EmergeInstaller>,
35 slackpkg: Option<SlackpkgInstaller>,
36 fleek: Option<FleekInstaller>,
37 home_manager: Option<HomeManagerInstaller>,
38}
39
40impl From<Box<dyn Installer + 'static>> for Vertex {
41 fn from(installer: Box<dyn Installer + 'static>) -> Self {
42 Self {
43 name: installer.name().to_string(),
44 dependencies: installer
45 .dependencies()
46 .iter()
47 .map(|x| x.to_string())
48 .collect(),
49 provider: installer.provider().to_string(),
50 apt: downcast_installer!("apt", installer, AptInstaller),
51 brew: downcast_installer!("brew", installer, BrewInstaller),
52 curl: downcast_installer!("curl", installer, CurlInstaller),
53 git: downcast_installer!("git", installer, GitInstaller),
54 nix: downcast_installer!("nix", installer, NixInstaller),
55 yum: downcast_installer!("yum", installer, YumInstaller),
56 dnf: downcast_installer!("dnf", installer, DnfInstaller),
57 zypper: downcast_installer!("zypper", installer, ZypperInstaller),
58 apk: downcast_installer!("apk", installer, ApkInstaller),
59 pacman: downcast_installer!("pacman", installer, PacmanInstaller),
60 emerge: downcast_installer!("emerge", installer, EmergeInstaller),
61 slackpkg: downcast_installer!("slackpkg", installer, SlackpkgInstaller),
62 fleek: downcast_installer!("fleek", installer, FleekInstaller),
63 home_manager: downcast_installer!("home-manager", installer, HomeManagerInstaller),
64 }
65 }
66}
67
68impl Into<Box<dyn Installer>> for Vertex {
69 fn into(self) -> Box<dyn Installer> {
70 match self.provider.as_str() {
71 "apt" => Box::new(self.apt.unwrap()),
72 "brew" => Box::new(self.brew.unwrap()),
73 "curl" => Box::new(self.curl.unwrap()),
74 "git" => Box::new(self.git.unwrap()),
75 "nix" => Box::new(self.nix.unwrap()),
76 "yum" => Box::new(self.yum.unwrap()),
77 "dnf" => Box::new(self.dnf.unwrap()),
78 "zypper" => Box::new(self.zypper.unwrap()),
79 "apk" => Box::new(self.apk.unwrap()),
80 "pacman" => Box::new(self.pacman.unwrap()),
81 "emerge" => Box::new(self.emerge.unwrap()),
82 "slackpkg" => Box::new(self.slackpkg.unwrap()),
83 "fleek" => Box::new(self.fleek.unwrap()),
84 "home-manager" => Box::new(self.home_manager.unwrap()),
85 _ => panic!("Unknown installer: {}", self.name),
86 }
87 }
88}
89
90#[derive(Clone, Debug)]
91pub struct Edge {
92 from: usize,
93 to: usize,
94}
95
96#[derive(Clone)]
97pub struct InstallerGraph {
98 vertices: Vec<Vertex>,
99 edges: Vec<Edge>,
100}
101
102impl Into<Vec<Box<dyn Installer>>> for InstallerGraph {
103 fn into(self) -> Vec<Box<dyn Installer>> {
104 self.vertices.into_iter().map(|x| x.into()).collect()
105 }
106}
107
108pub fn autodetect_installer(config: &mut Configuration) {
109 if let Some(generic_install) = &config.install {
110 if cfg!(target_os = "linux") {
112 if let Ok(os_release) = OsRelease::new() {
114 let os = os_release.id.to_lowercase();
115 let os = os.as_str();
116
117 let package_manager = match os {
118 "ubuntu" | "debian" | "linuxmint" | "pop" | "elementary" | "zorin" => {
119 convert_generic_installer!(config, generic_install, apt);
120 "apt-get"
121 }
122 "fedora" | "centos" | "rhel" | "rocky" | "amazon" => {
123 convert_generic_installer!(config, generic_install, dnf);
124 "dnf"
125 }
126 "opensuse" | "sles" => {
127 convert_generic_installer!(config, generic_install, zypper);
128 "zypper"
129 }
130 "arch" | "manjaro" => {
131 convert_generic_installer!(config, generic_install, pacman);
132 "pacman"
133 }
134 "gentoo" => {
135 convert_generic_installer!(config, generic_install, emerge);
136 "emerge"
137 }
138 "alpine" => {
139 convert_generic_installer!(config, generic_install, apk);
140 "apk"
141 }
142 "slackware" => {
143 convert_generic_installer!(config, generic_install, slackpkg);
144 "slackpkg"
145 }
146 _ => panic!("Unsupported OS: {}", os),
147 };
148
149 let os_pretty = os_release.pretty_name;
150 println!("-> Detected OS:🐧 {}", os_pretty.magenta());
151 println!(
152 "-> Using package manager: 📦 {}",
153 package_manager.bright_green()
154 );
155 }
156 }
157 if cfg!(target_os = "macos") {
158 println!("-> Detected OS: 🍎 macOS");
159 println!("-> Using package manager: 📦 {}", "brew".bright_green());
160 convert_generic_installer!(config, generic_install, brew);
161 }
162 }
163}
164
165pub fn build_installer_graph(
166 config: &mut Configuration,
167 session: Option<Session>,
168) -> (InstallerGraph, Vec<Box<dyn Installer>>) {
169 let mut graph = InstallerGraph::new();
170
171 if config.clone().nix.is_some()
172 || config.clone().fleek.is_some()
173 || config.clone().packages.is_some()
174 {
175 if let Some(curl) = config.clone().curl {
176 if !curl.into_iter().any(|(_, y)| y.script.contains_key("nix")) {
177 let nix = default_nix_installer();
178 graph.add_vertex(Vertex::from(Box::new(CurlInstaller {
179 name: nix.name.clone(),
180 session: session.clone(),
181 ..CurlInstaller::from(nix.clone())
182 }) as Box<dyn Installer>));
183 }
184 } else {
185 let nix = default_nix_installer();
186 graph.add_vertex(Vertex::from(Box::new(CurlInstaller {
187 name: nix.name.clone(),
188 session: session.clone(),
189 ..CurlInstaller::from(nix.clone())
190 }) as Box<dyn Installer>));
191 }
192 }
193
194 if config.clone().brew.is_some() {
195 if let Some(curl) = config.clone().curl {
196 if !curl.into_iter().any(|(_, y)| y.script.contains_key("brew")) {
197 let brew = default_brew_installer();
198 graph.add_vertex(Vertex::from(Box::new(CurlInstaller {
199 name: brew.name.clone(),
200 session: session.clone(),
201 ..CurlInstaller::from(brew.clone())
202 }) as Box<dyn Installer>));
203 }
204 } else {
205 let brew = default_brew_installer();
206 graph.add_vertex(Vertex::from(Box::new(CurlInstaller {
207 name: brew.name.clone(),
208 session: session.clone(),
209 ..CurlInstaller::from(brew.clone())
210 }) as Box<dyn Installer>));
211 }
212 }
213
214 autodetect_installer(config);
215
216 if cfg!(target_os = "linux") {
217 if let Ok(os_release) = OsRelease::new() {
219 let os = os_release.id.to_lowercase();
220 let os = os.as_str();
221
222 match os {
223 "ubuntu" | "debian" | "linuxmint" | "pop" | "elementary" | "zorin" => {
224 add_vertex!(graph, AptInstaller, config, apt, pkg, session);
225 }
226 _ => {}
227 };
228 }
229 }
230
231 add_vertex!(graph, CurlInstaller, config, curl, script, session);
232 add_vertex!(graph, GitInstaller, config, git, repo, session);
233 add_vertex!(graph, NixInstaller, config, nix, pkg, session);
234 add_vertex!(graph, YumInstaller, config, yum, pkg, session);
235 add_vertex!(graph, DnfInstaller, config, dnf, pkg, session);
236 add_vertex!(graph, ZypperInstaller, config, zypper, pkg, session);
237 add_vertex!(graph, ApkInstaller, config, apk, pkg, session);
238 add_vertex!(graph, PacmanInstaller, config, pacman, pkg, session);
239 add_vertex!(graph, EmergeInstaller, config, emerge, pkg, session);
240 add_vertex!(graph, SlackpkgInstaller, config, slackpkg, pkg, session);
241 add_vertex!(graph, FleekInstaller, config, fleek, pkg, session);
242 add_vertex_with_condition!(graph, BrewInstaller, config, brew, pkg, session);
243
244 if let Some(package) = config.clone().packages {
245 package.iter().for_each(|name| {
246 graph.add_vertex(Vertex::from(Box::new(HomeManagerInstaller {
247 name: name.clone(),
248 session: session.clone(),
249 provider: "home-manager".into(),
250 packages: Some(vec![name.clone()]),
251 dependencies: vec!["nix".into()],
252 ..Default::default()
253 }) as Box<dyn Installer>));
254 });
255 }
256
257 setup_dependencies(&mut graph);
258
259 (graph.clone(), graph.into())
260}
261
262fn setup_dependencies(graph: &mut InstallerGraph) {
263 let mut edges = vec![];
264
265 for (i, vertex) in graph.vertices.iter().enumerate() {
266 for dependency in vertex.dependencies.iter() {
267 if let Some(j) = graph.vertices.iter().position(|x| x.name == *dependency) {
268 edges.push(Edge { from: i, to: j });
269 }
270 }
271 }
272
273 graph.edges = edges;
274}
275
276impl InstallerGraph {
277 pub fn new() -> Self {
278 Self {
279 vertices: vec![],
280 edges: vec![],
281 }
282 }
283
284 pub fn add_vertex(&mut self, vertex: Vertex) -> usize {
285 self.vertices.push(vertex);
286 self.vertices.len() - 1
287 }
288
289 pub fn add_edge(&mut self, from: usize, to: usize) {
290 self.edges.push(Edge { from, to });
291 }
292
293 pub fn contains(&self, name: &str) -> bool {
294 self.vertices.iter().any(|x| x.name == name)
295 }
296
297 pub fn install_all(&self) -> Result<(), Error> {
298 let mut visited = vec![false; self.vertices.len()];
299
300 for (index, vertex) in self.vertices.iter().enumerate() {
301 if !visited[index] {
302 self.install(vertex.clone().into(), &mut visited)?;
303 }
304 }
305
306 Ok(())
307 }
308
309 pub fn install(
310 &self,
311 package: Box<dyn Installer>,
312 visited: &mut Vec<bool>,
313 ) -> Result<(), Error> {
314 let index = self
315 .vertices
316 .iter()
317 .position(|x| x.name == package.name())
318 .unwrap();
319
320 if visited[index] {
321 return Ok(());
322 }
323
324 for edge in self.edges.iter().filter(|x| x.from == index) {
325 self.install(self.vertices[edge.to].clone().into(), visited)?;
326 }
327
328 package.install()?;
329
330 visited[index] = true;
331
332 Ok(())
333 }
334
335 pub fn size(&self) -> usize {
336 self.vertices.len()
337 }
338}