- Add a getenv() abstraction that is more versatile than the std:: onedefault_compile_flags
#include <dds/cli/dispatch_main.hpp> | #include <dds/cli/dispatch_main.hpp> | ||||
#include <dds/cli/options.hpp> | #include <dds/cli/options.hpp> | ||||
#include <dds/util/env.hpp> | |||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <dds/util/output.hpp> | #include <dds/util/output.hpp> | ||||
#include <dds/util/signal.hpp> | #include <dds/util/signal.hpp> | ||||
#include <locale> | #include <locale> | ||||
static void load_locale() { | static void load_locale() { | ||||
auto lang = std::getenv("LANG"); | |||||
auto lang = dds::getenv("LANG"); | |||||
if (!lang) { | if (!lang) { | ||||
return; | return; | ||||
} | } | ||||
try { | try { | ||||
std::locale::global(std::locale(lang)); | |||||
std::locale::global(std::locale(*lang)); | |||||
} catch (const std::runtime_error& e) { | } catch (const std::runtime_error& e) { | ||||
// No locale with the given name | // No locale with the given name | ||||
return; | return; |
#include <dds/error/errors.hpp> | #include <dds/error/errors.hpp> | ||||
#include <dds/error/nonesuch.hpp> | #include <dds/error/nonesuch.hpp> | ||||
#include <dds/solve/solve.hpp> | #include <dds/solve/solve.hpp> | ||||
#include <dds/util/env.hpp> | |||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <dds/util/paths.hpp> | #include <dds/util/paths.hpp> | ||||
namespace nsql = neo::sqlite3; | namespace nsql = neo::sqlite3; | ||||
using namespace neo::sqlite3::literals; | using namespace neo::sqlite3::literals; | ||||
namespace dds { | |||||
void add_init_repo(nsql::database_ref db) noexcept; | |||||
} // namespace dds | |||||
namespace { | namespace { | ||||
void migrate_repodb_1(nsql::database& db) { | void migrate_repodb_1(nsql::database& db) { | ||||
} | } | ||||
meta["version"] = current_database_version; | meta["version"] = current_database_version; | ||||
exec(db.prepare("UPDATE dds_cat_meta SET meta=?"), meta.dump()); | exec(db.prepare("UPDATE dds_cat_meta SET meta=?"), meta.dump()); | ||||
tr.commit(); | |||||
if (version < 3 && !getenv_bool("DDS_NO_ADD_INITIAL_REPO")) { | |||||
// Version 3 introduced remote repositories. If we're updating to 3, add that repo now | |||||
dds_log(info, "Downloading initial repository"); | |||||
dds::add_init_repo(db); | |||||
} | |||||
} | } | ||||
} // namespace | } // namespace |
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <dds/util/result.hpp> | #include <dds/util/result.hpp> | ||||
#include <boost/leaf/handle_exception.hpp> | |||||
#include <fansi/styled.hpp> | #include <fansi/styled.hpp> | ||||
#include <fmt/ostream.h> | |||||
#include <neo/event.hpp> | #include <neo/event.hpp> | ||||
#include <neo/io/stream/buffers.hpp> | #include <neo/io/stream/buffers.hpp> | ||||
#include <neo/io/stream/file.hpp> | #include <neo/io/stream/file.hpp> | ||||
} | } | ||||
void dds::remove_remote(pkg_db& pkdb, std::string_view name) { | void dds::remove_remote(pkg_db& pkdb, std::string_view name) { | ||||
auto& db = pkdb.database(); | |||||
neo::sqlite3::transaction_guard tr{db}; | |||||
auto& db = pkdb.database(); | |||||
nsql::transaction_guard tr{db}; | |||||
auto get_rowid_st = db.prepare("SELECT remote_id FROM dds_pkg_remotes WHERE name = ?"); | auto get_rowid_st = db.prepare("SELECT remote_id FROM dds_pkg_remotes WHERE name = ?"); | ||||
get_rowid_st.bindings()[1] = name; | get_rowid_st.bindings()[1] = name; | ||||
auto row = neo::sqlite3::unpack_single_opt<std::int64_t>(get_rowid_st); | |||||
auto row = nsql::unpack_single_opt<std::int64_t>(get_rowid_st); | |||||
if (!row) { | if (!row) { | ||||
BOOST_LEAF_THROW_EXCEPTION( // | BOOST_LEAF_THROW_EXCEPTION( // | ||||
make_user_error<errc::no_catalog_remote_info>("There is no remote with name '{}'", | make_user_error<errc::no_catalog_remote_info>("There is no remote with name '{}'", | ||||
name), | name), | ||||
[&] { | [&] { | ||||
auto all_st = db.prepare("SELECT name FROM dds_pkg_remotes"); | auto all_st = db.prepare("SELECT name FROM dds_pkg_remotes"); | ||||
auto tups = neo::sqlite3::iter_tuples<std::string>(all_st); | |||||
auto tups = nsql::iter_tuples<std::string>(all_st); | |||||
auto names = tups | ranges::views::transform([](auto&& tup) { | auto names = tups | ranges::views::transform([](auto&& tup) { | ||||
auto&& [n] = tup; | auto&& [n] = tup; | ||||
return n; | return n; | ||||
}); | }); | ||||
} | } | ||||
auto [rowid] = *row; | auto [rowid] = *row; | ||||
neo::sqlite3::exec(db.prepare("DELETE FROM dds_pkg_remotes WHERE remote_id = ?"), rowid); | |||||
nsql::exec(db.prepare("DELETE FROM dds_pkg_remotes WHERE remote_id = ?"), rowid); | |||||
} | |||||
void dds::add_init_repo(nsql::database_ref db) noexcept { | |||||
std::string_view init_repo = "https://repo-1.dds.pizza"; | |||||
// _Do not_ let errors stop us from continuing | |||||
bool okay = boost::leaf::try_catch( | |||||
[&]() -> bool { | |||||
try { | |||||
auto remote = pkg_remote::connect(init_repo); | |||||
remote.store(db); | |||||
update_all_remotes(db); | |||||
return true; | |||||
} catch (...) { | |||||
capture_exception(); | |||||
} | |||||
}, | |||||
[](http_status_error err, http_response_info resp, neo::url url) { | |||||
dds_log(error, | |||||
"An HTTP error occurred while adding the initial repository [{}]: HTTP Status " | |||||
"{} {}", | |||||
err.what(), | |||||
url.to_string(), | |||||
resp.status, | |||||
resp.status_message); | |||||
return false; | |||||
}, | |||||
[](e_sqlite3_error_exc e, neo::url url) { | |||||
dds_log(error, | |||||
"Error accessing remote database while adding initial repository: {}: {}", | |||||
url.to_string(), | |||||
e.message); | |||||
return false; | |||||
}, | |||||
[](e_sqlite3_error_exc e) { | |||||
dds_log(error, "Unexpected database error: {}", e.message); | |||||
return false; | |||||
}, | |||||
[](e_system_error_exc e, network_origin conn) { | |||||
dds_log(error, | |||||
"Error communicating with [.br.red[{}://{}:{}]`]: {}"_styled, | |||||
conn.protocol, | |||||
conn.hostname, | |||||
conn.port, | |||||
e.message); | |||||
return false; | |||||
}, | |||||
[](boost::leaf::diagnostic_info const& diag) -> int { | |||||
dds_log(critical, "Unhandled error while adding initial package repository: ", diag); | |||||
throw; | |||||
}); | |||||
if (!okay) { | |||||
dds_log(warn, "We failed to add the initial package repository [{}]", init_repo); | |||||
dds_log(warn, "No remote packages will be available until the above issue is resolved."); | |||||
dds_log( | |||||
warn, | |||||
"The remote package repository can be added again with [.br.yellow[dds pkg repo add \"{}\"]]"_styled, | |||||
init_repo); | |||||
} | |||||
} | } |
void update_all_remotes(neo::sqlite3::database_ref); | void update_all_remotes(neo::sqlite3::database_ref); | ||||
void remove_remote(pkg_db& db, std::string_view name); | void remove_remote(pkg_db& db, std::string_view name); | ||||
void add_init_repo(neo::sqlite3::database_ref db) noexcept; | |||||
} // namespace dds | } // namespace dds |
#include "./env.hpp" | |||||
#include <neo/utility.hpp> | |||||
#include <cstdlib> | |||||
std::optional<std::string> dds::getenv(const std::string& varname) noexcept { | |||||
auto cptr = std::getenv(varname.data()); | |||||
if (cptr) { | |||||
return std::string(cptr); | |||||
} | |||||
return {}; | |||||
} | |||||
bool dds::getenv_bool(const std::string& varname) noexcept { | |||||
auto s = getenv(varname); | |||||
return s == neo::oper::any_of("1", "true", "on", "TRUE", "ON", "YES", "yes"); | |||||
} |
#pragma once | |||||
#include <neo/concepts.hpp> | |||||
#include <optional> | |||||
#include <string> | |||||
namespace dds { | |||||
std::optional<std::string> getenv(const std::string& env) noexcept; | |||||
bool getenv_bool(const std::string& env) noexcept; | |||||
template <neo::invocable Func> | |||||
std::string getenv(const std::string& name, Func&& fn) noexcept(noexcept(fn())) { | |||||
auto val = getenv(name); | |||||
if (!val) { | |||||
return std::string(fn()); | |||||
} | |||||
return *val; | |||||
} | |||||
} // namespace dds |
#include "./paths.hpp" | #include "./paths.hpp" | ||||
#include <dds/util/env.hpp> | |||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
fs::path dds::user_home_dir() { | fs::path dds::user_home_dir() { | ||||
static auto ret = []() -> fs::path { | static auto ret = []() -> fs::path { | ||||
auto home_env = std::getenv("HOME"); | |||||
if (!home_env) { | |||||
return fs::absolute(dds::getenv("HOME", [] { | |||||
dds_log(error, "No HOME environment variable set!"); | dds_log(error, "No HOME environment variable set!"); | ||||
return "/"; | return "/"; | ||||
} | |||||
return fs::absolute(fs::path(home_env)); | |||||
})); | |||||
}(); | }(); | ||||
return ret; | return ret; | ||||
} | } | ||||
fs::path dds::user_data_dir() { | fs::path dds::user_data_dir() { | ||||
static auto ret = []() -> fs::path { | static auto ret = []() -> fs::path { | ||||
auto xdg_data_home = std::getenv("XDG_DATA_HOME"); | |||||
if (xdg_data_home) { | |||||
return fs::absolute(fs::path(xdg_data_home)); | |||||
} | |||||
return user_home_dir() / ".local/share"; | |||||
return fs::absolute( | |||||
dds::getenv("XDG_DATA_HOME", [] { return user_home_dir() / ".local/share"; })); | |||||
}(); | }(); | ||||
return ret; | return ret; | ||||
} | } | ||||
fs::path dds::user_cache_dir() { | fs::path dds::user_cache_dir() { | ||||
static auto ret = []() -> fs::path { | static auto ret = []() -> fs::path { | ||||
auto xdg_cache_home = std::getenv("XDG_CACHE_HOME"); | |||||
if (xdg_cache_home) { | |||||
return fs::absolute(fs::path(xdg_cache_home)); | |||||
} | |||||
return user_home_dir() / ".cache"; | |||||
return fs::absolute( | |||||
dds::getenv("XDG_CACHE_HOME", [] { return user_home_dir() / ".cache"; })); | |||||
}(); | }(); | ||||
return ret; | return ret; | ||||
} | } | ||||
fs::path dds::user_config_dir() { | fs::path dds::user_config_dir() { | ||||
static auto ret = []() -> fs::path { | static auto ret = []() -> fs::path { | ||||
auto xdg_config_home = std::getenv("XDG_CONFIG_HOME"); | |||||
if (xdg_config_home) { | |||||
return fs::absolute(fs::path(xdg_config_home)); | |||||
} | |||||
return user_home_dir() / ".config"; | |||||
return fs::absolute( | |||||
dds::getenv("XDG_CONFIG_HOME", [] { return user_home_dir() / ".config"; })); | |||||
}(); | }(); | ||||
return ret; | return ret; | ||||
} | } |
#include "./paths.hpp" | #include "./paths.hpp" | ||||
#include <dds/util/env.hpp> | |||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
fs::path dds::user_home_dir() { | fs::path dds::user_home_dir() { | ||||
static auto ret = []() -> fs::path { | static auto ret = []() -> fs::path { | ||||
auto home_env = std::getenv("HOME"); | |||||
if (!home_env) { | |||||
dds_log(warn, "No HOME environment variable set!"); | |||||
return fs::absolute(dds::getenv("HOME", [] { | |||||
dds_log(error, "No HOME environment variable set!"); | |||||
return "/"; | return "/"; | ||||
} | |||||
return fs::absolute(fs::path(home_env)); | |||||
})); | |||||
}(); | }(); | ||||
return ret; | return ret; | ||||
} | } | ||||
fs::path dds::user_cache_dir() { return user_home_dir() / "Library/Caches"; } | fs::path dds::user_cache_dir() { return user_home_dir() / "Library/Caches"; } | ||||
fs::path dds::user_config_dir() { return user_home_dir() / "Preferences"; } | fs::path dds::user_config_dir() { return user_home_dir() / "Preferences"; } | ||||
#endif | |||||
#endif |
#include "./result.hpp" | #include "./result.hpp" | ||||
#include <dds/util/env.hpp> | |||||
#include <dds/util/log.hpp> | #include <dds/util/log.hpp> | ||||
#include <fmt/ostream.h> | #include <fmt/ostream.h> | ||||
void dds::write_error_marker(std::string_view error) noexcept { | void dds::write_error_marker(std::string_view error) noexcept { | ||||
dds_log(trace, "[error marker {}]", error); | dds_log(trace, "[error marker {}]", error); | ||||
auto efile_path = std::getenv("DDS_WRITE_ERROR_MARKER"); | |||||
auto efile_path = dds::getenv("DDS_WRITE_ERROR_MARKER"); | |||||
if (efile_path) { | if (efile_path) { | ||||
std::ofstream outfile{efile_path, std::ios::binary}; | |||||
std::ofstream outfile{*efile_path, std::ios::binary}; | |||||
fmt::print(outfile, "{}", error); | fmt::print(outfile, "{}", error); | ||||
} | } | ||||
} | } |
import multiprocessing | import multiprocessing | ||||
import shutil | import shutil | ||||
import os | |||||
from pathlib import Path | from pathlib import Path | ||||
import copy | import copy | ||||
from typing import Optional, TypeVar, Iterable | from typing import Optional, TypeVar, Iterable | ||||
def run(self, args: proc.CommandLine, *, cwd: Optional[Pathish] = None) -> None: | def run(self, args: proc.CommandLine, *, cwd: Optional[Pathish] = None) -> None: | ||||
"""Execute the 'dds' executable with the given arguments""" | """Execute the 'dds' executable with the given arguments""" | ||||
proc.check_run([self.path, args], cwd=cwd or self.default_cwd) | |||||
env = os.environ.copy() | |||||
env['DDS_NO_ADD_INITIAL_REPO'] = '1' | |||||
proc.check_run([self.path, args], cwd=cwd or self.default_cwd, env=env) | |||||
def catalog_json_import(self, path: Path) -> None: | def catalog_json_import(self, path: Path) -> None: | ||||
"""Run 'catalog import' to import the given JSON. Only applicable to older 'dds'""" | """Run 'catalog import' to import the given JSON. Only applicable to older 'dds'""" |
from pathlib import PurePath | from pathlib import PurePath | ||||
from typing import Iterable, Union, Optional, Iterator, NoReturn, Sequence | |||||
from typing import Iterable, Union, Optional, Iterator, NoReturn, Sequence, Mapping | |||||
from typing_extensions import Protocol | from typing_extensions import Protocol | ||||
import subprocess | import subprocess | ||||
assert False, f'Invalid command line element: {repr(cmd)}' | assert False, f'Invalid command line element: {repr(cmd)}' | ||||
def run(*cmd: CommandLine, cwd: Optional[Pathish] = None, check: bool = False) -> ProcessResult: | |||||
def run(*cmd: CommandLine, | |||||
cwd: Optional[Pathish] = None, | |||||
check: bool = False, | |||||
env: Optional[Mapping[str, str]] = None) -> ProcessResult: | |||||
command = list(flatten_cmd(cmd)) | command = list(flatten_cmd(cmd)) | ||||
res = subprocess.run(command, cwd=cwd, check=False) | |||||
res = subprocess.run(command, cwd=cwd, check=False, env=env) | |||||
if res.returncode and check: | if res.returncode and check: | ||||
raise_error(res) | raise_error(res) | ||||
return res | return res | ||||
raise subprocess.CalledProcessError(proc.returncode, proc.args, output=proc.stdout, stderr=proc.stderr) | raise subprocess.CalledProcessError(proc.returncode, proc.args, output=proc.stdout, stderr=proc.stderr) | ||||
def check_run(*cmd: CommandLine, cwd: Optional[Pathish] = None) -> ProcessResult: | |||||
return run(cmd, cwd=cwd, check=True) | |||||
def check_run(*cmd: CommandLine, | |||||
cwd: Optional[Pathish] = None, | |||||
env: Optional[Mapping[str, str]] = None) -> ProcessResult: | |||||
return run(cmd, cwd=cwd, check=True, env=env) |