conans/client/remover.py
import os
from conans.client.cache.remote_registry import Remote
from conans.errors import ConanException, PackageNotFoundException, RecipeNotFoundException
from conans.errors import NotFoundException
from conans.model.ref import ConanFileReference, PackageReference, check_valid_ref
from conans.paths import SYSTEM_REQS, rm_conandir
from conans.search.search import filter_outdated, search_packages, search_recipes
from conans.util.log import logger
class DiskRemover(object):
def _remove(self, path, ref, msg=""):
try:
logger.debug("REMOVE: folder %s" % path)
rm_conandir(path)
except OSError:
error_msg = "Folder busy (open or some file open): %s" % path
raise ConanException("%s: Unable to remove %s\n\t%s" % (repr(ref), msg, error_msg))
def _remove_file(self, path, ref, msg=""):
try:
logger.debug("REMOVE: file %s" % path)
if os.path.exists(path):
os.remove(path)
except OSError:
error_msg = "File busy (open): %s" % path
raise ConanException("Unable to remove %s %s\n\t%s" % (repr(ref), msg, error_msg))
def remove_recipe(self, package_layout, output):
self.remove_src(package_layout)
package_layout.export_remove()
for f in package_layout.conanfile_lock_files(output=output):
try:
os.remove(f)
except OSError:
pass
def remove(self, package_layout, output):
self.remove_recipe(package_layout, output=output)
self.remove_builds(package_layout)
self.remove_packages(package_layout)
self._remove(package_layout.base_folder(), package_layout.ref)
def remove_src(self, package_layout):
package_layout.sources_remove()
def remove_builds(self, package_layout, ids=None):
if not ids:
path = package_layout.builds()
for build in package_layout.conan_builds():
self._remove(os.path.join(path, build), package_layout.ref,
"build folder:%s" % build)
self._remove(path, package_layout.ref, "builds")
else:
for id_ in ids:
# Removal build IDs should be those of the build_id if present
pkg_path = package_layout.build(PackageReference(package_layout.ref, id_))
self._remove(pkg_path, package_layout.ref, "package:%s" % id_)
def remove_packages(self, package_layout, ids_filter=None):
if not ids_filter: # Remove all
path = package_layout.packages()
# Necessary for short_paths removal
for package_id in package_layout.package_ids():
pref = PackageReference(package_layout.ref, package_id)
package_layout.package_remove(pref)
self._remove(path, package_layout.ref, "packages")
self._remove_file(package_layout.system_reqs(), package_layout.ref, SYSTEM_REQS)
else:
for package_id in ids_filter: # remove just the specified packages
pref = PackageReference(package_layout.ref, package_id)
if not package_layout.package_exists(pref):
raise PackageNotFoundException(pref)
package_layout.package_remove(pref)
self._remove_file(package_layout.system_reqs_package(pref), package_layout.ref,
"%s/%s" % (package_id, SYSTEM_REQS))
class ConanRemover(object):
""" Class responsible for removing locally/remotely conans, package folders, etc. """
def __init__(self, cache, remote_manager, user_io, remotes):
self._user_io = user_io
self._cache = cache
self._remote_manager = remote_manager
self._remotes = remotes
def _remote_remove(self, ref, package_ids, remote):
assert(isinstance(remote, Remote))
if package_ids is None:
result = self._remote_manager.remove_recipe(ref, remote)
return result
else:
tmp = self._remote_manager.remove_packages(ref, package_ids, remote)
return tmp
@staticmethod
def _message_removing_editable(ref):
return "Package '{r}' is installed as editable, remove it first using " \
"command 'conan editable remove {r}'".format(r=ref)
def _local_remove(self, ref, src, build_ids, package_ids):
if self._cache.installed_as_editable(ref):
self._user_io.out.warn(self._message_removing_editable(ref))
return
# Get the package layout using 'short_paths=False', remover will make use of the
# function 'rm_conandir' which already takes care of the linked folder.
package_layout = self._cache.package_layout(ref, short_paths=False)
package_layout.remove_package_locks() # Make sure to clean the locks too
remover = DiskRemover()
if src:
remover.remove_src(package_layout)
if build_ids is not None:
remover.remove_builds(package_layout, build_ids)
if package_ids is not None:
remover.remove_packages(package_layout, package_ids)
with package_layout.update_metadata() as metadata:
for package_id in package_ids:
metadata.clear_package(package_id)
if not src and build_ids is None and package_ids is None:
remover.remove(package_layout, output=self._user_io.out)
def remove(self, pattern, remote_name, src=None, build_ids=None, package_ids_filter=None,
force=False, packages_query=None, outdated=False):
""" Remove local/remote conans, package folders, etc.
@param src: Remove src folder
@param pattern: it could be OpenCV* or OpenCV or a ConanFileReference
@param build_ids: Lists with ids or empty for all. (Its a filter)
@param package_ids_filter: Lists with ids or empty for all. (Its a filter)
@param force: if True, it will be deleted without requesting anything
@param packages_query: Only if src is a reference. Query settings and options
"""
if remote_name and (build_ids is not None or src):
raise ConanException("Remotes don't have 'build' or 'src' folder, just packages")
is_reference = check_valid_ref(pattern)
input_ref = ConanFileReference.loads(pattern) if is_reference else None
if not input_ref and packages_query is not None:
raise ConanException("query parameter only allowed with a valid recipe "
"reference as the search pattern.")
if input_ref and package_ids_filter and not input_ref.revision:
for package_id in package_ids_filter:
if "#" in package_id:
raise ConanException("Specify a recipe revision if you specify a package "
"revision")
if remote_name:
remote = self._remotes[remote_name]
if input_ref:
if not self._cache.config.revisions_enabled and input_ref.revision:
raise ConanException("Revisions not enabled in the client, cannot remove "
"revisions in the server")
refs = [input_ref]
else:
refs = self._remote_manager.search_recipes(remote, pattern)
else:
if input_ref:
refs = []
if self._cache.installed_as_editable(input_ref):
raise ConanException(self._message_removing_editable(input_ref))
if not self._cache.package_layout(input_ref).recipe_exists():
raise RecipeNotFoundException(input_ref,
print_rev=self._cache.config.revisions_enabled)
refs.append(input_ref)
else:
refs = search_recipes(self._cache, pattern)
if not refs:
self._user_io.out.warn("No package recipe matches '%s'" % str(pattern))
return
if input_ref and not input_ref.revision:
# Ignore revisions for deleting if the input was not with a revision
# (Removing all the recipe revisions from a reference)
refs = [r.copy_clear_rev() for r in refs]
deleted_refs = []
for ref in refs:
assert isinstance(ref, ConanFileReference)
package_layout = self._cache.package_layout(ref)
package_ids = package_ids_filter
if packages_query or outdated:
# search packages
if remote_name:
packages = self._remote_manager.search_packages(remote, ref, packages_query)
else:
packages = search_packages(package_layout, packages_query)
if outdated:
if remote_name:
manifest, ref = self._remote_manager.get_recipe_manifest(ref, remote)
recipe_hash = manifest.summary_hash
else:
recipe_hash = package_layout.recipe_manifest().summary_hash
packages = filter_outdated(packages, recipe_hash)
if package_ids_filter:
package_ids = [p for p in packages if p in package_ids_filter]
else:
package_ids = list(packages.keys())
if not package_ids:
self._user_io.out.warn("No matching packages to remove for %s"
% ref.full_str())
continue
if self._ask_permission(ref, src, build_ids, package_ids, force):
try:
if remote_name:
self._remote_remove(ref, package_ids, remote)
else:
self._local_remove(ref, src, build_ids, package_ids)
except NotFoundException:
# If we didn't specify a pattern but a concrete ref, fail if there is no
# ref to remove
if input_ref:
raise
else:
deleted_refs.append(ref)
if not remote_name:
self._cache.delete_empty_dirs(deleted_refs)
def _ask_permission(self, ref, src, build_ids, package_ids_filter, force):
def stringlist(alist):
return ", ".join(['"%s"' % p for p in alist])
if force:
return True
aux_str = []
if src:
aux_str.append(" src folder")
if build_ids is not None:
if build_ids:
aux_str.append(" %s builds" % stringlist(build_ids))
else:
aux_str.append(" all builds")
if package_ids_filter is not None:
if package_ids_filter:
aux_str.append(" %s packages" % stringlist(package_ids_filter))
else: # All packages to remove, no filter
aux_str.append(" all packages")
return self._user_io.request_boolean("Are you sure you want to delete%s from '%s'"
% (", ".join(aux_str), str(ref)))