Skip to content

Commit 5cef483

Browse files
authored
Fix install using remote yaml files (#3875)
1 parent d7c14cd commit 5cef483

File tree

4 files changed

+141
-28
lines changed

4 files changed

+141
-28
lines changed

libmamba/include/mamba/api/install.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ namespace mamba
105105

106106
bool eval_selector(const std::string& selector, const std::string& platform);
107107

108-
yaml_file_contents read_yaml_file(fs::u8path yaml_file, const std::string platform);
108+
yaml_file_contents
109+
read_yaml_file(const Context& ctx, const std::string& yaml_file, const std::string& platform);
109110

110111
inline void to_json(nlohmann::json&, const other_pkg_mgr_spec&)
111112
{

libmamba/src/api/install.cpp

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,54 @@ namespace mamba
8282
return found_it->second;
8383
}
8484

85-
yaml_file_contents read_yaml_file(fs::u8path yaml_file, const std::string platform)
85+
std::unique_ptr<TemporaryFile>
86+
downloaded_file_from_url(const Context& ctx, const std::string& url_str)
8687
{
87-
auto file = fs::weakly_canonical(util::expand_home(yaml_file.string()));
88-
if (!fs::exists(file))
88+
if (url_str.find("://") != std::string::npos)
89+
{
90+
LOG_INFO << "Downloading file from " << url_str;
91+
auto url_parts = util::rsplit(url_str, '/');
92+
std::string filename = (url_parts.size() == 1) ? "" : url_parts.back();
93+
auto tmp_file = std::make_unique<TemporaryFile>("mambaf", util::concat("_", filename));
94+
download::Request request(
95+
"Environment lock or yaml file",
96+
download::MirrorName(""),
97+
url_str,
98+
tmp_file->path()
99+
);
100+
const download::Result res = download::download(std::move(request), ctx.mirrors, ctx);
101+
102+
if (!res || res.value().transfer.http_status != 200)
103+
{
104+
throw std::runtime_error(
105+
fmt::format("Could not download environment lock or yaml file from {}", url_str)
106+
);
107+
}
108+
109+
return tmp_file;
110+
}
111+
return nullptr;
112+
}
113+
114+
yaml_file_contents
115+
read_yaml_file(const Context& ctx, const std::string& yaml_file, const std::string& platform)
116+
{
117+
// Download content of environment yaml file
118+
auto tmp_yaml_file = downloaded_file_from_url(ctx, yaml_file);
119+
fs::u8path file;
120+
121+
if (tmp_yaml_file)
122+
{
123+
file = tmp_yaml_file->path();
124+
}
125+
else
89126
{
90-
LOG_ERROR << "YAML spec file '" << file.string() << "' not found";
91-
throw std::runtime_error("File not found. Aborting.");
127+
file = fs::weakly_canonical(util::expand_home(yaml_file));
128+
if (!fs::exists(file))
129+
{
130+
LOG_ERROR << "YAML spec file '" << file.string() << "' not found";
131+
throw std::runtime_error("File not found. Aborting.");
132+
}
92133
}
93134

94135
yaml_file_contents result;
@@ -148,8 +189,15 @@ namespace mamba
148189
}
149190
else if (key == "pip")
150191
{
151-
const auto yaml_parent_path = fs::absolute(yaml_file).parent_path().string(
152-
);
192+
std::string yaml_parent_path;
193+
if (tmp_yaml_file) // yaml file is fetched remotely
194+
{
195+
yaml_parent_path = yaml_file;
196+
}
197+
else
198+
{
199+
yaml_parent_path = fs::absolute(yaml_file).parent_path().string();
200+
}
153201
result.others_pkg_mgrs_specs.push_back({
154202
"pip",
155203
map_el.second.as<std::vector<std::string>>(),
@@ -695,28 +743,11 @@ namespace mamba
695743
bool remove_prefix_on_failure
696744
)
697745
{
698-
std::unique_ptr<TemporaryFile> tmp_lock_file;
699746
fs::u8path file;
747+
auto tmp_lock_file = detail::downloaded_file_from_url(ctx, lockfile);
700748

701-
if (lockfile.find("://") != std::string::npos)
749+
if (tmp_lock_file)
702750
{
703-
LOG_INFO << "Downloading lockfile";
704-
tmp_lock_file = std::make_unique<TemporaryFile>();
705-
download::Request request(
706-
"Environment Lockfile",
707-
download::MirrorName(""),
708-
lockfile,
709-
tmp_lock_file->path()
710-
);
711-
const download::Result res = download::download(std::move(request), ctx.mirrors, ctx);
712-
713-
if (!res || res.value().transfer.http_status != 200)
714-
{
715-
throw std::runtime_error(
716-
fmt::format("Could not download environment lockfile from {}", lockfile)
717-
);
718-
}
719-
720751
file = tmp_lock_file->path();
721752
}
722753
else
@@ -833,7 +864,7 @@ namespace mamba
833864
}
834865
else if (is_yaml_file_name(file))
835866
{
836-
const auto parse_result = read_yaml_file(file, context.platform);
867+
const auto parse_result = read_yaml_file(context, file, context.platform);
837868

838869
if (parse_result.channels.size() != 0)
839870
{

libmamba/tests/src/core/test_env_file_reading.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace mamba
4848
const auto& context = mambatests::context();
4949
using V = std::vector<std::string>;
5050
auto res = detail::read_yaml_file(
51+
context,
5152
mambatests::test_data_dir / "env_file/env_1.yaml",
5253
context.platform
5354
);
@@ -57,6 +58,7 @@ namespace mamba
5758
REQUIRE_FALSE(res.others_pkg_mgrs_specs.size());
5859

5960
auto res2 = detail::read_yaml_file(
61+
context,
6062
mambatests::test_data_dir / "env_file/env_2.yaml",
6163
context.platform
6264
);
@@ -77,6 +79,7 @@ namespace mamba
7779
const auto& context = mambatests::context();
7880
using V = std::vector<std::string>;
7981
auto res = detail::read_yaml_file(
82+
context,
8083
mambatests::test_data_dir / "env_file/env_3.yaml",
8184
context.platform
8285
);
@@ -90,6 +93,66 @@ namespace mamba
9093
REQUIRE(o.deps == V({ "pytest", "numpy" }));
9194
REQUIRE(o.cwd == fs::absolute(mambatests::test_data_dir / "env_file"));
9295
}
96+
97+
TEST_CASE("remote_yaml_file")
98+
{
99+
SECTION("classic_env_yaml_file")
100+
{
101+
const auto& context = mambatests::context();
102+
using V = std::vector<std::string>;
103+
auto res = detail::read_yaml_file(
104+
context,
105+
"https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/micromamba/tests/env-create-export.yaml",
106+
context.platform
107+
);
108+
REQUIRE(res.name == "");
109+
REQUIRE(res.channels == V({ "https://conda.anaconda.org/conda-forge" }));
110+
REQUIRE(res.dependencies == V({ "micromamba=0.24.0" }));
111+
}
112+
113+
SECTION("env_yaml_file_with_pip")
114+
{
115+
const auto& context = mambatests::context();
116+
using V = std::vector<std::string>;
117+
auto res = detail::read_yaml_file(
118+
context,
119+
"https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/libmamba/tests/data/env_file/env_3.yaml",
120+
context.platform
121+
);
122+
REQUIRE(res.name == "env_3");
123+
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));
124+
REQUIRE(res.dependencies == V({ "test1", "test2", "test3", "pip" }));
125+
126+
REQUIRE(res.others_pkg_mgrs_specs.size() == 1);
127+
auto o = res.others_pkg_mgrs_specs[0];
128+
REQUIRE(o.pkg_mgr == "pip");
129+
REQUIRE(o.deps == V({ "pytest", "numpy" }));
130+
REQUIRE(
131+
o.cwd == "https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/libmamba/tests/data/env_file/env_3.yaml"
132+
);
133+
}
134+
135+
SECTION("env_yaml_file_with_specs_selection")
136+
{
137+
const auto& context = mambatests::context();
138+
using V = std::vector<std::string>;
139+
auto res = detail::read_yaml_file(
140+
context,
141+
"https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/libmamba/tests/data/env_file/env_2.yaml",
142+
context.platform
143+
);
144+
REQUIRE(res.name == "env_2");
145+
REQUIRE(res.channels == V({ "conda-forge", "bioconda" }));
146+
#ifdef __linux__
147+
REQUIRE(res.dependencies == V({ "test1-unix", "test1-linux", "test2-linux", "test4" }));
148+
#elif __APPLE__
149+
REQUIRE(res.dependencies == V({ "test1-unix", "test1-osx", "test4" }));
150+
#elif _WIN32
151+
REQUIRE(res.dependencies == V({ "test1-win", "test4" }));
152+
#endif
153+
REQUIRE_FALSE(res.others_pkg_mgrs_specs.size());
154+
}
155+
}
93156
}
94157

95158
} // namespace mamba

micromamba/tests/test_create.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,24 @@ def test_requires_pip_install_no_parent_dir_specified(
12221222
os.chdir(initial_working_dir) # Switch back to original working dir.
12231223

12241224

1225+
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
1226+
def test_create_from_remote_yaml_file(tmp_home, tmp_root_prefix, tmp_path):
1227+
env_prefix = tmp_path / "myenv"
1228+
spec_file = "https://raw.githubusercontent.com/mamba-org/mamba/refs/heads/main/micromamba/tests/env-create-export.yaml"
1229+
1230+
res = helpers.create("-p", env_prefix, "-f", spec_file, "--json")
1231+
assert res["success"]
1232+
1233+
packages = helpers.umamba_list("-p", env_prefix, "--json")
1234+
assert any(
1235+
package["name"] == "micromamba"
1236+
and package["version"] == "0.24.0"
1237+
and package["channel"] == "conda-forge"
1238+
and package["base_url"] == "https://conda.anaconda.org/conda-forge"
1239+
for package in packages
1240+
)
1241+
1242+
12251243
@pytest.mark.parametrize("shared_pkgs_dirs", [True], indirect=True)
12261244
def test_pre_commit_compat(tmp_home, tmp_root_prefix, tmp_path):
12271245
# We test compatibility with the downstream pre-commit package here because the pre-commit project does not currently accept any code changes related to Conda, see https://github.com/pre-commit/pre-commit/pull/2446#issuecomment-1353394177.

0 commit comments

Comments
 (0)