|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <boost/leaf/handle_exception.hpp> |
|
|
#include <boost/leaf/handle_exception.hpp> |
|
|
#include <fansi/styled.hpp> |
|
|
#include <fansi/styled.hpp> |
|
|
#include <fmt/ostream.h> |
|
|
|
|
|
#include <neo/assert.hpp> |
|
|
#include <neo/assert.hpp> |
|
|
#include <neo/platform.hpp> |
|
|
#include <neo/platform.hpp> |
|
|
#include <neo/scope.hpp> |
|
|
#include <neo/scope.hpp> |
|
|
|
|
|
|
|
|
#endif |
|
|
#endif |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#if _WIN32 |
|
|
|
|
|
void fixup_path_env(const wil::unique_hkey& env_hkey, fs::path want_path) { |
|
|
|
|
|
DWORD len = 0; |
|
|
|
|
|
// Get the length |
|
|
|
|
|
auto err = ::RegGetValueW(env_hkey.get(), |
|
|
|
|
|
nullptr, |
|
|
|
|
|
L"PATH", |
|
|
|
|
|
RRF_RT_REG_EXPAND_SZ | RRF_RT_REG_SZ | RRF_NOEXPAND, |
|
|
|
|
|
nullptr, |
|
|
|
|
|
nullptr, |
|
|
|
|
|
&len); |
|
|
|
|
|
if (err != ERROR_SUCCESS) { |
|
|
|
|
|
throw std::system_error(std::error_code(err, std::system_category()), |
|
|
|
|
|
"Failed to access PATH environment variable [1]"); |
|
|
|
|
|
} |
|
|
|
|
|
// Now get the value |
|
|
|
|
|
std::wstring buffer; |
|
|
|
|
|
buffer.resize(len / 2); |
|
|
|
|
|
err = ::RegGetValueW(env_hkey.get(), |
|
|
|
|
|
nullptr, |
|
|
|
|
|
L"PATH", |
|
|
|
|
|
RRF_RT_REG_EXPAND_SZ | RRF_RT_REG_SZ | RRF_NOEXPAND, |
|
|
|
|
|
nullptr, |
|
|
|
|
|
buffer.data(), |
|
|
|
|
|
&len); |
|
|
|
|
|
if (err != ERROR_SUCCESS) { |
|
|
|
|
|
throw std::system_error(std::error_code(err, std::system_category()), |
|
|
|
|
|
"Failed to access PATH environment variable [2]"); |
|
|
|
|
|
} |
|
|
|
|
|
// Strip null-term |
|
|
|
|
|
buffer.resize(len); |
|
|
|
|
|
while (!buffer.empty() && buffer.back() == 0) { |
|
|
|
|
|
buffer.pop_back(); |
|
|
|
|
|
} |
|
|
|
|
|
// Check if we need to append the user-local binaries dir to the path |
|
|
|
|
|
const auto want_entry = fs::path(want_path).make_preferred().lexically_normal(); |
|
|
|
|
|
const auto path_env_str = fs::path(buffer).string(); |
|
|
|
|
|
auto path_elems = split_view(path_env_str, ";"); |
|
|
|
|
|
const bool any_match = std::any_of(path_elems.cbegin(), path_elems.cend(), [&](auto view) { |
|
|
|
|
|
auto existing = fs::weakly_canonical(view).make_preferred().lexically_normal(); |
|
|
|
|
|
dds_log(trace, "Existing PATH entry: '{}'", existing.string()); |
|
|
|
|
|
return existing.native() == want_entry.native(); |
|
|
|
|
|
}); |
|
|
|
|
|
if (any_match) { |
|
|
|
|
|
dds_log(info, "PATH is up-to-date"); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
// It's not there. Add it now. |
|
|
|
|
|
auto want_str = want_entry.string(); |
|
|
|
|
|
path_elems.insert(path_elems.begin(), want_str); |
|
|
|
|
|
auto joined = joinstr(";", path_elems); |
|
|
|
|
|
buffer = fs::path(joined).native(); |
|
|
|
|
|
// Put the new PATH entry back into the environment |
|
|
|
|
|
err = ::RegSetValueExW(env_hkey.get(), |
|
|
|
|
|
L"Path", |
|
|
|
|
|
0, |
|
|
|
|
|
REG_EXPAND_SZ, |
|
|
|
|
|
reinterpret_cast<const BYTE*>(buffer.data()), |
|
|
|
|
|
(buffer.size() + 1) * 2); |
|
|
|
|
|
if (err != ERROR_SUCCESS) { |
|
|
|
|
|
throw std::system_error(std::error_code(err, std::system_category()), |
|
|
|
|
|
"Failed to modify PATH environment variable"); |
|
|
|
|
|
} |
|
|
|
|
|
dds_log( |
|
|
|
|
|
info, |
|
|
|
|
|
"The directory [.br.cyan[{}]] has been added to your PATH environment variables."_styled, |
|
|
|
|
|
want_path.string()); |
|
|
|
|
|
dds_log( |
|
|
|
|
|
info, |
|
|
|
|
|
".bold.cyan[NOTE:] You may need to restart running applications to see this change!"_styled); |
|
|
|
|
|
} |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
void fixup_system_path(const options&) { |
|
|
void fixup_system_path(const options&) { |
|
|
#if !_WIN32 |
|
|
#if !_WIN32 |
|
|
// We install into /usr/local/bin, and every nix-like system we support already has that on the |
|
|
// We install into /usr/local/bin, and every nix-like system we support already has that on the |
|
|
// global PATH |
|
|
// global PATH |
|
|
#else // Windows! |
|
|
#else // Windows! |
|
|
#error "Not yet implemented" |
|
|
|
|
|
|
|
|
wil::unique_hkey env_hkey; |
|
|
|
|
|
auto err = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, |
|
|
|
|
|
L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", |
|
|
|
|
|
0, |
|
|
|
|
|
KEY_WRITE | KEY_READ, |
|
|
|
|
|
&env_hkey); |
|
|
|
|
|
if (err != ERROR_SUCCESS) { |
|
|
|
|
|
throw std::system_error(std::error_code(err, std::system_category()), |
|
|
|
|
|
"Failed to open user-local environment variables registry " |
|
|
|
|
|
"entry"); |
|
|
|
|
|
} |
|
|
|
|
|
fixup_path_env(env_hkey, "C:/bin"); |
|
|
#endif |
|
|
#endif |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto profile_content = dds::slurp_file(profile_file); |
|
|
auto profile_content = dds::slurp_file(profile_file); |
|
|
if (dds::contains(profile_content, "$HOME/.local/bin")) { |
|
|
if (dds::contains(profile_content, "$HOME/.local/bin")) { |
|
|
// We'll assume that this is properly loading .local/bin for .profile |
|
|
// We'll assume that this is properly loading .local/bin for .profile |
|
|
dds_log(info, ".br.cyan[{}] is okay"_styled, profile_file); |
|
|
|
|
|
|
|
|
dds_log(info, "[.br.cyan[{}]] is okay"_styled, profile_file.string()); |
|
|
} else { |
|
|
} else { |
|
|
// Let's add it |
|
|
// Let's add it |
|
|
profile_content |
|
|
profile_content |
|
|
|
|
|
|
|
|
"binaries\nPATH=$HOME/bin:$HOME/.local/bin:$PATH\n"); |
|
|
"binaries\nPATH=$HOME/bin:$HOME/.local/bin:$PATH\n"); |
|
|
if (opts.dry_run) { |
|
|
if (opts.dry_run) { |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
"Would update .br.cyan[{}] to have ~/.local/bin on $PATH"_styled, |
|
|
|
|
|
profile_file); |
|
|
|
|
|
|
|
|
"Would update [.br.cyan[{}]] to have ~/.local/bin on $PATH"_styled, |
|
|
|
|
|
profile_file.string()); |
|
|
} else { |
|
|
} else { |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
"Updating .br.cyan[{}] with a user-local binaries PATH entry"_styled, |
|
|
|
|
|
profile_file); |
|
|
|
|
|
|
|
|
"Updating [.br.cyan[{}]] with a user-local binaries PATH entry"_styled, |
|
|
|
|
|
profile_file.string()); |
|
|
auto tmp_file = profile_file; |
|
|
auto tmp_file = profile_file; |
|
|
tmp_file += ".tmp"; |
|
|
tmp_file += ".tmp"; |
|
|
auto bak_file = profile_file; |
|
|
auto bak_file = profile_file; |
|
|
|
|
|
|
|
|
safe_rename(tmp_file, profile_file); |
|
|
safe_rename(tmp_file, profile_file); |
|
|
// Okay! |
|
|
// Okay! |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
".br.green[{}] was updated. Prior contents are safe in .br.cyan[{}]"_styled, |
|
|
|
|
|
profile_file, |
|
|
|
|
|
bak_file); |
|
|
|
|
|
|
|
|
"[.br.green[{}]] was updated. Prior contents are safe in [.br.cyan[{}]]"_styled, |
|
|
|
|
|
profile_file.string(), |
|
|
|
|
|
bak_file.string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto fish_config_content = slurp_file(fish_config); |
|
|
auto fish_config_content = slurp_file(fish_config); |
|
|
if (dds::contains(fish_config_content, "$HOME/.local/bin")) { |
|
|
if (dds::contains(fish_config_content, "$HOME/.local/bin")) { |
|
|
// Assume that this is up-to-date |
|
|
// Assume that this is up-to-date |
|
|
dds_log(info, "Fish configuration in .br.cyan[{}] is okay"_styled, fish_config); |
|
|
|
|
|
|
|
|
dds_log(info, |
|
|
|
|
|
"Fish configuration in [.br.cyan[{}]] is okay"_styled, |
|
|
|
|
|
fish_config.string()); |
|
|
} else { |
|
|
} else { |
|
|
dds_log( |
|
|
dds_log( |
|
|
info, |
|
|
info, |
|
|
"Updating Fish shell configuration .br.cyan[{}] with user-local binaries PATH entry"_styled, |
|
|
|
|
|
fish_config); |
|
|
|
|
|
|
|
|
"Updating Fish shell configuration [.br.cyan[{}]] with user-local binaries PATH entry"_styled, |
|
|
|
|
|
fish_config.string()); |
|
|
fish_config_content |
|
|
fish_config_content |
|
|
+= ("\n# This line was added by 'dds install-yourself' to add the usre-local " |
|
|
+= ("\n# This line was added by 'dds install-yourself' to add the usre-local " |
|
|
"binaries directory to $PATH\nset -x PATH $PATH \"$HOME/.local/bin\"\n"); |
|
|
"binaries directory to $PATH\nset -x PATH $PATH \"$HOME/.local/bin\"\n"); |
|
|
|
|
|
|
|
|
safe_rename(tmp_file, fish_config); |
|
|
safe_rename(tmp_file, fish_config); |
|
|
// Okay! |
|
|
// Okay! |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
".br.green[{}] was updated. Prior contents are safe in .br.cyan[{}]"_styled, |
|
|
|
|
|
fish_config, |
|
|
|
|
|
bak_file); |
|
|
|
|
|
|
|
|
"[.br.green[{}]] was updated. Prior contents are safe in [.br.cyan[{}]]"_styled, |
|
|
|
|
|
fish_config.string(), |
|
|
|
|
|
bak_file.string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
#else // _WIN32 |
|
|
#else // _WIN32 |
|
|
|
|
|
wil::unique_hkey env_hkey; |
|
|
|
|
|
auto err |
|
|
|
|
|
= ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_WRITE | KEY_READ, &env_hkey); |
|
|
|
|
|
if (err != ERROR_SUCCESS) { |
|
|
|
|
|
throw std::system_error(std::error_code(err, std::system_category()), |
|
|
|
|
|
"Failed to open user-local environment variables registry " |
|
|
|
|
|
"entry"); |
|
|
|
|
|
} |
|
|
|
|
|
fixup_path_env(env_hkey, "%LocalAppData%/bin"); |
|
|
#endif |
|
|
#endif |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dest_path += ".exe"; |
|
|
dest_path += ".exe"; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (fs::canonical(dest_path) == fs::canonical(self_exe)) { |
|
|
|
|
|
dds_log(error, "We cannot install over our own executable (.br.red[{}])"_styled, self_exe); |
|
|
|
|
|
|
|
|
if (fs::weakly_canonical(dest_path) == fs::canonical(self_exe)) { |
|
|
|
|
|
dds_log(error, |
|
|
|
|
|
"We cannot install over our own executable (.br.red[{}])"_styled, |
|
|
|
|
|
self_exe.string()); |
|
|
return 1; |
|
|
return 1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!fs::is_directory(dest_dir)) { |
|
|
if (!fs::is_directory(dest_dir)) { |
|
|
if (opts.dry_run) { |
|
|
if (opts.dry_run) { |
|
|
dds_log(info, "Would create directory .br.cyan[{}]"_styled, dest_dir); |
|
|
|
|
|
|
|
|
dds_log(info, "Would create directory [.br.cyan[{}]]"_styled, dest_dir.string()); |
|
|
} else { |
|
|
} else { |
|
|
dds_log(info, "Creating directory .br.cyan[{}]"_styled, dest_dir); |
|
|
|
|
|
|
|
|
dds_log(info, "Creating directory [.br.cyan[{}]]"_styled, dest_dir.string()); |
|
|
fs::create_directories(dest_dir); |
|
|
fs::create_directories(dest_dir); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (opts.dry_run) { |
|
|
if (opts.dry_run) { |
|
|
if (fs::is_symlink(dest_path)) { |
|
|
if (fs::is_symlink(dest_path)) { |
|
|
dds_log(info, "Would remove symlink .br.cyan[{}]"_styled, dest_path); |
|
|
|
|
|
|
|
|
dds_log(info, "Would remove symlink [.br.cyan[{}]]"_styled, dest_path.string()); |
|
|
} |
|
|
} |
|
|
if (fs::exists(dest_path) && !fs::is_symlink(dest_path)) { |
|
|
if (fs::exists(dest_path) && !fs::is_symlink(dest_path)) { |
|
|
if (opts.install_yourself.symlink) { |
|
|
if (opts.install_yourself.symlink) { |
|
|
dds_log( |
|
|
dds_log( |
|
|
info, |
|
|
info, |
|
|
"Would overwrite .br.yellow[{0}] with a symlink .br.green[{0}] -> .br.cyan[{1}]"_styled, |
|
|
"Would overwrite .br.yellow[{0}] with a symlink .br.green[{0}] -> .br.cyan[{1}]"_styled, |
|
|
dest_path, |
|
|
|
|
|
self_exe); |
|
|
|
|
|
|
|
|
dest_path.string(), |
|
|
|
|
|
self_exe.string()); |
|
|
} else { |
|
|
} else { |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
"Would overwrite .br.yellow[{}] with .br.cyan[{}]"_styled, |
|
|
|
|
|
dest_path, |
|
|
|
|
|
self_exe); |
|
|
|
|
|
|
|
|
"Would overwrite .br.yellow[{}] with [.br.cyan[{}]]"_styled, |
|
|
|
|
|
dest_path.string(), |
|
|
|
|
|
self_exe.string()); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
if (opts.install_yourself.symlink) { |
|
|
if (opts.install_yourself.symlink) { |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
"Would create a symlink .br.green[{}] -> .br.cyan[{}]"_styled, |
|
|
|
|
|
dest_path, |
|
|
|
|
|
self_exe); |
|
|
|
|
|
|
|
|
"Would create a symlink [.br.green[{}]] -> [.br.cyan[{}]]"_styled, |
|
|
|
|
|
dest_path.string(), |
|
|
|
|
|
self_exe.string()); |
|
|
} else { |
|
|
} else { |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
"Would install .br.cyan[{}] to .br.yellow[{}]"_styled, |
|
|
|
|
|
self_exe, |
|
|
|
|
|
dest_path); |
|
|
|
|
|
|
|
|
"Would install [.br.cyan[{}]] to .br.yellow[{}]"_styled, |
|
|
|
|
|
self_exe.string(), |
|
|
|
|
|
dest_path.string()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
if (fs::is_symlink(dest_path)) { |
|
|
if (fs::is_symlink(dest_path)) { |
|
|
dds_log(info, "Removing old symlink file .br.cyan[{}]"_styled, dest_path); |
|
|
|
|
|
|
|
|
dds_log(info, "Removing old symlink file [.br.cyan[{}]]"_styled, dest_path.string()); |
|
|
dds::remove_file(dest_path).value(); |
|
|
dds::remove_file(dest_path).value(); |
|
|
} |
|
|
} |
|
|
if (opts.install_yourself.symlink) { |
|
|
if (opts.install_yourself.symlink) { |
|
|
if (fs::exists(dest_path)) { |
|
|
if (fs::exists(dest_path)) { |
|
|
dds_log(info, "Removing previous file .br.cyan[{}]"_styled, dest_path); |
|
|
|
|
|
|
|
|
dds_log(info, "Removing previous file [.br.cyan[{}]]"_styled, dest_path.string()); |
|
|
dds::remove_file(dest_path).value(); |
|
|
dds::remove_file(dest_path).value(); |
|
|
} |
|
|
} |
|
|
dds_log(info, |
|
|
dds_log(info, |
|
|
"Creating symbolic link .br.green[{}] -> .br.cyan[{}]"_styled, |
|
|
|
|
|
dest_path, |
|
|
|
|
|
self_exe); |
|
|
|
|
|
|
|
|
"Creating symbolic link [.br.green[{}]] -> [.br.cyan[{}]]"_styled, |
|
|
|
|
|
dest_path.string(), |
|
|
|
|
|
self_exe.string()); |
|
|
dds::create_symlink(self_exe, dest_path).value(); |
|
|
dds::create_symlink(self_exe, dest_path).value(); |
|
|
} else { |
|
|
} else { |
|
|
dds_log(info, "Installing .br.cyan[{}] to .br.green[{}]"_styled, self_exe, dest_path); |
|
|
|
|
|
|
|
|
dds_log(info, |
|
|
|
|
|
"Installing [.br.cyan[{}]] to [.br.green[{}]]"_styled, |
|
|
|
|
|
self_exe.string(), |
|
|
|
|
|
dest_path.string()); |
|
|
dds::copy_file(self_exe, dest_path, fs::copy_options::overwrite_existing).value(); |
|
|
dds::copy_file(self_exe, dest_path, fs::copy_options::overwrite_existing).value(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
}, |
|
|
}, |
|
|
[](std::error_code ec, e_copy_file copy) { |
|
|
[](std::error_code ec, e_copy_file copy) { |
|
|
dds_log(error, |
|
|
dds_log(error, |
|
|
"Failed to copy file .br.cyan[{}] to .br.yellow[{}]: .bold.red[{}]"_styled, |
|
|
|
|
|
copy.source, |
|
|
|
|
|
copy.dest, |
|
|
|
|
|
|
|
|
"Failed to copy file [.br.cyan[{}]] to .br.yellow[{}]: .bold.red[{}]"_styled, |
|
|
|
|
|
copy.source.string(), |
|
|
|
|
|
copy.dest.string(), |
|
|
ec.message()); |
|
|
ec.message()); |
|
|
return 1; |
|
|
return 1; |
|
|
}, |
|
|
}, |
|
|
[](std::error_code ec, e_remove_file file) { |
|
|
[](std::error_code ec, e_remove_file file) { |
|
|
dds_log(error, |
|
|
dds_log(error, |
|
|
"Failed to delete file .br.yellow[{}]: .bold.red[{}]"_styled, |
|
|
"Failed to delete file .br.yellow[{}]: .bold.red[{}]"_styled, |
|
|
file.value, |
|
|
|
|
|
|
|
|
file.value.string(), |
|
|
ec.message()); |
|
|
ec.message()); |
|
|
return 1; |
|
|
return 1; |
|
|
}, |
|
|
}, |
|
|
[](std::error_code ec, e_symlink oper) { |
|
|
[](std::error_code ec, e_symlink oper) { |
|
|
dds_log( |
|
|
dds_log( |
|
|
error, |
|
|
error, |
|
|
"Failed to create symlink from .br.yellow[{}] to .br.cyan[{}]: .bold.red[{}]"_styled, |
|
|
|
|
|
oper.symlink, |
|
|
|
|
|
oper.target, |
|
|
|
|
|
|
|
|
"Failed to create symlink from .br.yellow[{}] to [.br.cyan[{}]]: .bold.red[{}]"_styled, |
|
|
|
|
|
oper.symlink.string(), |
|
|
|
|
|
oper.target.string(), |
|
|
ec.message()); |
|
|
ec.message()); |
|
|
return 1; |
|
|
return 1; |
|
|
}, |
|
|
}, |