mirror of https://github.com/mamba-org/mamba.git
use remove_or_rename to fix deleting files in use on Windows
This commit is contained in:
parent
ccc00331f1
commit
6241df934d
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "fetch.hpp"
|
||||
#include "mamba_fs.hpp"
|
||||
#include "match_spec.hpp"
|
||||
#include "output.hpp"
|
||||
#include "package_cache.hpp"
|
||||
#include "package_handling.hpp"
|
||||
|
@ -152,6 +153,8 @@ namespace mamba
|
|||
History::UserRequest m_history_entry;
|
||||
Transaction* m_transaction;
|
||||
|
||||
std::vector<MatchSpec> m_requested_specs;
|
||||
|
||||
bool m_force_reinstall = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "context.hpp"
|
||||
#include "mamba_fs.hpp"
|
||||
#include "match_spec.hpp"
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
|
@ -26,7 +27,9 @@ namespace mamba
|
|||
{
|
||||
public:
|
||||
TransactionContext();
|
||||
TransactionContext(const fs::path& prefix, const std::string& py_version);
|
||||
TransactionContext(const fs::path& prefix,
|
||||
const std::string& py_version,
|
||||
const std::vector<MatchSpec>& requested_specs);
|
||||
|
||||
bool has_python;
|
||||
fs::path target_prefix;
|
||||
|
@ -38,6 +41,7 @@ namespace mamba
|
|||
bool always_copy = false;
|
||||
bool always_softlink = false;
|
||||
bool compile_pyc = true;
|
||||
std::vector<MatchSpec> requested_specs;
|
||||
};
|
||||
} // namespace mamba
|
||||
|
||||
|
|
|
@ -385,7 +385,7 @@ namespace mamba
|
|||
std::string quote_for_shell(const std::vector<std::string>& arguments,
|
||||
const std::string& shell = "");
|
||||
|
||||
void remove_or_rename(const fs::path& path);
|
||||
std::size_t remove_or_rename(const fs::path& path);
|
||||
|
||||
// Unindent a string literal
|
||||
std::string unindent(const char* p);
|
||||
|
@ -417,7 +417,7 @@ namespace mamba
|
|||
#endif
|
||||
if (!outfile.good())
|
||||
{
|
||||
LOG_ERROR << "Error opening " << path << ": " << strerror(errno);
|
||||
LOG_ERROR << "Error opening for writing " << path << ": " << strerror(errno);
|
||||
}
|
||||
|
||||
return outfile;
|
||||
|
@ -434,7 +434,7 @@ namespace mamba
|
|||
#endif
|
||||
if (!infile.good())
|
||||
{
|
||||
LOG_ERROR << "Error opening " << path << ": " << strerror(errno);
|
||||
LOG_ERROR << "Error opening for reading " << path << ": " << strerror(errno);
|
||||
}
|
||||
|
||||
return infile;
|
||||
|
|
|
@ -684,7 +684,8 @@ namespace mamba
|
|||
|
||||
LOG_TRACE << "Unlinking '" << dst.string() << "'";
|
||||
std::error_code err;
|
||||
if (!fs::remove(dst, err))
|
||||
|
||||
if (remove_or_rename(dst) == 0)
|
||||
LOG_DEBUG << "Error when removing file '" << dst.string() << "' will be ignored";
|
||||
|
||||
// TODO what do we do with empty directories?
|
||||
|
@ -692,7 +693,7 @@ namespace mamba
|
|||
auto parent_path = dst.parent_path();
|
||||
while (fs::is_empty(parent_path))
|
||||
{
|
||||
fs::remove(parent_path);
|
||||
remove_or_rename(parent_path);
|
||||
parent_path = parent_path.parent_path();
|
||||
}
|
||||
return true;
|
||||
|
@ -859,7 +860,6 @@ namespace mamba
|
|||
}
|
||||
return std::make_tuple(validate::sha256sum(dst), rel_dst);
|
||||
}
|
||||
|
||||
#else
|
||||
std::size_t padding_size
|
||||
= (path_data.prefix_placeholder.size() > new_prefix.size())
|
||||
|
@ -917,7 +917,6 @@ namespace mamba
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return std::make_tuple(validate::sha256sum(dst), rel_dst);
|
||||
}
|
||||
|
||||
|
@ -968,6 +967,9 @@ namespace mamba
|
|||
LOG_TRACE << "soft-linked '" << src.string() << "'" << std::endl
|
||||
<< " --> '" << dst.string() << "'";
|
||||
fs::copy_symlink(src, dst);
|
||||
// we need to wait until all files are linked to compute the SHA256 sum!
|
||||
// otherwise the file that's pointed to might not be linked yet.
|
||||
return std::make_tuple("", rel_dst);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1084,13 +1086,12 @@ namespace mamba
|
|||
LOG_TRACE << "Opening: " << m_source / "info" / "paths.json";
|
||||
auto paths_data = read_paths(m_source);
|
||||
|
||||
LOG_TRACE << "Opening: " << m_source / "info" / "repodata_record.json";
|
||||
LOG_INFO << "Opening: " << m_source / "info" / "repodata_record.json";
|
||||
|
||||
std::ifstream repodata_f = open_ifstream(m_source / "info" / "repodata_record.json");
|
||||
repodata_f >> index_json;
|
||||
|
||||
std::string f_name = index_json["name"].get<std::string>() + "-"
|
||||
+ index_json["version"].get<std::string>() + "-"
|
||||
+ index_json["build"].get<std::string>();
|
||||
std::string f_name = m_pkg_info.str();
|
||||
|
||||
LOG_DEBUG << "Linking package '" << f_name << "' from '" << m_source.string() << "'";
|
||||
|
||||
|
@ -1162,12 +1163,32 @@ namespace mamba
|
|||
|
||||
paths_json["paths"].push_back(json_record);
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < paths_data.size(); ++i)
|
||||
{
|
||||
auto& path = paths_data[i];
|
||||
if (path.path_type == PathType::SOFTLINK)
|
||||
{
|
||||
paths_json["paths"][i]["sha256_in_prefix"]
|
||||
= validate::sha256sum(m_context->target_prefix / files_record[i]);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG << paths_data.size() << " files linked";
|
||||
|
||||
out_json = index_json;
|
||||
out_json["paths_data"] = paths_json;
|
||||
out_json["files"] = files_record;
|
||||
out_json["requested_spec"] = "TODO";
|
||||
|
||||
MatchSpec* requested_spec = nullptr;
|
||||
for (auto& ms : m_context->requested_specs)
|
||||
{
|
||||
if (ms.name == m_pkg_info.name)
|
||||
{
|
||||
requested_spec = &ms;
|
||||
}
|
||||
}
|
||||
out_json["requested_spec"] = requested_spec != nullptr ? requested_spec->str() : "";
|
||||
out_json["package_tarball_full_path"] = std::string(m_source) + ".tar.bz2";
|
||||
out_json["extracted_package_dir"] = m_source;
|
||||
|
||||
|
|
|
@ -447,6 +447,9 @@ namespace mamba
|
|||
queue_free(&q);
|
||||
queue_free(&decision);
|
||||
queue_free(&job);
|
||||
|
||||
m_transaction_context = TransactionContext(
|
||||
Context::instance().target_prefix, find_python_version(), specs_to_install);
|
||||
}
|
||||
|
||||
|
||||
|
@ -533,6 +536,8 @@ namespace mamba
|
|||
Console::instance().json_down("actions");
|
||||
Console::instance().json_write({ { "PREFIX", Context::instance().target_prefix } });
|
||||
}
|
||||
m_transaction_context = TransactionContext(
|
||||
Context::instance().target_prefix, find_python_version(), solver.install_specs());
|
||||
}
|
||||
|
||||
MTransaction::~MTransaction()
|
||||
|
@ -691,7 +696,6 @@ namespace mamba
|
|||
}
|
||||
|
||||
Console::stream() << "\nTransaction starting";
|
||||
m_transaction_context = TransactionContext(prefix.path(), find_python_version());
|
||||
History::UserRequest ur = History::UserRequest::prefilled();
|
||||
|
||||
TransactionRollback rollback;
|
||||
|
|
|
@ -74,10 +74,13 @@ namespace mamba
|
|||
compile_pyc = Context::instance().compile_pyc;
|
||||
}
|
||||
|
||||
TransactionContext::TransactionContext(const fs::path& prefix, const std::string& py_version)
|
||||
TransactionContext::TransactionContext(const fs::path& prefix,
|
||||
const std::string& py_version,
|
||||
const std::vector<MatchSpec>& requested_specs)
|
||||
: has_python(py_version.size() != 0)
|
||||
, target_prefix(prefix)
|
||||
, python_version(py_version)
|
||||
, requested_specs(requested_specs)
|
||||
{
|
||||
auto& ctx = Context::instance();
|
||||
compile_pyc = ctx.compile_pyc;
|
||||
|
|
|
@ -586,32 +586,41 @@ namespace mamba
|
|||
}
|
||||
}
|
||||
|
||||
void remove_or_rename(const fs::path& path)
|
||||
std::size_t remove_or_rename(const fs::path& path)
|
||||
{
|
||||
if (fs::is_directory(path))
|
||||
std::error_code ec;
|
||||
std::size_t result = 0;
|
||||
if (!fs::exists(path, ec))
|
||||
return 0;
|
||||
|
||||
if (fs::is_directory(path, ec))
|
||||
{
|
||||
try
|
||||
{
|
||||
fs::remove_all(path);
|
||||
}
|
||||
catch (const fs::filesystem_error& e)
|
||||
{
|
||||
LOG_ERROR << "Caught a filesystem error: " << e.what();
|
||||
throw std::runtime_error("Could not remove directory " + path.string());
|
||||
}
|
||||
result = fs::remove_all(path, ec);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
result = fs::remove(path, ec);
|
||||
}
|
||||
|
||||
if (ec)
|
||||
{
|
||||
int counter = 0;
|
||||
while (ec)
|
||||
{
|
||||
fs::remove(path);
|
||||
}
|
||||
catch (const fs::filesystem_error& e)
|
||||
{
|
||||
LOG_ERROR << "Caught a filesystem error: " << e.what();
|
||||
throw std::runtime_error("Could not remove file " + path.string());
|
||||
LOG_ERROR << "Caught a filesystem error: " << ec.message();
|
||||
fs::path new_path = path;
|
||||
new_path.replace_extension(new_path.extension().string() + ".mamba_trash");
|
||||
fs::rename(path, new_path, ec);
|
||||
if (!ec)
|
||||
return 1;
|
||||
counter += 1;
|
||||
LOG_ERROR << "ERROR " << ec.message() << " sleeping for " << counter * 2;
|
||||
if (counter > 5)
|
||||
throw std::runtime_error("Couldnt delete file.");
|
||||
std::this_thread::sleep_for(std::chrono::seconds(counter * 2));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string unindent(const char* p)
|
||||
|
|
|
@ -5,6 +5,7 @@ import random
|
|||
import shutil
|
||||
import string
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -115,6 +116,29 @@ class TestRemove:
|
|||
assert "xframe" in removed_names
|
||||
assert res["actions"]["PREFIX"] == TestRemove.prefix
|
||||
|
||||
def test_remove_in_use(self, env_created):
|
||||
install("python=3.9", "-n", self.env_name, "--json", no_dry_run=True)
|
||||
if platform.system() == "Windows":
|
||||
pyexe = Path(self.prefix) / "python.exe"
|
||||
else:
|
||||
pyexe = Path(self.prefix) / "bin" / "python"
|
||||
|
||||
pyproc = subprocess.Popen(pyexe, stdin=None, stdout=None, stderr=None)
|
||||
|
||||
res = remove("python", "-v", "-p", self.prefix, no_dry_run=True)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
print(pyexe.exists())
|
||||
pyexe_trash = Path(str(pyexe) + ".mamba_trash")
|
||||
print(pyexe_trash)
|
||||
print(pyexe_trash.exists())
|
||||
assert pyexe.exists() == False
|
||||
assert pyexe_trash.exists()
|
||||
subprocess.Popen("TASKKILL /F /PID {pid} /T".format(pid=pyproc.pid))
|
||||
else:
|
||||
assert pyexe.exists() == False
|
||||
pyproc.kill()
|
||||
|
||||
|
||||
class TestRemoveConfig:
|
||||
|
||||
|
|
Loading…
Reference in New Issue