From 0c28f65405f0ff42327de7e62be367f4b11cdf29 Mon Sep 17 00:00:00 2001 From: davfsa Date: Sun, 19 May 2024 13:16:49 +0200 Subject: [PATCH 01/10] Add support for per workspace member locks This functionality can be enabled by setting `tools.rye.workspace.per_member_lock` to `true` --- docs/guide/pyproject.md | 3 +- docs/guide/workspaces.md | 16 ++ rye/src/lock.rs | 31 ++-- rye/src/pyproject.rs | 5 + rye/tests/common/mod.rs | 34 ++++ rye/tests/test_sync.rs | 357 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 434 insertions(+), 12 deletions(-) diff --git a/docs/guide/pyproject.md b/docs/guide/pyproject.md index 34db616b37..cf997ad242 100644 --- a/docs/guide/pyproject.md +++ b/docs/guide/pyproject.md @@ -204,7 +204,7 @@ hello-world = { call = "builtins:print('Hello World!')" } ## `tool.rye.workspace` When a table with that key is stored, then a project is declared to be a -[workspace](../workspaces/) root. By default all Python projects discovered in +[workspace](../workspaces/) root. By default, all Python projects discovered in sub folders will then become members of this workspace and share a virtualenv. Optionally the `members` key (an array) can be used to restrict these members. In that list globs can be used. The root project itself is always a member. @@ -212,6 +212,7 @@ In that list globs can be used. The root project itself is always a member. ```toml [tool.rye.workspace] members = ["mylib-*"] +per_member_lock = false ``` For more information consult the [Workspaces Guide](../workspaces/). diff --git a/docs/guide/workspaces.md b/docs/guide/workspaces.md index 67a3fd0b2b..ae3cf632ea 100644 --- a/docs/guide/workspaces.md +++ b/docs/guide/workspaces.md @@ -46,3 +46,19 @@ of the `myname-bar` package you would need to do this: ``` rye sync --features=myname-bar/foo ``` + +## Per member lockfile + ++++ 0.34.0 + +Rye will always merge the requirements of all members in a workspace and generate a top level +lockfile to keep the virtual environment consistent and up to date. In cases where you need to +still keep a different lockfile per member to split installation (for example, in a docker context, +where members live in different containers), you can enable `per_member_lock`, which will generate +a lockfile in the root of each member. + +```toml +[tool.rye.workspace] +members = ["myname-*"] +per_member_lock = true +``` \ No newline at end of file diff --git a/rye/src/lock.rs b/rye/src/lock.rs index d910401274..7238fec2b8 100644 --- a/rye/src/lock.rs +++ b/rye/src/lock.rs @@ -174,24 +174,36 @@ pub fn update_workspace_lockfile( } local_projects.insert(pyproject.normalized_name()?, rel_url); - projects.push(pyproject); - } - for pyproject in &projects { dump_dependencies( - pyproject, + &pyproject, &local_projects, req_file.as_file_mut(), DependencyKind::Normal, )?; if lock_mode == LockMode::Dev { dump_dependencies( - pyproject, + &pyproject, &local_projects, req_file.as_file_mut(), DependencyKind::Dev, )?; } + + if workspace.per_member_lock && !pyproject.is_workspace_root() { + update_single_project_lockfile( + py_ver, + &pyproject, + lock_mode, + &pyproject.root_path().join(lockfile.file_name().unwrap()), + output, + sources, + &lock_options, + keyring_provider, + )?; + } + + projects.push(pyproject); } req_file.flush()?; @@ -347,12 +359,9 @@ pub fn update_single_project_lockfile( if !pyproject.is_virtual() { let features_by_project = collect_workspace_features(&lock_options); let applicable_extras = format_project_extras(features_by_project.as_ref(), pyproject)?; - writeln!( - req_file, - "-e {}{}", - make_relative_url(&pyproject.root_path(), &pyproject.workspace_path())?, - applicable_extras - )?; + // We can always write `file:.` here as this will only ever be called when updating + // a lockfile of a project de-attached from the workspace + writeln!(req_file, "-e file:.{}", applicable_extras)?; } for dep in pyproject.iter_dependencies(DependencyKind::Normal) { diff --git a/rye/src/pyproject.rs b/rye/src/pyproject.rs index bf32b073f4..582eef7498 100644 --- a/rye/src/pyproject.rs +++ b/rye/src/pyproject.rs @@ -373,6 +373,7 @@ pub struct Workspace { root: PathBuf, doc: DocumentMut, members: Option>, + pub per_member_lock: bool, } impl Workspace { @@ -393,6 +394,10 @@ impl Workspace { .filter_map(|item| item.as_str().map(|x| x.to_string())) .collect::>() }), + per_member_lock: workspace + .get("per_member_lock") + .and_then(|x| x.as_bool()) + .unwrap_or(false), }) } diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index 081ab7dfd8..3bd70003b1 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -219,6 +219,40 @@ impl Space { assert!(status.success()); } + #[allow(unused)] + pub fn init_virtual(&self, name: &str) { + let status = self + .cmd(get_bin()) + .arg("init") + .arg("--name") + .arg(name) + .arg("--virtual") + .arg("-q") + .current_dir(self.project_path()) + .status() + .unwrap(); + assert!(status.success()); + } + + #[allow(unused)] + pub fn init_workspace_member(&self, name: &str) { + // First we need to create the directory where it will be placed + let p = self.project_path().join(name); + fs::create_dir(p).ok(); + + // Create the workspace member + let status = self + .cmd(get_bin()) + .arg("init") + .arg("--name") + .arg(name) + .arg("-q") + .current_dir(self.project_path().join(name)) + .status() + .unwrap(); + assert!(status.success()); + } + pub fn rye_home(&self) -> &Path { &self.rye_home } diff --git a/rye/tests/test_sync.rs b/rye/tests/test_sync.rs index 61bf9e768b..da76599376 100644 --- a/rye/tests/test_sync.rs +++ b/rye/tests/test_sync.rs @@ -1,6 +1,7 @@ use std::fs; use insta::{assert_snapshot, Settings}; +use toml_edit::{value, Array}; use crate::common::{rye_cmd_snapshot, Space}; @@ -263,3 +264,359 @@ fn test_autosync_remember() { werkzeug==3.0.1 "###); } + +#[test] +fn test_workspace_sync() { + // 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_virtual("my-workspace-project"); + space.init_workspace_member("my-workspace-member"); + space.init_workspace_member("my-other-workspace-member"); + + space.edit_toml("pyproject.toml", |doc| { + let mut member_array = Array::default(); + member_array.push("my-workspace-member"); + member_array.push("my-other-workspace-member"); + + doc["tool"]["rye"]["virtual"] = value(true); + doc["tool"]["rye"]["workspace"]["members"] = value(member_array); + }); + + rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.3 + Generating production lockfile: [TEMP_PATH]/project/requirements.lock + Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock + Installing dependencies + Done! + + ----- stderr ----- + Built 2 editables in [EXECUTION_TIME] + Installed 2 packages in [EXECUTION_TIME] + + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + + my-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) + "###); + + rye_cmd_snapshot!(space.rye_cmd() + .current_dir(space.project_path().join("my-workspace-member")) + .arg("add").arg("flask==3.0.0").arg("colorama"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + Added flask==3.0.0 as regular dependency + Added colorama>=0.4.6 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 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-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) + + my-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) + + werkzeug==3.0.1 + "###); + + rye_cmd_snapshot!(space.rye_cmd() + .current_dir(space.project_path().join("my-other-workspace-member")) + .arg("add").arg("fastapi==0.104.1").arg("colorama"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + Added fastapi==0.104.1 as regular dependency + Added colorama>=0.4.6 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 9 packages in [EXECUTION_TIME] + Downloaded 9 packages in [EXECUTION_TIME] + Uninstalled 1 package in [EXECUTION_TIME] + Installed 10 packages in [EXECUTION_TIME] + + annotated-types==0.6.0 + + anyio==3.7.1 + + fastapi==0.104.1 + + idna==3.4 + - my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + + pydantic==2.5.1 + + pydantic-core==2.14.3 + + sniffio==1.3.0 + + starlette==0.27.0 + + typing-extensions==4.8.0 + "###); + + 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: false + # with-sources: false + # generate-hashes: false + + -e file:my-other-workspace-member + -e file:my-workspace-member + annotated-types==0.6.0 + anyio==3.7.1 + blinker==1.7.0 + click==8.1.7 + colorama==0.4.6 + fastapi==0.104.1 + flask==3.0.0 + idna==3.4 + itsdangerous==2.1.2 + jinja2==3.1.2 + markupsafe==2.1.3 + pydantic==2.5.1 + pydantic-core==2.14.3 + sniffio==1.3.0 + starlette==0.27.0 + typing-extensions==4.8.0 + werkzeug==3.0.1 + "###); + + // Ensure we didn't create per member file locks + assert!(std::fs::metadata( + space + .project_path() + .join("my-workspace-member") + .join("requirements.lock") + ) + .is_err()); + assert!(std::fs::metadata( + space + .project_path() + .join("my-other-workspace-member") + .join("requirements.lock") + ) + .is_err()); +} + +#[test] +fn test_workspace_sync_with_per_member_lock() { + // 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_virtual("my-workspace-project"); + space.init_workspace_member("my-workspace-member"); + space.init_workspace_member("my-other-workspace-member"); + + space.edit_toml("pyproject.toml", |doc| { + let mut member_array = Array::default(); + member_array.push("my-workspace-member"); + member_array.push("my-other-workspace-member"); + + doc["tool"]["rye"]["virtual"] = value(true); + doc["tool"]["rye"]["workspace"]["members"] = value(member_array); + doc["tool"]["rye"]["workspace"]["per_member_lock"] = value(true); + }); + + rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.3 + Generating production lockfile: [TEMP_PATH]/project/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements.lock + Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements-dev.lock + Installing dependencies + Done! + + ----- stderr ----- + Built 2 editables in [EXECUTION_TIME] + Installed 2 packages in [EXECUTION_TIME] + + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + + my-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) + "###); + + rye_cmd_snapshot!(space.rye_cmd() + .current_dir(space.project_path().join("my-workspace-member")) + .arg("add").arg("flask==3.0.0").arg("colorama"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + Added flask==3.0.0 as regular dependency + Added colorama>=0.4.6 as regular dependency + Reusing already existing virtualenv + Generating production lockfile: [TEMP_PATH]/project/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements.lock + Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-other-workspace-member/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-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) + + my-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) + + werkzeug==3.0.1 + "###); + + rye_cmd_snapshot!(space.rye_cmd() + .current_dir(space.project_path().join("my-other-workspace-member")) + .arg("add").arg("fastapi==0.104.1").arg("colorama"), + @r###" + success: true + exit_code: 0 + ----- stdout ----- + Added fastapi==0.104.1 as regular dependency + Added colorama>=0.4.6 as regular dependency + Reusing already existing virtualenv + Generating production lockfile: [TEMP_PATH]/project/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements.lock + Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements-dev.lock + Installing dependencies + Done! + + ----- stderr ----- + Built 1 editable in [EXECUTION_TIME] + Resolved 9 packages in [EXECUTION_TIME] + Downloaded 9 packages in [EXECUTION_TIME] + Uninstalled 1 package in [EXECUTION_TIME] + Installed 10 packages in [EXECUTION_TIME] + + annotated-types==0.6.0 + + anyio==3.7.1 + + fastapi==0.104.1 + + idna==3.4 + - my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + + pydantic==2.5.1 + + pydantic-core==2.14.3 + + sniffio==1.3.0 + + starlette==0.27.0 + + typing-extensions==4.8.0 + "###); + + 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: false + # with-sources: false + # generate-hashes: false + + -e file:my-other-workspace-member + -e file:my-workspace-member + annotated-types==0.6.0 + anyio==3.7.1 + blinker==1.7.0 + click==8.1.7 + colorama==0.4.6 + fastapi==0.104.1 + flask==3.0.0 + idna==3.4 + itsdangerous==2.1.2 + jinja2==3.1.2 + markupsafe==2.1.3 + pydantic==2.5.1 + pydantic-core==2.14.3 + sniffio==1.3.0 + starlette==0.27.0 + typing-extensions==4.8.0 + werkzeug==3.0.1 + "###); + + // Check per member locks + assert_snapshot!(std::fs::read_to_string(space.project_path().join("my-workspace-member").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: false + # with-sources: false + # generate-hashes: false + + -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 + "###); + + assert_snapshot!(std::fs::read_to_string(space.project_path().join("my-other-workspace-member").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: false + # with-sources: false + # generate-hashes: false + + -e file:. + annotated-types==0.6.0 + anyio==3.7.1 + colorama==0.4.6 + fastapi==0.104.1 + idna==3.4 + pydantic==2.5.1 + pydantic-core==2.14.3 + sniffio==1.3.0 + starlette==0.27.0 + typing-extensions==4.8.0 + "###); +} From a88a0b6aae51ff11b60108e5337ab303f3c6ff6b Mon Sep 17 00:00:00 2001 From: davfsa Date: Sun, 19 May 2024 15:14:01 +0200 Subject: [PATCH 02/10] Remove simplification of pyproject for loop I missed to see that local_projects was being populated through the loop --- rye/src/lock.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rye/src/lock.rs b/rye/src/lock.rs index 7238fec2b8..fe20726259 100644 --- a/rye/src/lock.rs +++ b/rye/src/lock.rs @@ -175,15 +175,19 @@ pub fn update_workspace_lockfile( local_projects.insert(pyproject.normalized_name()?, rel_url); + projects.push(pyproject); + } + + for pyproject in &projects { dump_dependencies( - &pyproject, + pyproject, &local_projects, req_file.as_file_mut(), DependencyKind::Normal, )?; if lock_mode == LockMode::Dev { dump_dependencies( - &pyproject, + pyproject, &local_projects, req_file.as_file_mut(), DependencyKind::Dev, @@ -193,7 +197,7 @@ pub fn update_workspace_lockfile( if workspace.per_member_lock && !pyproject.is_workspace_root() { update_single_project_lockfile( py_ver, - &pyproject, + pyproject, lock_mode, &pyproject.root_path().join(lockfile.file_name().unwrap()), output, @@ -202,8 +206,6 @@ pub fn update_workspace_lockfile( keyring_provider, )?; } - - projects.push(pyproject); } req_file.flush()?; From d1b869283d8446f2de4c3546a994dba87dd68bac Mon Sep 17 00:00:00 2001 From: davfsa Date: Sun, 19 May 2024 15:14:30 +0200 Subject: [PATCH 03/10] Remove extra spaces --- rye/src/lock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rye/src/lock.rs b/rye/src/lock.rs index fe20726259..e6ba4f788b 100644 --- a/rye/src/lock.rs +++ b/rye/src/lock.rs @@ -177,7 +177,7 @@ pub fn update_workspace_lockfile( projects.push(pyproject); } - + for pyproject in &projects { dump_dependencies( pyproject, From decf6f942953c6682bfefd261bc074d35b875a6b Mon Sep 17 00:00:00 2001 From: davfsa Date: Sun, 19 May 2024 16:42:50 +0200 Subject: [PATCH 04/10] Attempt to fix windows test issues by sorting the projects --- rye/src/pyproject.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rye/src/pyproject.rs b/rye/src/pyproject.rs index 582eef7498..9d6716e50c 100644 --- a/rye/src/pyproject.rs +++ b/rye/src/pyproject.rs @@ -474,6 +474,11 @@ impl Workspace { self: &'a Arc, ) -> impl Iterator> + 'a { walkdir::WalkDir::new(&self.root) + .sort_by( + // Perform proper sorting to avoid platform dependency to ensure + // output reproducibility. This is important for tests + |x,y| x.file_name().cmp(y.file_name()) + ) .into_iter() .filter_entry(|entry| { !(entry.file_type().is_dir() && skip_recurse_into(entry.file_name())) From 40a09465fc436dfbbdd8c9df75bf99dc94773ddc Mon Sep 17 00:00:00 2001 From: davfsa Date: Sun, 19 May 2024 16:55:45 +0200 Subject: [PATCH 05/10] Fix tests output and run fmt --- rye/src/pyproject.rs | 2 +- rye/tests/test_sync.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rye/src/pyproject.rs b/rye/src/pyproject.rs index 9d6716e50c..cffacd3743 100644 --- a/rye/src/pyproject.rs +++ b/rye/src/pyproject.rs @@ -477,7 +477,7 @@ impl Workspace { .sort_by( // Perform proper sorting to avoid platform dependency to ensure // output reproducibility. This is important for tests - |x,y| x.file_name().cmp(y.file_name()) + |x, y| x.file_name().cmp(y.file_name()), ) .into_iter() .filter_entry(|entry| { diff --git a/rye/tests/test_sync.rs b/rye/tests/test_sync.rs index da76599376..57e2524334 100644 --- a/rye/tests/test_sync.rs +++ b/rye/tests/test_sync.rs @@ -451,11 +451,11 @@ fn test_workspace_sync_with_per_member_lock() { Initializing new virtualenv in [TEMP_PATH]/project/.venv Python version: cpython@3.12.3 Generating production lockfile: [TEMP_PATH]/project/requirements.lock - Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock Generating production lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock - Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock Generating dev lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock Installing dependencies Done! @@ -477,11 +477,11 @@ fn test_workspace_sync_with_per_member_lock() { Added colorama>=0.4.6 as regular dependency Reusing already existing virtualenv Generating production lockfile: [TEMP_PATH]/project/requirements.lock - Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock Generating production lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock - Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock Generating dev lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock Installing dependencies Done! @@ -514,11 +514,11 @@ fn test_workspace_sync_with_per_member_lock() { Added colorama>=0.4.6 as regular dependency Reusing already existing virtualenv Generating production lockfile: [TEMP_PATH]/project/requirements.lock - Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock Generating production lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements.lock + Generating production lockfile: [TEMP_PATH]/project/my-workspace-member/requirements.lock Generating dev lockfile: [TEMP_PATH]/project/requirements-dev.lock - Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock Generating dev lockfile: [TEMP_PATH]/project/my-other-workspace-member/requirements-dev.lock + Generating dev lockfile: [TEMP_PATH]/project/my-workspace-member/requirements-dev.lock Installing dependencies Done! From 54ea11f01da2fb05f74842c3083bb1f64ec2908e Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 20 May 2024 08:33:45 +0200 Subject: [PATCH 06/10] Disable `test_workspace_sync` and test_workspace_sync_with_per_member_lock` on windows --- rye/src/lock.rs | 1 - rye/tests/test_sync.rs | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/rye/src/lock.rs b/rye/src/lock.rs index e6ba4f788b..834331ad07 100644 --- a/rye/src/lock.rs +++ b/rye/src/lock.rs @@ -174,7 +174,6 @@ pub fn update_workspace_lockfile( } local_projects.insert(pyproject.normalized_name()?, rel_url); - projects.push(pyproject); } diff --git a/rye/tests/test_sync.rs b/rye/tests/test_sync.rs index 57e2524334..b1f248d1f4 100644 --- a/rye/tests/test_sync.rs +++ b/rye/tests/test_sync.rs @@ -265,6 +265,10 @@ fn test_autosync_remember() { "###); } +// `test_workspace_sync` and `test_workspace_sync_with_per_member_lock` are disabled on Windows +// due to Windows quirks when it comes to handling directory change times. +// For more context, see https://github.com/astral-sh/rye/pull/1094 +#[cfg(unix)] #[test] fn test_workspace_sync() { // remove the dependency source markers since they are instable between platforms @@ -421,6 +425,7 @@ fn test_workspace_sync() { .is_err()); } +#[cfg(unix)] #[test] fn test_workspace_sync_with_per_member_lock() { // remove the dependency source markers since they are instable between platforms From 743fb8c65fa69c091ff43305c004d4f2e9d5299c Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 20 May 2024 10:39:27 +0200 Subject: [PATCH 07/10] Re-use path calculation and add missing trailing line --- docs/guide/workspaces.md | 2 +- rye/tests/common/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/workspaces.md b/docs/guide/workspaces.md index ae3cf632ea..39d3748bac 100644 --- a/docs/guide/workspaces.md +++ b/docs/guide/workspaces.md @@ -61,4 +61,4 @@ a lockfile in the root of each member. [tool.rye.workspace] members = ["myname-*"] per_member_lock = true -``` \ No newline at end of file +``` diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index 3bd70003b1..c39dd1f1d2 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -247,7 +247,7 @@ impl Space { .arg("--name") .arg(name) .arg("-q") - .current_dir(self.project_path().join(name)) + .current_dir(p) .status() .unwrap(); assert!(status.success()); From 2d9f4475ced066daa726d64b06d662dcca796594 Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 20 May 2024 10:40:56 +0200 Subject: [PATCH 08/10] Add missing clone --- rye/tests/common/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index c39dd1f1d2..c49ffe26cc 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -238,7 +238,7 @@ impl Space { pub fn init_workspace_member(&self, name: &str) { // First we need to create the directory where it will be placed let p = self.project_path().join(name); - fs::create_dir(p).ok(); + fs::create_dir(p.clone()).ok(); // Create the workspace member let status = self From 3cc60c96424964f1b9b9c888573eba9bb688cfb8 Mon Sep 17 00:00:00 2001 From: davfsa Date: Mon, 20 May 2024 22:22:55 +0200 Subject: [PATCH 09/10] Improve testing scope of workspace test - Have a virtual and non-virtual project and ensure that only one is used - This has the "side effect" that the windows CI inconsistencies should no longer occur - Use glob for members matching - Remove redundant pyproject toml edits --- rye/tests/common/mod.rs | 20 +++++++++++++++++++ rye/tests/test_sync.rs | 44 +++++++++++------------------------------ 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index c49ffe26cc..8c8575a8c0 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -253,6 +253,26 @@ impl Space { assert!(status.success()); } + #[allow(unused)] + pub fn init_virtual_workspace_member(&self, name: &str) { + // First we need to create the directory where it will be placed + let p = self.project_path().join(name); + fs::create_dir(p.clone()).ok(); + + // Create the workspace member + let status = self + .cmd(get_bin()) + .arg("init") + .arg("--name") + .arg(name) + .arg("-q") + .arg("--virtual") + .current_dir(p) + .status() + .unwrap(); + assert!(status.success()); + } + pub fn rye_home(&self) -> &Path { &self.rye_home } diff --git a/rye/tests/test_sync.rs b/rye/tests/test_sync.rs index b1f248d1f4..6c889da39f 100644 --- a/rye/tests/test_sync.rs +++ b/rye/tests/test_sync.rs @@ -265,10 +265,6 @@ fn test_autosync_remember() { "###); } -// `test_workspace_sync` and `test_workspace_sync_with_per_member_lock` are disabled on Windows -// due to Windows quirks when it comes to handling directory change times. -// For more context, see https://github.com/astral-sh/rye/pull/1094 -#[cfg(unix)] #[test] fn test_workspace_sync() { // remove the dependency source markers since they are instable between platforms @@ -280,14 +276,12 @@ fn test_workspace_sync() { let space = Space::new(); space.init_virtual("my-workspace-project"); space.init_workspace_member("my-workspace-member"); - space.init_workspace_member("my-other-workspace-member"); + space.init_virtual_workspace_member("my-other-workspace-member"); space.edit_toml("pyproject.toml", |doc| { let mut member_array = Array::default(); - member_array.push("my-workspace-member"); - member_array.push("my-other-workspace-member"); + member_array.push("my-*"); - doc["tool"]["rye"]["virtual"] = value(true); doc["tool"]["rye"]["workspace"]["members"] = value(member_array); }); @@ -303,9 +297,8 @@ fn test_workspace_sync() { Done! ----- stderr ----- - Built 2 editables in [EXECUTION_TIME] - Installed 2 packages in [EXECUTION_TIME] - + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + Built 1 editable in [EXECUTION_TIME] + Installed 1 package in [EXECUTION_TIME] + my-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) "###); @@ -358,17 +351,13 @@ fn test_workspace_sync() { Done! ----- stderr ----- - Built 1 editable in [EXECUTION_TIME] Resolved 9 packages in [EXECUTION_TIME] Downloaded 9 packages in [EXECUTION_TIME] - Uninstalled 1 package in [EXECUTION_TIME] - Installed 10 packages in [EXECUTION_TIME] + Installed 9 packages in [EXECUTION_TIME] + annotated-types==0.6.0 + anyio==3.7.1 + fastapi==0.104.1 + idna==3.4 - - my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) - + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + pydantic==2.5.1 + pydantic-core==2.14.3 + sniffio==1.3.0 @@ -387,7 +376,6 @@ fn test_workspace_sync() { # with-sources: false # generate-hashes: false - -e file:my-other-workspace-member -e file:my-workspace-member annotated-types==0.6.0 anyio==3.7.1 @@ -425,7 +413,6 @@ fn test_workspace_sync() { .is_err()); } -#[cfg(unix)] #[test] fn test_workspace_sync_with_per_member_lock() { // remove the dependency source markers since they are instable between platforms @@ -435,16 +422,14 @@ fn test_workspace_sync_with_per_member_lock() { let _guard = settings.bind_to_scope(); let space = Space::new(); - space.init_virtual("my-workspace-project"); + space.init_virtual("my-project"); space.init_workspace_member("my-workspace-member"); - space.init_workspace_member("my-other-workspace-member"); + space.init_virtual_workspace_member("my-other-workspace-member"); space.edit_toml("pyproject.toml", |doc| { let mut member_array = Array::default(); - member_array.push("my-workspace-member"); - member_array.push("my-other-workspace-member"); + member_array.push("my-*"); - doc["tool"]["rye"]["virtual"] = value(true); doc["tool"]["rye"]["workspace"]["members"] = value(member_array); doc["tool"]["rye"]["workspace"]["per_member_lock"] = value(true); }); @@ -465,9 +450,8 @@ fn test_workspace_sync_with_per_member_lock() { Done! ----- stderr ----- - Built 2 editables in [EXECUTION_TIME] - Installed 2 packages in [EXECUTION_TIME] - + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + Built 1 editable in [EXECUTION_TIME] + Installed 1 package in [EXECUTION_TIME] + my-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-workspace-member) "###); @@ -528,17 +512,13 @@ fn test_workspace_sync_with_per_member_lock() { Done! ----- stderr ----- - Built 1 editable in [EXECUTION_TIME] Resolved 9 packages in [EXECUTION_TIME] Downloaded 9 packages in [EXECUTION_TIME] - Uninstalled 1 package in [EXECUTION_TIME] - Installed 10 packages in [EXECUTION_TIME] + Installed 9 packages in [EXECUTION_TIME] + annotated-types==0.6.0 + anyio==3.7.1 + fastapi==0.104.1 + idna==3.4 - - my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) - + my-other-workspace-member==0.1.0 (from file:[TEMP_PATH]/project/my-other-workspace-member) + pydantic==2.5.1 + pydantic-core==2.14.3 + sniffio==1.3.0 @@ -557,7 +537,6 @@ fn test_workspace_sync_with_per_member_lock() { # with-sources: false # generate-hashes: false - -e file:my-other-workspace-member -e file:my-workspace-member annotated-types==0.6.0 anyio==3.7.1 @@ -612,7 +591,6 @@ fn test_workspace_sync_with_per_member_lock() { # with-sources: false # generate-hashes: false - -e file:. annotated-types==0.6.0 anyio==3.7.1 colorama==0.4.6 From 24c8c30f81ac680be425d32d430d8d5332eb8e42 Mon Sep 17 00:00:00 2001 From: davfsa Date: Thu, 27 Jun 2024 02:45:08 +0200 Subject: [PATCH 10/10] Bump addition version --- docs/guide/workspaces.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/workspaces.md b/docs/guide/workspaces.md index 39d3748bac..bb08188670 100644 --- a/docs/guide/workspaces.md +++ b/docs/guide/workspaces.md @@ -49,7 +49,7 @@ rye sync --features=myname-bar/foo ## Per member lockfile -+++ 0.34.0 ++++ 0.36.0 Rye will always merge the requirements of all members in a workspace and generate a top level lockfile to keep the virtual environment consistent and up to date. In cases where you need to