- Add a getenv() abstraction that is more versatile than the std:: onedefault_compile_flags
| @@ -1,5 +1,6 @@ | |||
| #include <dds/cli/dispatch_main.hpp> | |||
| #include <dds/cli/options.hpp> | |||
| #include <dds/util/env.hpp> | |||
| #include <dds/util/log.hpp> | |||
| #include <dds/util/output.hpp> | |||
| #include <dds/util/signal.hpp> | |||
| @@ -17,12 +18,12 @@ | |||
| #include <locale> | |||
| static void load_locale() { | |||
| auto lang = std::getenv("LANG"); | |||
| auto lang = dds::getenv("LANG"); | |||
| if (!lang) { | |||
| return; | |||
| } | |||
| try { | |||
| std::locale::global(std::locale(lang)); | |||
| std::locale::global(std::locale(*lang)); | |||
| } catch (const std::runtime_error& e) { | |||
| // No locale with the given name | |||
| return; | |||
| @@ -4,6 +4,7 @@ | |||
| #include <dds/error/errors.hpp> | |||
| #include <dds/error/nonesuch.hpp> | |||
| #include <dds/solve/solve.hpp> | |||
| #include <dds/util/env.hpp> | |||
| #include <dds/util/log.hpp> | |||
| #include <dds/util/paths.hpp> | |||
| @@ -24,6 +25,12 @@ using namespace dds; | |||
| namespace nsql = neo::sqlite3; | |||
| using namespace neo::sqlite3::literals; | |||
| namespace dds { | |||
| void add_init_repo(nsql::database_ref db) noexcept; | |||
| } // namespace dds | |||
| namespace { | |||
| void migrate_repodb_1(nsql::database& db) { | |||
| @@ -225,6 +232,13 @@ void ensure_migrated(nsql::database& db) { | |||
| } | |||
| meta["version"] = current_database_version; | |||
| 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 | |||
| @@ -9,7 +9,9 @@ | |||
| #include <dds/util/log.hpp> | |||
| #include <dds/util/result.hpp> | |||
| #include <boost/leaf/handle_exception.hpp> | |||
| #include <fansi/styled.hpp> | |||
| #include <fmt/ostream.h> | |||
| #include <neo/event.hpp> | |||
| #include <neo/io/stream/buffers.hpp> | |||
| #include <neo/io/stream/file.hpp> | |||
| @@ -224,18 +226,18 @@ void dds::update_all_remotes(nsql::database_ref db) { | |||
| } | |||
| 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 = ?"); | |||
| 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) { | |||
| BOOST_LEAF_THROW_EXCEPTION( // | |||
| make_user_error<errc::no_catalog_remote_info>("There is no remote with name '{}'", | |||
| name), | |||
| [&] { | |||
| 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&& [n] = tup; | |||
| return n; | |||
| @@ -245,5 +247,63 @@ void dds::remove_remote(pkg_db& pkdb, std::string_view name) { | |||
| }); | |||
| } | |||
| 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); | |||
| } | |||
| } | |||
| @@ -34,4 +34,6 @@ public: | |||
| void update_all_remotes(neo::sqlite3::database_ref); | |||
| void remove_remote(pkg_db& db, std::string_view name); | |||
| void add_init_repo(neo::sqlite3::database_ref db) noexcept; | |||
| } // namespace dds | |||
| @@ -0,0 +1,18 @@ | |||
| #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"); | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| #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 | |||
| @@ -2,6 +2,7 @@ | |||
| #include "./paths.hpp" | |||
| #include <dds/util/env.hpp> | |||
| #include <dds/util/log.hpp> | |||
| #include <cstdlib> | |||
| @@ -10,45 +11,34 @@ using namespace dds; | |||
| fs::path dds::user_home_dir() { | |||
| 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!"); | |||
| return "/"; | |||
| } | |||
| return fs::absolute(fs::path(home_env)); | |||
| })); | |||
| }(); | |||
| return ret; | |||
| } | |||
| fs::path dds::user_data_dir() { | |||
| 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; | |||
| } | |||
| fs::path dds::user_cache_dir() { | |||
| 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; | |||
| } | |||
| fs::path dds::user_config_dir() { | |||
| 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; | |||
| } | |||
| @@ -2,6 +2,7 @@ | |||
| #include "./paths.hpp" | |||
| #include <dds/util/env.hpp> | |||
| #include <dds/util/log.hpp> | |||
| #include <cstdlib> | |||
| @@ -10,12 +11,10 @@ using namespace dds; | |||
| fs::path dds::user_home_dir() { | |||
| 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 fs::absolute(fs::path(home_env)); | |||
| })); | |||
| }(); | |||
| return ret; | |||
| } | |||
| @@ -24,4 +23,4 @@ fs::path dds::user_data_dir() { return user_home_dir() / "Library/Application Su | |||
| fs::path dds::user_cache_dir() { return user_home_dir() / "Library/Caches"; } | |||
| fs::path dds::user_config_dir() { return user_home_dir() / "Preferences"; } | |||
| #endif | |||
| #endif | |||
| @@ -1,5 +1,6 @@ | |||
| #include "./result.hpp" | |||
| #include <dds/util/env.hpp> | |||
| #include <dds/util/log.hpp> | |||
| #include <fmt/ostream.h> | |||
| @@ -23,9 +24,9 @@ void dds::capture_exception() { | |||
| void dds::write_error_marker(std::string_view error) noexcept { | |||
| 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) { | |||
| std::ofstream outfile{efile_path, std::ios::binary}; | |||
| std::ofstream outfile{*efile_path, std::ios::binary}; | |||
| fmt::print(outfile, "{}", error); | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| import multiprocessing | |||
| import shutil | |||
| import os | |||
| from pathlib import Path | |||
| import copy | |||
| from typing import Optional, TypeVar, Iterable | |||
| @@ -61,7 +62,9 @@ class DDSWrapper: | |||
| def run(self, args: proc.CommandLine, *, cwd: Optional[Pathish] = None) -> None: | |||
| """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: | |||
| """Run 'catalog import' to import the given JSON. Only applicable to older 'dds'""" | |||
| @@ -1,5 +1,5 @@ | |||
| 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 | |||
| import subprocess | |||
| @@ -40,9 +40,12 @@ def flatten_cmd(cmd: CommandLine) -> Iterable[str]: | |||
| 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)) | |||
| res = subprocess.run(command, cwd=cwd, check=False) | |||
| res = subprocess.run(command, cwd=cwd, check=False, env=env) | |||
| if res.returncode and check: | |||
| raise_error(res) | |||
| return res | |||
| @@ -52,5 +55,7 @@ def raise_error(proc: ProcessResult) -> NoReturn: | |||
| 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) | |||