tidalcycles_rs/
install.rs1use std::process::Command;
2use std::fs;
3use std::cmp::Ordering;
4
5pub fn ensure_supercollider_installed() -> Option<std::path::PathBuf> {
6
7 if let Ok(path) = which::which("sclang") {
9 return Some(path);
10 }
11
12 let program_files = std::env::var("ProgramFiles").unwrap_or_else(|_| r"C:\Program Files".to_string());
14 let sc_prefix = "SuperCollider-";
15 let mut latest_version: Option<(semver::Version, std::path::PathBuf)> = None;
16
17 if let Ok(entries) = fs::read_dir(&program_files) {
18 for entry in entries.flatten() {
19 let file_name = entry.file_name();
20 let file_name = file_name.to_string_lossy();
21 if file_name.starts_with(sc_prefix) {
22 let version_str = &file_name[sc_prefix.len()..];
23 if let Ok(version) = semver::Version::parse(version_str) {
24 let exe_path = entry.path().join("sclang.exe");
25 if exe_path.exists() {
26 match &latest_version {
27 Some((latest, _)) if version.cmp(latest) == Ordering::Greater => {
28 latest_version = Some((version, exe_path));
29 }
30 None => {
31 latest_version = Some((version, exe_path));
32 }
33 _ => {}
34 }
35 }
36 }
37 }
38 }
39 }
40
41 if let Some((_, path)) = latest_version {
42 return Some(path);
43 }
44
45 let status = Command::new("powershell")
47 .args([
48 "-Command",
49 "Start-Process winget -ArgumentList 'install --id=SuperCollider.SuperCollider -e --accept-source-agreements --accept-package-agreements' -Verb RunAs -Wait",
50 ])
51 .status();
52
53 match status {
54 Ok(s) if s.success() => {
55 if let Ok(path) = which::which("sclang") {
57 Some(path)
58 } else {
59 let mut latest_version: Option<(semver::Version, std::path::PathBuf)> = None;
61 if let Ok(entries) = fs::read_dir(&program_files) {
62 for entry in entries.flatten() {
63 let file_name = entry.file_name();
64 let file_name = file_name.to_string_lossy();
65 if file_name.starts_with(sc_prefix) {
66 let version_str = &file_name[sc_prefix.len()..];
67 if let Ok(version) = semver::Version::parse(version_str) {
68 let exe_path = entry.path().join("sclang.exe");
69 if exe_path.exists() {
70 match &latest_version {
71 Some((latest, _)) if version.cmp(latest) == Ordering::Greater => {
72 latest_version = Some((version, exe_path));
73 }
74 None => {
75 latest_version = Some((version, exe_path));
76 }
77 _ => {}
78 }
79 }
80 }
81 }
82 }
83 }
84 latest_version.map(|(_, path)| path)
85 }
86 }
87 _ => None,
88 }
89}
90
91
92pub fn ensure_ghcup_installed() -> Option<std::path::PathBuf> {
93 if let Ok(path) = which::which("ghcup") {
94 Some(path)
95 } else if std::path::Path::new(r"C:\ghcup\bin\ghcup.exe").exists() {
96 Some(std::path::PathBuf::from(r"C:\ghcup\bin\ghcup.exe"))
97 } else {
98 let ps_script = r#"Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; & ([ScriptBlock]::Create((Invoke-WebRequest https://www.haskell.org/ghcup/sh/bootstrap-haskell.ps1 -UseBasicParsing))) -Interactive -DisableCurl"#;
99 let status = Command::new("powershell")
100 .args([
101 "-Command",
102 &format!(
103 "Start-Process powershell -Verb runAs -ArgumentList '{}'",
104 ps_script.replace("'", "''")
105 ),
106 ])
107 .status();
108 match status {
109 Ok(s) if s.success() => {
110 if let Ok(path) = which::which("ghcup") {
111 Some(path)
112 } else if std::path::Path::new(r"C:\ghcup\bin\ghcup.exe").exists() {
113 Some(std::path::PathBuf::from(r"C:\ghcup\bin\ghcup.exe"))
114 } else {
115 None
116 }
117 },
118 _ => None,
119 }
120 }
121}
122
123pub fn ensure_cabal_installed() -> Option<std::path::PathBuf> {
124 if let Ok(path) = which::which("cabal") {
125 Some(path)
126 } else if std::path::Path::new(r"C:\ghcup\bin\cabal.exe").exists() {
127 Some(std::path::PathBuf::from(r"C:\ghcup\bin\cabal.exe"))
128 } else {
129 let _ = ensure_ghcup_installed();
131 if let Ok(path) = which::which("cabal") {
132 Some(path)
133 } else if std::path::Path::new(r"C:\ghcup\bin\cabal.exe").exists() {
134 Some(std::path::PathBuf::from(r"C:\ghcup\bin\cabal.exe"))
135 } else {
136 None
137 }
138 }
139}