Skip to content

Commit

Permalink
fix: JSON output for environment export (#3559)
Browse files Browse the repository at this point in the history
Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>
Co-authored-by: Johan Mabille <johan.mabille@gmail.com>
  • Loading branch information
jjerphan and JohanMabille authored Oct 22, 2024
1 parent d26c7f5 commit d2d1651
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 12 deletions.
88 changes: 88 additions & 0 deletions micromamba/src/env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ set_env_command(CLI::App* com, Configuration& config)
config.load();

auto channel_context = mamba::ChannelContext::make_conda_compatible(ctx);

auto json_format = config.at("json").get_cli_config<bool>();

// Raise a warning if `--json` and `--explicit` are used together.
if (json_format && explicit_format)
{
std::cerr << "Warning: `--json` and `--explicit` are used together but are incompatible. The `--json` flag will be ignored."
<< std::endl;
}

if (explicit_format)
{
// TODO: handle error
Expand Down Expand Up @@ -164,6 +174,83 @@ set_env_command(CLI::App* com, Configuration& config)
std::cout << "\n";
}
}
else if (json_format)
{
auto pd = PrefixData::create(ctx.prefix_params.target_prefix, channel_context).value();
History& hist = pd.history();

const auto& versions_map = pd.records();
auto requested_specs_map = hist.get_requested_specs_map();
std::stringstream dependencies;
std::set<std::string> channels;

for (auto it = versions_map.begin(); it != versions_map.end(); ++it)
{
auto k = it->first;
auto v = it->second;
if (from_history && requested_specs_map.find(k) == requested_specs_map.end())
{
continue;
}

auto chans = channel_context.make_channel(v.channel);

if (from_history)
{
dependencies << " \"" << requested_specs_map[k].str() << "\",\n";
}
else
{
dependencies << " \"";
if (channel_subdir)
{
dependencies
// If the size is not one, it's a custom multi channel
<< ((chans.size() == 1) ? chans.front().display_name() : v.channel)
<< "/" << v.platform << "::";
}
dependencies << v.name << "=" << v.version;
if (!no_build)
{
dependencies << "=" << v.build_string;
}
if (no_md5 == -1)
{
dependencies << "[md5=" << v.md5 << "]";
}
auto last_dep = std::next(it) == versions_map.end();
dependencies << "\"" << (last_dep ? "" : ",") << "\n";
}

for (const auto& chan : chans)
{
channels.insert(chan.display_name());
}
}
std::cout << "{\n";

if (!channels.empty())
{
std::cout << " \"channels\": [\n";
for (auto channel_it = channels.begin(); channel_it != channels.end();
++channel_it)
{
auto last_channel = std::next(channel_it) == channels.end();
std::cout << " \"" << *channel_it << "\"" << (last_channel ? "" : ",")
<< "\n";
}
std::cout << " ],\n";
}
std::cout << " \"dependencies\": [\n" << dependencies.str() << " ],\n";

std::cout << " \"name\": \"" << get_env_name(ctx, ctx.prefix_params.target_prefix)
<< "\",\n";
std::cout << " \"prefix\": " << ctx.prefix_params.target_prefix << "\n";

std::cout << "}\n";

std::cout.flush();
}
else
{
auto pd = PrefixData::create(ctx.prefix_params.target_prefix, channel_context).value();
Expand All @@ -177,6 +264,7 @@ set_env_command(CLI::App* com, Configuration& config)
auto requested_specs_map = hist.get_requested_specs_map();
std::stringstream dependencies;
std::set<std::string> channels;

for (const auto& [k, v] : versions_map)
{
if (from_history && requested_specs_map.find(k) == requested_specs_map.end())
Expand Down
34 changes: 22 additions & 12 deletions micromamba/tests/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,39 @@ def export_env():
@pytest.mark.parametrize("md5_flag", [None, "--md5", "--no-md5"])
@pytest.mark.parametrize("explicit_flag", [None, "--explicit"])
@pytest.mark.parametrize("no_build_flag", [None, "--no-build", "--no-builds"])
def test_env_export(export_env, no_build_flag, explicit_flag, md5_flag, channel_subdir_flag):
flags = filter(None, [explicit_flag, md5_flag, channel_subdir_flag])
@pytest.mark.parametrize("json_flag", [None, "--json"])
def test_env_export(
export_env, json_flag, no_build_flag, explicit_flag, md5_flag, channel_subdir_flag
):
if explicit_flag and json_flag:
# `--explicit` has precedence over `--json`, which is tested bellow.
# But we need to omit here to avoid `helpers.run_env` to parse the output as JSON and fail.
json_flag = None

flags = filter(None, [no_build_flag, json_flag, explicit_flag, md5_flag, channel_subdir_flag])
output = helpers.run_env("export", "-n", export_env, *flags)
if explicit_flag:
assert "/micromamba-0.24.0-0." in output
if md5_flag != "--no-md5":
assert re.search("#[a-f0-9]{32}$", output.replace("\r", ""))
else:
ret = yaml.safe_load(output)
if json_flag:
# Already parsed
ret = output
else:
ret = yaml.safe_load(output)
assert ret["name"] == export_env
assert "env-create-export" in ret["prefix"]
assert set(ret["channels"]) == {"conda-forge"}
assert (
"micromamba=0.24.0\n"
if no_build_flag
else "micromamba=0.24.0=0" in str(ret["dependencies"])
)
if md5_flag == "--md5":
micromamba_spec_prefix = "micromamba=0.24.0" if no_build_flag else "micromamba=0.24.0=0"
assert micromamba_spec_prefix in str(ret["dependencies"])
if md5_flag == "--md5" and no_build_flag:
assert re.search(r"micromamba=0.24.0\[md5=[a-f0-9]{32}\]", str(ret["dependencies"]))
if md5_flag == "--md5" and no_build_flag is None:
assert re.search(r"micromamba=0.24.0=0\[md5=[a-f0-9]{32}\]", str(ret["dependencies"]))

if channel_subdir_flag:
assert re.search(
r"conda-forge/[a-z0-9-]+::micromamba=0.24.0=0", str(ret["dependencies"])
)
assert re.search(r"conda-forge/[a-z0-9-]+::micromamba=0.24.0", str(ret["dependencies"]))


def test_create():
Expand Down

0 comments on commit d2d1651

Please sign in to comment.