diff --git a/CHANGELOG.md b/CHANGELOG.md index 82389fe30a..6076ddc5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ _Unreleased_ - When `uv` is used the prompt is now set to the project name. #773 +- Allow `rye fetch --force` to force re-fetch a downloaded toolchain. #778 + ## 0.26.0 diff --git a/docs/guide/commands/fetch.md b/docs/guide/commands/fetch.md index b1d1de9bef..d85be0980d 100644 --- a/docs/guide/commands/fetch.md +++ b/docs/guide/commands/fetch.md @@ -11,7 +11,8 @@ Fetch a specific version of Python: $ rye fetch 3.8.13 Downloading cpython@3.8.13 Checking checksum -success: Downloaded cpython@3.8.13 +Unpacking +Downloaded cpython@3.8.13 ``` To fetch the pinned version of Python you can leave out the argument: @@ -20,7 +21,8 @@ To fetch the pinned version of Python you can leave out the argument: $ rye fetch Downloading cpython@3.8.17 Checking checksum -success: Downloaded cpython@3.8.17 +Unpacking +Downloaded cpython@3.8.17 ``` ## Arguments @@ -31,6 +33,8 @@ success: Downloaded cpython@3.8.17 ## Options +* `-f, --force`: Fetch the Python toolchain even if it is already installed. + * `-v, --verbose`: Enables verbose diagnostics * `-q, --quiet`: Turns off all output diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index da62bb7557..3a55016cee 100644 --- a/rye/src/bootstrap.rs +++ b/rye/src/bootstrap.rs @@ -277,7 +277,7 @@ fn ensure_latest_self_toolchain(output: CommandOutput) -> Result Result { if let Ok(version) = PythonVersion::try_from(version.clone()) { let py_bin = get_toolchain_python_bin(&version)?; - if py_bin.is_file() { + if !force && py_bin.is_file() { if output == CommandOutput::Verbose { echo!("Python version already downloaded. Skipping."); } @@ -339,10 +340,17 @@ pub fn fetch( echo!("target dir: {}", target_dir.display()); } if target_dir.is_dir() && target_py_bin.is_file() { - if output == CommandOutput::Verbose { - echo!("Python version already downloaded. Skipping."); + if !force { + if output == CommandOutput::Verbose { + echo!("Python version already downloaded. Skipping."); + } + return Ok(version); + } + if output != CommandOutput::Quiet { + echo!("Removing the existing Python version"); } - return Ok(version); + fs::remove_dir_all(&target_dir) + .with_context(|| format!("failed to remove target folder {}", target_dir.display()))?; } fs::create_dir_all(&target_dir).path_context(&target_dir, "failed to create target folder")?; diff --git a/rye/src/cli/fetch.rs b/rye/src/cli/fetch.rs index 71f919d481..376ff774ae 100644 --- a/rye/src/cli/fetch.rs +++ b/rye/src/cli/fetch.rs @@ -15,6 +15,9 @@ pub struct Args { /// /// If no version is provided, the requested version from local project or `.python-version` will be fetched. version: Option, + /// Fetch the Python toolchain even if it is already installed. + #[arg(short, long)] + force: bool, /// Enables verbose diagnostics. #[arg(short, long)] verbose: bool, @@ -40,6 +43,6 @@ pub fn execute(cmd: Args) -> Result<(), Error> { } }; - fetch(&version, output).context("error while fetching Python installation")?; + fetch(&version, output, cmd.force).context("error while fetching Python installation")?; Ok(()) } diff --git a/rye/src/installer.rs b/rye/src/installer.rs index f90cf7f63a..0994d45457 100644 --- a/rye/src/installer.rs +++ b/rye/src/installer.rs @@ -131,7 +131,7 @@ pub fn install( uninstall_helper(&target_venv_path, &shim_dir)?; // make sure we have a compatible python version - let py_ver = fetch(py_ver, output)?; + let py_ver = fetch(py_ver, output, false)?; create_virtualenv( output, diff --git a/rye/src/sync.rs b/rye/src/sync.rs index 2a549d34b4..9846ca4313 100644 --- a/rye/src/sync.rs +++ b/rye/src/sync.rs @@ -147,7 +147,7 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> { // make sure we have a compatible python version let py_ver = - fetch(&py_ver.into(), output).context("failed fetching toolchain ahead of sync")?; + fetch(&py_ver.into(), output, false).context("failed fetching toolchain ahead of sync")?; // kill the virtualenv if it's there and we need to get rid of it. if recreate && venv.is_dir() { diff --git a/rye/tests/test_toolchain.rs b/rye/tests/test_toolchain.rs new file mode 100644 index 0000000000..2292426208 --- /dev/null +++ b/rye/tests/test_toolchain.rs @@ -0,0 +1,38 @@ +use crate::common::{rye_cmd_snapshot, Space}; + +mod common; + +#[test] +fn test_fetch() { + let space = Space::new(); + // Use a version not in use by other tests and will be supported for a long time. + let version = "cpython@3.12.1"; + + // Make sure the version is installed. + let status = space.rye_cmd().arg("fetch").arg(version).status().unwrap(); + assert!(status.success()); + + // Fetching the same version again should be a no-op. + rye_cmd_snapshot!(space.rye_cmd().arg("fetch").arg(version).arg("--verbose"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Python version already downloaded. Skipping. + + ----- stderr ----- + "###); + + // Fetching the same version again with --force should re-download it. + rye_cmd_snapshot!(space.rye_cmd().arg("fetch").arg(version).arg("--force"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Removing the existing Python version + Downloading cpython@3.12.1 + Checking checksum + Unpacking + Downloaded cpython@3.12.1 + + ----- stderr ----- + "###); +}