mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:47 +00:00
Allow passing a virtual environment to ruff analyze graph
(#17743)
Summary
--
Fixes #16598 by adding the `--python` flag to `ruff analyze graph`,
which adds a `PythonPath` to the `SearchPathSettings` for module
resolution. For the [albatross-virtual-workspace] example from the uv
repo, this updates the output from the initial issue:
```shell
> ruff analyze graph packages/albatross
{
"packages/albatross/check_installed_albatross.py": [
"packages/albatross/src/albatross/__init__.py"
],
"packages/albatross/src/albatross/__init__.py": []
}
```
To include both the the workspace `bird_feeder` import _and_ the
third-party `tqdm` import in the output:
```shell
> myruff analyze graph packages/albatross --python .venv
{
"packages/albatross/check_installed_albatross.py": [
"packages/albatross/src/albatross/__init__.py"
],
"packages/albatross/src/albatross/__init__.py": [
".venv/lib/python3.12/site-packages/tqdm/__init__.py",
"packages/bird-feeder/src/bird_feeder/__init__.py"
]
}
```
Note the hash in the uv link! I was temporarily very confused why my
local tests were showing an `iniconfig` import instead of `tqdm` until I
realized that the example has been updated on the uv main branch, which
I had locally.
Test Plan
--
A new integration test with a stripped down venv based on the
`albatross` example.
[albatross-virtual-workspace]:
aa629c4a54/scripts/workspaces/albatross-virtual-workspace
This commit is contained in:
parent
75effb8ed7
commit
163d526407
4 changed files with 164 additions and 2 deletions
|
@ -177,6 +177,9 @@ pub struct AnalyzeGraphCommand {
|
||||||
/// The minimum Python version that should be supported.
|
/// The minimum Python version that should be supported.
|
||||||
#[arg(long, value_enum)]
|
#[arg(long, value_enum)]
|
||||||
target_version: Option<PythonVersion>,
|
target_version: Option<PythonVersion>,
|
||||||
|
/// Path to a virtual environment to use for resolving additional dependencies
|
||||||
|
#[arg(long)]
|
||||||
|
python: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
|
// The `Parser` derive is for ruff_dev, for ruff `Args` would be sufficient
|
||||||
|
@ -796,6 +799,7 @@ impl AnalyzeGraphCommand {
|
||||||
let format_arguments = AnalyzeGraphArgs {
|
let format_arguments = AnalyzeGraphArgs {
|
||||||
files: self.files,
|
files: self.files,
|
||||||
direction: self.direction,
|
direction: self.direction,
|
||||||
|
python: self.python,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cli_overrides = ExplicitConfigOverrides {
|
let cli_overrides = ExplicitConfigOverrides {
|
||||||
|
@ -1261,6 +1265,7 @@ impl LineColumnParseError {
|
||||||
pub struct AnalyzeGraphArgs {
|
pub struct AnalyzeGraphArgs {
|
||||||
pub files: Vec<PathBuf>,
|
pub files: Vec<PathBuf>,
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
|
pub python: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration overrides provided via dedicated CLI flags:
|
/// Configuration overrides provided via dedicated CLI flags:
|
||||||
|
|
|
@ -75,6 +75,8 @@ pub(crate) fn analyze_graph(
|
||||||
.target_version
|
.target_version
|
||||||
.as_tuple()
|
.as_tuple()
|
||||||
.into(),
|
.into(),
|
||||||
|
args.python
|
||||||
|
.and_then(|python| SystemPathBuf::from_path_buf(python).ok()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let imports = {
|
let imports = {
|
||||||
|
|
|
@ -422,3 +422,153 @@ fn nested_imports() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for venv resolution with the `--python` flag.
|
||||||
|
///
|
||||||
|
/// Based on the [albatross-virtual-workspace] example from the uv repo and the report in [#16598].
|
||||||
|
///
|
||||||
|
/// [albatross-virtual-workspace]: https://github.com/astral-sh/uv/tree/aa629c4a/scripts/workspaces/albatross-virtual-workspace
|
||||||
|
/// [#16598]: https://github.com/astral-sh/ruff/issues/16598
|
||||||
|
#[test]
|
||||||
|
fn venv() -> Result<()> {
|
||||||
|
let tempdir = TempDir::new()?;
|
||||||
|
let root = ChildPath::new(tempdir.path());
|
||||||
|
|
||||||
|
// packages
|
||||||
|
// ├── albatross
|
||||||
|
// │ ├── check_installed_albatross.py
|
||||||
|
// │ ├── pyproject.toml
|
||||||
|
// │ └── src
|
||||||
|
// │ └── albatross
|
||||||
|
// │ └── __init__.py
|
||||||
|
// └── bird-feeder
|
||||||
|
// ├── check_installed_bird_feeder.py
|
||||||
|
// ├── pyproject.toml
|
||||||
|
// └── src
|
||||||
|
// └── bird_feeder
|
||||||
|
// └── __init__.py
|
||||||
|
|
||||||
|
let packages = root.child("packages");
|
||||||
|
|
||||||
|
let albatross = packages.child("albatross");
|
||||||
|
albatross
|
||||||
|
.child("check_installed_albatross.py")
|
||||||
|
.write_str("from albatross import fly")?;
|
||||||
|
albatross
|
||||||
|
.child("pyproject.toml")
|
||||||
|
.write_str(indoc::indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "albatross"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["bird-feeder", "tqdm>=4,<5"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
bird-feeder = { workspace = true }
|
||||||
|
"#})?;
|
||||||
|
albatross
|
||||||
|
.child("src")
|
||||||
|
.child("albatross")
|
||||||
|
.child("__init__.py")
|
||||||
|
.write_str("import tqdm; from bird_feeder import use")?;
|
||||||
|
|
||||||
|
let bird_feeder = packages.child("bird-feeder");
|
||||||
|
bird_feeder
|
||||||
|
.child("check_installed_bird_feeder.py")
|
||||||
|
.write_str("from bird_feeder import use; from albatross import fly")?;
|
||||||
|
bird_feeder
|
||||||
|
.child("pyproject.toml")
|
||||||
|
.write_str(indoc::indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "bird-feeder"
|
||||||
|
version = "1.0.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["anyio>=4.3.0,<5"]
|
||||||
|
"#})?;
|
||||||
|
bird_feeder
|
||||||
|
.child("src")
|
||||||
|
.child("bird_feeder")
|
||||||
|
.child("__init__.py")
|
||||||
|
.write_str("import anyio")?;
|
||||||
|
|
||||||
|
let venv = root.child(".venv");
|
||||||
|
let bin = venv.child("bin");
|
||||||
|
bin.child("python").touch()?;
|
||||||
|
let home = format!("home = {}", bin.to_string_lossy());
|
||||||
|
venv.child("pyvenv.cfg").write_str(&home)?;
|
||||||
|
let site_packages = venv.child("lib").child("python3.12").child("site-packages");
|
||||||
|
site_packages
|
||||||
|
.child("_albatross.pth")
|
||||||
|
.write_str(&albatross.join("src").to_string_lossy())?;
|
||||||
|
site_packages
|
||||||
|
.child("_bird_feeder.pth")
|
||||||
|
.write_str(&bird_feeder.join("src").to_string_lossy())?;
|
||||||
|
site_packages.child("tqdm").child("__init__.py").touch()?;
|
||||||
|
|
||||||
|
// without `--python .venv`, the result should only include dependencies within the albatross
|
||||||
|
// package
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
command().arg("packages/albatross").current_dir(&root),
|
||||||
|
@r#"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
{
|
||||||
|
"packages/albatross/check_installed_albatross.py": [
|
||||||
|
"packages/albatross/src/albatross/__init__.py"
|
||||||
|
],
|
||||||
|
"packages/albatross/src/albatross/__init__.py": []
|
||||||
|
}
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"#);
|
||||||
|
});
|
||||||
|
|
||||||
|
// with `--python .venv` both workspace and third-party dependencies are included
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
command().args(["--python", ".venv"]).arg("packages/albatross").current_dir(&root),
|
||||||
|
@r#"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
{
|
||||||
|
"packages/albatross/check_installed_albatross.py": [
|
||||||
|
"packages/albatross/src/albatross/__init__.py"
|
||||||
|
],
|
||||||
|
"packages/albatross/src/albatross/__init__.py": [
|
||||||
|
".venv/lib/python3.12/site-packages/tqdm/__init__.py",
|
||||||
|
"packages/bird-feeder/src/bird_feeder/__init__.py"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"#);
|
||||||
|
});
|
||||||
|
|
||||||
|
// test the error message for a non-existent venv. it's important that the `ruff analyze graph`
|
||||||
|
// flag matches the red-knot flag used to generate the error message (`--python`)
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec(),
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(
|
||||||
|
command().args(["--python", "none"]).arg("packages/albatross").current_dir(&root),
|
||||||
|
@r"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
ruff failed
|
||||||
|
Cause: Invalid search path settings
|
||||||
|
Cause: Failed to discover the site-packages directory: Invalid `--python` argument: `none` could not be canonicalized
|
||||||
|
");
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ use zip::CompressionMethod;
|
||||||
|
|
||||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||||
use red_knot_python_semantic::{
|
use red_knot_python_semantic::{
|
||||||
default_lint_registry, Db, Program, ProgramSettings, PythonPlatform, SearchPathSettings,
|
default_lint_registry, Db, Program, ProgramSettings, PythonPath, PythonPlatform,
|
||||||
|
SearchPathSettings,
|
||||||
};
|
};
|
||||||
use ruff_db::files::{File, Files};
|
use ruff_db::files::{File, Files};
|
||||||
use ruff_db::system::{OsSystem, System, SystemPathBuf};
|
use ruff_db::system::{OsSystem, System, SystemPathBuf};
|
||||||
|
@ -32,8 +33,12 @@ impl ModuleDb {
|
||||||
pub fn from_src_roots(
|
pub fn from_src_roots(
|
||||||
src_roots: Vec<SystemPathBuf>,
|
src_roots: Vec<SystemPathBuf>,
|
||||||
python_version: PythonVersion,
|
python_version: PythonVersion,
|
||||||
|
venv_path: Option<SystemPathBuf>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let search_paths = SearchPathSettings::new(src_roots);
|
let mut search_paths = SearchPathSettings::new(src_roots);
|
||||||
|
if let Some(venv_path) = venv_path {
|
||||||
|
search_paths.python_path = PythonPath::from_cli_flag(venv_path);
|
||||||
|
}
|
||||||
|
|
||||||
let db = Self::default();
|
let db = Self::default();
|
||||||
Program::from_settings(
|
Program::from_settings(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue