Skip to content

Commit

Permalink
Fix a bug that caused impartial lock options recalling (astral-sh#830)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored Mar 4, 2024
1 parent fa0b306 commit 91b1f3d
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

<!-- released start -->

## 0.27.0
Expand Down
50 changes: 31 additions & 19 deletions rye/src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ static REQUIREMENTS_HEADER: &str = r#"# generated by rye
"#;
static PARAM_RE: Lazy<Regex> =
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 {
Expand Down Expand Up @@ -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!(),
}
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -185,14 +188,27 @@ pub fn update_workspace_lockfile(
req_file.path(),
lockfile,
sources,
lock_options,
&lock_options,
&exclusions,
true,
)?;

Ok(())
}

/// Tries to restore the lock options from the given lockfile.
fn restore_lock_options<'o>(
lockfile: &Path,
lock_options: &'o LockOptions,
) -> Result<Cow<'o, LockOptions>, 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<String, HashSet<&str>>>,
project: &PyProject,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -340,7 +357,7 @@ pub fn update_single_project_lockfile(
req_file.path(),
lockfile,
sources,
lock_options,
&lock_options,
&exclusions,
false,
)?;
Expand All @@ -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 {
Expand Down Expand Up @@ -453,7 +465,7 @@ fn generate_lockfile(
workspace_path,
exclusions,
sources,
&lock_options,
lock_options,
)?;

Ok(())
Expand Down
142 changes: 142 additions & 0 deletions rye/tests/test_sync.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::fs;

use insta::{assert_snapshot, Settings};

use crate::common::{rye_cmd_snapshot, Space};

mod common;
Expand Down Expand Up @@ -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: [email protected]
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
"###);
}

0 comments on commit 91b1f3d

Please sign in to comment.