Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uninstall subcommand #112

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open

Conversation

marcoesters
Copy link
Contributor

@marcoesters marcoesters commented Nov 13, 2024

Description

Uninstalling distributions for conda currently only has incomplete solutions with files and broken auto-run/initializer scripts left behind (see conda/constructor#642, conda/constructor#588, and conda/constructor#572). A clean uninstallation is currently not available without manually running a series of commands that are still not 100% safe - e.g., conda init --reverse --all will remove initialization of conda, no matter what installation it is pointed to.

This PR introduces a cross-platform uninstallation subcommand that uses conda-internal features to perform the following tasks:

  • Uninstall all environments within a base environment or environments directory (such as a directory set by envs_dirs). This uninstalls all shortcuts, performs pre-unlink actions, and de-registers the environments from the environments.txt file.
  • Runs conda init reverse if a configuration file points to any of the environments that are removed.
  • Allows a user to remove .condarc files, either for the user, system-wide, or all.
  • Allows the user to run conda clean --all to remove package caches outside the installation directory.
  • Allows the user to remove cache directories, including the .conda file.

This is implemented in conda-standalone so that a single binary can be used on macOS and Linux. It also allows decoupling of the installer "front end" (built by constructor) from the installer "back end" (such as conda-standalone or micromamba), as outlined in the following issue: conda/constructor#549

Known gap: Soft links are currently not well supported: files are unlinked, but no efforts are made to ensure that this doesn't leave unused files behind.

Checklist - did you ...

  • Add a file to the news directory (using the template) for the next release's release notes?
  • Add / update necessary tests?
  • Add / update outdated documentation?

src/entry_point.py Outdated Show resolved Hide resolved
src/entry_point.py Outdated Show resolved Hide resolved
except PermissionError:
pass

def _remove_file_and_parents(file: Path, stop_at: Path | None = None):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be quite a destructive function, so I'd recommend adding separate tests and making it a global function.

For example, we need to ensure that file is contained in stop_at, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a check, which actually made me find a bug. This made me add a warning to the README file that support for softlinks is limited.

The problem with writing separate tests is that we would have to create an entry point for it, which would expose that destructive function even more. I added another tests to ensure that the function does not a non-empty directory.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, cannot we import the entry_point.py module in the tests directly and target that function?

tests/test_uninstall.py Outdated Show resolved Hide resolved
tests/test_uninstall.py Outdated Show resolved Hide resolved
Comment on lines +93 to +94
# Cannot make this a required argument or `conda.exe constructor uninstall --prefix`
# will not work (would have to be `conda constructor --prefix uninstall`)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are checking against prefix=None later right? So in practice it is required for the user. A comment here to clarify would be perfect.

@@ -110,7 +116,7 @@ def __call__(self, parser, namespace, values, option_string=None):
f"Defaults to {DEFAULT_NUM_PROCESSORS}.",
)

g = p.add_mutually_exclusive_group(required=True)
g = p.add_mutually_exclusive_group()
Copy link
Collaborator

@jaimergp jaimergp Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'd need to add checks to make this required manually if uninstall is not in the ARGV. Maybe an else clause in _constructor_main() (line 602, I think?).

# See: https://github.com/conda/conda/blob/475e6acbdc98122fcbef4733eb8cb8689324c1c8/conda/gateways/disk/create.py#L482-L488 # noqa
ENVS_DIR_MAGIC_FILE = ".conda_envs_dir_test"

uninstall_prefix = Path(uninstall_dir).expanduser().resolve()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to os.path.expandvars too or is that not expected?

_remove_file_directory(parent)
parent = parent.parent

def _remove_config_file_and_parents(file: Path):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's precedent for users not liking this default behaviour, see conda/conda#13113. Not sure if we should delete empty parents recursively 🤔 Maybe just the config-containing one? Happy to have my mind changed here, though, so let me know.

Comment on lines +445 to +448
# Sort by path depth. This will place the root prefix first
# Since it is more likely that profiles contain the root prefix,
# this makes loops more efficient.
prefixes.sort(key=lambda x: len(x.parts))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 👏 👏

Comment on lines +465 to +466
run_plan(plan)
run_plan_elevated(plan)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, these functions do not raise errors? They just write to plan?

continue
expected_files = [pkgs_dir / "urls", pkgs_dir / "urls.txt"]
if all(file in expected_files for file in pkgs_dir.iterdir()):
_remove_file_and_parents(pkgs_dir)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here about parents (and elsewhere), I don't think we need to recursively remove all parents.

> This can cause files to be left behind.
> [!WARNING]
> Support for softlinks is still limited.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate? Are errors expected, or undefined behavior?

),
)
uninstall_subcommand.add_argument(
"--remove-caches",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"--remove-caches",
"--remove-additional-caches",

The term is a bit convoluted with pkgs/ and pkgs/cache being taken care of in conda-clean, but this argument targets ~/.conda and anaconda-client stuff.

required=False,
help=(
"Remove all cache directories created by conda."
" This includes the .conda directory inside HOME/USERPROFILE."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
" This includes the .conda directory inside HOME/USERPROFILE."
" This includes the ~/.conda directory and anaconda-client data."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-signed [bot] added once the contributor has signed the CLA
Projects
Status: 🆕 New
Development

Successfully merging this pull request may close these issues.

3 participants