- 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) |