Skip to content

Commit

Permalink
feat: experimental GC (#130)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kha authored Jun 18, 2024
1 parent 891c4ef commit 0531a0e
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 4 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
* The `update-hashes/` directory is not used anymore, deleting `toolchains/` or direct
subdirectories does not break elan anymore.
- More useful download and installation info messages
- Experimental `elan toolchain gc` command. See `elan toolchain gc --help` for documentation.
`lean-toolchain` files will only be known to the GC after being used at least once with this
version of elan.

# 3.1.1 - 2024-02-22

Expand Down
31 changes: 29 additions & 2 deletions src/elan-cli/elan_mode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand};
use common;
use elan::{command, lookup_toolchain_desc, Cfg, Toolchain};
use elan::{command, gc, lookup_toolchain_desc, Cfg, Toolchain};
use elan_dist::dist::ToolchainDesc;
use elan_utils::utils;
use errors::*;
Expand Down Expand Up @@ -30,6 +30,7 @@ pub fn main() -> Result<()> {
("list", Some(_)) => list_toolchains(cfg)?,
("link", Some(m)) => toolchain_link(cfg, m)?,
("uninstall", Some(m)) => toolchain_remove(cfg, m)?,
("gc", Some(m)) => toolchain_gc(cfg, m)?,
(_, _) => unreachable!(),
},
("override", Some(c)) => match c.subcommand() {
Expand Down Expand Up @@ -126,7 +127,13 @@ pub fn cli() -> App<'static, 'static> {
.help(TOOLCHAIN_ARG_HELP)
.required(true))
.arg(Arg::with_name("path")
.required(true))))
.required(true)))
.subcommand(SubCommand::with_name("gc")
.about("Garbage-collect toolchains not used by any known project")
.after_help(TOOLCHAIN_GC_HELP)
.arg(Arg::with_name("delete")
.long("delete")
.help("Delete collected toolchains instead of only reporting them"))))
.subcommand(SubCommand::with_name("override")
.about("Modify directory toolchain overrides")
.after_help(OVERRIDE_HELP)
Expand Down Expand Up @@ -418,6 +425,26 @@ fn toolchain_remove(cfg: &Cfg, m: &ArgMatches) -> Result<()> {
Ok(())
}

fn toolchain_gc(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> {
let toolchains = gc::get_unreachable_toolchains(cfg)?;
if toolchains.is_empty() {
println!("No unused toolchains found");
return Ok(())
}
let delete = m.is_present("delete");
if !delete {
println!("The following toolchains are not used by any known project; rerun with `--delete` to delete them:");
}
for t in toolchains.into_iter() {
if delete {
t.remove()?;
} else {
println!("- {}", t.path().display());
}
}
Ok(())
}

fn override_add(cfg: &Cfg, m: &ArgMatches) -> Result<()> {
let ref toolchain = m.value_of("toolchain").expect("");
let desc = lookup_toolchain_desc(cfg, toolchain)?;
Expand Down
11 changes: 11 additions & 0 deletions src/elan-cli/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ pub static TOOLCHAIN_LINK_HELP: &'static str = r"DISCUSSION:
If you now compile a crate in the current directory, the custom
toolchain 'master' will be used.";

pub static TOOLCHAIN_GC_HELP: &'static str = r"DISCUSSION:
Experimental. A toolchain is classified as 'in use' if
* it is the default toolchain,
* it is registered as an override, or
* there is a directory with a `lean-toolchain` file referencing the
toolchain and elan has been used in the directory before.
For safety reasons, the command currently requires passing `--delete`
to actually remove toolchains but this may be relaxed in the future
when the implementation is deemed stable.";

pub static OVERRIDE_HELP: &'static str = r"DISCUSSION:
Overrides configure elan to use a specific toolchain when
running in a specific directory.
Expand Down
11 changes: 9 additions & 2 deletions src/elan/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ use elan_dist::dist::ToolchainDesc;
use elan_dist::temp;
use elan_utils::utils;
use errors::*;
use itertools::Itertools;
use notifications::*;
use settings::{Settings, SettingsFile};
use toolchain::Toolchain;

use toml;

use crate::lookup_toolchain_desc;
use crate::{gc, lookup_toolchain_desc};

#[derive(Debug)]
pub enum OverrideReason {
Expand Down Expand Up @@ -238,6 +239,7 @@ impl Cfg {
let toolchain_name = s.trim();
let desc = lookup_toolchain_desc(&self, toolchain_name)?;
let reason = OverrideReason::ToolchainFile(toolchain_file);
gc::add_root(self, d)?;
return Ok(Some((desc, reason)));
}
}
Expand Down Expand Up @@ -297,6 +299,12 @@ impl Cfg {
)
}

pub fn get_overrides(&self) -> Result<Vec<(String, ToolchainDesc)>> {
self.settings_file.with(|s| {
Ok(s.overrides.clone().into_iter().collect_vec())
})
}

pub fn list_toolchains(&self) -> Result<Vec<ToolchainDesc>> {
if utils::is_directory(&self.toolchains_dir) {
let mut toolchains: Vec<_> = utils::read_dir("toolchains", &self.toolchains_dir)?
Expand All @@ -311,7 +319,6 @@ impl Cfg {

utils::toolchain_sort(&mut toolchains);

// ignore legacy toolchains in non-resolved format
let toolchains: Vec<_> = toolchains.iter().flat_map(|s| ToolchainDesc::from_resolved_str(&s)).collect();
Ok(toolchains)
} else {
Expand Down
57 changes: 57 additions & 0 deletions src/elan/gc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::{collections::HashSet, path::{Path, PathBuf}};

use itertools::Itertools;

use crate::{lookup_toolchain_desc, Cfg, Toolchain};

fn get_root_file(cfg: &Cfg) -> PathBuf {
cfg.elan_dir.join("known-projects")
}

fn get_roots(cfg: &Cfg) -> elan_utils::Result<Vec<String>> {
let path = get_root_file(cfg);
if path.exists() {
let roots = std::fs::read_to_string(&path)?;
Ok(roots.split("\n").map(|s| s.to_string()).collect_vec())
} else {
Ok(vec![])
}
}

pub fn add_root(cfg: &Cfg, root: &Path) -> elan_utils::Result<()> {
let path = get_root_file(cfg);
let mut roots = get_roots(cfg)?;
let root = root.to_str().unwrap().to_string();
if !roots.contains(&root) {
roots.push(root);
let roots = roots.join("\n");
std::fs::write(path, roots)?;
}
Ok(())
}

pub fn get_unreachable_toolchains(cfg: &Cfg) -> crate::Result<Vec<Toolchain>> {
let roots = get_roots(cfg)?;
let mut used_toolchains = roots.into_iter().filter_map(|r| {
let path = PathBuf::from(r).join("lean-toolchain");
if path.exists() {
Some(std::fs::read_to_string(path).unwrap().trim().to_string())
} else {
None
}
}).collect::<HashSet<_>>();
if let Some(default) = cfg.get_default()? {
let default = lookup_toolchain_desc(cfg, &default)?;
used_toolchains.insert(default.to_string());
}
if let Some(ref env_override) = cfg.env_override {
used_toolchains.insert(env_override.clone());
}
for o in cfg.get_overrides()? {
used_toolchains.insert(o.1.to_string());
}
Ok(cfg.list_toolchains()?.into_iter()
.map(|t| Toolchain::from(cfg, &t))
.filter(|t| !t.is_custom() && !used_toolchains.contains(&t.desc.to_string()))
.collect_vec())
}
1 change: 1 addition & 0 deletions src/elan/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ pub mod install;
mod notifications;
pub mod settings;
mod toolchain;
pub mod gc;

0 comments on commit 0531a0e

Please sign in to comment.