# Copyright (C) 2012 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause """CLI implementation for `conda remove`. Removes the specified packages from an existing environment. """ import logging from argparse import ArgumentParser, Namespace, _SubParsersAction from os.path import isfile, join from .common import confirm_yn log = logging.getLogger(__name__) def configure_parser(sub_parsers: _SubParsersAction, **kwargs) -> ArgumentParser: from ..auxlib.ish import dals from ..common.constants import NULL from .actions import NullCountAction from .helpers import ( add_output_and_prompt_options, add_parser_channels, add_parser_networking, add_parser_prefix, add_parser_prune, add_parser_pscheck, add_parser_solver, ) summary = "Remove a list of packages from a specified conda environment. " description = dals( f""" {summary} Use `--all` flag to remove all packages and the environment itself. This command will also remove any package that depends on any of the specified packages as well---unless a replacement can be found without that dependency. If you wish to skip this dependency checking and remove just the requested packages, add the '--force' option. Note however that this may result in a broken environment, so use this with caution. """ ) epilog = dals( """ Examples: Remove the package 'scipy' from the currently-active environment:: conda remove scipy Remove a list of packages from an environemnt 'myenv':: conda remove -n myenv scipy curl wheel Remove all packages from environment `myenv` and the environment itself:: conda remove -n myenv --all Remove all packages from the environment `myenv` but retain the environment:: conda remove -n myenv --all --keep-env """ ) p = sub_parsers.add_parser( "remove", help=summary, description=description, epilog=epilog, **kwargs, ) add_parser_pscheck(p) add_parser_prefix(p) add_parser_channels(p) solver_mode_options = p.add_argument_group("Solver Mode Modifiers") solver_mode_options.add_argument( "--features", action="store_true", help="Remove features (instead of packages).", ) solver_mode_options.add_argument( "--force-remove", "--force", action="store_true", help="Forces removal of a package without removing packages that depend on it. " "Using this option will usually leave your environment in a broken and " "inconsistent state.", dest="force_remove", ) solver_mode_options.add_argument( "--no-pin", action="store_true", dest="ignore_pinned", default=NULL, help="Ignore pinned package(s) that apply to the current operation. " "These pinned packages might come from a .condarc file or a file in " "/conda-meta/pinned.", ) add_parser_prune(solver_mode_options) add_parser_solver(solver_mode_options) add_parser_networking(p) add_output_and_prompt_options(p) p.add_argument( "--all", action="store_true", help="Remove all packages, i.e., the entire environment.", ) p.add_argument( "--keep-env", action="store_true", help="Used with `--all`, delete all packages but keep the environment.", ) p.add_argument( "package_names", metavar="package_name", action="store", nargs="*", help="Package names to remove from the environment.", ) p.add_argument( "--dev", action=NullCountAction, help="Use `sys.executable -m conda` in wrapper scripts instead of CONDA_EXE. " "This is mainly for use during tests where we test new conda sources " "against old Python versions.", dest="dev", default=NULL, ) p.set_defaults(func="conda.cli.main_remove.execute") return p def execute(args: Namespace, parser: ArgumentParser) -> int: from ..base.context import context from ..core.envs_manager import unregister_env from ..core.link import PrefixSetup, UnlinkLinkTransaction from ..core.prefix_data import PrefixData from ..exceptions import ( CondaEnvironmentError, CondaValueError, DirectoryNotACondaEnvironmentError, PackagesNotFoundError, ) from ..gateways.disk.delete import path_is_clean, rm_rf from ..models.match_spec import MatchSpec from .common import check_non_admin, specs_from_args from .install import handle_txn if not (args.all or args.package_names): raise CondaValueError( "no package names supplied,\n" ' try "conda remove -h" for more details' ) prefix = context.target_prefix check_non_admin() if args.all and prefix == context.default_prefix: msg = "cannot remove current environment. deactivate and run conda remove again" raise CondaEnvironmentError(msg) if args.all and path_is_clean(prefix): # full environment removal was requested, but environment doesn't exist anyway # .. but you know what? If you call `conda remove --all` you'd expect the dir # not to exist afterwards, would you not? If not (fine, I can see the argument # about deleting people's work in envs being a very bad thing indeed), but if # being careful is the goal it would still be nice if after `conda remove --all` # to be able to do `conda create` on the same environment name. # # try: # rm_rf(prefix, clean_empty_parents=True) # except: # log.warning("Failed rm_rf() of partially existent env {}".format(prefix)) return 0 if args.all: if prefix == context.root_prefix: raise CondaEnvironmentError( "cannot remove root environment, add -n NAME or -p PREFIX option" ) if not isfile(join(prefix, "conda-meta", "history")): raise DirectoryNotACondaEnvironmentError(prefix) if not args.json: print(f"\nRemove all packages in environment {prefix}:\n") if "package_names" in args: stp = PrefixSetup( target_prefix=prefix, unlink_precs=tuple(PrefixData(prefix).iter_records()), link_precs=(), remove_specs=(), update_specs=(), neutered_specs={}, ) txn = UnlinkLinkTransaction(stp) try: handle_txn(txn, prefix, args, False, True) except PackagesNotFoundError: if not args.json: print( f"No packages found in {prefix}. Continuing environment removal" ) if not context.dry_run: if not args.keep_env: if not args.json: confirm_yn( f"Everything found within the environment ({prefix}), including any conda environment configurations and any non-conda files, will be deleted. Do you wish to continue?\n", default="no", dry_run=False, ) rm_rf(prefix, clean_empty_parents=True) unregister_env(prefix) return 0 else: if args.features: specs = tuple(MatchSpec(track_features=f) for f in set(args.package_names)) else: specs = specs_from_args(args.package_names) channel_urls = () subdirs = () solver_backend = context.plugin_manager.get_cached_solver_backend() solver = solver_backend(prefix, channel_urls, subdirs, specs_to_remove=specs) txn = solver.solve_for_transaction() handle_txn(txn, prefix, args, False, True) return 0