From 91b1f3dbb3e27617823cea5c5682cead82b37061 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 4 Mar 2024 01:03:59 +0100 Subject: [PATCH] Fix a bug that caused impartial lock options recalling (#830) --- CHANGELOG.md | 2 + rye/src/lock.rs | 50 +++++++++------ rye/tests/test_sync.rs | 142 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c94944d1..342624cbf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ _Unreleased_ - Retain markers when adding dependencies with features when uv is used. #807 +- Fixed a bug that caused repeated syncs not to recall all previous options. #830 + ## 0.27.0 diff --git a/rye/src/lock.rs b/rye/src/lock.rs index 350a487df2..354a5bb279 100644 --- a/rye/src/lock.rs +++ b/rye/src/lock.rs @@ -38,7 +38,7 @@ static REQUIREMENTS_HEADER: &str = r#"# generated by rye "#; static PARAM_RE: Lazy = - Lazy::new(|| Regex::new(r"^# (pre|features|all-features|with_sources):\s*(.*?)$").unwrap()); + Lazy::new(|| Regex::new(r"^# (pre|features|all-features|with-sources):\s*(.*?)$").unwrap()); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum LockMode { @@ -111,7 +111,9 @@ impl LockOptions { "all-features" => { rv.all_features = rv.all_features || serde_json::from_str(value)? } - "with-sources" => rv.with_sources = serde_json::from_str(value)?, + "with-sources" => { + rv.with_sources = rv.with_sources || serde_json::from_str(value)? + } _ => unreachable!(), } } @@ -139,7 +141,8 @@ pub fn update_workspace_lockfile( echo!("Generating {} lockfile: {}", lock_mode, lockfile.display()); } - let features_by_project = collect_workspace_features(lock_options); + let lock_options = restore_lock_options(lockfile, lock_options)?; + let features_by_project = collect_workspace_features(&lock_options); let mut req_file = NamedTempFile::new()?; let mut local_projects = HashMap::new(); @@ -185,7 +188,7 @@ pub fn update_workspace_lockfile( req_file.path(), lockfile, sources, - lock_options, + &lock_options, &exclusions, true, )?; @@ -193,6 +196,19 @@ pub fn update_workspace_lockfile( Ok(()) } +/// Tries to restore the lock options from the given lockfile. +fn restore_lock_options<'o>( + lockfile: &Path, + lock_options: &'o LockOptions, +) -> Result, Error> { + if lockfile.is_file() { + let requirements = fs::read_to_string(lockfile)?; + Ok(LockOptions::restore(&requirements, lock_options)?) + } else { + Ok(Cow::Borrowed(lock_options)) + } +} + fn format_project_extras<'a>( features_by_project: Option<&'a HashMap>>, project: &PyProject, @@ -307,11 +323,12 @@ pub fn update_single_project_lockfile( echo!("Generating {} lockfile: {}", lock_mode, lockfile.display()); } + let lock_options = restore_lock_options(lockfile, lock_options)?; let mut req_file = NamedTempFile::new()?; // virtual packages are themselves not installed if !pyproject.is_virtual() { - let features_by_project = collect_workspace_features(lock_options); + let features_by_project = collect_workspace_features(&lock_options); let applicable_extras = format_project_extras(features_by_project.as_ref(), pyproject)?; writeln!( req_file, @@ -340,7 +357,7 @@ pub fn update_single_project_lockfile( req_file.path(), lockfile, sources, - lock_options, + &lock_options, &exclusions, false, )?; @@ -363,19 +380,14 @@ fn generate_lockfile( let use_uv = Config::current().use_uv(); let scratch = tempfile::tempdir()?; let requirements_file = scratch.path().join("requirements.txt"); - let lock_options = if lockfile.is_file() { - let requirements = fs::read_to_string(lockfile)?; - fs::write(&requirements_file, &requirements) + if lockfile.is_file() { + fs::copy(lockfile, &requirements_file) .path_context(&requirements_file, "unable to restore requirements file")?; - LockOptions::restore(&requirements, lock_options)? - } else { - if !use_uv { - fs::write(&requirements_file, b"").path_context( - &requirements_file, - "unable to write empty requirements file", - )?; - } - Cow::Borrowed(lock_options) + } else if !use_uv { + fs::write(&requirements_file, b"").path_context( + &requirements_file, + "unable to write empty requirements file", + )?; }; let mut cmd = if use_uv { @@ -453,7 +465,7 @@ fn generate_lockfile( workspace_path, exclusions, sources, - &lock_options, + lock_options, )?; Ok(()) diff --git a/rye/tests/test_sync.rs b/rye/tests/test_sync.rs index 6c2002a9fd..d0a069a994 100644 --- a/rye/tests/test_sync.rs +++ b/rye/tests/test_sync.rs @@ -1,5 +1,7 @@ use std::fs; +use insta::{assert_snapshot, Settings}; + use crate::common::{rye_cmd_snapshot, Space}; mod common; @@ -119,3 +121,143 @@ fn test_add_autosync() { + werkzeug==3.0.1 "###); } + +#[test] +fn test_autosync_remember() { + // remove the dependency source markers since they are instable between platforms + let mut settings = Settings::clone_current(); + settings.add_filter(r"(?m)^\s+# via .*\r?\n", ""); + settings.add_filter(r"(?m)^(\s+)\d+\.\d+s( \d+ms)?", "$1[TIMING]"); + let _guard = settings.bind_to_scope(); + + let space = Space::new(); + space.init("my-project"); + rye_cmd_snapshot!(space.rye_cmd().arg("sync").arg("--with-sources").arg("--all-features"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.2 + Generating production lockfile: [TEMP_PATH]/project/requirements.lock + Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock + Installing dependencies + Done! + + ----- stderr ----- + Built 1 editable in [EXECUTION_TIME] + Installed 1 package in [EXECUTION_TIME] + + my-project==0.1.0 (from file:[TEMP_PATH]/project) + "###); + + rye_cmd_snapshot!(space.rye_cmd() + .arg("add").arg("--optional=web").arg("flask==3.0.0").arg("colorama"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + Added flask>=3.0.0 as optional (web) dependency + Added colorama>=0.4.6 as optional (web) dependency + Reusing already existing virtualenv + Generating production lockfile: [TEMP_PATH]/project/requirements.lock + Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock + Installing dependencies + Done! + + ----- stderr ----- + Built 1 editable in [EXECUTION_TIME] + Resolved 8 packages in [EXECUTION_TIME] + Downloaded 8 packages in [EXECUTION_TIME] + Uninstalled 1 package in [EXECUTION_TIME] + Installed 9 packages in [EXECUTION_TIME] + + blinker==1.7.0 + + click==8.1.7 + + colorama==0.4.6 + + flask==3.0.0 + + itsdangerous==2.1.2 + + jinja2==3.1.2 + + markupsafe==2.1.3 + - my-project==0.1.0 (from file:[TEMP_PATH]/project) + + my-project==0.1.0 (from file:[TEMP_PATH]/project) + + werkzeug==3.0.1 + "###); + assert_snapshot!(std::fs::read_to_string(space.project_path().join("requirements.lock")).unwrap(), @r###" + # generated by rye + # use `rye lock` or `rye sync` to update this lockfile + # + # last locked with the following flags: + # pre: false + # features: [] + # all-features: true + # with-sources: true + + --index-url https://pypi.org/simple/ + + -e file:. + blinker==1.7.0 + click==8.1.7 + colorama==0.4.6 + flask==3.0.0 + itsdangerous==2.1.2 + jinja2==3.1.2 + markupsafe==2.1.3 + werkzeug==3.0.1 + "###); + + rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("urllib3"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + Added urllib3>=2.1.0 as regular dependency + Reusing already existing virtualenv + Generating production lockfile: [TEMP_PATH]/project/requirements.lock + Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock + Installing dependencies + Done! + + ----- stderr ----- + Built 1 editable in [EXECUTION_TIME] + Resolved 1 package in [EXECUTION_TIME] + Downloaded 1 package in [EXECUTION_TIME] + Uninstalled 1 package in [EXECUTION_TIME] + Installed 2 packages in [EXECUTION_TIME] + - my-project==0.1.0 (from file:[TEMP_PATH]/project) + + my-project==0.1.0 (from file:[TEMP_PATH]/project) + + urllib3==2.1.0 + "###); + + // would be nice to assert on the non quiet output here but unfortunately + // on CI we seem to have some flakage on this command with regards to + // rebuilding the editable. + rye_cmd_snapshot!(space.rye_cmd().arg("sync").arg("-q"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + "###); + + assert_snapshot!(std::fs::read_to_string(space.project_path().join("requirements.lock")).unwrap(), @r###" + # generated by rye + # use `rye lock` or `rye sync` to update this lockfile + # + # last locked with the following flags: + # pre: false + # features: [] + # all-features: true + # with-sources: true + + --index-url https://pypi.org/simple/ + + -e file:. + blinker==1.7.0 + click==8.1.7 + colorama==0.4.6 + flask==3.0.0 + itsdangerous==2.1.2 + jinja2==3.1.2 + markupsafe==2.1.3 + urllib3==2.1.0 + werkzeug==3.0.1 + "###); +}