Skip to content

Add module owners to workspace metadata#19122

Open
zsol wants to merge 1 commit intomainfrom
zsol/jj-kxyvzzmqmzrk
Open

Add module owners to workspace metadata#19122
zsol wants to merge 1 commit intomainfrom
zsol/jj-kxyvzzmqmzrk

Conversation

@zsol
Copy link
Copy Markdown
Member

@zsol zsol commented Apr 23, 2026

Summary

This adds a --module-owners flag to uv workspace metadata that makes uv collect a module -> package mapping and emit it in the metadata.

Passing this flag means the command will now do a sync --imprecise operation with all extras and dependency groups.

Comment thread crates/uv/src/commands/workspace/module_owners.rs Outdated
Comment thread crates/uv-resolver/src/lock/export/metadata.rs Outdated
Comment thread crates/uv-cli/src/lib.rs
@zsol zsol force-pushed the zsol/jj-kxyvzzmqmzrk branch 2 times, most recently from ac76385 to 604bf7a Compare April 23, 2026 15:21
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 23, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks


Comparing zsol/jj-kxyvzzmqmzrk (854af2c) with main (d46a7b6)

Open in CodSpeed

@zsol zsol force-pushed the zsol/jj-kxyvzzmqmzrk branch from 604bf7a to 854af2c Compare April 23, 2026 18:01
@zsol zsol marked this pull request as ready for review April 23, 2026 18:08
@zsol zsol requested a review from Gankra April 23, 2026 18:08
@zanieb
Copy link
Copy Markdown
Member

zanieb commented Apr 23, 2026

I don't think using this flag should cause a sync to happen implicitly, that seems confusing.

Comment on lines +163 to +177
"module_owners": {
"gpu": [
"gpu-a",
"gpu-b"
],
"gpu.a": [
"gpu-a"
],
"gpu.b": [
"gpu-b"
],
"typing_extensions": [
"typing-extensions"
]
},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd expect this to just be "modules" and I think I'd expect to use package ids not names in the mapping?

Why can a module have multiple packages? Should we report an owner for gpu?

Copy link
Copy Markdown
Contributor

@Gankra Gankra Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • namespace packages let a namespace be defined multiple times
  • legacy namespace packages let an actual module be defined multiple times
  • packages can both define the same module and there will be a deterministic preference based on search-path ordering and the kind of module (foo.py, foo/__init__.py and foo/ (namespace)).

Comment thread crates/uv-cli/src/lib.rs
/// This adds a mapping from importable module names to the package names that provide
/// them. To do this, the venv will be synced in "inexact" mode.
#[arg(long)]
pub module_owners: bool,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might prefer --report-modules, but not sure if I feel strongly yet. I'll mull it over.

Comment on lines +72 to +74
/// A mapping from importable module names to the distributions that provide them
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
module_owners: BTreeMap<ModuleName, Vec<PackageName>>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably reverse key and value in this map and make it package centric, in the sense that all metadata is attached to package node(s), rather than the other thing pointing towards a package.

use std::fmt::Display;

/// The name of an importable Python module.
type ModuleName = String;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about uv_pypi_types::identifier::Identifier?

Comment thread crates/uv-cli/src/lib.rs
/// Include module ownership metadata in the output.
///
/// This adds a mapping from importable module names to the package names that provide
/// them. To do this, the venv will be synced in "inexact" mode.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// them. To do this, the venv will be synced in "inexact" mode.
/// them. To do this, the venv will be synced in inexact mode.

.collect())
}

fn inspect_installed_modules(dist_info: &Path) -> Result<BTreeSet<String>> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move the inspection logic out of the uv crate, as the uv crate is already overly large.

Comment on lines +135 to +154
if !has_extension(dist_info, "dist-info") {
return Ok(BTreeSet::new());
}

let mut modules = BTreeSet::new();

let top_level = dist_info.join("top_level.txt");
if let Ok(contents) = fs_err::read_to_string(top_level) {
for line in contents.lines() {
add_module_name(line.trim(), &mut modules);
}
}

let record_path = dist_info.join("RECORD");
let record = read_record(fs_err::File::open(&record_path)?)?;
for entry in record {
add_record_module(&entry.path, &mut modules);
}

Ok(modules)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where's that logic from?

top_level.txt is an old setuptools artifact, we shouldn't parse it.

Ok(modules)
}

fn add_record_module(path: &str, modules: &mut BTreeSet<String>) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RECORD parsing logic should also handle the RECORD edge cases we handle in the existing RECORD logic

let mut module_components = parents.to_vec();
if *file_name == "__init__.py" {
// The parent path is the package.
} else if let Some(stem) = file_name.strip_suffix(".py") {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing .pyc files.

} else if let Some(stem) = file_name.strip_suffix(".py") {
module_components.push(stem);
} else if let Some(stem) = extension_module_stem(file_name) {
if stem != "__init__" {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this line do?

Comment on lines +227 to +236
fn is_identifier(component: &str) -> bool {
let mut chars = component.chars();
let Some(first) = chars.next() else {
return false;
};
if !(first == '_' || first.is_ascii_alphabetic()) {
return false;
}
chars.all(|char| char == '_' || char.is_ascii_alphanumeric())
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should follow python's rules here, which includes unicode support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants