Skip to content

Commit 4ee4ecc

Browse files
author
Eric Swanson
authored
feat: clean up previous dfx installation (#58)
1 parent ea1e0a1 commit 4ee4ecc

15 files changed

Lines changed: 411 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
## [Unreleased] - ReleaseDate
1111

12-
- Removed openssl dependencies
12+
- Removed openssl dependencies.
1313
- Added `dfxvm self uninstall` command, which uninstalls dfxvm and all versions of dfx.
14+
- `dfxvm-init` now removes older dfx versions found on the path, by default.
15+
- `dfxvm-init` deletes the uninstall.sh script that the dfx install script used to create.
1416

1517
## [0.2.0] - 2024-01-30
1618

17-
- `dfxvm --version` now reports the version
18-
- Changed the dfxvm-init `--proceed` parameter to `--yes`
19-
- Static link to openssl
19+
- `dfxvm --version` now reports the version.
20+
- Changed the dfxvm-init `--proceed` parameter to `--yes`.
21+
- Static link to openssl.
2022

2123
## [0.1.3] - 2024-01-19
2224

src/dfxvm/self_uninstall.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub fn self_uninstall(yes: bool, locations: &Locations) -> Result<(), SelfUninst
2929

3030
killall_dfx(locations);
3131
delete_dir(&locations.network_dir())?;
32-
delete_dir(locations.dfx_cache_dir())?;
32+
delete_dir(locations.dfinity_cache_dir())?;
3333
delete_dir(locations.versions_dir())?;
3434
delete_file(&locations.dfx_proxy_path())?;
3535
uninstall_from_profile_scripts()?;
@@ -78,14 +78,14 @@ fn killall_dfx(locations: &Locations) {
7878

7979
fn killany_dfx(locations: &Locations) -> bool {
8080
let versions_dir = locations.versions_dir();
81-
let dfx_cache_dir = locations.dfx_cache_dir();
81+
let dfinity_cache_versions_dir = locations.dfinity_cache_versions_dir();
8282

8383
let mut info = System::new();
8484
info.refresh_processes();
8585
let mut n = 0;
8686
for (pid, proc) in info.processes() {
8787
if let Some(exe) = proc.exe() {
88-
if exe.starts_with(versions_dir) || exe.starts_with(dfx_cache_dir) {
88+
if exe.starts_with(versions_dir) || exe.starts_with(&dfinity_cache_versions_dir) {
8989
info!("killing {} {}", pid.as_u32(), exe.display());
9090
n += 1;
9191
proc.kill();

src/dfxvm_init/initialize.rs

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,28 @@ use crate::dfxvm;
22
use crate::dfxvm_init::{
33
plan::{DfxVersion, Plan, PlanOptions},
44
ui,
5-
ui::Confirmation,
5+
ui::{select_deletion_strategy, Confirmation, DeletionStrategy},
66
};
77
use crate::error::{
88
dfxvm_init,
9-
dfxvm_init::{ExecutePlanError, UpdateProfileScriptsError},
10-
fs::WriteFileError,
9+
dfxvm_init::{
10+
DeleteDfxOnPathError, DeleteDfxOnPathError::CallSudoRm, ExecutePlanError,
11+
UpdateProfileScriptsError,
12+
},
13+
fs::{RemoveFileError, WriteFileError},
1114
};
12-
use crate::fs::{append_to_file, create_dir_all, read_to_string};
15+
use crate::fs::{append_to_file, create_dir_all, read_to_string, remove_file};
1316
use crate::installation::{env_file_contents, install_binaries, ProfileScript};
1417
use crate::locations::Locations;
15-
use std::path::Path;
18+
use std::path::{Path, PathBuf};
19+
use std::process::Command;
1620

1721
pub async fn initialize(
1822
options: PlanOptions,
1923
confirmation: Option<Confirmation>,
2024
locations: &Locations,
2125
) -> Result<(), dfxvm_init::Error> {
22-
let mut plan = Plan::new(options, locations);
26+
let mut plan = Plan::new(options, locations)?;
2327

2428
ui::display::introduction(&plan);
2529

@@ -46,6 +50,11 @@ pub async fn initialize(
4650
}
4751

4852
pub async fn execute(plan: &Plan, locations: &Locations) -> Result<(), ExecutePlanError> {
53+
remove_uninstall_script(locations)?;
54+
if plan.options.delete_dfx_on_path {
55+
delete_dfx_on_path(plan)?;
56+
}
57+
4958
create_dir_all(&plan.bin_dir)?;
5059

5160
create_env_file(&plan.env_path)?;
@@ -64,6 +73,60 @@ pub async fn execute(plan: &Plan, locations: &Locations) -> Result<(), ExecutePl
6473
Ok(())
6574
}
6675

76+
fn remove_uninstall_script(locations: &Locations) -> Result<(), RemoveFileError> {
77+
let path = locations.dfinity_cache_dir().join("uninstall.sh");
78+
if path.exists() {
79+
remove_file(&path)?;
80+
info!("deleted: {}", path.display());
81+
}
82+
Ok(())
83+
}
84+
85+
fn delete_dfx_on_path(plan: &Plan) -> Result<(), DeleteDfxOnPathError> {
86+
loop {
87+
let remaining = delete_and_filter(&plan.dfx_on_path);
88+
89+
if remaining.is_empty() {
90+
break Ok(());
91+
}
92+
93+
ui::display::need_to_delete_old_dfx(plan);
94+
95+
match select_deletion_strategy()? {
96+
DeletionStrategy::Manual => {}
97+
DeletionStrategy::CallSudo => sudo_rm(remaining)?,
98+
DeletionStrategy::DontDelete => break Ok(()),
99+
}
100+
}
101+
}
102+
103+
fn delete_and_filter(dfx_on_path: &[PathBuf]) -> Vec<PathBuf> {
104+
dfx_on_path
105+
.iter()
106+
.filter(|p| {
107+
if !p.exists() {
108+
false
109+
} else if remove_file(p).is_ok() {
110+
info!("deleted: {}", p.display());
111+
false
112+
} else {
113+
true
114+
}
115+
})
116+
.cloned()
117+
.collect()
118+
}
119+
120+
fn sudo_rm(remaining: Vec<PathBuf>) -> Result<(), DeleteDfxOnPathError> {
121+
let _ = Command::new("sudo")
122+
.arg("rm")
123+
.arg("-f")
124+
.args(remaining.iter().map(|p| (*p).clone().into_os_string()))
125+
.status()
126+
.map_err(CallSudoRm)?;
127+
Ok(())
128+
}
129+
67130
fn create_env_file(path: &Path) -> Result<(), WriteFileError> {
68131
info!("creating {}", path.display());
69132
crate::fs::write(path, env_file_contents())

src/dfxvm_init/plan.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use crate::error::fs::CanonicalizePathError;
2+
use crate::fs::canonicalize;
13
use crate::installation::{get_detected_profile_scripts, get_env_path_user_facing, ProfileScript};
24
use crate::locations::Locations;
35
use semver::Version;
4-
use std::path::PathBuf;
6+
use std::path::{Path, PathBuf};
57

68
#[derive(Clone)]
79
pub enum DfxVersion {
@@ -13,13 +15,15 @@ pub enum DfxVersion {
1315
pub struct PlanOptions {
1416
pub dfx_version: DfxVersion,
1517
pub modify_path: bool,
18+
pub delete_dfx_on_path: bool,
1619
}
1720

1821
impl PlanOptions {
1922
pub fn new() -> Self {
2023
Self {
2124
dfx_version: DfxVersion::Latest,
2225
modify_path: true,
26+
delete_dfx_on_path: true,
2327
}
2428
}
2529

@@ -36,6 +40,13 @@ impl PlanOptions {
3640
..self
3741
}
3842
}
43+
44+
pub fn delete_dfx_on_path(self, delete_dfx_on_path: bool) -> Self {
45+
Self {
46+
delete_dfx_on_path,
47+
..self
48+
}
49+
}
3950
}
4051

4152
pub struct Plan {
@@ -51,25 +62,50 @@ pub struct Plan {
5162
// altering profile scripts.
5263
pub env_path_user_facing: String,
5364

65+
pub dfx_on_path: Vec<PathBuf>,
5466
pub profile_scripts: Vec<ProfileScript>,
5567
}
5668

5769
impl Plan {
58-
pub fn new(options: PlanOptions, locations: &Locations) -> Self {
70+
pub fn new(options: PlanOptions, locations: &Locations) -> Result<Self, CanonicalizePathError> {
5971
let bin_dir = locations.bin_dir();
6072
let env_path = locations.data_local_dir().join("env");
6173
let env_path_user_facing = get_env_path_user_facing().to_string();
6274
let profile_scripts = get_detected_profile_scripts();
63-
Self {
75+
let dfx_on_path = legacy_binaries(&locations.dfx_proxy_path())?;
76+
77+
Ok(Self {
6478
options,
6579
bin_dir,
6680
env_path,
6781
env_path_user_facing,
82+
dfx_on_path,
6883
profile_scripts,
69-
}
84+
})
7085
}
7186

7287
pub fn with_options(self, options: PlanOptions) -> Self {
7388
Self { options, ..self }
7489
}
7590
}
91+
92+
// find dfx binaries on PATH, excluding the dfx proxy
93+
fn legacy_binaries(dfx_proxy: &Path) -> Result<Vec<PathBuf>, CanonicalizePathError> {
94+
let all_dfx_on_path = std::env::split_paths(&std::env::var_os("PATH").unwrap_or_default())
95+
.filter_map(|dir| {
96+
let dfx_on_path = dir.join("dfx");
97+
dfx_on_path.exists().then(|| canonicalize(&dfx_on_path))
98+
})
99+
.collect::<Result<Vec<_>, _>>()?;
100+
101+
let legacy_binaries = if dfx_proxy.exists() {
102+
let canonical_dfx_proxy = canonicalize(dfx_proxy)?;
103+
all_dfx_on_path
104+
.into_iter()
105+
.filter(|p| *p != canonical_dfx_proxy)
106+
.collect()
107+
} else {
108+
all_dfx_on_path
109+
};
110+
Ok(legacy_binaries)
111+
}

src/dfxvm_init/ui.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
mod confirm;
22
mod customize;
3+
mod deletion_strategy;
34
pub mod display;
45

56
pub use confirm::confirm;
67
pub use confirm::Confirmation;
78
pub use customize::customize;
9+
pub use deletion_strategy::{select_deletion_strategy, DeletionStrategy};

src/dfxvm_init/ui/customize.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ pub fn customize(plan: Plan) -> Result<Plan, InteractError> {
1414
let dfx_version = select_dfx_version(&options.dfx_version)?;
1515
options = options.with_dfx_version(dfx_version);
1616

17+
if !plan.dfx_on_path.is_empty() {
18+
let delete_dfx_on_path = delete_dfx_on_path(options.delete_dfx_on_path)?;
19+
options = options.delete_dfx_on_path(delete_dfx_on_path);
20+
}
21+
1722
let modify_path = select_modify_path(options.modify_path)?;
1823
options = options.with_modify_path(modify_path);
1924

@@ -49,6 +54,14 @@ fn select_dfx_version(install_dfx: &DfxVersion) -> Result<DfxVersion, InteractEr
4954
Ok(dfx_version)
5055
}
5156

57+
fn delete_dfx_on_path(current: bool) -> Result<bool, InteractError> {
58+
let modify = Confirm::new()
59+
.with_prompt("Delete dfx binaries found on PATH?")
60+
.default(current)
61+
.interact()?;
62+
Ok(modify)
63+
}
64+
5265
fn select_modify_path(current: bool) -> Result<bool, InteractError> {
5366
let modify = Confirm::new()
5467
.with_prompt("Modify PATH variable?")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use crate::error::dfxvm_init::InteractError;
2+
use dialoguer::Select;
3+
4+
pub enum DeletionStrategy {
5+
Manual,
6+
CallSudo,
7+
DontDelete,
8+
}
9+
10+
pub fn select_deletion_strategy() -> Result<DeletionStrategy, InteractError> {
11+
let items = vec![
12+
"I've deleted them manually (default)",
13+
"Call sudo rm for me (I'll enter my password)",
14+
"Don't delete anything. I'll do it later",
15+
];
16+
17+
let index = Select::new()
18+
.with_prompt("How would you like to proceed?")
19+
.default(0)
20+
.items(&items)
21+
.interact()?;
22+
23+
let deletion_strategy = match index {
24+
0 => DeletionStrategy::Manual,
25+
1 => DeletionStrategy::CallSudo,
26+
2 => DeletionStrategy::DontDelete,
27+
_ => unreachable!(),
28+
};
29+
30+
Ok(deletion_strategy)
31+
}

0 commit comments

Comments
 (0)