Skip to content

Commit

Permalink
Install new executable name (and link old executable) (#1398)
Browse files Browse the repository at this point in the history
This installs (when --features gel is passed) to both edgedb and gel executables. The alternative executable is hard linked, symlinked or copied (in that order of preference).

Note that the gel executable is installed even w/o --features gel right now, but it is installed as the "alternate" executable.

We also check the name of the executable that is invoked and warn if the alternative is there. If the two executables are out of sync (determined by checking executable length), we warn as well.
  • Loading branch information
mmastrac authored Nov 14, 2024
1 parent 8367287 commit dbc9333
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 20 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ github_action_install = []
github_nightly = []
portable_tests = []
docker_test_wrapper = []
gel = []

[workspace.dependencies]
clap = "4.4.6"
Expand Down
44 changes: 40 additions & 4 deletions src/branding.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
#![allow(unused)]
use const_format::concatcp;

pub const BRANDING: &str = "EdgeDB";
pub const BRANDING_CLI: &str = "EdgeDB CLI";
pub const BRANDING_CLOUD: &str = "EdgeDB Cloud";
pub const BRANDING_CLI_CMD: &str = "edgedb";
/// The product name.
pub const BRANDING: &str = if cfg!(feature = "gel") {
"Gel"
} else {
"EdgeDB"
};
/// The CLI name.
pub const BRANDING_CLI: &str = concatcp!(BRANDING, " CLI");
/// The cloud name.
pub const BRANDING_CLOUD: &str = concatcp!(BRANDING, " Cloud");

/// The CLI command name.
pub const BRANDING_CLI_CMD: &str = if cfg!(feature = "gel") {
"gel"
} else {
"edgedb"
};
/// The CLI command name for the alternative executable.
pub const BRANDING_CLI_CMD_ALT: &str = if cfg!(feature = "gel") {
"edgedb"
} else {
"gel"
};
/// The executable file name for the CLI.
pub const BRANDING_CLI_CMD_FILE: &str = if cfg!(windows) {
concatcp!(BRANDING_CLI_CMD, ".exe")
} else {
BRANDING_CLI_CMD
};
/// The executable file name for the CLI alternative.
pub const BRANDING_CLI_CMD_ALT_FILE: &str = if cfg!(windows) {
concatcp!(BRANDING_CLI_CMD_ALT, ".exe")
} else {
BRANDING_CLI_CMD_ALT
};

/// The WSL distribution name.
pub const BRANDING_WSL: &str = "EdgeDB.WSL.1";

/// The display name for the configuration file.
pub const CONFIG_FILE_DISPLAY_NAME: &str = "`gel.toml` (or `edgedb.toml`)";
130 changes: 114 additions & 16 deletions src/cli/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@

use std::env;
use std::fs;
use std::fs::OpenOptions;
use std::io::Seek;
use std::io::SeekFrom;
use std::io::{stdout, BufWriter, Write};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::str::FromStr;

use anyhow::Context;
use clap_complete::{generate, shells};
use const_format::concatcp;
use edgedb_tokio::get_stash_path;
use fn_error_context::context;
use prettytable::{Cell, Row, Table};

use crate::branding::BRANDING_CLI_CMD_ALT_FILE;
use crate::branding::BRANDING_CLI_CMD_FILE;
use crate::branding::{BRANDING, BRANDING_CLI, BRANDING_CLI_CMD};
use crate::cli::{migrate, upgrade};
use crate::commands::ExitCode;
Expand Down Expand Up @@ -503,22 +509,9 @@ fn _main(options: &CliInstall) -> anyhow::Result<()> {
return upgrade::upgrade_to_arm64();
}

let tmp_path = settings.installation_path.join(".edgedb.tmp");
let path = if cfg!(windows) {
settings.installation_path.join("edgedb.exe")
} else {
settings.installation_path.join("edgedb")
};
let exe_path = current_exe()?;
fs::create_dir_all(&settings.installation_path)
.with_context(|| format!("failed to create {:?}", settings.installation_path))?;
if exe_path.parent() == path.parent() {
fs::rename(&exe_path, &path).with_context(|| format!("failed to rename {exe_path:?}"))?;
} else {
fs::remove_file(&tmp_path).ok();
fs::copy(&exe_path, &tmp_path).with_context(|| format!("failed to write {tmp_path:?}"))?;
fs::rename(&tmp_path, &path).with_context(|| format!("failed to rename {tmp_path:?}"))?;
}
copy_to_installation_path(&settings.installation_path)?;
copy_to_alternative_executable(&settings.installation_path)?;

write_completions_home()?;

if settings.modify_path {
Expand Down Expand Up @@ -595,6 +588,111 @@ fn _main(options: &CliInstall) -> anyhow::Result<()> {
Ok(())
}

fn copy_to_installation_path<P: AsRef<Path>>(installation_path: P) -> anyhow::Result<()> {
let installation_path = installation_path.as_ref();
let tmp_path = installation_path.join(concatcp!(BRANDING_CLI_CMD, ".tmp"));
let path = installation_path.join(BRANDING_CLI_CMD_FILE);
let exe_path = current_exe()?;
fs::create_dir_all(installation_path)
.with_context(|| format!("failed to create {:?}", installation_path))?;

// Attempt to rename from the current executable to the target path. If this fails, we try a copy.
if fs::rename(&exe_path, &path).is_ok() {
return Ok(());
}

// If we can't rename, try to copy to the neighboring temporary file and
// then rename on top of the executable.
if tmp_path
.try_exists()
.with_context(|| format!("failed to check if {tmp_path:?} exists"))?
{
_ = fs::remove_file(&tmp_path);
}
fs::copy(&exe_path, &tmp_path).with_context(|| format!("failed to write {tmp_path:?}"))?;
fs::rename(&tmp_path, &path).with_context(|| format!("failed to rename {tmp_path:?}"))?;

Ok(())
}

fn copy_to_alternative_executable<P: AsRef<Path>>(installation_path: P) -> anyhow::Result<()> {
let path = installation_path.as_ref().join(BRANDING_CLI_CMD_FILE);
let alt_path = installation_path.as_ref().join(BRANDING_CLI_CMD_ALT_FILE);

if alt_path
.try_exists()
.with_context(|| format!("failed to check if {alt_path:?} exists"))?
{
_ = fs::remove_file(&alt_path);
}

// Try a hard link first.
if fs::hard_link(&path, &alt_path).is_ok() {
log::debug!("hard linked {path:?} to {alt_path:?}");
return Ok(());
}

// If that fails, try a symlink.
#[cfg(unix)]
if std::os::unix::fs::symlink(&path, &alt_path).is_ok() {
log::debug!("symlinked {path:?} to {alt_path:?}");
return Ok(());
}

// If that fails, try a copy.
fs::copy(&path, &alt_path)
.with_context(|| format!("failed to copy or symlink {path:?} to {alt_path:?}"))?;

Ok(())
}

pub fn check_executables() {
let exe_path = current_exe().unwrap();

let exe_dir = exe_path.parent().unwrap();
let old_executable = exe_dir.join(BRANDING_CLI_CMD_ALT_FILE);
let new_executable = exe_dir.join(BRANDING_CLI_CMD_FILE);
log::debug!("exe_path: {exe_path:?}");
log::debug!("old_executable: {old_executable:?}");
log::debug!("new_executable: {new_executable:?}");

if exe_path.file_name().unwrap() == BRANDING_CLI_CMD_ALT_FILE {
if new_executable.exists() {
log::warn!("{exe_path:?} is the old name for the {BRANDING_CLI_CMD_FILE} executable. \
Please update your scripts (and muscle memory) to use the new executable at {new_executable:?}.");
} else {
log::warn!(
"{exe_path:?} is the old name for the {BRANDING_CLI_CMD_FILE} executable, but \
{BRANDING_CLI_CMD_FILE} does not exist. You may need to reinstall {BRANDING} to fix this."
);
}
}

if old_executable.exists() && new_executable.exists() {
let mut opts = OpenOptions::new();
opts.read(true);
let length_old = if let Ok(mut file) = opts.open(&old_executable) {
file.seek(SeekFrom::End(0)).ok().map(|n| n as usize)
} else {
None
};
let length_new = if let Ok(mut file) = opts.open(&new_executable) {
file.seek(SeekFrom::End(0)).ok().map(|n| n as usize)
} else {
None
};
match (length_old, length_new) {
(Some(length_old), Some(length_new)) if length_old != length_new => {
log::warn!(
"{old_executable:?} and {new_executable:?} have different sizes. \
You mean need to reinstall {BRANDING}."
);
}
_ => {}
}
}
}

// This is used to decode the value of HKCU\Environment\PATH. If that
// key is not unicode (or not REG_SZ | REG_EXPAND_SZ) then this
// returns null. The winreg library itself does a lossy unicode
Expand Down
10 changes: 10 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ fn is_cli_upgrade(cmd: &Option<options::Command>) -> bool {
)
}

fn is_cli_self_install(cmd: &Option<options::Command>) -> bool {
use options::Command::_SelfInstall;
matches!(cmd, Some(_SelfInstall(..)))
}

fn _main() -> anyhow::Result<()> {
// If a crash happens we want the backtrace to be printed by default
// to ease bug reporting and troubleshooting.
Expand Down Expand Up @@ -146,6 +151,11 @@ fn _main() -> anyhow::Result<()> {
Default::default()
});

// Check the executable name and warn on older names, but not for self-install.
if !is_cli_self_install(&opt.subcommand) && cfg!(feature = "gel") {
cli::install::check_executables();
}

if !is_cli_upgrade(&opt.subcommand) {
version_check::check(opt.no_cli_update_check)?;
}
Expand Down

0 comments on commit dbc9333

Please sign in to comment.