1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::{
env,
fs::{self, File, Permissions},
io,
os::unix::fs::PermissionsExt,
path::PathBuf,
process::Command,
};
use anyhow::{Context, Result};
use chrono::Utc;
use displaydoc::Display;
use log::{debug, info, trace};
use serde_derive::Deserialize;
use thiserror::Error;
use self::UpdateSelfError as E;
use crate::{args::UpdateSelfOptions, tasks::ResolveEnv};
#[derive(Debug, Deserialize)]
struct GitHubReleaseJsonResponse {
tag_name: String,
}
const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
const CURRENT_VERSION: &str = env!("CARGO_PKG_VERSION");
impl ResolveEnv for UpdateSelfOptions {}
pub(crate) fn run(opts: &UpdateSelfOptions) -> Result<()> {
let up_path = env::current_exe()?.canonicalize().unwrap();
if !opts.always_update && up_path.starts_with(env!("CARGO_MANIFEST_DIR")) {
info!(
"Skipping up-rs update, current version '{}' is a dev build.",
&up_path.display(),
);
return Ok(());
}
let client = reqwest::blocking::Client::builder()
.user_agent(APP_USER_AGENT)
.build()?;
if opts.url == crate::args::SELF_UPDATE_URL {
let latest_github_release = client
.get(crate::args::LATEST_RELEASE_URL)
.send()?
.error_for_status()?
.json::<GitHubReleaseJsonResponse>()?;
trace!("latest_github_release: {:?}", latest_github_release,);
let latest_github_release = latest_github_release.tag_name;
if CURRENT_VERSION == latest_github_release {
info!(
"Skipping up-rs update, current version '{}' is latest GitHub version '{:?}'",
CURRENT_VERSION, &latest_github_release,
);
return Ok(());
}
}
let temp_dir = env::temp_dir();
let temp_path = &temp_dir.join(format!("up_rs-{}", Utc::now().to_rfc3339()));
debug!(
"Downloading url {} to path {}",
&opts.url,
up_path.display()
);
debug!("Using temporary path: {}", temp_path.display());
let mut response = reqwest::blocking::get(&opts.url)?.error_for_status()?;
fs::create_dir_all(&temp_dir).with_context(|| E::CreateDir { path: temp_dir })?;
let mut dest = File::create(&temp_path).with_context(|| E::CreateFile {
path: temp_path.to_path_buf(),
})?;
io::copy(&mut response, &mut dest).context(E::Copy {})?;
let permissions = Permissions::from_mode(0o755);
fs::set_permissions(&temp_path, permissions).with_context(|| E::SetPermissions {
path: temp_path.to_owned(),
})?;
let output = Command::new(temp_path).arg("--version").output()?;
let new_version = String::from_utf8_lossy(&output.stdout);
let new_version = new_version
.trim()
.trim_start_matches(concat!(env!("CARGO_PKG_NAME"), " "));
if semver::Version::parse(new_version) > semver::Version::parse(CURRENT_VERSION) {
info!(
"Updating up-rs from '{}' to '{}'",
CURRENT_VERSION, &new_version,
);
fs::rename(&temp_path, &up_path).with_context(|| E::Rename {
from: temp_path.clone(),
to: up_path.clone(),
})?;
} else {
info!(
"Skipping up-rs update, current version '{}' and new version '{}'",
CURRENT_VERSION, &new_version,
);
}
Ok(())
}
#[derive(Error, Debug, Display)]
pub enum UpdateSelfError {
CreateDir { path: PathBuf },
CreateFile { path: PathBuf },
Copy,
SetPermissions { path: PathBuf },
Rename { from: PathBuf, to: PathBuf },
}