Skip to content

Commit dc8695a

Browse files
author
Eric Swanson
authored
feat: init mode alters profile scripts to update PATH (#35)
Fixes https://dfinity.atlassian.net/browse/SDK-1275
1 parent 9aed32e commit dc8695a

16 files changed

Lines changed: 893 additions & 17 deletions

File tree

CHANGELOG.md

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

1010
## [Unreleased] - ReleaseDate
1111

12+
- dfxvm-init now alters profile scripts to modify the PATH environment variable.
13+
1214
## [0.1.1] - 2023-12-04
1315

1416
- Added `dfx` mode, which selects a dfx version and dispatches execution to it.

docs/cli-reference/dfxvm-init/dfxvm-init.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dfxvm-init [--dfx-version <version>] [--proceed]
1717
|---------------------------| --- |
1818
| `--dfx-version <version>` | The version of dfx to install. Defaults to the latest dfx version |
1919
| `--proceed` | Skip confirmation prompt |
20+
| `--no-modify-path` | Do not alter profile scripts to modify the PATH environment variable |
2021

2122
## Examples
2223

src/dfxvm_init/cli.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ pub struct Cli {
2121
/// Automatically confirm options and proceed with install.
2222
#[clap(long)]
2323
proceed: bool,
24+
25+
/// Don't configure the PATH environment variable in profile scripts.
26+
#[clap(long)]
27+
no_modify_path: bool,
2428
}
2529

2630
pub async fn main(args: &[OsString]) -> Result<ExitCode, dfxvm_init::Error> {
@@ -34,7 +38,9 @@ pub async fn main(args: &[OsString]) -> Result<ExitCode, dfxvm_init::Error> {
3438

3539
let dfx_version = opts.dfx_version.map_or_else(|| Latest, Specific);
3640

37-
let options = PlanOptions::new().with_dfx_version(dfx_version);
41+
let options = PlanOptions::new()
42+
.with_dfx_version(dfx_version)
43+
.with_modify_path(!opts.no_modify_path);
3844

3945
initialize(options, confirmation).await?;
4046

src/dfxvm_init/initialize.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ use crate::dfxvm_init::{
44
ui,
55
ui::Confirmation,
66
};
7-
use crate::error::{dfxvm_init, dfxvm_init::ExecutePlanError, fs::WriteFileError};
8-
use crate::fs::create_dir_all;
9-
use crate::installation::{env_file_contents, install_binaries};
7+
use crate::error::{
8+
dfxvm_init,
9+
dfxvm_init::{ExecutePlanError, UpdateProfileScriptsError},
10+
fs::WriteFileError,
11+
};
12+
use crate::fs::{append_to_file, create_dir_all, read_to_string};
13+
use crate::installation::{env_file_contents, install_binaries, ProfileScript};
1014
use crate::locations::Locations;
1115
use std::path::Path;
1216

@@ -53,10 +57,44 @@ pub async fn execute(plan: &Plan, locations: &Locations) -> Result<(), ExecutePl
5357
DfxVersion::Specific(version) => dfxvm::set_default(version, locations).await?,
5458
}
5559

60+
if plan.options.modify_path {
61+
update_profile_scripts(&plan.profile_scripts)?;
62+
}
63+
5664
Ok(())
5765
}
5866

5967
fn create_env_file(path: &Path) -> Result<(), WriteFileError> {
6068
info!("creating {}", path.display());
6169
crate::fs::write(path, env_file_contents())
6270
}
71+
72+
fn update_profile_scripts(
73+
profile_scripts: &Vec<ProfileScript>,
74+
) -> Result<(), UpdateProfileScriptsError> {
75+
for profile_script in profile_scripts {
76+
let path = &profile_script.path;
77+
let rc = if path.exists() {
78+
read_to_string(&profile_script.path)?
79+
} else {
80+
"".to_string()
81+
};
82+
83+
let source_command = profile_script.source_string();
84+
if rc.contains(&source_command) {
85+
info!("already updates path: {}", path.display());
86+
continue;
87+
}
88+
89+
info!("updating {}", path.display());
90+
91+
let source_to_append = if rc.ends_with('\n') || rc.is_empty() {
92+
source_command
93+
} else {
94+
format!("\n{}", source_command)
95+
};
96+
97+
append_to_file(&profile_script.path, &source_to_append)?;
98+
}
99+
Ok(())
100+
}

src/dfxvm_init/plan.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::installation::get_env_path_user_facing;
1+
use crate::installation::{get_detected_profile_scripts, get_env_path_user_facing, ProfileScript};
22
use crate::locations::Locations;
33
use semver::Version;
44
use std::path::PathBuf;
@@ -12,17 +12,29 @@ pub enum DfxVersion {
1212
#[derive(Clone)]
1313
pub struct PlanOptions {
1414
pub dfx_version: DfxVersion,
15+
pub modify_path: bool,
1516
}
1617

1718
impl PlanOptions {
1819
pub fn new() -> Self {
1920
Self {
2021
dfx_version: DfxVersion::Latest,
22+
modify_path: true,
2123
}
2224
}
2325

2426
pub fn with_dfx_version(self, dfx_version: DfxVersion) -> Self {
25-
Self { dfx_version }
27+
Self {
28+
dfx_version,
29+
..self
30+
}
31+
}
32+
33+
pub fn with_modify_path(self, modify_path: bool) -> Self {
34+
Self {
35+
modify_path,
36+
..self
37+
}
2638
}
2739
}
2840

@@ -38,18 +50,22 @@ pub struct Plan {
3850
// to use with the "source" command, and also the path that we will use when
3951
// altering profile scripts.
4052
pub env_path_user_facing: String,
53+
54+
pub profile_scripts: Vec<ProfileScript>,
4155
}
4256

4357
impl Plan {
4458
pub fn new(options: PlanOptions, locations: &Locations) -> Self {
4559
let bin_dir = locations.data_local_dir().join("bin");
4660
let env_path = locations.data_local_dir().join("env");
4761
let env_path_user_facing = get_env_path_user_facing().to_string();
62+
let profile_scripts = get_detected_profile_scripts();
4863
Self {
4964
options,
5065
bin_dir,
5166
env_path,
5267
env_path_user_facing,
68+
profile_scripts,
5369
}
5470
}
5571

src/dfxvm_init/ui/customize.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::dfxvm_init::plan::{DfxVersion, Plan};
22
use crate::error::dfxvm_init::InteractError;
33
use crate::log::log_error;
4+
use dialoguer::Confirm;
45
use semver::Version;
56

67
pub fn customize(plan: Plan) -> Result<Plan, InteractError> {
@@ -13,6 +14,9 @@ pub fn customize(plan: Plan) -> Result<Plan, InteractError> {
1314
let dfx_version = select_dfx_version(&options.dfx_version)?;
1415
options = options.with_dfx_version(dfx_version);
1516

17+
let modify_path = select_modify_path(options.modify_path)?;
18+
options = options.with_modify_path(modify_path);
19+
1620
println!();
1721

1822
Ok(plan.with_options(options))
@@ -44,3 +48,11 @@ fn select_dfx_version(install_dfx: &DfxVersion) -> Result<DfxVersion, InteractEr
4448
};
4549
Ok(dfx_version)
4650
}
51+
52+
fn select_modify_path(current: bool) -> Result<bool, InteractError> {
53+
let modify = Confirm::new()
54+
.with_prompt("Modify PATH variable?")
55+
.default(current)
56+
.interact()?;
57+
Ok(modify)
58+
}

src/dfxvm_init/ui/display.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::dfxvm_init::plan::{DfxVersion, Plan};
22
use console::style;
33

4+
// Most of the text in this file is derived/copied/modified from:
5+
// https://github.com/rust-lang/rustup/blob/master/src/cli/self_update.rs
6+
47
pub fn introduction(plan: &Plan) {
58
println!();
69
println!("{}", style("Welcome to dfxvm!").bold());
@@ -14,6 +17,16 @@ pub fn introduction(plan: &Plan) {
1417
);
1518
println!();
1619
println!(" {}", plan.bin_dir.display());
20+
if !plan.profile_scripts.is_empty() {
21+
println!();
22+
println!("This path will then be added to your PATH environment variable by");
23+
println!("modifying the profile files located at:");
24+
println!();
25+
26+
for script in &plan.profile_scripts {
27+
println!(" {}", script.path.display());
28+
}
29+
}
1730
println!();
1831
}
1932

@@ -23,20 +36,43 @@ pub fn options(plan: &Plan) {
2336
DfxVersion::Latest => "latest".to_string(),
2437
DfxVersion::Specific(version) => version.to_string(),
2538
};
39+
let modify_path = if options.modify_path { "yes" } else { "no" };
2640

2741
println!("Current installation options:");
2842
println!();
29-
println!(" dfx version: {}", style(dfx_version).bold());
43+
println!(" dfx version: {}", style(dfx_version).bold());
44+
println!(" modify PATH variable: {}", style(modify_path).bold());
3045
println!();
3146
}
3247

3348
pub fn success(plan: &Plan) {
3449
println!();
3550
println!("{}", style("dfxvm is installed now.").bold());
3651
println!();
37-
println!("The installation process doesn't yet update profile scripts");
38-
println!("to add the dfxvm bin directory to your $PATH.");
52+
if plan.options.modify_path {
53+
post_install_msg_unix_modify_path(plan);
54+
} else {
55+
post_install_msg_unix_no_modify_path(plan);
56+
}
57+
}
58+
59+
pub fn post_install_msg_unix_modify_path(plan: &Plan) {
60+
println!("To get started you may need to restart your current shell.");
61+
println!("This would reload your PATH environment variable to include");
62+
println!("the dfxvm bin directory.");
3963
println!();
64+
post_install_msg_unix_configure_shell(plan);
65+
}
66+
67+
pub fn post_install_msg_unix_no_modify_path(plan: &Plan) {
68+
println!("To get started you need the dfxvm bin directory in your PATH:");
69+
println!(" {}", style(&plan.bin_dir.display()).bold());
70+
println!("This has not been done automatically.");
71+
println!();
72+
post_install_msg_unix_configure_shell(plan);
73+
}
74+
75+
pub fn post_install_msg_unix_configure_shell(plan: &Plan) {
4076
println!("To configure your shell, run:");
4177
println!(r#" source "{}""#, plan.env_path_user_facing);
4278
println!();

src/error/dfxvm_init.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::error::{
22
dfxvm,
33
env::NoHomeDirectoryError,
4-
fs::{CreateDirAllError, WriteFileError},
4+
fs::{AppendToFileError, CreateDirAllError, ReadToStringError, WriteFileError},
55
installation::InstallBinariesError,
66
};
77
use thiserror::Error;
@@ -32,6 +32,9 @@ pub enum ExecutePlanError {
3232
#[error(transparent)]
3333
Update(#[from] dfxvm::UpdateError),
3434

35+
#[error(transparent)]
36+
UpdateProfileScripts(#[from] UpdateProfileScriptsError),
37+
3538
#[error(transparent)]
3639
WriteFile(#[from] WriteFileError),
3740
}
@@ -42,3 +45,12 @@ pub struct InteractError {
4245
#[from]
4346
source: dialoguer::Error,
4447
}
48+
49+
#[derive(Error, Debug)]
50+
pub enum UpdateProfileScriptsError {
51+
#[error(transparent)]
52+
AppendToFile(#[from] AppendToFileError),
53+
54+
#[error(transparent)]
55+
ReadProfileScript(#[from] ReadToStringError),
56+
}

src/error/fs.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
use std::path::PathBuf;
22
use thiserror::Error;
33

4+
#[derive(Error, Debug)]
5+
pub enum AppendToFileError {
6+
#[error(transparent)]
7+
Open(#[from] OpenFileError),
8+
9+
#[error(transparent)]
10+
Write(#[from] WriteFileError),
11+
12+
#[error(transparent)]
13+
Sync(#[from] SyncDataError),
14+
}
15+
416
#[derive(Error, Debug)]
517
#[error("failed to canonicalize '{path}'")]
618
pub struct CanonicalizePathError {
@@ -87,6 +99,13 @@ pub struct SetPermissionsError {
8799
pub source: std::io::Error,
88100
}
89101

102+
#[derive(Error, Debug)]
103+
#[error("failed to sync data for {path}")]
104+
pub struct SyncDataError {
105+
pub path: PathBuf,
106+
pub source: std::io::Error,
107+
}
108+
90109
#[derive(Error, Debug)]
91110
#[error("failed to write {path}")]
92111
pub struct WriteFileError {

src/fs.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,40 @@
11
use crate::error::fs::{
2-
CanonicalizePathError, CopyFileError, CreateDirAllError, CreateFileError, OpenFileError,
3-
ReadFileError, ReadMetadataError, ReadToStringError, RemoveDirAllError, RemoveFileError,
4-
RenameError, SetPermissionsError, WriteFileError,
2+
AppendToFileError, CanonicalizePathError, CopyFileError, CreateDirAllError, CreateFileError,
3+
OpenFileError, ReadFileError, ReadMetadataError, ReadToStringError, RemoveDirAllError,
4+
RemoveFileError, RenameError, SetPermissionsError, SyncDataError, WriteFileError,
55
};
6+
use std::io::Write;
67
use std::path::{Path, PathBuf};
78

9+
// Derived from append_file() in https://github.com/rust-lang/rustup/blob/master/src/utils/raw.rs
10+
pub fn append_to_file(dest: &Path, line: &str) -> Result<(), AppendToFileError> {
11+
let mut file = std::fs::OpenOptions::new()
12+
.write(true)
13+
.append(true)
14+
.create(true)
15+
.open(dest)
16+
.map_err(|source| {
17+
AppendToFileError::Open(OpenFileError {
18+
path: dest.to_path_buf(),
19+
source,
20+
})
21+
})?;
22+
23+
writeln!(file, "{line}").map_err(|source| {
24+
AppendToFileError::Write(WriteFileError {
25+
path: dest.to_path_buf(),
26+
source,
27+
})
28+
})?;
29+
30+
file.sync_data().map_err(|source| {
31+
AppendToFileError::Sync(SyncDataError {
32+
path: dest.to_path_buf(),
33+
source,
34+
})
35+
})
36+
}
37+
838
pub fn canonicalize(path: &Path) -> Result<PathBuf, CanonicalizePathError> {
939
path.canonicalize().map_err(|source| CanonicalizePathError {
1040
path: path.to_path_buf(),

0 commit comments

Comments
 (0)