Remove __pycache__ directories when uninstalling (#397)

According to the [packaging
documentation](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#binary-distribution-format),
"uninstallers should be smart enough to remove .pyc even if it is not
mentioned in RECORD". Previously, we weren't handling this case, so if
you installed via Puffin, then imported a file (to trigger bytecode
compilation), then uninstalled, we'd leave spare `__pycache__`
directories around.

Closes https://github.com/astral-sh/puffin/issues/395.
This commit is contained in:
Charlie Marsh 2023-11-10 11:55:33 -08:00 committed by GitHub
parent 63f7f65190
commit e8108cb28b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 1 deletions

View file

@ -64,6 +64,19 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result<Uninstall, Error> {
break;
}
// If the directory contains a `__pycache__` directory, always remove it. `__pycache__`
// may or may not be listed in the RECORD, but installers are expected to be smart
// enough to remove it either way.
let pycache = path.join("__pycache__");
match fs::remove_dir_all(&pycache) {
Ok(()) => {
debug!("Removed directory: {}", pycache.display());
dir_count += 1;
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => return Err(err.into()),
}
// Try to read from the directory. If it doesn't exist, assume we deleted it in a
// previous iteration.
let mut read_dir = match fs::read_dir(path) {

View file

@ -1,10 +1,11 @@
use std::process::Command;
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use common::BIN_NAME;
use common::{BIN_NAME, INSTA_FILTERS};
mod common;
@ -125,3 +126,62 @@ dependencies = ["flask==1.0.x"]
Ok(())
}
#[test]
fn uninstall() -> Result<()> {
let temp_dir = assert_fs::TempDir::new()?;
let cache_dir = assert_fs::TempDir::new()?;
let venv = temp_dir.child(".venv");
Command::new(get_cargo_bin(BIN_NAME))
.arg("venv")
.arg(venv.as_os_str())
.arg("--cache-dir")
.arg(cache_dir.path())
.current_dir(&temp_dir)
.assert()
.success();
venv.assert(predicates::path::is_dir());
let requirements_txt = temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("MarkupSafe==2.1.3")?;
Command::new(get_cargo_bin(BIN_NAME))
.arg("pip-sync")
.arg("requirements.txt")
.arg("--cache-dir")
.arg(cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&temp_dir)
.assert()
.success();
Command::new(venv.join("bin").join("python"))
.arg("-c")
.arg("import markupsafe")
.current_dir(&temp_dir)
.assert()
.success();
insta::with_settings!({
filters => INSTA_FILTERS.to_vec()
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.arg("pip-uninstall")
.arg("MarkupSafe")
.arg("--cache-dir")
.arg(cache_dir.path())
.env("VIRTUAL_ENV", venv.as_os_str())
.current_dir(&temp_dir));
});
Command::new(venv.join("bin").join("python"))
.arg("-c")
.arg("import markupsafe")
.current_dir(&temp_dir)
.assert()
.failure();
Ok(())
}

View file

@ -0,0 +1,20 @@
---
source: crates/puffin-cli/tests/pip_uninstall.rs
info:
program: puffin
args:
- pip-uninstall
- MarkupSafe
- "--cache-dir"
- /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp2hOiz7
env:
VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmp9AcnyS/.venv
---
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Uninstalled 1 package in [TIME]
- markupsafe==2.1.3