Skip to content
9 changes: 5 additions & 4 deletions .github/workflows/windows_impl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ jobs:
$env:MAMBA_TEST_SHELL_TYPE='powershell'
Remove-Item -Path "env:CONDARC"
# Only rerun flaky tests on the `main` branch
python -m pytest micromamba/tests/ `
-vv --durations=50 `
${{ runner.debug == 'true' && '--capture=tee-sys' || '' }} `
${{ github.ref == 'refs/heads/main' && '--reruns 3' || '' }}
python -m pytest -s micromamba/tests/test_shell.py::test_hook_cmd_exe `
-vv
#--durations=50 `
#${{ runner.debug == 'true' && '--capture=no' || '' }} `
#${{ github.ref == 'refs/heads/main' && '--reruns 3' || '' }}
2 changes: 1 addition & 1 deletion libmamba/src/api/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ namespace mamba
}
else
{
throw std::move(result).value();
throw std::move(result).error();
}

LOG_TRACE << "Using default root prefix for micromamba: " << root_prefix;
Expand Down
12 changes: 11 additions & 1 deletion libmamba/src/core/shell_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -800,10 +800,20 @@ namespace mamba
{
const ShellInitPathsWindowsCmd paths{ root_prefix };

// The default root prefix is validated on the next invocation, so it must
// be a valid conda prefix (pkgs/conda-meta/envs) from the start. It used
// to contain only the hook scripts -> every following run aborted silently.
// std::error_code ec;
// fs::create_directories(root_prefix / "conda-meta", ec);
// if (ec)
// {
// LOG_ERROR << "Failed to create directory '" << (root_prefix / "conda-meta").string()
// << "' : " << ec.message();
// }
for (const auto& directory : paths.every_generated_directories_paths())
{
// Maybe the prefix isn't writable. No big deal, just keep going.
std::error_code maybe_error [[maybe_unused]];
std::error_code maybe_error;
fs::create_directories(directory, maybe_error);
if (maybe_error)
{
Expand Down
263 changes: 152 additions & 111 deletions micromamba/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#endif

#include <algorithm>
#include <iostream>

#include <CLI/CLI.hpp>

Expand Down Expand Up @@ -83,158 +84,198 @@ decide_log_handler(const ContextOptions& options) -> mamba::logging::AnyLogHandl
return mamba::logging::spdlogimpl::LogHandler_spdlog{};
}

void
report_error(int argc, char** argv, const std::string& message)
{
const auto options = decide_preconfig_context_options(argc, argv);
if (options.output_params and options.output_params->json)
{
nlohmann::json output{
{ "success", false },
{ "log_history",
nlohmann::json::array(
{ { { "message", message }, { "level", "critical" }, { "source", "libmamba" } } }
) }
};
std::cout << output.dump(4) << std::endl;
}
else if (not options.output_params or not options.output_params->quiet)
{
std::cerr << message << std::endl;
}
}

int
main(int argc, char** argv)
{
mamba::MainExecutor scoped_threads;
const auto pre_config_options = decide_preconfig_context_options(argc, argv);
mamba::Context ctx{ pre_config_options, decide_log_handler(pre_config_options) };
mamba::Console console{ ctx };
mamba::Configuration config{ ctx };
try
{
mamba::MainExecutor scoped_threads;
const auto pre_config_options = decide_preconfig_context_options(argc, argv);
mamba::Context ctx{ pre_config_options, decide_log_handler(pre_config_options) };
mamba::Console console{ ctx };
mamba::Configuration config{ ctx };

init_console();
mamba::on_scope_exit _console_reset{ [] { reset_console(); } };
init_console();
mamba::on_scope_exit _console_reset{ [] { reset_console(); } };

ctx.command_params.is_mamba_exe = true;
ctx.command_params.is_mamba_exe = true;

CLI::App app{ "Version: " + version() + "\n" };
set_umamba_command(&app, config);
CLI::App app{ "Version: " + version() + "\n" };
set_umamba_command(&app, config);

char** utf8argv;
char** utf8argv;

#ifdef _WIN32
wchar_t** wargv;
wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
wchar_t** wargv;
wargv = CommandLineToArgvW(GetCommandLineW(), &argc);

std::vector<std::string> utf8Args;
std::vector<char*> utf8CharArgs;
for (int i = 0; i < argc; i++)
{
utf8Args.push_back(util::windows_encoding_to_utf8(wargv[i]));
}
for (int i = 0; i < argc; ++i)
{
utf8CharArgs.push_back(utf8Args[i].data());
}
utf8argv = utf8CharArgs.data();
std::vector<std::string> utf8Args;
std::vector<char*> utf8CharArgs;
for (int i = 0; i < argc; i++)
{
utf8Args.push_back(util::windows_encoding_to_utf8(wargv[i]));
}
for (int i = 0; i < argc; ++i)
{
utf8CharArgs.push_back(utf8Args[i].data());
}
utf8argv = utf8CharArgs.data();
#else
utf8argv = argv;
utf8argv = argv;
#endif

if (argc >= 2 && strcmp(argv[1], "completer") == 0)
{
get_completions(&app, config, argc, utf8argv);
return 0;
}
if (argc >= 2 && strcmp(argv[1], "completer") == 0)
{
get_completions(&app, config, argc, utf8argv);
return 0;
}

std::stringstream full_command;
for (int i = 0; i < argc; ++i)
{
full_command << utf8argv[i];
if (i < argc - 1)
std::stringstream full_command;
for (int i = 0; i < argc; ++i)
{
full_command << " ";
full_command << utf8argv[i];
if (i < argc - 1)
{
full_command << " ";
}
}
}
ctx.command_params.current_command = full_command.str();
ctx.command_params.current_command = full_command.str();

std::optional<std::string> error_to_report;
auto handle_exception = [&](auto& e, const auto&... additional_messages)
{
using namespace std::literals;
error_to_report.emplace(e.what());
(error_to_report->append(additional_messages), ...);
set_sig_interrupted();
};
std::optional<std::string> error_to_report;
auto handle_exception = [&](auto& e, const auto&... additional_messages)
{
using namespace std::literals;
error_to_report.emplace(e.what());
(error_to_report->append(additional_messages), ...);
set_sig_interrupted();
};

int return_value = EXIT_SUCCESS;
int return_value = EXIT_SUCCESS;

try
{
// Note: do not use CLI11_PARSE macro as its error handling
// would bypass ours.
app.parse(argc, utf8argv);
if (app.get_subcommands().size() == 0)
try
{
config.load();
Console::instance().print(app.help());
// Note: do not use CLI11_PARSE macro as its error handling
// would bypass ours.
app.parse(argc, utf8argv);
if (app.get_subcommands().size() == 0)
{
config.load();
Console::instance().print(app.help());
}
if (app.got_subcommand("config")
&& app.get_subcommand("config")->get_subcommands().size() == 0)
{
config.load();
Console::instance().print(app.get_subcommand("config")->help());
}
}
if (app.got_subcommand("config")
&& app.get_subcommand("config")->get_subcommands().size() == 0)
catch (const mamba::mamba_error& e)
{
config.load();
Console::instance().print(app.get_subcommand("config")->help());
}
}
catch (const mamba::mamba_error& e)
{
// We treat interruptions (ctrl-c) specially by not logging a critical error.
const bool is_interruption = [&]
{
if (e.error_code() == mamba::mamba_error_code::aggregated)
// We treat interruptions (ctrl-c) specially by not logging a critical error.
const bool is_interruption = [&]
{
// Prefer dynamic_cast: a plain mamba_error can incorrectly carry
// error_code::aggregated after object slicing (mamba-org/mamba#4352).
if (const auto* aggregated_error = dynamic_cast<const mamba::mamba_aggregated_error*>(
&e
))
if (e.error_code() == mamba::mamba_error_code::aggregated)
{
// Prefer dynamic_cast: a plain mamba_error can incorrectly carry
// error_code::aggregated after object slicing (mamba-org/mamba#4352).
if (const auto* aggregated_error = dynamic_cast<const mamba::mamba_aggregated_error*>(
&e
))
{
return aggregated_error->has_only_error(
mamba::mamba_error_code::user_interrupted
);
}
return false;
}
else
{
return aggregated_error->has_only_error(mamba::mamba_error_code::user_interrupted);
return e.error_code() == mamba::mamba_error_code::user_interrupted;
}
return false;
}();

if (is_interruption)
{
LOG_WARNING << e.what();
return 0;
}
else
{
return e.error_code() == mamba::mamba_error_code::user_interrupted;
handle_exception(e);
}
}();

if (is_interruption)
{
LOG_WARNING << e.what();
return 0;
}
else
catch (const CLI::Error& e)
{
handle_exception(e);
using namespace std::literals;
// We only preserve CLI11 output behavior when errors from CLI11
// occurs because of `--help` or `--version` is used. Otherwise we follow the
// logic that `--json` outputs everything as JSON.
static constexpr std::array non_error_request_names = { "CallForHelp"sv,
"CallForAllHelp"sv,
"CallForVersion"sv };
const bool is_non_error_request = std::ranges::find(non_error_request_names, e.get_name())
!= non_error_request_names.end();

if (ctx.output_params.json and not is_non_error_request)
{
// we want the output to end up in the json log history
std::stringstream output;
return_value = app.exit(e, output, output);
LOG_WARNING << output.str();
}
else
{
// we don't want any json output even if requested, CLI11 will handle this
console.cancel_json_print();
return_value = app.exit(e);
}
}
}
catch (const CLI::Error& e)
{
using namespace std::literals;
// We only preserve CLI11 output behavior when errors from CLI11
// occurs because of `--help` or `--version` is used. Otherwise we follow the
// logic that `--json` outputs everything as JSON.
static constexpr std::array non_error_request_names = { "CallForHelp"sv,
"CallForAllHelp"sv,
"CallForVersion"sv };
const bool is_non_error_request = std::ranges::find(non_error_request_names, e.get_name())
!= non_error_request_names.end();

if (ctx.output_params.json and not is_non_error_request)
catch (const std::exception& e)
{
// we want the output to end up in the json log history
std::stringstream output;
return_value = app.exit(e, output, output);
LOG_WARNING << output.str();
handle_exception(e);
}
else

if (error_to_report)
{
// we don't want any json output even if requested, CLI11 will handle this
console.cancel_json_print();
return_value = app.exit(e);
LOG_CRITICAL << error_to_report.value();
return_value = EXIT_FAILURE;
}

return return_value;
}

// Handle errors printing - specifically for `--json` and `--quiet` options
// as `Console` is unreachable here
catch (const std::exception& e)
{
handle_exception(e);
report_error(argc, argv, e.what());
return EXIT_FAILURE;
}

if (error_to_report)
catch (...)
{
LOG_CRITICAL << error_to_report.value();
return_value = EXIT_FAILURE;
report_error(argc, argv, "Unhandled non-standard exception");
return EXIT_FAILURE;
}

return return_value;
}
Loading
Loading