diff --git a/lix/libmain/common-args.cc b/lix/libmain/common-args.cc index ba2d3ee0c..0367c53cc 100644 --- a/lix/libmain/common-args.cc +++ b/lix/libmain/common-args.cc @@ -1,8 +1,8 @@ #include "lix/libmain/common-args.hh" #include "lix/libutil/args/root.hh" +#include "lix/libutil/config-impl.hh" // IWYU pragma: keep #include "lix/libutil/error.hh" #include "lix/libstore/globals.hh" -#include "lix/libmain/loggers.hh" #include "lix/libutil/logging.hh" namespace nix { @@ -62,7 +62,9 @@ MixCommonArgs::MixCommonArgs(const std::string & programName) .description = "Set the format of log output; one of `raw`, `internal-json`, `bar`, `bar-with-logs`, `multiline` or `multiline-with-logs`.", .category = loggingCategory, .labels = {"format"}, - .handler = {[](std::string format) { setLogFormat(format); }}, + .handler = {[&](std::string format) { + loggerSettings.logFormat.set(format); + }}, }); addFlag({ diff --git a/lix/libmain/loggers.cc b/lix/libmain/loggers.cc index ff9a03360..12301c392 100644 --- a/lix/libmain/loggers.cc +++ b/lix/libmain/loggers.cc @@ -1,29 +1,17 @@ #include "lix/libutil/environment-variables.hh" #include "lix/libmain/loggers.hh" #include "lix/libmain/progress-bar.hh" +#include "lix/libutil/log-format.hh" +#include "lix/libutil/config-impl.hh" // IWYU pragma: keep namespace nix { -LogFormat defaultLogFormat = LogFormat::Auto; - -[[deprecated]] -LogFormat parseLogFormat(const std::string & logFormatStr) { - if (auto const parsed = LogFormat::parse(logFormatStr)) { - return *parsed; - } - throw Error("setting 'log-format' has an invalid value '%s'", logFormatStr); -} - -Logger * makeDefaultLogger() { - return getLoggerByFormat(defaultLogFormat); -} - -void setLogFormat(const std::string & logFormatStr) { - setLogFormat(parseLogFormat(logFormatStr)); +static Logger * makeDefaultLogger() { + return getLoggerByFormat(loggerSettings.logFormat); } void setLogFormat(const LogFormat & logFormat) { - defaultLogFormat = logFormat; + loggerSettings.logFormat.override(logFormat); createDefaultLogger(); } @@ -36,7 +24,7 @@ Logger * getLoggerByFormat(LogFormat logFormat) using enum LogFormatValue; switch (logFormat) { case LogFormat::Auto: - return getLoggerByFormat(defaultLogFormat); + return getLoggerByFormat(loggerSettings.logFormat.autoValue); case LogFormat::Raw: return makeSimpleLogger(false); case LogFormat::RawWithLogs: diff --git a/lix/libmain/loggers.hh b/lix/libmain/loggers.hh index 33cf133ac..323a913b4 100644 --- a/lix/libmain/loggers.hh +++ b/lix/libmain/loggers.hh @@ -8,9 +8,7 @@ namespace nix { class Logger; -[[deprecated]] -void setLogFormat(const std::string & logFormatStr); -[[deprecated]] +/** Overrides the current log format, and re-creates the current logger. */ void setLogFormat(const LogFormat & logFormat); void createDefaultLogger(); diff --git a/lix/libmain/shared.cc b/lix/libmain/shared.cc index bcf8ea3e8..e2cba0663 100644 --- a/lix/libmain/shared.cc +++ b/lix/libmain/shared.cc @@ -200,7 +200,10 @@ LegacyArgs::LegacyArgs(AsyncIoRoot & aio, const std::string & programName, .longName = "no-build-output", .shortName = 'Q', .description = "Do not show build output.", - .handler = {[&]() {setLogFormat(LogFormat::Raw); }}, + .handler = {[&]() { + loggerSettings.logFormat.setDefault(loggerSettings.logFormat.get().withoutLogs()); + loggerSettings.logFormat.autoValue = loggerSettings.logFormat.get().withoutLogs(); + }}, }); addFlag({ diff --git a/lix/libutil/config-impl.hh b/lix/libutil/config-impl.hh index 9cdc5964b..d25ec5fd1 100644 --- a/lix/libutil/config-impl.hh +++ b/lix/libutil/config-impl.hh @@ -133,6 +133,7 @@ DECLARE_CONFIG_SERIALISER(StringSet) DECLARE_CONFIG_SERIALISER(StringMap) DECLARE_CONFIG_SERIALISER(ExperimentalFeatures) DECLARE_CONFIG_SERIALISER(DeprecatedFeatures) +DECLARE_CONFIG_SERIALISER(LogFormat) template T BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const diff --git a/lix/libutil/log-format.cc b/lix/libutil/log-format.cc index 6062cc203..dee1616d5 100644 --- a/lix/libutil/log-format.cc +++ b/lix/libutil/log-format.cc @@ -1,4 +1,48 @@ #include "lix/libutil/log-format.hh" +#include "lix/libutil/abstract-setting-to-json.hh" // IWYU pragma: keep +#include "lix/libutil/json.hh" +#include "lix/libutil/log-format.hh" + +#include + +namespace nix { +template<> +std::string BaseSetting::to_string() const +{ + return std::format("{}", value); +} + +template<> +LogFormat +BaseSetting::parse(const std::string & str, const ApplyConfigOptions & options) const +{ + if (auto const parsed = LogFormat::parse(str)) { + return *parsed; + } + throw UsageError("setting '%s' has invalid value '%s'", name, str); +} + +void to_json(JSON & j, const LogFormat & self) +{ + j = std::format("{}", self); +} + +void from_json(const JSON & j, LogFormat & self) +{ + std::string asStr = ensureType(j, JSON::value_t::string); + auto const parsed = LogFormat::parse(asStr); + if (!parsed) { + throw Error("invalid json for 'log-format': %s", j); + } + self = *parsed; +} + +// Explicitly instantiate the non-specialized templates. +// `abstract-setting-to-json.hh` is IWYU-kept so this line also instantiates that template. +template class BaseSetting; + +} + static_assert(std::formattable); static_assert(std::formattable); diff --git a/lix/libutil/log-format.hh b/lix/libutil/log-format.hh index 79378d543..a35f951dd 100644 --- a/lix/libutil/log-format.hh +++ b/lix/libutil/log-format.hh @@ -1,6 +1,7 @@ #pragma once ///@file +#include "lix/libutil/config.hh" #include "lix/libutil/fmt.hh" #include "lix/libutil/json-fwd.hh" @@ -156,6 +157,30 @@ struct json::is_integral_enum : std::true_type {}; template<> struct json::is_integral_enum : std::true_type {}; +/** Note: you'll have to include `config-impl.hh` when you want to use methods from this type. */ +struct LogFormatSetting : public BaseSetting +{ + // I hate global state, man. + LogFormat autoValue = LogFormat::RawWithLogs; + + LogFormatSetting( + Config * options, + const LogFormat & def, + const std::string & name, + const std::string & description, + const std::set & aliases = {}, + bool documentDefault = true, + std::optional experimentalFeature = std::nullopt, + bool deprecated = false + ) : BaseSetting(def, true, name, description, aliases, experimentalFeature, deprecated) + { + options->addSetting(this); + } +}; + +void to_json(JSON & j, const LogFormat & self); +void from_json(const JSON & j, LogFormat & self); + } template<> diff --git a/lix/libutil/logging-settings/log-format.md b/lix/libutil/logging-settings/log-format.md new file mode 100644 index 000000000..b0e1b614f --- /dev/null +++ b/lix/libutil/logging-settings/log-format.md @@ -0,0 +1,11 @@ +--- +name: log-format +internalName: logFormat +settingType: LogFormatSetting +defaultExpr: 'LogFormat::Auto' +defaultText: auto +--- +Set the format of log output; one of `raw`, `internal-json`, `bar`, `bar-with-logs`, `multiline` or `multiline-with-logs`. + +For legacy reasons, the default value "auto" makes the actual log format depend on which command you're using. +The legacy `nix-` CLI will use `raw-with-logs` (or `raw` with `-Q`/`--no-build-output`), and `nix3` commands will use `bar-with-logs`. diff --git a/lix/libutil/logging.cc b/lix/libutil/logging.cc index 45196cac8..57fe06787 100644 --- a/lix/libutil/logging.cc +++ b/lix/libutil/logging.cc @@ -1,4 +1,5 @@ #include "c-calls.hh" +#include "lix/libutil/config-impl.hh" #include "lix/libutil/environment-variables.hh" #include "lix/libutil/file-descriptor.hh" #include "lix/libutil/logging.hh" diff --git a/lix/libutil/logging.hh b/lix/libutil/logging.hh index 1da9d53bc..e37df0025 100644 --- a/lix/libutil/logging.hh +++ b/lix/libutil/logging.hh @@ -4,7 +4,7 @@ #include "lix/libutil/types.hh" #include "lix/libutil/error.hh" #include "lix/libutil/config.hh" -#include "lix/libutil/log-format.hh" +#include "lix/libutil/log-format.hh" // IWYU pragma: keep #include "result.hh" #include "serialise.hh" #include diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index bbd766689..4b8334c73 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -282,7 +282,10 @@ libutil_settings_headers += custom_target( ) logging_setting_definitions = files( + # keep-sorted start + 'logging-settings/log-format.md', 'logging-settings/show-trace.md', + # keep-sorted end ) libutil_settings_headers += custom_target( command : [ diff --git a/lix/nix/main.cc b/lix/nix/main.cc index 0798f1cdf..05f7ee3a8 100644 --- a/lix/nix/main.cc +++ b/lix/nix/main.cc @@ -228,7 +228,9 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs .shortName = 'L', .description = "Print full build logs on standard error.", .category = loggingCategory, - .handler = {[&]() { logger->setPrintBuildLogs(true); }}, + .handler = {[&]() { + loggerSettings.logFormat.setDefault(loggerSettings.logFormat.get().withLogs()); + }}, .experimentalFeature = Xp::NixCommand, }); @@ -500,7 +502,7 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv) evalSettings.pureEval.setDefault(true); - setLogFormat(LogFormat::BarWithLogs); + loggerSettings.logFormat.autoValue = LogFormat::BarWithLogs; // FIXME: stop messing about with log verbosity depending on if it is interactive use if (isatty(STDERR_FILENO)) { @@ -578,6 +580,9 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv) if (!args.helpRequested && !args.completions) throw; } + // HACK: after args.parseCmdline() we re-create the default logger, to apply --option flags. + createDefaultLogger(); + if (args.completions) { switch (args.completions->type) { case Completions::Type::Normal: diff --git a/lix/nix/prefetch.cc b/lix/nix/prefetch.cc index a3161263d..e5fd29919 100644 --- a/lix/nix/prefetch.cc +++ b/lix/nix/prefetch.cc @@ -188,8 +188,9 @@ static int main_nix_prefetch_url(AsyncIoRoot & aio, std::string programName, Str if (args.size() > 2) throw UsageError("too many arguments"); - if (isOutputARealTerminal(StandardOutputStream::Stderr)) - setLogFormat(LogFormat::Bar); + if (isOutputARealTerminal(StandardOutputStream::Stderr)) { + loggerSettings.logFormat.autoValue = LogFormat::Bar; + } auto store = aio.blockOn(openStore()); auto evaluator = std::make_unique(aio, myArgs.searchPath, store); diff --git a/tests/functional2/cli/test_completions.py b/tests/functional2/cli/test_completions.py index e9bded03f..0a3194d0a 100644 --- a/tests/functional2/cli/test_completions.py +++ b/tests/functional2/cli/test_completions.py @@ -131,9 +131,9 @@ def test_completions_flake_update(nix: Nix, files: Path): def test_flag_completion(nix: Nix): nix.env["NIX_GET_COMPLETIONS"] = "2" - res = nix.nix(["build", "--log-form"]).run().ok() - assert "--log-format" in res.stdout_plain - assert "Set the format of log output; one of" in res.stdout_plain + res = nix.nix(["build", "--dry"]).run().ok() + assert "--dry-run" in res.stdout_plain + assert "Show what this command would do without doing it" in res.stdout_plain def test_option_completion(nix: Nix): diff --git a/tests/unit/libmain/progress-bar.cc b/tests/unit/libmain/progress-bar.cc index 0862111e0..99ba991ea 100644 --- a/tests/unit/libmain/progress-bar.cc +++ b/tests/unit/libmain/progress-bar.cc @@ -3,6 +3,7 @@ #include "lix/libexpr/eval.hh" #include "lix/libmain/progress-bar.hh" #include "lix/libmain/loggers.hh" +#include "lix/libutil/config-impl.hh" #include "lix/libutil/logging.hh" #include "lix/libmain/shared.hh"