Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased] - ReleaseDate

- Added `dfxvm self update` command, which updates dfxvm to the latest version.

## [0.1.2] - 2023-12-19

- dfxvm-init now alters profile scripts to modify the PATH environment variable.
Expand Down
6 changes: 6 additions & 0 deletions docs/cli-reference/dfxvm/dfxvm-self-update.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# dfxvm self update

Updates to the newest version of dfxvm.

## Usage

```bash
dfxvm self update
```
2 changes: 2 additions & 0 deletions src/dfx.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::dfxvm::cleanup_self_updater;
use crate::error::dfx;
use crate::error::dfx::Error::Exec;
use crate::error::dfx::{
Expand All @@ -18,6 +19,7 @@ use std::path::PathBuf;
use std::process::ExitCode;

pub fn main(args: &[OsString], locations: &Locations) -> Result<ExitCode, dfx::Error> {
cleanup_self_updater(locations)?;
let Some((version, args)) = get_dfx_version_and_command_args(args, locations)? else {
err!("Unable to determine which dfx version to call. To set a default version, run:");
err!(" {}", style_command("dfxvm default <version>"));
Expand Down
2 changes: 2 additions & 0 deletions src/dfxvm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ mod update;

pub use cli::main;
pub use default::set_default;
pub use self_update::cleanup_self_updater;
pub use self_update::self_replace;
pub use update::update;
7 changes: 4 additions & 3 deletions src/dfxvm/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::dfxvm::{
default::default, install::install, list::list, self_uninstall::self_uninstall,
self_update::self_update, uninstall::uninstall, update::update,
cleanup_self_updater, default::default, install::install, list::list,
self_uninstall::self_uninstall, self_update::self_update, uninstall::uninstall, update::update,
};
use crate::error::dfxvm;
use crate::locations::Locations;
Expand Down Expand Up @@ -80,13 +80,14 @@ pub struct SelfUpdateOpts {}
pub struct SelfUninstallOpts {}

pub async fn main(args: &[OsString], locations: &Locations) -> Result<ExitCode, dfxvm::Error> {
cleanup_self_updater(locations)?;
let cli = Cli::parse_from(args);
match cli.command {
Command::Default(opts) => default(opts.version, locations).await?,
Command::Install(opts) => install(opts.version, locations).await?,
Command::List(_opts) => list(locations)?,
Command::SelfCommand(opts) => match opts.command {
SelfCommand::Update(_opts) => self_update(locations)?,
SelfCommand::Update(_opts) => self_update(locations).await?,
SelfCommand::Uninstall(_opts) => self_uninstall(locations)?,
},
Command::Uninstall(opts) => uninstall(opts.version, locations)?,
Expand Down
137 changes: 134 additions & 3 deletions src/dfxvm/self_update.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,138 @@
use crate::error::dfxvm::SelfUpdateError;
use crate::dist_manifest::lookup_latest_version;
use crate::download::{download_file, verify_checksum};
use crate::error::dfxvm::self_update::CleanupSelfUpdaterError;
use crate::error::dfxvm::{
self_update::{
DownloadLatestBinaryError,
DownloadLatestBinaryError::CreateTempDirIn,
ExtractBinaryError,
ExtractBinaryError::{DfxvmNotFound, ReadArchiveEntries, UnpackBinary},
FormatTarballUrlError, SelfReplaceError,
},
SelfUpdateError,
SelfUpdateError::Exec,
};
use crate::fs::{open_file, remove_file};
use crate::installation::install_binaries;
use crate::locations::Locations;
use crate::settings::Settings;
use flate2::read::GzDecoder;
use reqwest::{Client, Url};
use std::os::unix::prelude::CommandExt;
use std::path::Path;
use tar::Archive;

pub fn self_update(_locations: &Locations) -> Result<(), SelfUpdateError> {
println!("update dfxvm to latest");
pub async fn self_update(locations: &Locations) -> Result<(), SelfUpdateError> {
info!("checking for self-update");
let settings = Settings::load_or_default(&locations.settings_path())?;
let latest_version = lookup_latest_version(&settings).await?;
let our_version = env!("CARGO_PKG_VERSION");
if latest_version == our_version {
info!("dfxvm unchanged - {latest_version}");
return Ok(());
}

info!("updating to {latest_version}");

let tarball_url = format_tarball_url(&settings)?;
let self_update_path = locations.self_update_path();

download_latest_binary(&tarball_url, &self_update_path, locations).await?;

let mut command = std::process::Command::new(self_update_path);
command.arg("--self-replace");
let err = command.exec();
Err(Exec {
command,
source: err,
})
}

pub fn self_replace(locations: &Locations) -> Result<(), SelfReplaceError> {
install_binaries(&locations.bin_dir())?;
Ok(())
}

// called on next execution of dfx or dfxvm
pub fn cleanup_self_updater(locations: &Locations) -> Result<(), CleanupSelfUpdaterError> {
let path = locations.self_update_path();

if path.exists() {
remove_file(&path)?;
}

Ok(())
}

fn format_tarball_url(settings: &Settings) -> Result<Url, FormatTarballUrlError> {
#[cfg(target_arch = "aarch64")]
let architecture = "aarch64-apple-darwin";
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
let architecture = "x86_64-apple-darwin";
#[cfg(target_os = "linux")]
let architecture = "x86_64-unknown-linux-gnu";

let basename = format!("dfxvm-{}", architecture);
let url = format!(
"{}/{basename}.tar.gz",
settings.dfxvm_latest_download_root()
);

Url::parse(&url).map_err(|source| FormatTarballUrlError { url, source })
}

async fn download_latest_binary(
tarball_url: &Url,
binary_path: &Path,
locations: &Locations,
) -> Result<(), DownloadLatestBinaryError> {
let shasum_url = Url::parse(&format!("{tarball_url}.sha256"))?;

let download_dir = tempfile::Builder::new()
.prefix("dfxvm-download")
.tempdir_in(locations.data_local_dir())
.map_err(|source| CreateTempDirIn {
path: locations.data_local_dir().to_path_buf(),
source,
})?;

let downloaded_tarball_path = download_dir.path().join("dfxvm.tar.gz");
let downloaded_shasum_path = download_dir.path().join("dfxvm.tar.gz.sha256");

let client = Client::new();

download_file(&client, &shasum_url, &downloaded_shasum_path).await?;
let computed_hash = download_file(&client, tarball_url, &downloaded_tarball_path).await?;
verify_checksum(computed_hash, &downloaded_shasum_path)?;

extract_binary(binary_path, &downloaded_tarball_path)?;
Ok(())
}

fn extract_binary(
binary_path: &Path,
downloaded_tarball_path: &Path,
) -> Result<(), ExtractBinaryError> {
let tar_gz = open_file(downloaded_tarball_path)?;
let tar = GzDecoder::new(tar_gz);

Archive::new(tar)
.entries()
.map_err(ReadArchiveEntries)?
.enumerate()
.filter_map(|(_i, entry)| entry.ok())
.find(|entry| {
entry
.header()
.path()
.ok()
.as_ref()
.and_then(|x| x.to_str())
.map(|str_path| str_path.ends_with("dfxvm"))
.unwrap_or(false)
})
.ok_or(DfxvmNotFound)?
.unpack(binary_path)
.map_err(UnpackBinary)?;
Ok(())
}
7 changes: 7 additions & 0 deletions src/dfxvm_init/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::dfxvm::self_replace;
use crate::dfxvm_init::initialize::initialize;
use crate::dfxvm_init::plan::{
DfxVersion::{Latest, Specific},
Expand Down Expand Up @@ -29,6 +30,12 @@ pub struct Cli {
}

pub async fn main(args: &[OsString], locations: &Locations) -> Result<ExitCode, dfxvm_init::Error> {
let arg1 = args.get(1).map(|a| &**a);
if arg1 == Some("--self-replace".as_ref()) {
self_replace(locations)?;
return Ok(ExitCode::SUCCESS);
}

let opts = Cli::parse_from(args);

let confirmation = if opts.proceed {
Expand Down
2 changes: 1 addition & 1 deletion src/dfxvm_init/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub struct Plan {

impl Plan {
pub fn new(options: PlanOptions, locations: &Locations) -> Self {
let bin_dir = locations.data_local_dir().join("bin");
let bin_dir = locations.bin_dir();
let env_path = locations.data_local_dir().join("env");
let env_path_user_facing = get_env_path_user_facing().to_string();
let profile_scripts = get_detected_profile_scripts();
Expand Down
38 changes: 38 additions & 0 deletions src/dist_manifest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use crate::error::dfxvm::self_update::LookupLatestVersionError;
use crate::json::fetch_json;
use crate::settings::Settings;
use serde::Deserialize;
use url::Url;

#[derive(Deserialize, Debug)]
struct Release {
app_name: String,
app_version: String,
}

#[derive(Deserialize, Debug)]
struct DistManifest {
releases: Vec<Release>,
}

pub async fn lookup_latest_version(
settings: &Settings,
) -> Result<String, LookupLatestVersionError> {
let dist_manifest_url = format!(
"{}/dist-manifest.json",
settings.dfxvm_latest_download_root()
);
let url =
Url::parse(&dist_manifest_url).map_err(|source| LookupLatestVersionError::ParseUrl {
url: dist_manifest_url,
source,
})?;
let dist_manifest = fetch_json::<DistManifest>(&url).await?;
let dfxvm_release = dist_manifest
.releases
.iter()
.find(|release| release.app_name == "dfxvm")
.ok_or(LookupLatestVersionError::NoDfxvmRelease { url })?;
let latest_version = dfxvm_release.app_version.clone();
Ok(latest_version)
}
8 changes: 7 additions & 1 deletion src/error/dfx.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
use crate::error::{env::GetCurrentDirError, fs::CanonicalizePathError, json::LoadJsonFileError};
use crate::error::{
dfxvm::self_update::CleanupSelfUpdaterError, env::GetCurrentDirError,
fs::CanonicalizePathError, json::LoadJsonFileError,
};
use std::process::Command;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
CleanupSelfUpdater(#[from] CleanupSelfUpdaterError),

#[error(transparent)]
DetermineDfxVersion(#[from] DetermineDfxVersionError),

Expand Down
9 changes: 6 additions & 3 deletions src/error/dfxvm.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::{
dfxvm::self_update::CleanupSelfUpdaterError,
fs::{RemoveDirAllError, RemoveFileError, RenameError},
json::{FetchJsonDocError, LoadJsonFileError},
};
Expand All @@ -7,13 +8,18 @@ use thiserror::Error;

pub mod default;
pub mod install;
pub mod self_update;

pub use default::DefaultError;
pub use default::SetDefaultError;
pub use install::InstallError;
pub use self_update::SelfUpdateError;

#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
CleanupSelfUpdater(#[from] CleanupSelfUpdaterError),

#[error(transparent)]
Default(#[from] DefaultError),

Expand Down Expand Up @@ -77,6 +83,3 @@ pub enum UpdateError {

#[derive(Error, Debug)]
pub enum SelfUninstallError {}

#[derive(Error, Debug)]
pub enum SelfUpdateError {}
Loading