Browse Source

Implement install-yourself for Windows

default_compile_flags
vector-of-bool 3 years ago
parent
commit
f896c950c6
1 changed files with 144 additions and 45 deletions
  1. +144
    -45
      src/dds/cli/cmd/install_yourself.cpp

+ 144
- 45
src/dds/cli/cmd/install_yourself.cpp View File



#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;
}, },

Loading…
Cancel
Save