1use std::fs;
2use std::fs::DirEntry;
3use std::ops::Deref;
4use std::process::Command;
5use std::time::Duration;
6
7use anyhow::Result;
8use anyhow::anyhow;
9use regex::Regex;
10use reqwest::blocking::Client;
11use semver::Op;
12use semver::Version as SemVersion;
13use semver::VersionReq;
14use serde::{Deserialize, Serialize};
15use which::which;
16
17use super::Dir;
18use super::ToolchainFilter;
19use super::consts;
20
21const HTTP_TIMEOUT: Duration = Duration::from_secs(10);
22
23#[derive(Serialize, Deserialize, Debug)]
24pub struct GoFile {
25 pub arch: String,
26 pub filename: String,
27 pub kind: String,
28 pub os: String,
29 pub sha256: String,
30 pub size: isize,
31 pub version: String,
32}
33
34#[derive(Serialize, Deserialize, Debug)]
35struct GoRelease {
36 pub version: String,
37 pub stable: bool,
38 }
40
41#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
42pub struct Version {
43 pub version: String,
45 pub active: bool,
47}
48
49impl Version {
50 pub fn init_env(s: &str) -> Result<(), anyhow::Error> {
52 let goup_home = Dir::goup_home()?;
53 if !goup_home.exists() {
54 fs::create_dir_all(&goup_home)?;
55 }
56 let env_file = goup_home.env();
57 fs::write(env_file, s)?;
58 Ok(())
59 }
60 pub fn list_upstream_go_versions_filter(
61 host: &str,
62 filter: Option<ToolchainFilter>,
63 ) -> Result<Vec<String>, anyhow::Error> {
64 let ver = Self::list_upstream_go_versions(host)?;
65 let re = filter.map_or_else(
66 || "(.+)".to_owned(),
67 |f| match f {
68 ToolchainFilter::Stable => {
69 r#"(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))?\b"#.to_string()
70 }
71 ToolchainFilter::Unstable => {
72 r#"(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))?(?:rc(?:0|[1-9]\d*))"#
73 .to_string()
74 }
75 ToolchainFilter::Beta => {
76 r#"(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))?(?:beta(?:0|[1-9]\d*))"#
77 .to_string()
78 }
79 ToolchainFilter::Filter(s) => format!("(.*{s}.*)"),
80 },
81 );
82 let re = Regex::new(&re)?;
83 Ok(ver
84 .into_iter()
85 .filter_map(|v| re.is_match(&v).then_some(v))
86 .collect())
87 }
88
89 pub fn list_upstream_go_versions(host: &str) -> Result<Vec<String>, anyhow::Error> {
91 Self::list_upstream_go_versions_from_http(host).or_else(|e| {
92 which("git").map_or_else(|_| Err(e), |_| Self::list_upstream_go_versions_from_git())
93 })
94 }
95 fn list_upstream_go_versions_from_http(host: &str) -> Result<Vec<String>, anyhow::Error> {
97 Ok(Client::builder()
98 .timeout(HTTP_TIMEOUT)
99 .build()?
100 .get(format!("{host}/dl/?mode=json&include=all"))
101 .send()?
102 .json::<Vec<GoRelease>>()?
103 .into_iter()
104 .map(|v| v.version.trim_start_matches("go").to_string())
105 .rev()
106 .collect())
107 }
108 fn list_upstream_go_versions_from_git() -> Result<Vec<String>, anyhow::Error> {
110 let output = Command::new("git")
111 .args([
112 "ls-remote",
113 "--sort=version:refname",
114 "--tags",
115 &consts::go_source_git_url(),
116 ])
117 .output()?
118 .stdout;
119 Ok(Regex::new("refs/tags/go(.+)")?
120 .captures_iter(&String::from_utf8_lossy(&output))
121 .map(|capture| capture[1].to_string())
122 .collect())
123 }
124 pub fn match_version_req(host: &str, ver_pattern: &str) -> Result<String, anyhow::Error> {
125 log::debug!("version request pattern: {ver_pattern}");
126 let ver_req = VersionReq::parse(ver_pattern)?;
127 if ver_req.comparators.iter().all(|v| v.op == Op::Exact) {
129 return Ok(ver_pattern.trim_start_matches('=').to_owned());
130 }
131 for ver in Self::list_upstream_go_versions(host)?.iter().rev() {
132 if ver_req.matches(&Self::semantic(ver)?) {
133 return Ok(ver.to_owned());
134 }
135 }
136 Err(anyhow!("not any match version!"))
137 }
138
139 pub fn get_upstream_latest_go_version(host: &str) -> Result<String, anyhow::Error> {
141 let body = Client::builder()
142 .timeout(HTTP_TIMEOUT)
143 .build()?
144 .get(format!("{host}/VERSION?m=text"))
145 .send()?
146 .text()?;
147 body.split('\n')
148 .nth(0)
149 .ok_or_else(|| anyhow!("Getting latest Go version failed"))
150 .map(|v| v.to_owned())
151 }
152 pub fn list_go_version() -> Result<Vec<Version>, anyhow::Error> {
154 let goup_home = Dir::goup_home()?;
155 if !goup_home.exists() {
157 return Ok(Vec::new());
158 }
159
160 let current = goup_home.current().read_link();
162 let current = current.as_ref();
163 let dir: Result<Vec<DirEntry>, _> = goup_home.read_dir()?.collect();
164 let mut version_dirs: Vec<_> = dir?
165 .iter()
166 .filter_map(|v| {
167 if !v.path().is_dir() {
168 return None;
169 }
170
171 let ver = v.file_name().to_string_lossy().to_string();
172 if ver != "gotip" && !goup_home.is_dot_unpacked_success_file_exists(&ver) {
173 return None;
174 }
175 Some(Version {
176 version: ver.trim_start_matches("go").into(),
177 active: current.is_ok_and(|vv| vv == goup_home.version_go(ver).deref()),
178 })
179 })
180 .collect();
181 version_dirs.sort();
182 Ok(version_dirs)
183 }
184
185 pub fn set_go_version(version: &str) -> Result<(), anyhow::Error> {
187 let version = Self::normalize(version);
188 let goup_home = Dir::goup_home()?;
189 let original = goup_home.version_go(&version);
190 if !original.exists() {
191 return Err(anyhow!(
192 "Go version {version} is not installed. Install it with `goup install`."
193 ));
194 }
195 let link = goup_home.current();
196 let _ = fs::remove_dir_all(&link);
197 #[cfg(unix)]
198 {
199 use std::os::unix::fs as unix_fs;
200 unix_fs::symlink(original, &link)?;
201 }
202 #[cfg(windows)]
203 {
204 junction::create(original, &link)?;
205 }
206 log::info!("Default Go is set to '{version}'");
207 Ok(())
208 }
209 pub fn remove_go_version(version: &str) -> Result<(), anyhow::Error> {
211 let version = Self::normalize(version);
212 let cur = Self::current_go_version()?;
213 if Some(&version) == cur.as_ref() {
214 log::warn!("{version} is current active version, ignore deletion!");
215 } else {
216 let version_dir = Dir::goup_home()?.version(version);
217 if version_dir.exists() {
218 fs::remove_dir_all(&version_dir)?;
219 }
220 }
221 Ok(())
222 }
223
224 pub fn remove_go_versions(vers: &[&str]) -> Result<(), anyhow::Error> {
226 if !vers.is_empty() {
227 let goup_home = Dir::goup_home()?;
228 let cur = Self::current_go_version()?;
229 for ver in vers {
230 let version = Self::normalize(ver);
231 if Some(&version) == cur.as_ref() {
232 log::warn!("{ver} is current active version, ignore deletion!");
233 continue;
234 }
235 let version_dir = goup_home.version(&version);
236 if version_dir.exists() {
237 fs::remove_dir_all(&version_dir)?;
238 }
239 }
240 }
241 Ok(())
242 }
243
244 pub fn current_go_version() -> Result<Option<String>, anyhow::Error> {
246 let current = Dir::goup_home()?.current().read_link().ok().and_then(|p| {
248 p.parent()
249 .and_then(|v| v.file_name().map(|vv| vv.to_string_lossy().to_string()))
250 });
251 Ok(current)
252 }
253
254 pub fn list_cache(contain_sha256: Option<bool>) -> Result<Vec<String>, anyhow::Error> {
256 let goup_home = Dir::goup_home()?;
257 if !goup_home.exists() || !goup_home.cache().exists() {
259 return Ok(Vec::new());
260 }
261 let contain_sha256 = contain_sha256.unwrap_or_default();
262 let dir: Result<Vec<DirEntry>, _> = goup_home.cache().read_dir()?.collect();
263 let mut archive_files: Vec<_> = dir?
264 .iter()
265 .filter_map(|v| {
266 if v.path().is_dir() {
267 return None;
268 }
269 let filename = v.file_name();
270 let filename = filename.to_string_lossy();
271 (contain_sha256 || !filename.ends_with(".sha256")).then(|| filename.to_string())
272 })
273 .collect();
274 archive_files.sort();
275 Ok(archive_files)
276 }
277
278 pub fn remove_cache() -> Result<(), anyhow::Error> {
280 let dl_dir = Dir::goup_home()?.cache();
281 if dl_dir.exists() {
282 fs::remove_dir_all(&dl_dir)?;
283 }
284 Ok(())
285 }
286
287 pub fn remove_goup_home() -> Result<(), anyhow::Error> {
289 let goup_home_dir = Dir::goup_home()?;
290 if goup_home_dir.exists() {
291 fs::remove_dir_all(&goup_home_dir)?;
292 }
293 Ok(())
294 }
295
296 pub fn normalize(ver: &str) -> String {
302 if ver.starts_with("go") {
303 ver.to_string()
304 } else {
305 format!("go{ver}")
306 }
307 }
308 pub fn semantic(ver: &str) -> Result<SemVersion> {
317 let count_dot = |name: &str| name.chars().filter(|&v| v == '.').count();
318 let name = ver
319 .find("alpha")
320 .or_else(|| ver.find("beta"))
321 .or_else(|| ver.find("rc"))
322 .map_or_else(
323 || match count_dot(ver) {
324 0 => format!("{ver}.0.0"),
325 1 => format!("{ver}.0"),
326 _ => ver.to_string(),
327 },
328 |idx| {
329 let start = &ver[..idx].trim_end_matches('-');
330 if count_dot(start) == 2 {
331 format!("{}-{}", start, &ver[idx..])
332 } else {
333 format!("{}.0-{}", start, &ver[idx..])
334 }
335 },
336 );
337 Ok(SemVersion::parse(&name)?)
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::Version;
344 use semver::Version as SemVersion;
345
346 #[test]
347 fn test_normalize() {
348 assert_eq!(Version::normalize("1.21.1"), "go1.21.1",);
349 assert_eq!(Version::normalize("go1.21.1"), "go1.21.1",);
350 assert_eq!(Version::normalize("tip"), "gotip",);
351 assert_eq!(Version::normalize("gotip"), "gotip",);
352 }
353
354 #[test]
355 fn test_semantic() {
356 assert_eq!(
357 Version::semantic("1").unwrap(),
358 "1.0.0".parse::<SemVersion>().unwrap(),
359 );
360 assert_eq!(
361 Version::semantic("1.21").unwrap(),
362 "1.21.0".parse::<SemVersion>().unwrap(),
363 );
364 assert_eq!(
365 Version::semantic("1.21rc2").unwrap(),
366 "1.21.0-rc2".parse::<SemVersion>().unwrap(),
367 );
368 assert_eq!(
369 Version::semantic("1.21.1rc2").unwrap(),
370 "1.21.1-rc2".parse::<SemVersion>().unwrap(),
371 );
372 assert_eq!(
373 Version::semantic("1.21-rc2").unwrap(),
374 "1.21.0-rc2".parse::<SemVersion>().unwrap(),
375 );
376 assert_eq!(
377 Version::semantic("1.21.1-rc2").unwrap(),
378 "1.21.1-rc2".parse::<SemVersion>().unwrap(),
379 );
380 assert_eq!(
381 Version::semantic("1.21.1").unwrap(),
382 "1.21.1".parse::<SemVersion>().unwrap(),
383 );
384 }
385
386 #[test]
387 fn test_all_go_version_semantic() {
388 let go_versions = [
389 "1",
390 "1.2.2",
391 "1.3rc1",
392 "1.3rc2",
393 "1.3",
394 "1.3.1",
395 "1.3.2",
396 "1.3.3",
397 "1.4beta1",
398 "1.4rc1",
399 "1.4rc2",
400 "1.4",
401 "1.4.1",
402 "1.4.2",
403 "1.4.3",
404 "1.5beta1",
405 "1.5beta2",
406 "1.5beta3",
407 "1.5rc1",
408 "1.5",
409 "1.5.1",
410 "1.5.2",
411 "1.5.3",
412 "1.5.4",
413 "1.6beta1",
414 "1.6beta2",
415 "1.6rc1",
416 "1.6rc2",
417 "1.6",
418 "1.6.1",
419 "1.6.2",
420 "1.6.3",
421 "1.6.4",
422 "1.7beta1",
423 "1.7beta2",
424 "1.7rc1",
425 "1.7rc2",
426 "1.7rc3",
427 "1.7rc4",
428 "1.7rc5",
429 "1.7rc6",
430 "1.7",
431 "1.7.1",
432 "1.7.3",
433 "1.7.4",
434 "1.7.5",
435 "1.7.6",
436 "1.8beta1",
437 "1.8beta2",
438 "1.8rc1",
439 "1.8rc2",
440 "1.8rc3",
441 "1.8",
442 "1.8.1",
443 "1.8.2",
444 "1.8.3",
445 "1.8.4",
446 "1.8.5",
447 "1.8.6",
448 "1.8.7",
449 "1.9beta1",
450 "1.9beta2",
451 "1.9rc1",
452 "1.9rc2",
453 "1.9",
454 "1.9.1",
455 "1.9.2rc2",
456 "1.9.2",
457 "1.9.3",
458 "1.9.4",
459 "1.9.5",
460 "1.9.6",
461 "1.9.7",
462 "1.10beta1",
463 "1.10beta2",
464 "1.10rc1",
465 "1.10rc2",
466 "1.10",
467 "1.10.1",
468 "1.10.2",
469 "1.10.3",
470 "1.10.4",
471 "1.10.5",
472 "1.10.6",
473 "1.10.7",
474 "1.10.8",
475 "1.11beta1",
476 "1.11beta2",
477 "1.11beta3",
478 "1.11rc1",
479 "1.11rc2",
480 "1.11",
481 "1.11.1",
482 "1.11.2",
483 "1.11.3",
484 "1.11.4",
485 "1.11.5",
486 "1.11.6",
487 "1.11.7",
488 "1.11.8",
489 "1.11.9",
490 "1.11.10",
491 "1.11.11",
492 "1.11.12",
493 "1.11.13",
494 "1.12beta1",
495 "1.12beta2",
496 "1.12rc1",
497 "1.12",
498 "1.12.1",
499 "1.12.2",
500 "1.12.3",
501 "1.12.4",
502 "1.12.5",
503 "1.12.6",
504 "1.12.7",
505 "1.12.8",
506 "1.12.9",
507 "1.12.10",
508 "1.12.11",
509 "1.12.12",
510 "1.12.13",
511 "1.12.14",
512 "1.12.15",
513 "1.12.16",
514 "1.12.17",
515 "1.13beta1",
516 "1.13rc1",
517 "1.13rc2",
518 "1.13",
519 "1.13.1",
520 "1.13.2",
521 "1.13.3",
522 "1.13.4",
523 "1.13.5",
524 "1.13.6",
525 "1.13.7",
526 "1.13.8",
527 "1.13.9",
528 "1.13.10",
529 "1.13.11",
530 "1.13.12",
531 "1.13.13",
532 "1.13.14",
533 "1.13.15",
534 "1.14beta1",
535 "1.14rc1",
536 "1.14",
537 "1.14.1",
538 "1.14.2",
539 "1.14.3",
540 "1.14.4",
541 "1.14.5",
542 "1.14.6",
543 "1.14.7",
544 "1.14.8",
545 "1.14.9",
546 "1.14.10",
547 "1.14.11",
548 "1.14.12",
549 "1.14.13",
550 "1.14.14",
551 "1.14.15",
552 "1.15beta1",
553 "1.15rc1",
554 "1.15rc2",
555 "1.15",
556 "1.15.1",
557 "1.15.2",
558 "1.15.3",
559 "1.15.4",
560 "1.15.5",
561 "1.15.6",
562 "1.15.7",
563 "1.15.8",
564 "1.15.9",
565 "1.15.10",
566 "1.15.11",
567 "1.15.12",
568 "1.15.13",
569 "1.15.14",
570 "1.15.15",
571 "1.16beta1",
572 "1.16rc1",
573 "1.16",
574 "1.16.1",
575 "1.16.2",
576 "1.16.3",
577 "1.16.4",
578 "1.16.5",
579 "1.16.6",
580 "1.16.7",
581 "1.16.8",
582 "1.16.9",
583 "1.16.10",
584 "1.16.11",
585 "1.16.12",
586 "1.16.13",
587 "1.16.14",
588 "1.16.15",
589 "1.17beta1",
590 "1.17rc1",
591 "1.17rc2",
592 "1.17",
593 "1.17.1",
594 "1.17.2",
595 "1.17.3",
596 "1.17.4",
597 "1.17.5",
598 "1.17.6",
599 "1.17.7",
600 "1.17.8",
601 "1.17.9",
602 "1.17.10",
603 "1.17.11",
604 "1.17.12",
605 "1.17.13",
606 "1.18beta1",
607 "1.18beta2",
608 "1.18rc1",
609 "1.18",
610 "1.18.1",
611 "1.18.2",
612 "1.18.3",
613 "1.18.4",
614 "1.18.5",
615 "1.18.6",
616 "1.18.7",
617 "1.18.8",
618 "1.18.9",
619 "1.18.10",
620 "1.19beta1",
621 "1.19rc1",
622 "1.19rc2",
623 "1.19",
624 "1.19.1",
625 "1.19.2",
626 "1.19.3",
627 "1.19.4",
628 "1.19.5",
629 "1.19.6",
630 "1.19.7",
631 "1.19.8",
632 "1.19.9",
633 "1.19.10",
634 "1.19.11",
635 "1.19.12",
636 "1.19.13",
637 "1.20rc1",
638 "1.20rc2",
639 "1.20rc3",
640 "1.20",
641 "1.20.1",
642 "1.20.2",
643 "1.20.3",
644 "1.20.4",
645 "1.20.5",
646 "1.20.6",
647 "1.20.7",
648 "1.20.8",
649 "1.20.9",
650 "1.20.10",
651 "1.20.11",
652 "1.20.12",
653 "1.20.13",
654 "1.20.14",
655 "1.21rc2",
656 "1.21rc3",
657 "1.21rc4",
658 "1.21.0",
659 "1.21.1",
660 "1.21.2",
661 "1.21.3",
662 "1.21.4",
663 "1.21.5",
664 "1.21.6",
665 "1.21.7",
666 "1.21.8",
667 "1.21.9",
668 "1.21.10",
669 "1.21.12",
670 "1.21.13",
671 "1.22rc1",
672 "1.22rc2",
673 "1.22.0",
674 "1.22.1",
675 "1.22.2",
676 "1.22.3",
677 "1.22.4",
678 "1.22.5",
679 "1.22.6",
680 "1.22.7",
681 "1.22.8",
682 "1.22.9",
683 "1.22.10",
684 "1.22.11",
685 "1.23rc1",
686 "1.23rc2",
687 "1.23.0",
688 "1.23.1",
689 "1.23.2",
690 "1.23.3",
691 "1.23.4",
692 "1.23.5",
693 "1.23.6",
694 "1.24rc1",
695 "1.24rc2",
696 "1.24rc3",
697 "1.24.0",
698 "1.24.1",
699 "1.24.2",
700 "1.24.3",
701 "1.24.4",
702 "1.25rc1",
703 ];
704 for ver in go_versions {
705 assert!(Version::semantic(ver).is_ok())
706 }
707 }
708}