Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for per workspace member locks #1094

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion docs/guide/pyproject.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,15 @@ 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.

```toml
[tool.rye.workspace]
members = ["mylib-*"]
per_member_lock = false
```

For more information consult the [Workspaces Guide](../workspaces/).
16 changes: 16 additions & 0 deletions docs/guide/workspaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.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
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
```
22 changes: 16 additions & 6 deletions rye/src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ pub fn update_workspace_lockfile(
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,
)?;
}
}

req_file.flush()?;
Expand Down Expand Up @@ -347,12 +360,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) {
Expand Down
10 changes: 10 additions & 0 deletions rye/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ pub struct Workspace {
root: PathBuf,
doc: DocumentMut,
members: Option<Vec<String>>,
pub per_member_lock: bool,
}

impl Workspace {
Expand All @@ -393,6 +394,10 @@ impl Workspace {
.filter_map(|item| item.as_str().map(|x| x.to_string()))
.collect::<Vec<_>>()
}),
per_member_lock: workspace
.get("per_member_lock")
.and_then(|x| x.as_bool())
.unwrap_or(false),
})
}

Expand Down Expand Up @@ -469,6 +474,11 @@ impl Workspace {
self: &'a Arc<Self>,
) -> impl Iterator<Item = Result<PyProject, Error>> + '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()))
Expand Down
54 changes: 54 additions & 0 deletions rye/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,60 @@ 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.clone()).ok();

// Create the workspace member
let status = self
.cmd(get_bin())
.arg("init")
.arg("--name")
.arg(name)
.arg("-q")
.current_dir(p)
.status()
.unwrap();
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
}
Expand Down
Loading
Loading