From c62db651f00f1470a6514a58e3a9053e7ee66cd8 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:26:07 +0800 Subject: [PATCH 01/34] Set ruff cache dir to workspace root (#689) --- CHANGELOG.md | 2 ++ rye/src/cli/fmt.rs | 74 ++++---------------------------------- rye/src/cli/lint.rs | 74 ++++---------------------------------- rye/src/utils/mod.rs | 1 + rye/src/utils/ruff.rs | 82 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 134 deletions(-) create mode 100644 rye/src/utils/ruff.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6ac6d2fe..1cdc0c0dc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ _Unreleased_ - Fixed the `-q` parameter not working for the `init` command. #686 +- Configure the ruff cache directory to be located within the workspace root. #689 + ## 0.24.0 diff --git a/rye/src/cli/fmt.rs b/rye/src/cli/fmt.rs index 663a3b56ac..4d0e0a9413 100644 --- a/rye/src/cli/fmt.rs +++ b/rye/src/cli/fmt.rs @@ -1,85 +1,25 @@ -use std::ffi::OsString; -use std::path::PathBuf; -use std::process::Command; - use anyhow::Error; use clap::Parser; -use crate::bootstrap::ensure_self_venv; -use crate::consts::VENV_BIN; -use crate::pyproject::{locate_projects, PyProject}; -use crate::utils::{CommandOutput, QuietExit}; +use crate::utils::ruff; /// Run the code formatter on the project. /// /// This invokes ruff in format mode. #[derive(Parser, Debug)] pub struct Args { - /// List of files or directories to format - paths: Vec, - /// Format all packages - #[arg(short, long)] - all: bool, - /// Format a specific package - #[arg(short, long)] - package: Vec, - /// Use this pyproject.toml file - #[arg(long, value_name = "PYPROJECT_TOML")] - pyproject: Option, + #[command(flatten)] + ruff: ruff::RuffArgs, /// Run format in check mode #[arg(long)] check: bool, - /// Enables verbose diagnostics. - #[arg(short, long)] - verbose: bool, - /// Turns off all output. - #[arg(short, long, conflicts_with = "verbose")] - quiet: bool, - /// Extra arguments to the formatter - #[arg(last = true)] - extra_args: Vec, } pub fn execute(cmd: Args) -> Result<(), Error> { - let project = PyProject::load_or_discover(cmd.pyproject.as_deref())?; - let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); - let venv = ensure_self_venv(output)?; - let ruff = venv.join(VENV_BIN).join("ruff"); - - let mut ruff_cmd = Command::new(ruff); - ruff_cmd.arg("format"); - match output { - CommandOutput::Normal => {} - CommandOutput::Verbose => { - ruff_cmd.arg("--verbose"); - } - CommandOutput::Quiet => { - ruff_cmd.arg("-q"); - } - } - + let mut args = Vec::new(); + args.push("format"); if cmd.check { - ruff_cmd.arg("--check"); - } - ruff_cmd.args(cmd.extra_args); - - ruff_cmd.arg("--"); - if cmd.paths.is_empty() { - let projects = locate_projects(project, cmd.all, &cmd.package[..])?; - for project in projects { - ruff_cmd.arg(project.root_path().as_os_str()); - } - } else { - for file in cmd.paths { - ruff_cmd.arg(file.as_os_str()); - } - } - - let status = ruff_cmd.status()?; - if !status.success() { - let code = status.code().unwrap_or(1); - Err(QuietExit(code).into()) - } else { - Ok(()) + args.push("--check"); } + ruff::execute_ruff(cmd.ruff, &args) } diff --git a/rye/src/cli/lint.rs b/rye/src/cli/lint.rs index 75b2ebafe8..6b07e85078 100644 --- a/rye/src/cli/lint.rs +++ b/rye/src/cli/lint.rs @@ -1,85 +1,25 @@ -use std::ffi::OsString; -use std::path::PathBuf; -use std::process::Command; - use anyhow::Error; use clap::Parser; -use crate::bootstrap::ensure_self_venv; -use crate::consts::VENV_BIN; -use crate::pyproject::{locate_projects, PyProject}; -use crate::utils::{CommandOutput, QuietExit}; +use crate::utils::ruff; /// Run the linter on the project. /// /// This invokes ruff in lint mode. #[derive(Parser, Debug)] pub struct Args { - /// List of files or directories to lint - paths: Vec, - /// Lint all packages - #[arg(short, long)] - all: bool, - /// Lint a specific package - #[arg(short, long)] - package: Vec, - /// Use this pyproject.toml file - #[arg(long, value_name = "PYPROJECT_TOML")] - pyproject: Option, + #[command(flatten)] + ruff: ruff::RuffArgs, /// Apply fixes. #[arg(long)] fix: bool, - /// Enables verbose diagnostics. - #[arg(short, long)] - verbose: bool, - /// Turns off all output. - #[arg(short, long, conflicts_with = "verbose")] - quiet: bool, - /// Extra arguments to the linter - #[arg(last = true)] - extra_args: Vec, } pub fn execute(cmd: Args) -> Result<(), Error> { - let project = PyProject::load_or_discover(cmd.pyproject.as_deref())?; - let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); - let venv = ensure_self_venv(output)?; - let ruff = venv.join(VENV_BIN).join("ruff"); - - let mut ruff_cmd = Command::new(ruff); - ruff_cmd.arg("check"); - match output { - CommandOutput::Normal => {} - CommandOutput::Verbose => { - ruff_cmd.arg("--verbose"); - } - CommandOutput::Quiet => { - ruff_cmd.arg("-q"); - } - } - + let mut args = Vec::new(); + args.push("check"); if cmd.fix { - ruff_cmd.arg("--fix"); - } - ruff_cmd.args(cmd.extra_args); - - ruff_cmd.arg("--"); - if cmd.paths.is_empty() { - let projects = locate_projects(project, cmd.all, &cmd.package[..])?; - for project in projects { - ruff_cmd.arg(project.root_path().as_os_str()); - } - } else { - for file in cmd.paths { - ruff_cmd.arg(file.as_os_str()); - } - } - - let status = ruff_cmd.status()?; - if !status.success() { - let code = status.code().unwrap_or(1); - Err(QuietExit(code).into()) - } else { - Ok(()) + args.push("--fix"); } + ruff::execute_ruff(cmd.ruff, &args) } diff --git a/rye/src/utils/mod.rs b/rye/src/utils/mod.rs index 77e4a8e7cb..9d712f192e 100644 --- a/rye/src/utils/mod.rs +++ b/rye/src/utils/mod.rs @@ -34,6 +34,7 @@ pub(crate) mod windows; #[cfg(unix)] pub(crate) mod unix; +pub(crate) mod ruff; pub(crate) mod toml; #[cfg(windows)] diff --git a/rye/src/utils/ruff.rs b/rye/src/utils/ruff.rs new file mode 100644 index 0000000000..39e2ffe44e --- /dev/null +++ b/rye/src/utils/ruff.rs @@ -0,0 +1,82 @@ +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::Command; + +use anyhow::Error; +use clap::Parser; + +use crate::bootstrap::ensure_self_venv; +use crate::consts::VENV_BIN; +use crate::pyproject::{locate_projects, PyProject}; +use crate::utils::{CommandOutput, QuietExit}; + +#[derive(Parser, Debug)] +pub struct RuffArgs { + /// List of files or directories to limit the operation to + paths: Vec, + /// Perform the operation on all packages + #[arg(short, long)] + all: bool, + /// Perform the operation on a specific package + #[arg(short, long)] + package: Vec, + /// Use this pyproject.toml file + #[arg(long, value_name = "PYPROJECT_TOML")] + pyproject: Option, + /// Enables verbose diagnostics. + #[arg(short, long)] + verbose: bool, + /// Turns off all output. + #[arg(short, long, conflicts_with = "verbose")] + quiet: bool, + /// Extra arguments to ruff + #[arg(last = true)] + extra_args: Vec, +} + +pub fn execute_ruff(args: RuffArgs, extra_args: &[&str]) -> Result<(), Error> { + let project = PyProject::load_or_discover(args.pyproject.as_deref())?; + let output = CommandOutput::from_quiet_and_verbose(args.quiet, args.verbose); + let venv = ensure_self_venv(output)?; + let ruff = venv.join(VENV_BIN).join("ruff"); + + let mut ruff_cmd = Command::new(ruff); + if env::var_os("RUFF_CACHE_DIR").is_none() { + ruff_cmd.env( + "RUFF_CACHE_DIR", + project.workspace_path().join(".ruff_cache"), + ); + } + match output { + CommandOutput::Normal => {} + CommandOutput::Verbose => { + ruff_cmd.arg("--verbose"); + } + CommandOutput::Quiet => { + ruff_cmd.arg("-q"); + } + } + ruff_cmd.args(extra_args); + ruff_cmd.args(args.extra_args); + + ruff_cmd.arg("--"); + if args.paths.is_empty() { + let projects = locate_projects(project, args.all, &args.package[..])?; + for project in projects { + ruff_cmd.arg(project.root_path().as_os_str()); + } + } else { + for file in args.paths { + ruff_cmd.arg(file.as_os_str()); + } + } + + let status = ruff_cmd.status()?; + if !status.success() { + let code = status.code().unwrap_or(1); + Err(QuietExit(code).into()) + } else { + Ok(()) + } +} From a2790fd674a0434d6885fa8d0cb27836939f27de Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:27:05 +0800 Subject: [PATCH 02/34] `rye tools list` shows broken tools (#692) --- CHANGELOG.md | 2 ++ rye/src/cli/tools.rs | 6 +++++- rye/src/installer.rs | 26 +++++++++++++++++--------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cdc0c0dc0..f72087bdf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ _Unreleased_ - Fixed the `-q` parameter not working for the `init` command. #686 +- `rye tools list` shows broken tools if the toolchain was removed. #692 + - Configure the ruff cache directory to be located within the workspace root. #689 diff --git a/rye/src/cli/tools.rs b/rye/src/cli/tools.rs index 1995eec674..edcf7bd052 100644 --- a/rye/src/cli/tools.rs +++ b/rye/src/cli/tools.rs @@ -43,8 +43,12 @@ fn list_tools(cmd: ListCommand) -> Result<(), Error> { tools.sort(); for (tool, mut info) in tools { - if cmd.version_show { + if !info.valid { echo!("{} {}", style(tool).cyan(), style(info.version).cyan()); + continue; + } + if cmd.version_show { + echo!("{} ({})", style(tool).cyan(), info.version); } else { echo!("{}", style(tool).cyan()); } diff --git a/rye/src/installer.rs b/rye/src/installer.rs index b051bc1bcf..73f0d31a63 100644 --- a/rye/src/installer.rs +++ b/rye/src/installer.rs @@ -72,11 +72,16 @@ static SUCCESSFULLY_DOWNLOADED_RE: Lazy = pub struct ToolInfo { pub version: String, pub scripts: Vec, + pub valid: bool, } impl ToolInfo { - pub fn new(version: String, scripts: Vec) -> Self { - Self { version, scripts } + pub fn new(version: String, scripts: Vec, valid: bool) -> Self { + Self { + version, + scripts, + valid, + } } } @@ -347,17 +352,20 @@ pub fn list_installed_tools() -> Result, Error> { } } } - let tool_version = Command::new(target_venv_bin_path.join("python")) + + let output = Command::new(target_venv_bin_path.join("python")) .arg("-c") .arg(TOOL_VERSION_SCRIPT) .arg(tool_name.clone()) .stdout(Stdio::piped()) - .output()?; - let tool_version = String::from_utf8_lossy(&tool_version.stdout) - .trim() - .to_string(); - - rv.insert(tool_name, ToolInfo::new(tool_version, scripts)); + .output(); + let valid = output.is_ok(); + let tool_version = match output { + Ok(output) => String::from_utf8_lossy(&output.stdout).trim().to_string(), + Err(_) => String::new(), + }; + + rv.insert(tool_name, ToolInfo::new(tool_version, scripts, valid)); } Ok(rv) From 93ed740b9ec68423c1c5bc65c17c09e74a735db5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 13:45:42 +0100 Subject: [PATCH 03/34] Added tests for lint and format --- rye/tests/common/mod.rs | 13 ++++++++ rye/tests/test_ruff.rs | 74 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 rye/tests/test_ruff.rs diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index 06bf8adfcb..0833a7e3d8 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -130,6 +130,19 @@ impl Space { self.cmd(get_bin()) } + #[allow(unused)] + pub fn write, B: AsRef<[u8]>>(&self, path: P, contents: B) { + let p = self.project_path().join(path.as_ref()); + fs::create_dir_all(p.parent().unwrap()).ok(); + fs::write(p, contents).unwrap(); + } + + #[allow(unused)] + pub fn read_string>(&self, path: P) -> String { + let p = self.project_path().join(path.as_ref()); + fs::read_to_string(p).unwrap() + } + pub fn init(&self, name: &str) { let status = self .cmd(get_bin()) diff --git a/rye/tests/test_ruff.rs b/rye/tests/test_ruff.rs new file mode 100644 index 0000000000..cc88b1ebff --- /dev/null +++ b/rye/tests/test_ruff.rs @@ -0,0 +1,74 @@ +use insta::assert_snapshot; + +use crate::common::{rye_cmd_snapshot, Space}; + +mod common; + +#[test] +fn test_lint_and_format() { + let space = Space::new(); + space.init("my-project"); + space.write( + "src/my_project/__init__.py", + r#"import os + +def hello(): + + + return "foo"; +"#, + ); + + // start with lint + rye_cmd_snapshot!(space.rye_cmd().arg("lint"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + src/my_project/__init__.py:1:8: F401 [*] `os` imported but unused + src/my_project/__init__.py:6:17: E703 [*] Statement ends with an unnecessary semicolon + Found 2 errors. + [*] 2 fixable with the `--fix` option. + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("lint").arg("--fix"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Found 2 errors (2 fixed, 0 remaining). + + ----- stderr ----- + "###); + assert_snapshot!(format!("|{}|", space.read_string("src/my_project/__init__.py")), @r###" + | + def hello(): + + + return "foo" + | + "###); + + // fmt next + rye_cmd_snapshot!(space.rye_cmd().arg("fmt").arg("--check"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + Would reformat: src/my_project/__init__.py + 1 file would be reformatted + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("fmt"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + 1 file reformatted + + ----- stderr ----- + "###); + assert_snapshot!(format!("|{}|", space.read_string("src/my_project/__init__.py")), @r###" + |def hello(): + return "foo" + | + "###); +} From 1036b043b1e79fc65161ba66207efbe417018096 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 18:13:12 +0100 Subject: [PATCH 04/34] Upgrade insta --- Cargo.lock | 8 ++++---- rye/Cargo.toml | 4 ++-- rye/tests/test_ruff.rs | 10 ++++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f032d6175..f400b8d9dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1070,9 +1070,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.34.0" +version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d64600be34b2fcfc267740a243fa7744441bb4947a619ac4e5bb6507f35fbfc" +checksum = "763c0d70648606f5969a50fa8bbea4b52e3702432c622c0b59d619cd654fcb27" dependencies = [ "console", "lazy_static", @@ -1085,9 +1085,9 @@ dependencies = [ [[package]] name = "insta-cmd" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809d3023d1d6e8d5c2206f199251f75cb26180e41f18cb0f22dd119161cb5127" +checksum = "1980f17994b79f75670aa90cfc8d35edc4aa248f16aa48b5e27835b080e452a2" dependencies = [ "insta", "serde", diff --git a/rye/Cargo.toml b/rye/Cargo.toml index 79d0752a4c..8b2ccd709e 100644 --- a/rye/Cargo.toml +++ b/rye/Cargo.toml @@ -74,5 +74,5 @@ static_vcruntime = "2.0.0" [dev-dependencies] fslock = "0.2.1" -insta = { version = "1.34.0", features = ["filters"] } -insta-cmd = "0.4.0" +insta = { version = "1.35.0", features = ["filters"] } +insta-cmd = "0.5.0" diff --git a/rye/tests/test_ruff.rs b/rye/tests/test_ruff.rs index cc88b1ebff..c4341f627c 100644 --- a/rye/tests/test_ruff.rs +++ b/rye/tests/test_ruff.rs @@ -39,13 +39,12 @@ def hello(): ----- stderr ----- "###); - assert_snapshot!(format!("|{}|", space.read_string("src/my_project/__init__.py")), @r###" - | + assert_snapshot!(space.read_string("src/my_project/__init__.py"), @r###" + def hello(): return "foo" - | "###); // fmt next @@ -66,9 +65,8 @@ def hello(): ----- stderr ----- "###); - assert_snapshot!(format!("|{}|", space.read_string("src/my_project/__init__.py")), @r###" - |def hello(): + assert_snapshot!(space.read_string("src/my_project/__init__.py"), @r###" + def hello(): return "foo" - | "###); } From 2e2cde653061826aa82ade4fef0b0d4f542aa25c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 22:30:36 +0100 Subject: [PATCH 05/34] Bump insta once more --- Cargo.lock | 4 ++-- rye/Cargo.toml | 2 +- rye/tests/test_ruff.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f400b8d9dd..41b2e013d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1070,9 +1070,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.35.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763c0d70648606f5969a50fa8bbea4b52e3702432c622c0b59d619cd654fcb27" +checksum = "7c985c1bef99cf13c58fade470483d81a2bfe846ebde60ed28cc2dddec2df9e2" dependencies = [ "console", "lazy_static", diff --git a/rye/Cargo.toml b/rye/Cargo.toml index 8b2ccd709e..08cba98913 100644 --- a/rye/Cargo.toml +++ b/rye/Cargo.toml @@ -74,5 +74,5 @@ static_vcruntime = "2.0.0" [dev-dependencies] fslock = "0.2.1" -insta = { version = "1.35.0", features = ["filters"] } +insta = { version = "1.35.1", features = ["filters"] } insta-cmd = "0.5.0" diff --git a/rye/tests/test_ruff.rs b/rye/tests/test_ruff.rs index c4341f627c..1d2a9027bd 100644 --- a/rye/tests/test_ruff.rs +++ b/rye/tests/test_ruff.rs @@ -15,7 +15,7 @@ fn test_lint_and_format() { def hello(): - return "foo"; + return "Hello World"; "#, ); @@ -25,7 +25,7 @@ def hello(): exit_code: 1 ----- stdout ----- src/my_project/__init__.py:1:8: F401 [*] `os` imported but unused - src/my_project/__init__.py:6:17: E703 [*] Statement ends with an unnecessary semicolon + src/my_project/__init__.py:6:25: E703 [*] Statement ends with an unnecessary semicolon Found 2 errors. [*] 2 fixable with the `--fix` option. @@ -44,7 +44,7 @@ def hello(): def hello(): - return "foo" + return "Hello World" "###); // fmt next @@ -67,6 +67,6 @@ def hello(): "###); assert_snapshot!(space.read_string("src/my_project/__init__.py"), @r###" def hello(): - return "foo" + return "Hello World" "###); } From 570d677ee9d1f84ae9eb78a4c04f1ab5f15f8b90 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 19 Feb 2024 17:19:28 -0500 Subject: [PATCH 06/34] bump uv to 0.1.5 (#698) --- CHANGELOG.md | 4 ++-- rye/src/bootstrap.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f72087bdf6..700e398dc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ _Unreleased_ - Improved the error message if `config` is invoked without arguments. #660 -- Bump `uv` to 0.1.3. #665, #675 +- Bump `uv` to 0.1.5. #665, #675, #698 - When `uv` is enabled, `rye add` now uses `uv` instead of `unearth` internally. #667 @@ -164,7 +164,7 @@ Released on 2024-01-15 - Fixed default generated script reference. #527 -- Correctly fall back to home folder if HOME is unset. #533 +- Correctly fall back to home folder if HOME is unset. #533 ## 0.16.0 diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index 317f123301..84468f4117 100644 --- a/rye/src/bootstrap.rs +++ b/rye/src/bootstrap.rs @@ -57,7 +57,7 @@ unearth==0.14.0 urllib3==2.0.7 virtualenv==20.25.0 ruff==0.1.14 -uv==0.1.3 +uv==0.1.5 "#; static FORCED_TO_UPDATE: AtomicBool = AtomicBool::new(false); From 8b3703b8b94480647ad88e292830b91159cff8f3 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 06:19:57 +0800 Subject: [PATCH 07/34] Fix `rye tools list` format issue introduced in #692 (#694) The last commit wrongly changed the format, restore it. --- rye/src/cli/tools.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rye/src/cli/tools.rs b/rye/src/cli/tools.rs index edcf7bd052..a426e981ac 100644 --- a/rye/src/cli/tools.rs +++ b/rye/src/cli/tools.rs @@ -44,11 +44,11 @@ fn list_tools(cmd: ListCommand) -> Result<(), Error> { for (tool, mut info) in tools { if !info.valid { - echo!("{} {}", style(tool).cyan(), style(info.version).cyan()); + echo!("{} ({})", style(tool).red(), style("seems broken").red()); continue; } if cmd.version_show { - echo!("{} ({})", style(tool).cyan(), info.version); + echo!("{} {}", style(tool).cyan(), style(info.version).cyan()); } else { echo!("{}", style(tool).cyan()); } From 9cc9d239996f299dcbf906d1ce133eaf4817d84b Mon Sep 17 00:00:00 2001 From: loicleyendecker Date: Mon, 19 Feb 2024 22:20:27 +0000 Subject: [PATCH 08/34] Use default toolchain to install tools (#666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Loïc Leyendecker --- CHANGELOG.md | 2 ++ rye/src/cli/install.rs | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 700e398dc2..1c59b18ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ _Unreleased_ - Configure the ruff cache directory to be located within the workspace root. #689 +- Use default toolchain to install tools. #666 + ## 0.24.0 diff --git a/rye/src/cli/install.rs b/rye/src/cli/install.rs index 61e5a57d8c..c1b15cb205 100644 --- a/rye/src/cli/install.rs +++ b/rye/src/cli/install.rs @@ -5,6 +5,7 @@ use clap::Parser; use pep508_rs::Requirement; use crate::cli::add::ReqExtras; +use crate::config::Config; use crate::installer::{install, resolve_local_requirement}; use crate::sources::PythonVersionRequest; use crate::utils::CommandOutput; @@ -53,15 +54,17 @@ pub fn execute(mut cmd: Args) -> Result<(), Error> { let py_ver: PythonVersionRequest = match cmd.python { Some(ref py) => py.parse()?, - None => PythonVersionRequest { - name: None, - arch: None, - os: None, - major: 3, - minor: None, - patch: None, - suffix: None, - }, + None => Config::current() + .default_toolchain() + .unwrap_or(PythonVersionRequest { + name: None, + arch: None, + os: None, + major: 3, + minor: None, + patch: None, + suffix: None, + }), }; install( From afdb94a54976e3149f4e9fb885ea45a69b09a481 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 06:22:31 +0800 Subject: [PATCH 09/34] Refactor find-downloads.py (#680) --- requirements-dev.lock | 2 + requirements.lock | 2 + rye-devtools/pyproject.toml | 2 +- .../src/rye_devtools/find_downloads.py | 814 +++++++++++------- rye/find-downloads.py | 394 --------- rye/src/downloads.inc | 3 +- 6 files changed, 503 insertions(+), 714 deletions(-) delete mode 100644 rye/find-downloads.py diff --git a/requirements-dev.lock b/requirements-dev.lock index bc4c841d1c..9fd084536d 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -86,6 +86,8 @@ six==1.16.0 sniffio==1.3.0 # via anyio # via httpx +socksio==1.0.0 + # via httpx urllib3==2.0.2 # via requests watchdog==3.0.0 diff --git a/requirements.lock b/requirements.lock index 040b165533..4bf6f467c2 100644 --- a/requirements.lock +++ b/requirements.lock @@ -85,6 +85,8 @@ six==1.16.0 sniffio==1.3.0 # via anyio # via httpx +socksio==1.0.0 + # via httpx urllib3==2.0.2 # via requests watchdog==3.0.0 diff --git a/rye-devtools/pyproject.toml b/rye-devtools/pyproject.toml index 8dd51a15fc..5cc9abd2be 100644 --- a/rye-devtools/pyproject.toml +++ b/rye-devtools/pyproject.toml @@ -4,7 +4,7 @@ version = "1.0.0" description = "Development tools for rye" authors = [{ name = "Armin Ronacher", email = "armin.ronacher@active-4.com" }] dependencies = [ - "httpx>=0.26.0", + "httpx[socks]>=0.26.0", ] requires-python = ">= 3.11" diff --git a/rye-devtools/src/rye_devtools/find_downloads.py b/rye-devtools/src/rye_devtools/find_downloads.py index 6dc878489e..7d3713c5a6 100644 --- a/rye-devtools/src/rye_devtools/find_downloads.py +++ b/rye-devtools/src/rye_devtools/find_downloads.py @@ -1,11 +1,10 @@ """This script is used to generate rye/src/downloads.inc. -It find the latest python-build-standalone releases, sorts them by +It finds the latest Python releases, sorts them by various factors (arch, platform, flavor) and generates download -links to be included into rye at build time. In addition it maintains -a manual list of pypy downloads to be included into rye at build -time. +links to be included into rye at build time. """ +import abc import asyncio import itertools import re @@ -14,388 +13,567 @@ import unittest from dataclasses import dataclass from datetime import datetime, timezone -from enum import Enum -from itertools import chain -from typing import Callable, Optional, Self +from enum import StrEnum +from typing import NamedTuple, Self from urllib.parse import unquote import httpx +from httpx import HTTPStatusError def log(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) + def batched(iterable, n): "Batch data into tuples of length n. The last batch may be shorter." # batched('ABCDEFG', 3) --> ABC DEF G if n < 1: - raise ValueError('n must be at least one') + raise ValueError("n must be at least one") it = iter(iterable) while batch := tuple(itertools.islice(it, n)): yield batch -TOKEN = open("token.txt").read().strip() -RELEASE_URL = "https://api.github.com/repos/indygreg/python-build-standalone/releases" -HEADERS = { - "X-GitHub-Api-Version": "2022-11-28", - "Authorization": "Bearer " + TOKEN, -} -FLAVOR_PREFERENCES = [ - "shared-pgo", - "shared-noopt", - "shared-noopt", - "pgo+lto", - "lto", - "pgo", -] -HIDDEN_FLAVORS = [ - "debug", - "noopt", - "install_only", -] -SPECIAL_TRIPLES = { - "macos": "x86_64-apple-darwin", - "linux64": "x86_64-unknown-linux-gnu", - "windows-amd64": "x86_64-pc-windows-msvc", - "windows-x86-shared-pgo": "i686-pc-windows-msvc-shared-pgo", - "windows-amd64-shared-pgo": "x86_64-pc-windows-msvc-shared-pgo", - "windows-x86": "i686-pc-windows-msvc", - "linux64-musl": "x86_64-unknown-linux-musl", -} - -# matches these: https://doc.rust-lang.org/std/env/consts/constant.ARCH.html -ARCH_MAPPING = { - "x86_64": "x86_64", - "x86": "x86", - "i686": "x86", - "aarch64": "aarch64", -} - -# matches these: https://doc.rust-lang.org/std/env/consts/constant.OS.html -PLATFORM_MAPPING = { - "darwin": "macos", - "windows": "windows", - "linux": "linux", -} - -ENV_MAPPING = { - "gnu": "gnu", - # We must ignore musl for now - # "musl": "musl", -} - - -@dataclass(frozen=True) -class PlatformTriple: + +class PlatformTriple(NamedTuple): arch: str platform: str - environment: Optional[str] - flavor: str + environment: str | None + flavor: str | None + + +class PythonVersion(NamedTuple): + major: int + minor: int + patch: int @classmethod - def from_str(cls, triple: str) -> Optional[Self]: - """Parse a triple into a PlatformTriple object.""" + def from_str(cls, version: str) -> Self: + return cls(*map(int, version.split(".", 3))) - # The parsing functions are all very similar and we could abstract them into a single function - # but I think it's clearer to keep them separate. - def match_flavor(triple): - for flavor in FLAVOR_PREFERENCES + HIDDEN_FLAVORS: - if flavor in triple: - return flavor - return "" + def __neg__(self) -> Self: + return PythonVersion(-self.major, -self.minor, -self.patch) - def match_mapping(pieces: list[str], mapping: dict[str, str]): - for i in reversed(range(0, len(pieces))): - if pieces[i] in mapping: - return mapping[pieces[i]], pieces[:i] - return None, pieces - # We split by '-' and match back to front to extract the flavor, env, platform and archk - arch, platform, env, flavor = None, None, None, None +class PythonImplementation(StrEnum): + CPYTHON = "cpython" + PYPY = "pypy" - # Map, old, special triplets to proper triples for parsing, or - # return the triple if it's not a special one - triple = SPECIAL_TRIPLES.get(triple, triple) - pieces = triple.split("-") - flavor = match_flavor(triple) - env, pieces = match_mapping(pieces, ENV_MAPPING) - platform, pieces = match_mapping(pieces, PLATFORM_MAPPING) - arch, pieces = match_mapping(pieces, ARCH_MAPPING) - if flavor is None or arch is None or platform is None: - return +@dataclass +class PythonDownload: + version: PythonVersion + triple: PlatformTriple + implementation: PythonImplementation + filename: str + url: str + sha256: str | None = None - if env is None and platform == "linux": - return - return cls(arch, platform, env, flavor) +async def fetch(client: httpx.AsyncClient, url: str) -> httpx.Response: + """Fetch from GitHub API with rate limit awareness.""" + resp = await client.get(url, timeout=15) + if ( + resp.status_code in [403, 429] + and resp.headers.get("x-ratelimit-remaining") == "0" + ): + # See https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2022-11-28 + if (retry_after := resp.headers.get("retry-after")) is not None: + log(f"Got retry-after header, retry in {retry_after} seconds.") + time.sleep(int(retry_after)) + + return await fetch(client, url) - def grouped(self) -> tuple[str, str]: - # for now we only group by arch and platform, because rust's PythonVersion doesn't have a notion - # of environment. Flavor will never be used to sort download choices and must not be included in grouping. - return self.arch, self.platform - # return self.arch, self.platform, self.environment or "" + if (retry_at := resp.headers.get("x-ratelimit-reset")) is not None: + utc = datetime.now(timezone.utc).timestamp() + retry_after = max(int(retry_at) - int(utc), 0) + log(f"Got x-ratelimit-reset header, retry in {retry_after} seconds.") + time.sleep(retry_after) -@dataclass(frozen=True, order=True) -class PythonVersion: - major: int - minor: int - patch: int + return await fetch(client, url) - @classmethod - def from_str(cls, version: str) -> Self: - return cls(*map(int, version.split(".", 3))) + log("Got rate limited but no information how long, wait for 2 minutes.") + time.sleep(60 * 2) + return await fetch(client, url) + resp.raise_for_status() + return resp -@dataclass(frozen=True) -class IndygregDownload: - version: PythonVersion - triple: PlatformTriple - url: str + +class Finder: + implementation: PythonImplementation + + @abc.abstractmethod + async def find(self) -> list[PythonDownload]: + raise NotImplementedError + + +class CPythonFinder(Finder): + implementation = PythonImplementation.CPYTHON + + RELEASE_URL = ( + "https://api.github.com/repos/indygreg/python-build-standalone/releases" + ) + + FLAVOR_PREFERENCES = [ + "shared-pgo", + "shared-noopt", + "shared-noopt", + "pgo+lto", + "lto", + "pgo", + ] + HIDDEN_FLAVORS = [ + "debug", + "noopt", + "install_only", + ] + SPECIAL_TRIPLES = { + "macos": "x86_64-apple-darwin", + "linux64": "x86_64-unknown-linux-gnu", + "windows-amd64": "x86_64-pc-windows-msvc", + "windows-x86-shared-pgo": "i686-pc-windows-msvc-shared-pgo", + "windows-amd64-shared-pgo": "x86_64-pc-windows-msvc-shared-pgo", + "windows-x86": "i686-pc-windows-msvc", + "linux64-musl": "x86_64-unknown-linux-musl", + } + + # matches these: https://doc.rust-lang.org/std/env/consts/constant.ARCH.html + ARCH_MAPPING = { + "x86_64": "x86_64", + "x86": "x86", + "i686": "x86", + "aarch64": "aarch64", + } + + # matches these: https://doc.rust-lang.org/std/env/consts/constant.OS.html + PLATFORM_MAPPING = { + "darwin": "macos", + "windows": "windows", + "linux": "linux", + } + + ENV_MAPPING = { + "gnu": "gnu", + # We must ignore musl for now + # "musl": "musl", + } FILENAME_RE = re.compile( r"""(?x) - ^ - cpython-(?P\d+\.\d+\.\d+?) - (?:\+\d+)? - -(?P.*?) - (?:-[\dT]+)?\.tar\.(?:gz|zst) - $ - """ + ^ + cpython-(?P\d+\.\d+\.\d+?) + (?:\+\d+)? + -(?P.*?) + (?:-[\dT]+)?\.tar\.(?:gz|zst) + $ +""" ) + def __init__(self, client: httpx.AsyncClient): + self.client = client + + async def find(self) -> list[PythonDownload]: + downloads = await self.fetch_indygreg_downloads() + await self.fetch_indygreg_checksums(downloads, n=20) + return downloads + + async def fetch_indygreg_downloads(self, pages: int = 100) -> list[PythonDownload]: + """Fetch all the indygreg downloads from the release API.""" + results: dict[PythonVersion, dict[tuple[str, str], list[PythonDownload]]] = {} + + for page in range(1, pages): + log(f"Fetching indygreg release page {page}") + resp = await fetch(self.client, "%s?page=%d" % (self.RELEASE_URL, page)) + rows = resp.json() + if not rows: + break + for row in rows: + for asset in row["assets"]: + url = asset["browser_download_url"] + download = self.parse_download_url(url) + if download is not None: + ( + results.setdefault(download.version, {}) + # For now, we only group by arch and platform, because Rust's PythonVersion doesn't have a notion + # of environment. Flavor will never be used to sort download choices and must not be included in grouping. + .setdefault( + (download.triple.arch, download.triple.platform), [] + ) + .append(download) + ) + + downloads = [] + for version, platform_downloads in results.items(): + for flavors in platform_downloads.values(): + best = self.pick_best_download(flavors) + if best is not None: + downloads.append(best) + return downloads + @classmethod - def from_url(cls, url) -> Optional[Self]: - base_name = unquote(url.rsplit("/")[-1]) - if base_name.endswith(".sha256"): + def parse_download_url(cls, url: str) -> PythonDownload | None: + """Parse an indygreg download URL into a PythonDownload object.""" + # The URL looks like this: + # https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-lto-full.tar.zst + filename = unquote(url.rsplit("/", maxsplit=1)[-1]) + if filename.endswith(".sha256"): return - match = cls.FILENAME_RE.match(base_name) + match = cls.FILENAME_RE.match(filename) if match is None: return - # Parse version string and triplet string version_str, triple_str = match.groups() version = PythonVersion.from_str(version_str) - triple = PlatformTriple.from_str(triple_str) + triple = cls.parse_triple(triple_str) if triple is None: return - return cls(version, triple, url) + return PythonDownload( + version=version, + triple=triple, + implementation=PythonImplementation.CPYTHON, + filename=filename, + url=url, + ) - async def sha256(self, client: httpx.AsyncClient) -> Optional[str]: - """We only fetch the sha256 when needed. This generally is AFTER we have - decided that the download will be part of rye's download set""" - resp = await fetch(client, self.url + ".sha256", headers=HEADERS) - if 200 <= resp.status_code < 400: - return resp.text.strip() - return None + @classmethod + def parse_triple(cls, triple: str) -> PlatformTriple | None: + """Parse a triple into a PlatformTriple object.""" + def match_flavor(triple: str) -> str | None: + for flavor in cls.FLAVOR_PREFERENCES + cls.HIDDEN_FLAVORS: + if flavor in triple: + return flavor + return None -async def fetch(client: httpx.AsyncClient, page: str, headers: dict[str, str]): - """Fetch a page from GitHub API with ratelimit awareness.""" - resp = await client.get(page, headers=headers, timeout=90) - if ( - resp.status_code in [403, 429] - and resp.headers.get("x-ratelimit-remaining") == "0" - ): - # See https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2022-11-28 - if (retry_after := resp.headers.get("retry-after")) is not None: - log("got retry-after header. retrying in {retry_after} seconds.") - time.sleep(int(retry_after)) + def match_mapping( + pieces: list[str], mapping: dict[str, str] + ) -> tuple[str | None, list[str]]: + for i in reversed(range(0, len(pieces))): + if pieces[i] in mapping: + return mapping[pieces[i]], pieces[:i] + return None, pieces - return await fetch(client, page, headers) + # Map, old, special triplets to proper triples for parsing, or + # return the triple if it's not a special one + triple = cls.SPECIAL_TRIPLES.get(triple, triple) + pieces = triple.split("-") + flavor = match_flavor(triple) + env, pieces = match_mapping(pieces, cls.ENV_MAPPING) + platform, pieces = match_mapping(pieces, cls.PLATFORM_MAPPING) + arch, pieces = match_mapping(pieces, cls.ARCH_MAPPING) - if (retry_at := resp.headers.get("x-ratelimit-reset")) is not None: - utc = datetime.now(timezone.utc).timestamp() - retry_after = int(retry_at) - int(utc) + if arch is None or platform is None: + return - log("got x-ratelimit-reset header. retrying in {retry_after} seconds.") - time.sleep(max(int(retry_at) - int(utc), 0)) + if env is None and platform == "linux": + return - return await fetch(client, page, headers) + return PlatformTriple(arch, platform, env, flavor) - log("got rate limited but no information how long. waiting for 2 minutes") - time.sleep(60 * 2) - return await fetch(client, page, headers) - return resp + @classmethod + def pick_best_download( + cls, downloads: list[PythonDownload] + ) -> PythonDownload | None: + """Pick the best download from the list of downloads.""" + + def preference(download: PythonDownload) -> int: + try: + return cls.FLAVOR_PREFERENCES.index(download.triple.flavor) + except ValueError: + return len(cls.FLAVOR_PREFERENCES) + 1 + + downloads.sort(key=preference) + return downloads[0] if downloads else None + + async def fetch_indygreg_checksums( + self, downloads: list[PythonDownload], n: int = 10 + ) -> None: + """Fetch the checksums for the given downloads.""" + checksums_url = set() + for download in downloads: + release_url = download.url.rsplit("/", maxsplit=1)[0] + checksum_url = release_url + "/SHA256SUMS" + checksums_url.add(checksum_url) + + async def fetch_checksums(url: str): + try: + resp = await fetch(self.client, url) + except HTTPStatusError as e: + if e.response.status_code != 404: + raise + return None + return resp + + completed = 0 + tasks = [] + for batch in batched(checksums_url, n): + log(f"Fetching indygreg checksums: {completed}/{len(checksums_url)}") + async with asyncio.TaskGroup() as tg: + for url in batch: + task = tg.create_task(fetch_checksums(url)) + tasks.append(task) + completed += n + + checksums = {} + for task in tasks: + resp = task.result() + if resp is None: + continue + lines = resp.text.splitlines() + for line in lines: + checksum, filename = line.split(" ", maxsplit=1) + filename = filename.strip() + checksums[filename] = checksum + + for download in downloads: + download.sha256 = checksums.get(download.filename) + + +class PyPyFinder(Finder): + implementation = PythonImplementation.PYPY + + def __init__(self, client: httpx.AsyncClient): + self.client = client + + async def find(self) -> list[PythonDownload]: + # TODO These are manually maintained for now, fetch them from PyPy website in later PR. + return [ + PythonDownload( + version=PythonVersion(3, 10, 12), + triple=PlatformTriple( + arch="x86_64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.10-v7.3.12-linux64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 10, 12), + triple=PlatformTriple( + arch="aarch64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.10-v7.3.12-aarch64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 10, 12), + triple=PlatformTriple( + arch="x86_64", platform="macos", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.10-v7.3.12-macos_x86_64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 10, 12), + triple=PlatformTriple( + arch="aarch64", platform="macos", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.10-v7.3.12-macos_arm64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 10, 12), + triple=PlatformTriple( + arch="x86_64", platform="windows", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.10-v7.3.12-win64.zip", + url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", + ), + PythonDownload( + version=PythonVersion(3, 9, 16), + triple=PlatformTriple( + arch="x86_64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.9-v7.3.11-linux64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 9, 16), + triple=PlatformTriple( + arch="aarch64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.9-v7.3.11-aarch64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 9, 16), + triple=PlatformTriple( + arch="x86_64", platform="macos", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.9-v7.3.11-macos_x86_64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 9, 16), + triple=PlatformTriple( + arch="aarch64", platform="macos", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.9-v7.3.11-macos_arm64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 9, 16), + triple=PlatformTriple( + arch="x86_64", platform="windows", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.9-v7.3.11-win64.zip", + url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", + ), + PythonDownload( + version=PythonVersion(3, 8, 16), + triple=PlatformTriple( + arch="x86_64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.8-v7.3.11-linux64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 8, 16), + triple=PlatformTriple( + arch="aarch64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.8-v7.3.11-aarch64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 8, 16), + triple=PlatformTriple( + arch="x86_64", platform="macos", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.8-v7.3.11-macos_x86_64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 8, 16), + triple=PlatformTriple( + arch="aarch64", platform="macos", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.8-v7.3.11-macos_arm64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 8, 16), + triple=PlatformTriple( + arch="x86_64", platform="windows", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.8-v7.3.11-win64.zip", + url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", + ), + PythonDownload( + version=PythonVersion(3, 7, 13), + triple=PlatformTriple( + arch="x86_64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.7-v7.3.9-linux64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 7, 13), + triple=PlatformTriple( + arch="aarch64", platform="linux", environment="gnu", flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.7-v7.3.9-aarch64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 7, 13), + triple=PlatformTriple( + arch="x86_64", platform="macos", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.7-v7.3.9-osx64.tar.bz2", + url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", + ), + PythonDownload( + version=PythonVersion(3, 7, 13), + triple=PlatformTriple( + arch="x86_64", platform="windows", environment=None, flavor=None + ), + implementation=PythonImplementation.PYPY, + filename="pypy3.7-v7.3.9-win64.zip", + url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", + ), + ] + + +def render(downloads: list[PythonDownload]): + """Render downloads.inc.""" + + def sort_key(download: PythonDownload) -> tuple[int, PythonVersion, PlatformTriple]: + # Sort by implementation, version (latest first), and then by triple. + impl_order = [PythonImplementation.PYPY, PythonImplementation.CPYTHON] + return ( + impl_order.index(download.implementation), + -download.version, + download.triple, + ) + downloads.sort(key=sort_key) -async def fetch_indiygreg_downloads( - client: httpx.AsyncClient, - pages: int = 100, -) -> dict[PythonVersion, dict[PlatformTriple, list[IndygregDownload]]]: - """Fetch all the indygreg downloads from the release API.""" - results = {} - - for page in range(1, pages): - log(f"Fetching page {page}") - resp = await fetch(client, "%s?page=%d" % (RELEASE_URL, page), headers=HEADERS) - rows = resp.json() - if not rows: - break - for row in rows: - for asset in row["assets"]: - url = asset["browser_download_url"] - if (download := IndygregDownload.from_url(url)) is not None: - results.setdefault(download.version, {}).setdefault(download.triple.grouped(), []).append(download) - return results - - -def pick_best_download(downloads: list[IndygregDownload]) -> Optional[IndygregDownload]: - """Pick the best download from the list of downloads.""" - - def preference(download: IndygregDownload) -> int: - try: - return FLAVOR_PREFERENCES.index(download.triple.flavor) - except ValueError: - return len(FLAVOR_PREFERENCES) + 1 - - downloads.sort(key=preference) - return downloads[0] if downloads else None - -async def fetch_sha256s( - client: httpx.AsyncClient, - indys: dict[PythonVersion, list[IndygregDownload]], - n: int = 10, -) -> dict[str, str]: - # flatten - downloads = (download for downloads in indys.values() for download in downloads) - length = sum([len(downloads) for downloads in indys.values()]) - - completed = 0 - tasks = [] - for batch in batched(downloads, n=n): - log(f"fetching {n} sha256s: {completed}/{length} completed") - async with asyncio.TaskGroup() as tg: - for download in batch: - task = tg.create_task(download.sha256(client)) - tasks.append((download.url, task)) - completed += n - return {url: task.result() for url, task in tasks} - -def render( - indys: dict[PythonVersion, list[IndygregDownload]], - pypy: dict[PythonVersion, dict[PlatformTriple, str]], - sha256s: dict[str, str] -): - """Render downloads.inc""" - log("Generating code and fetching sha256 of all cpython downloads.") - log("This can be slow......") - - print("// generated code, do not edit") + print("// Generated by rye-devtools. DO NOT EDIT.") + print( + "// To regenerate, run `rye run find-downloads > rye/src/downloads.inc` from the root of the repository." + ) print("use std::borrow::Cow;") print("pub const PYTHON_VERSIONS: &[(PythonVersion, &str, Option<&str>)] = &[") - for version, downloads in sorted(pypy.items(), key=lambda v: v[0], reverse=True): - for triple, url in sorted(downloads.items(), key=lambda v: v[0].grouped()): - print( - f' (PythonVersion {{ name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("{triple.arch}"), os: Cow::Borrowed("{triple.platform}"), major: {version.major}, minor: {version.minor}, patch: {version.patch}, suffix: None }}, "{url}", None),' - ) - - for version, downloads in sorted(indys.items(), key=lambda v: v[0], reverse=True): - for download in sorted(downloads, key=lambda v: v.triple.grouped()): - if (sha256 := sha256s.get(download.url)) is not None: - sha256_str = f'Some("{sha256}")' - else: - sha256_str = "None" - print( - f' (PythonVersion {{ name: Cow::Borrowed("cpython"), arch: Cow::Borrowed("{download.triple.arch}"), os: Cow::Borrowed("{download.triple.platform}"), major: {version.major}, minor: {version.minor}, patch: {version.patch}, suffix: None }}, "{download.url}", {sha256_str}),' - ) + for download in downloads: + triple = download.triple + version = download.version + sha256 = f'Some("{download.sha256}")' if download.sha256 else "None" + print( + f' (PythonVersion {{ name: Cow::Borrowed("{download.implementation}"), arch: Cow::Borrowed("{triple.arch}"), os: Cow::Borrowed("{triple.platform}"), major: {version.major}, minor: {version.minor}, patch: {version.patch}, suffix: None }}, "{download.url}", {sha256}),' + ) + print("];") async def async_main(): - log("Rye download creator started.") - log("Fetching indygreg downloads...") - - indys = {} - # For every version, pick the best download per triple - # and store it in the results - async with httpx.AsyncClient(follow_redirects=True) as client: - downloads = await fetch_indiygreg_downloads(client, 100) - for version, download_choices in downloads.items(): - # Create a dict[PlatformTriple, list[IndygregDownload]]] - # for each version - for triple, choices in download_choices.items(): - if (best_download := pick_best_download(choices)) is not None: - indys.setdefault(version, []).append(best_download) - - sha256s = await fetch_sha256s(client, indys, n=25) - render(indys, PYPY_DOWNLOADS, sha256s) + token = open("token.txt").read().strip() + headers = { + "X-GitHub-Api-Version": "2022-11-28", + "Authorization": "Bearer " + token, + } + client = httpx.AsyncClient(follow_redirects=True, headers=headers) + + finders = [ + CPythonFinder(client), + PyPyFinder(client), + ] + downloads = [] + + log("Fetching all Python downloads and generating code.") + async with client: + for finder in finders: + log(f"Finding {finder.implementation} downloads...") + downloads.extend(await finder.find()) + + render(downloads) + def main(): asyncio.run(async_main()) -# These are manually maintained for now -PYPY_DOWNLOADS = { - PythonVersion(3, 10, 12): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", - }, - PythonVersion(3, 9, 16): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", - }, - PythonVersion(3, 8, 16): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", - }, - PythonVersion(3, 7, 13): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", - }, -} if __name__ == "__main__": main() @@ -417,7 +595,7 @@ def test_parse_triplets(self): "x86_64-unknown-linux-gnu-debug": PlatformTriple( "x86_64", "linux", "gnu", "debug" ), - "linux64": PlatformTriple("x86_64", "linux", "gnu", ""), + "linux64": PlatformTriple("x86_64", "linux", "gnu", None), "ppc64le-unknown-linux-gnu-noopt-full": None, "x86_64_v3-unknown-linux-gnu-lto": None, "x86_64-pc-windows-msvc-shared-pgo": PlatformTriple( @@ -426,4 +604,4 @@ def test_parse_triplets(self): } for input, expected in expected.items(): - self.assertEqual(PlatformTriple.from_str(input), expected, input) + self.assertEqual(CPythonFinder.parse_triple(input), expected, input) diff --git a/rye/find-downloads.py b/rye/find-downloads.py deleted file mode 100644 index fed44deabb..0000000000 --- a/rye/find-downloads.py +++ /dev/null @@ -1,394 +0,0 @@ -"""This script is used to generate rye/src/downloads.inc. - -It find the latest python-build-standalone releases, sorts them by -various factors (arch, platform, flavor) and generates download -links to be included into rye at build time. In addition it maintains -a manual list of pypy downloads to be included into rye at build -time. -""" -import re -import sys -import time -import unittest -from dataclasses import dataclass -from datetime import datetime, timezone -from enum import Enum -from itertools import chain -from typing import Callable, Optional, Self -from urllib.parse import unquote - -import requests - - -def log(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) - - -SESSION = requests.Session() -TOKEN = open("token.txt").read().strip() -RELEASE_URL = "https://api.github.com/repos/indygreg/python-build-standalone/releases" -HEADERS = { - "X-GitHub-Api-Version": "2022-11-28", - "Authorization": "Bearer " + TOKEN, -} -FLAVOR_PREFERENCES = [ - "shared-pgo", - "shared-noopt", - "shared-noopt", - "pgo+lto", - "lto", - "pgo", -] -HIDDEN_FLAVORS = [ - "debug", - "noopt", - "install_only", -] -SPECIAL_TRIPLES = { - "macos": "x86_64-apple-darwin", - "linux64": "x86_64-unknown-linux-gnu", - "windows-amd64": "x86_64-pc-windows-msvc", - "windows-x86-shared-pgo": "i686-pc-windows-msvc-shared-pgo", - "windows-amd64-shared-pgo": "x86_64-pc-windows-msvc-shared-pgo", - "windows-x86": "i686-pc-windows-msvc", - "linux64-musl": "x86_64-unknown-linux-musl", -} - -# matches these: https://doc.rust-lang.org/std/env/consts/constant.ARCH.html -ARCH_MAPPING = { - "x86_64": "x86_64", - "x86": "x86", - "i686": "x86", - "aarch64": "aarch64", -} - -# matches these: https://doc.rust-lang.org/std/env/consts/constant.OS.html -PLATFORM_MAPPING = { - "darwin": "macos", - "windows": "windows", - "linux": "linux", -} - -ENV_MAPPING = { - "gnu": "gnu", - # We must ignore musl for now - # "musl": "musl", -} - - -@dataclass(frozen=True) -class PlatformTriple: - arch: str - platform: str - environment: Optional[str] - flavor: str - - @classmethod - def from_str(cls, triple: str) -> Optional[Self]: - """Parse a triple into a PlatformTriple object.""" - - # The parsing functions are all very similar and we could abstract them into a single function - # but I think it's clearer to keep them separate. - def match_flavor(triple): - for flavor in FLAVOR_PREFERENCES + HIDDEN_FLAVORS: - if flavor in triple: - return flavor - return "" - - def match_mapping(pieces: list[str], mapping: dict[str, str]): - for i in reversed(range(0, len(pieces))): - if pieces[i] in mapping: - return mapping[pieces[i]], pieces[:i] - return None, pieces - - # We split by '-' and match back to front to extract the flavor, env, platform and archk - arch, platform, env, flavor = None, None, None, None - - # Map, old, special triplets to proper triples for parsing, or - # return the triple if it's not a special one - triple = SPECIAL_TRIPLES.get(triple, triple) - pieces = triple.split("-") - flavor = match_flavor(triple) - env, pieces = match_mapping(pieces, ENV_MAPPING) - platform, pieces = match_mapping(pieces, PLATFORM_MAPPING) - arch, pieces = match_mapping(pieces, ARCH_MAPPING) - - if flavor is None or arch is None or platform is None: - return - - if env is None and platform == "linux": - return - - return cls(arch, platform, env, flavor) - - def grouped(self) -> tuple[str, str]: - # for now we only group by arch and platform, because rust's PythonVersion doesn't have a notion - # of environment. Flavor will never be used to sort download choices and must not be included in grouping. - return self.arch, self.platform - # return self.arch, self.platform, self.environment or "" - - -@dataclass(frozen=True, order=True) -class PythonVersion: - major: int - minor: int - patch: int - - @classmethod - def from_str(cls, version: str) -> Self: - return cls(*map(int, version.split(".", 3))) - - -@dataclass(frozen=True) -class IndygregDownload: - version: PythonVersion - triple: PlatformTriple - url: str - - FILENAME_RE = re.compile( - r"""(?x) - ^ - cpython-(?P\d+\.\d+\.\d+?) - (?:\+\d+)? - -(?P.*?) - (?:-[\dT]+)?\.tar\.(?:gz|zst) - $ - """ - ) - - @classmethod - def from_url(cls, url) -> Optional[Self]: - base_name = unquote(url.rsplit("/")[-1]) - if base_name.endswith(".sha256"): - return - - match = cls.FILENAME_RE.match(base_name) - if match is None: - return - - # Parse version string and triplet string - version_str, triple_str = match.groups() - version = PythonVersion.from_str(version_str) - triple = PlatformTriple.from_str(triple_str) - if triple is None: - return - - return cls(version, triple, url) - - def sha256(self) -> Optional[str]: - """We only fetch the sha256 when needed. This generally is AFTER we have - decided that the download will be part of rye's download set""" - resp = fetch(self.url + ".sha256", headers=HEADERS) - if not resp.ok: - return None - return resp.text.strip() - - -def fetch(page, headers): - """Fetch a page from GitHub API with ratelimit awareness.""" - resp = SESSION.get(page, headers=headers, timeout=90) - if ( - resp.status_code in [403, 429] - and resp.headers.get("x-ratelimit-remaining") == "0" - ): - # See https://docs.github.com/en/rest/using-the-rest-api/troubleshooting-the-rest-api?apiVersion=2022-11-28 - if (retry_after := resp.headers.get("retry-after")) is not None: - log("got retry-after header. retrying in {retry_after} seconds.") - time.sleep(int(retry_after)) - - return fetch(page, headers) - - if (retry_at := resp.headers.get("x-ratelimit-reset")) is not None: - utc = datetime.now(timezone.utc).timestamp() - retry_after = int(retry_at) - int(utc) - - log("got x-ratelimit-reset header. retrying in {retry_after} seconds.") - time.sleep(max(int(retry_at) - int(utc), 0)) - - return fetch(page, headers) - - log("got rate limited but no information how long. waiting for 2 minutes") - time.sleep(60 * 2) - return fetch(page, headers) - return resp - - -def fetch_indiygreg_downloads( - pages: int = 100, -) -> dict[PythonVersion, dict[PlatformTriple, list[IndygregDownload]]]: - """Fetch all the indygreg downloads from the release API.""" - results = {} - - for page in range(1, pages): - log(f"Fetching page {page}") - resp = fetch("%s?page=%d" % (RELEASE_URL, page), headers=HEADERS) - rows = resp.json() - if not rows: - break - for row in rows: - for asset in row["assets"]: - url = asset["browser_download_url"] - if (download := IndygregDownload.from_url(url)) is not None: - results.setdefault(download.version, {}).setdefault(download.triple.grouped(), []).append(download) - return results - - -def pick_best_download(downloads: list[IndygregDownload]) -> Optional[IndygregDownload]: - """Pick the best download from the list of downloads.""" - - def preference(download: IndygregDownload) -> int: - try: - return FLAVOR_PREFERENCES.index(download.triple.flavor) - except ValueError: - return len(FLAVOR_PREFERENCES) + 1 - - downloads.sort(key=preference) - return downloads[0] if downloads else None - - -def render( - indys: dict[PythonVersion, list[IndygregDownload]], - pypy: dict[PythonVersion, dict[PlatformTriple, str]], -): - """Render downloads.inc""" - log("Generating code and fetching sha256 of all cpython downloads.") - log("This can be slow......") - - print("// generated code, do not edit") - print("use std::borrow::Cow;") - print("pub const PYTHON_VERSIONS: &[(PythonVersion, &str, Option<&str>)] = &[") - - for version, downloads in sorted(pypy.items(), key=lambda v: v[0], reverse=True): - for triple, url in sorted(downloads.items(), key=lambda v: v[0].grouped()): - print( - f' (PythonVersion {{ name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("{triple.arch}"), os: Cow::Borrowed("{triple.platform}"), major: {version.major}, minor: {version.minor}, patch: {version.patch}, suffix: None }}, "{url}", None),' - ) - - for version, downloads in sorted(indys.items(), key=lambda v: v[0], reverse=True): - for download in sorted(downloads, key=lambda v: v.triple.grouped()): - if (sha256 := download.sha256()) is not None: - sha256_str = f'Some("{sha256}")' - else: - sha256_str = "None" - print( - f' (PythonVersion {{ name: Cow::Borrowed("cpython"), arch: Cow::Borrowed("{download.triple.arch}"), os: Cow::Borrowed("{download.triple.platform}"), major: {version.major}, minor: {version.minor}, patch: {version.patch}, suffix: None }}, "{download.url}", {sha256_str}),' - ) - print("];") - - -def main(): - log("Rye download creator started.") - log("Fetching indygreg downloads...") - - indys = {} - # For every version, pick the best download per triple - # and store it in the results - for version, download_choices in fetch_indiygreg_downloads(100).items(): - # Create a dict[PlatformTriple, list[IndygregDownload]]] - # for each version - for triple, choices in download_choices.items(): - if (best_download := pick_best_download(choices)) is not None: - indys.setdefault(version, []).append(best_download) - - render(indys, PYPY_DOWNLOADS) - - -# These are manually maintained for now -PYPY_DOWNLOADS = { - PythonVersion(3, 10, 12): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", - }, - PythonVersion(3, 9, 16): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", - }, - PythonVersion(3, 8, 16): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", - }, - PythonVersion(3, 7, 13): { - PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", - PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", - PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor="" - ): "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", - }, -} - -if __name__ == "__main__": - main() - - -class Tests(unittest.TestCase): - def test_parse_triplets(self): - expected = { - "aarch64-apple-darwin-lto": PlatformTriple("aarch64", "macos", None, "lto"), - "aarch64-unknown-linux-gnu-pgo+lto": PlatformTriple( - "aarch64", "linux", "gnu", "pgo+lto" - ), - # "x86_64-unknown-linux-musl-debug": PlatformTriple( - # "x86_64", "linux", "musl", "debug" - # ), - "aarch64-unknown-linux-gnu-debug-full": PlatformTriple( - "aarch64", "linux", "gnu", "debug" - ), - "x86_64-unknown-linux-gnu-debug": PlatformTriple( - "x86_64", "linux", "gnu", "debug" - ), - "linux64": PlatformTriple("x86_64", "linux", "gnu", ""), - "ppc64le-unknown-linux-gnu-noopt-full": None, - "x86_64_v3-unknown-linux-gnu-lto": None, - "x86_64-pc-windows-msvc-shared-pgo": PlatformTriple( - "x86_64", "windows", None, "shared-pgo" - ), - } - - for input, expected in expected.items(): - self.assertEqual(PlatformTriple.from_str(input), expected, input) diff --git a/rye/src/downloads.inc b/rye/src/downloads.inc index 0f88451aea..2e2b1dbc4c 100644 --- a/rye/src/downloads.inc +++ b/rye/src/downloads.inc @@ -1,4 +1,5 @@ -// generated code, do not edit +// Generated by rye-devtools. DO NOT EDIT. +// To regenerate, run `rye run find-downloads > rye/src/downloads.inc` from the root of the repository. use std::borrow::Cow; pub const PYTHON_VERSIONS: &[(PythonVersion, &str, Option<&str>)] = &[ (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", None), From 7840866ac65fa4339c8f08901bcf5e0b409ecaf5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 23:31:10 +0100 Subject: [PATCH 10/34] Print uv flag in --version (#699) --- CHANGELOG.md | 2 ++ rye/src/cli/mod.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c59b18ede..1d21a34087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ _Unreleased_ - Use default toolchain to install tools. #666 +- `rye --version` now shows if `uv` is enabled. #699 + ## 0.24.0 diff --git a/rye/src/cli/mod.rs b/rye/src/cli/mod.rs index 25a57e708e..9c66b5bded 100644 --- a/rye/src/cli/mod.rs +++ b/rye/src/cli/mod.rs @@ -30,6 +30,7 @@ mod version; use git_testament::git_testament; use crate::bootstrap::SELF_PYTHON_TARGET_VERSION; +use crate::config::Config; use crate::platform::symlinks_supported; git_testament!(TESTAMENT); @@ -152,5 +153,6 @@ fn print_version() -> Result<(), Error> { ); echo!("self-python: {}", SELF_PYTHON_TARGET_VERSION); echo!("symlink support: {}", symlinks_supported()); + echo!("uv enabled: {}", Config::current().use_uv()); Ok(()) } From d8e00cea1b1b35f0012f946c886164f436f7da09 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 23:33:12 +0100 Subject: [PATCH 11/34] 0.25.0 --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d21a34087..548daa381b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,11 @@ This file contains tracks the changes landing in Rye. It includes changes that were not yet released. + + ## 0.25.0 -_Unreleased_ +Released on 2024-02-19 - Improved the error message if `config` is invoked without arguments. #660 @@ -30,8 +32,6 @@ _Unreleased_ - `rye --version` now shows if `uv` is enabled. #699 - - ## 0.24.0 Released on 2024-02-15 From 05179d34a74824987219affc00c55a2ba4ad3a29 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 23:33:54 +0100 Subject: [PATCH 12/34] 0.26.0 ready for dev --- CHANGELOG.md | 4 ++++ Cargo.lock | 2 +- rye/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 548daa381b..cf3b6ccb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ This file contains tracks the changes landing in Rye. It includes changes that were not yet released. +## 0.26.0 + +_Unreleased_ + ## 0.25.0 diff --git a/Cargo.lock b/Cargo.lock index 41b2e013d6..0d7c369080 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1834,7 +1834,7 @@ dependencies = [ [[package]] name = "rye" -version = "0.25.0" +version = "0.26.0" dependencies = [ "age", "anyhow", diff --git a/rye/Cargo.toml b/rye/Cargo.toml index 08cba98913..916733b17c 100644 --- a/rye/Cargo.toml +++ b/rye/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rye" -version = "0.25.0" +version = "0.26.0" edition = "2021" license = "MIT" From f5b9f0dc7ed4c63119ea2a15ab54b16a4d0631cd Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 23:50:32 +0100 Subject: [PATCH 13/34] Detect changelog changes for website --- .github/workflows/docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4d89b67108..85aeee34bc 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,8 +3,10 @@ on: push: paths: - 'docs/**' + - 'CHANGELOG.md' branches: - main + workflow_dispatch: jobs: build: From 04228e0cef1b18794a242c18bfb1cf8cfd682dc7 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 19 Feb 2024 23:59:03 +0100 Subject: [PATCH 14/34] Bump bundled ruff to 0.2.2 (#700) --- CHANGELOG.md | 2 ++ rye/src/bootstrap.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf3b6ccb72..45916caec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ that were not yet released. _Unreleased_ +- Bumped `ruff` to 0.2.2. #700 + ## 0.25.0 diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index 84468f4117..c8b6ccae05 100644 --- a/rye/src/bootstrap.rs +++ b/rye/src/bootstrap.rs @@ -37,7 +37,7 @@ pub const SELF_PYTHON_TARGET_VERSION: PythonVersionRequest = PythonVersionReques suffix: None, }; -const SELF_VERSION: u64 = 12; +const SELF_VERSION: u64 = 13; const SELF_REQUIREMENTS: &str = r#" build==1.0.3 @@ -56,7 +56,7 @@ twine==4.0.2 unearth==0.14.0 urllib3==2.0.7 virtualenv==20.25.0 -ruff==0.1.14 +ruff==0.2.2 uv==0.1.5 "#; From 845a06950a2f277db57766dd6d4cdd5e92db6562 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 20 Feb 2024 00:01:09 +0100 Subject: [PATCH 15/34] Mention uv --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 932add08ea..d9dc3b305f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Rye picks and ships the right tools so you can get started in minutes: * **Managing Virtualenvs:** it uses the well established virtualenv library under the hood. * **Building Wheels:** it delegates that work largely to [build](https://pypi.org/project/build/). * **Publishing:** its publish command uses [twine](https://pypi.org/project/twine/) to accomplish this task. -* **Locking and Dependency Installation:** is today implemented by using [unearth](https://pypi.org/project/unearth/) and [pip-tools](https://github.com/jazzband/pip-tools/). +* **Locking and Dependency Installation:** is today implemented by using [uv](https://github.com/astral-sh/uv) with a fallback to [unearth](https://pypi.org/project/unearth/) and [pip-tools](https://github.com/jazzband/pip-tools/). * **Workspace support:** Rye lets you work with complex projects consisting of multiple libraries. From b1542d9e237ae71958fd8bcdfb8e41603bd1a2b2 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:44:45 +0800 Subject: [PATCH 16/34] docs: clarify that `rye fetch/install/uninstall` are aliases (#703) --- rye/src/cli/fetch.rs | 4 ++-- rye/src/cli/install.rs | 2 +- rye/src/cli/uninstall.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rye/src/cli/fetch.rs b/rye/src/cli/fetch.rs index 0f6f5d6816..0a6d337622 100644 --- a/rye/src/cli/fetch.rs +++ b/rye/src/cli/fetch.rs @@ -8,12 +8,12 @@ use crate::pyproject::PyProject; use crate::sources::PythonVersionRequest; use crate::utils::CommandOutput; -/// Fetches a Python interpreter for the local machine. +/// Fetches a Python interpreter for the local machine. This is an alias of `rye toolchain fetch`. #[derive(Parser, Debug)] pub struct Args { /// The version of Python to fetch. /// - /// If no version is provided, the requested version will be fetched. + /// If no version is provided, the requested version from local project or `.python-version` will be fetched. version: Option, /// Enables verbose diagnostics. #[arg(short, long)] diff --git a/rye/src/cli/install.rs b/rye/src/cli/install.rs index c1b15cb205..82e03b4773 100644 --- a/rye/src/cli/install.rs +++ b/rye/src/cli/install.rs @@ -10,7 +10,7 @@ use crate::installer::{install, resolve_local_requirement}; use crate::sources::PythonVersionRequest; use crate::utils::CommandOutput; -/// Installs a package as global tool. +/// Installs a package as global tool. This is an alias of `rye tools install`. #[derive(Parser, Debug)] pub struct Args { /// The name of the package to install. diff --git a/rye/src/cli/uninstall.rs b/rye/src/cli/uninstall.rs index 1c76510e59..7039b30162 100644 --- a/rye/src/cli/uninstall.rs +++ b/rye/src/cli/uninstall.rs @@ -4,7 +4,7 @@ use clap::Parser; use crate::installer::uninstall; use crate::utils::CommandOutput; -/// Uninstalls a global tool. +/// Uninstalls a global tool. This is an alias of `rye tools uninstall`. #[derive(Parser, Debug)] pub struct Args { /// The package to uninstall From 7f9d4749a0c2e217af0b6ad1fa6eaa99356697e1 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:45:16 +0800 Subject: [PATCH 17/34] Add PyPy downloads finder (#683) --- CHANGELOG.md | 2 + docs/guide/toolchains/pypy.md | 3 +- .../src/rye_devtools/find_downloads.py | 246 +++++------------- rye/src/downloads.inc | 106 ++++++-- 4 files changed, 162 insertions(+), 195 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45916caec1..adb4593d72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ _Unreleased_ - Bumped `ruff` to 0.2.2. #700 +- Sync latest PyPy releases. #683 + ## 0.25.0 diff --git a/docs/guide/toolchains/pypy.md b/docs/guide/toolchains/pypy.md index 5f8bc6b4d1..39bd498ee3 100644 --- a/docs/guide/toolchains/pypy.md +++ b/docs/guide/toolchains/pypy.md @@ -15,8 +15,7 @@ target older Python packages. ## Sources PyPy builds are downloaded from -[downloads.python.org](https://downloads.python.org/pypy/). These downloads -are not verified today. +[downloads.python.org](https://downloads.python.org/pypy/). ## Usage diff --git a/rye-devtools/src/rye_devtools/find_downloads.py b/rye-devtools/src/rye_devtools/find_downloads.py index 7d3713c5a6..8b618d9ad3 100644 --- a/rye-devtools/src/rye_devtools/find_downloads.py +++ b/rye-devtools/src/rye_devtools/find_downloads.py @@ -336,184 +336,82 @@ async def fetch_checksums(url: str): class PyPyFinder(Finder): implementation = PythonImplementation.PYPY + RELEASE_URL = "https://raw.githubusercontent.com/pypy/pypy/main/pypy/tool/release/versions.json" + CHECKSUM_URL = ( + "https://raw.githubusercontent.com/pypy/pypy.org/main/pages/checksums.rst" + ) + CHECKSUM_RE = re.compile( + r"^\s*(?P\w{64})\s+(?Ppypy.+)$", re.MULTILINE + ) + + ARCH_MAPPING = { + "x64": "x86_64", + "i686": "x86", + "aarch64": "aarch64", + "arm64": "aarch64", + } + + PLATFORM_MAPPING = { + "darwin": "macos", + "win64": "windows", + "linux": "linux", + } + def __init__(self, client: httpx.AsyncClient): self.client = client async def find(self) -> list[PythonDownload]: - # TODO These are manually maintained for now, fetch them from PyPy website in later PR. - return [ - PythonDownload( - version=PythonVersion(3, 10, 12), - triple=PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.10-v7.3.12-linux64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 10, 12), - triple=PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.10-v7.3.12-aarch64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 10, 12), - triple=PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.10-v7.3.12-macos_x86_64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 10, 12), - triple=PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.10-v7.3.12-macos_arm64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 10, 12), - triple=PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.10-v7.3.12-win64.zip", - url="https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", - ), - PythonDownload( - version=PythonVersion(3, 9, 16), - triple=PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.9-v7.3.11-linux64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 9, 16), - triple=PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.9-v7.3.11-aarch64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 9, 16), - triple=PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.9-v7.3.11-macos_x86_64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 9, 16), - triple=PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.9-v7.3.11-macos_arm64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 9, 16), - triple=PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.9-v7.3.11-win64.zip", - url="https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", - ), - PythonDownload( - version=PythonVersion(3, 8, 16), - triple=PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.8-v7.3.11-linux64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 8, 16), - triple=PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.8-v7.3.11-aarch64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 8, 16), - triple=PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.8-v7.3.11-macos_x86_64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 8, 16), - triple=PlatformTriple( - arch="aarch64", platform="macos", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.8-v7.3.11-macos_arm64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 8, 16), - triple=PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.8-v7.3.11-win64.zip", - url="https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", - ), - PythonDownload( - version=PythonVersion(3, 7, 13), - triple=PlatformTriple( - arch="x86_64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.7-v7.3.9-linux64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 7, 13), - triple=PlatformTriple( - arch="aarch64", platform="linux", environment="gnu", flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.7-v7.3.9-aarch64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 7, 13), - triple=PlatformTriple( - arch="x86_64", platform="macos", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.7-v7.3.9-osx64.tar.bz2", - url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", - ), - PythonDownload( - version=PythonVersion(3, 7, 13), - triple=PlatformTriple( - arch="x86_64", platform="windows", environment=None, flavor=None - ), - implementation=PythonImplementation.PYPY, - filename="pypy3.7-v7.3.9-win64.zip", - url="https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", - ), - ] + downloads = await self.fetch_downloads() + await self.fetch_checksums(downloads) + return downloads + + async def fetch_downloads(self) -> list[PythonDownload]: + log("Fetching pypy downloads...") + resp = await fetch(self.client, self.RELEASE_URL) + versions = resp.json() + + results = {} + for version in versions: + if not version["stable"]: + continue + python_version = PythonVersion.from_str(version["python_version"]) + if python_version < (3, 7, 0): + continue + for file in version["files"]: + arch = self.ARCH_MAPPING.get(file["arch"]) + platform = self.PLATFORM_MAPPING.get(file["platform"]) + if arch is None or platform is None: + continue + environment = "gnu" if platform == "linux" else None + download = PythonDownload( + version=python_version, + triple=PlatformTriple( + arch=arch, + platform=platform, + environment=environment, + flavor=None, + ), + implementation=PythonImplementation.PYPY, + filename=file["filename"], + url=file["download_url"], + ) + # Only keep the latest pypy version of each arch/platform + if (python_version, arch, platform) not in results: + results[(python_version, arch, platform)] = download + + return list(results.values()) + + async def fetch_checksums(self, downloads: list[PythonDownload]) -> None: + log("Fetching pypy checksums...") + resp = await fetch(self.client, self.CHECKSUM_URL) + text = resp.text + + checksums = {} + for match in self.CHECKSUM_RE.finditer(text): + checksums[match.group("filename")] = match.group("checksum") + + for download in downloads: + download.sha256 = checksums.get(download.filename) def render(downloads: list[PythonDownload]): diff --git a/rye/src/downloads.inc b/rye/src/downloads.inc index 2e2b1dbc4c..b5d791f3d4 100644 --- a/rye/src/downloads.inc +++ b/rye/src/downloads.inc @@ -2,25 +2,93 @@ // To regenerate, run `rye run find-downloads > rye/src/downloads.inc` from the root of the repository. use std::borrow::Cow; pub const PYTHON_VERSIONS: &[(PythonVersion, &str, Option<&str>)] = &[ - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", None), - (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", None), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.15-aarch64.tar.bz2", Some("52146fccaf64e87e71d178dda8de63c01577ec3923073dc69e1519622bcacb74")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 10, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.15-macos_arm64.tar.bz2", Some("d927c5105ea7880f7596fe459183e35cc17c853ef5105678b2ad62a8d000a548")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.15-linux32.tar.bz2", Some("75dd58c9abd8b9d78220373148355bc3119febcf27a2c781d64ad85e7232c4aa")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.15-linux64.tar.bz2", Some("33c584e9a70a71afd0cb7dd8ba9996720b911b3b8ed0156aea298d4487ad22c3")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 10, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.15-macos_x86_64.tar.bz2", Some("559b61ba7e7c5a5c23cef5370f1fab47ccdb939ac5d2b42b4bef091abe3f6964")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 10, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.15-win64.zip", Some("b378b3ab1c3719aee0c3e5519e7bff93ff67b2d8aa987fe4f088b54382db676c")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-aarch64.tar.bz2", Some("26208b5a134d9860a08f74cce60960005758e82dc5f0e3566a48ed863a1f16a1")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_arm64.tar.bz2", Some("45671b1e9437f95ccd790af10dbeb57733cca1ed9661463b727d3c4f5caa7ba0")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux32.tar.bz2", Some("811667825ae58ada4b7c3d8bc1b5055b9f9d6a377e51aedfbe0727966603f60e")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-linux64.tar.bz2", Some("6c577993160b6f5ee8cab73cd1a807affcefafe2f7441c87bd926c10505e8731")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-macos_x86_64.tar.bz2", Some("dbc15d8570560d5f79366883c24bc42231a92855ac19a0f28cb0adeb11242666")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 10, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.10-v7.3.12-win64.zip", Some("8c3b1d34fb99100e230e94560410a38d450dc844effbee9ea183518e4aff595c")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 18, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.15-aarch64.tar.bz2", Some("03e35fcba290454bb0ccf7ee57fb42d1e63108d10d593776a382c0a2fe355de0")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 18, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.15-macos_arm64.tar.bz2", Some("300541c32125767a91b182b03d9cc4257f04971af32d747ecd4d62549d72acfd")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 18, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.15-linux32.tar.bz2", Some("c6209380977066c9e8b96e8258821c70f996004ce1bc8659ae83d4fd5a89ff5c")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 18, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.15-linux64.tar.bz2", Some("f062be307200bde434817e1620cebc13f563d6ab25309442c5f4d0f0d68f0912")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 18, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.15-macos_x86_64.tar.bz2", Some("18ad7c9cb91c5e8ef9d40442b2fd1f6392ae113794c5b6b7d3a45e04f19edec6")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 9, patch: 18, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.15-win64.zip", Some("a156dad8b58570597eaaabe05663f00f80c60bc11df4a9c46d0953b6c5eb9209")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 17, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.12-aarch64.tar.bz2", Some("e9327fb9edaf2ad91935d5b8563ec5ff24193bddb175c1acaaf772c025af1824")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 17, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_arm64.tar.bz2", Some("0e8a1a3468b9790c734ac698f5b00cc03fc16899ccc6ce876465fac0b83980e3")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 17, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux32.tar.bz2", Some("aa04370d38f451683ccc817d76c2b3e0f471dbb879e0bd618d9affbdc9cd37a4")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 17, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.12-linux64.tar.bz2", Some("84c89b966fab2b58f451a482ee30ca7fec3350435bd0b9614615c61dc6da2390")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 17, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.12-macos_x86_64.tar.bz2", Some("64f008ffa070c407e5ef46c8256b2e014de7196ea5d858385861254e7959f4eb")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 9, patch: 17, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.12-win64.zip", Some("0996054207b401aeacace1aa11bad82cfcb463838a1603c5f263626c47bbe0e6")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-aarch64.tar.bz2", Some("09175dc652ed895d98e9ad63d216812bf3ee7e398d900a9bf9eb2906ba8302b9")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_arm64.tar.bz2", Some("91ad7500f1a39531dbefa0b345a3dcff927ff9971654e8d2e9ef7c5ae311f57e")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux32.tar.bz2", Some("0099d72c2897b229057bff7e2c343624aeabdc60d6fb43ca882bff082f1ffa48")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-linux64.tar.bz2", Some("d506172ca11071274175d74e9c581c3166432d0179b036470e3b9e8d20eae581")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-macos_x86_64.tar.bz2", Some("d33f40b207099872585afd71873575ca6ea638a27d823bc621238c5ae82542ed")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 9, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.11-win64.zip", Some("57faad132d42d3e7a6406fcffafffe0b4f390cf0e2966abb8090d073c6edf405")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.10-aarch64.tar.bz2", Some("657a04fd9a5a992a2f116a9e7e9132ea0c578721f59139c9fb2083775f71e514")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.10-macos_arm64.tar.bz2", Some("e2a6bec7408e6497c7de8165aa4a1b15e2416aec4a72f2578f793fb06859ccba")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.10-linux32.tar.bz2", Some("b6db59613b9a1c0c1ab87bc103f52ee95193423882dc8a848b68850b8ba59cc5")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.10-linux64.tar.bz2", Some("95cf99406179460d63ddbfe1ec870f889d05f7767ce81cef14b88a3a9e127266")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.10-macos_x86_64.tar.bz2", Some("f90c8619b41e68ec9ffd7d5e913fe02e60843da43d3735b1c1bc75bcfe638d97")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 9, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.10-win64.zip", Some("07e18b7b24c74af9730dfaab16e24b22ef94ea9a4b64cbb2c0d80610a381192a")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.9-aarch64.tar.bz2", Some("2e1ae193d98bc51439642a7618d521ea019f45b8fb226940f7e334c548d2b4b9")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.9-linux32.tar.bz2", Some("0de4b9501cf28524cdedcff5052deee9ea4630176a512bdc408edfa30914bae7")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.9-linux64.tar.bz2", Some("46818cb3d74b96b34787548343d266e2562b531ddbaf330383ba930ff1930ed5")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.9-osx64.tar.bz2", Some("59c8852168b2b1ba1f0211ff043c678760380d2f9faf2f95042a8878554dbc25")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 9, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.9-win64.zip", Some("be48ab42f95c402543a7042c999c9433b17e55477c847612c8733a583ca6dff5")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.8-aarch64-portable.tar.bz2", Some("b7282bc4484bceae5bc4cc04e05ee4faf51cb624c8fc7a69d92e5fdf0d0c96aa")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.8-linux32.tar.bz2", Some("a0d18e4e73cc655eb02354759178b8fb161d3e53b64297d05e2fff91f7cf862d")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 9, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.8-linux64.tar.bz2", Some("129a055032bba700cd1d0acacab3659cf6b7180e25b1b2f730e792f06d5b3010")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 9, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.8-osx64.tar.bz2", Some("95bd88ac8d6372cd5b7b5393de7b7d5c615a0c6e42fdb1eb67f2d2d510965aee")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 9, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.9-v7.3.8-win64.zip", Some("c1b2e4cde2dcd1208d41ef7b7df8e5c90564a521e7a5db431673da335a1ba697")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-aarch64.tar.bz2", Some("9a2fa0b8d92b7830aa31774a9a76129b0ff81afbd22cd5c41fbdd9119e859f55")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2", Some("78cdc79ff964c4bfd13eb45a7d43a011cbe8d8b513323d204891f703fdc4fa1a")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux32.tar.bz2", Some("a79b31fce8f5bc1f9940b6777134189a1d3d18bda4b1c830384cda90077c9176")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-linux64.tar.bz2", Some("470330e58ac105c094041aa07bb05676b06292bc61409e26f5c5593ebb2292d9")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2", Some("194ca0b4d91ae409a9cb1a59eb7572d7affa8a451ea3daf26539aa515443433a")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 8, patch: 16, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip", Some("0f46fb6df32941ea016f77cfd7e9b426d5ac25a2af2453414df66103941c8435")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.10-aarch64.tar.bz2", Some("e4caa1a545f22cfee87d5b9aa6f8852347f223643ad7d2562e0b2a2f4663ad98")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.10-macos_arm64.tar.bz2", Some("6cb1429371e4854b718148a509d80143f801e3abfc72fef58d88aeeee1e98f9e")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.10-linux32.tar.bz2", Some("b70ed7fdc73a74ebdc04f07439f7bad1a849aaca95e26b4a74049d0e483f071c")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.10-linux64.tar.bz2", Some("ceef6496fd4ab1c99e3ec22ce657b8f10f8bb77a32427fadfb5e1dd943806011")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.10-macos_x86_64.tar.bz2", Some("399eb1ce4c65f62f6a096b7c273536601b7695e3c0dc0457393a659b95b7615b")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 8, patch: 15, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.10-win64.zip", Some("362dd624d95bd64743190ea2539b97452ecb3d53ea92ceb2fbe9f48dc60e6b8f")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.9-aarch64.tar.bz2", Some("5e124455e207425e80731dff317f0432fa0aba1f025845ffca813770e2447e32")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.9-linux32.tar.bz2", Some("4b261516c6c59078ab0c8bd7207327a1b97057b4ec1714ed5e79a026f9efd492")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.9-linux64.tar.bz2", Some("08be25ec82fc5d23b78563eda144923517daba481a90af0ace7a047c9c9a3c34")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.9-osx64.tar.bz2", Some("91a5c2c1facd5a4931a8682b7d792f7cf4f2ba25cd2e7e44e982139a6d5e4840")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 8, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.9-win64.zip", Some("05022baaa55db2b60880f2422312d9e4025e1267303ac57f33e8253559d0be88")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.8-aarch64-portable.tar.bz2", Some("0210536e9f1841ba283c13b04783394050837bb3e6f4091c9f1bd9c7f2b94b55")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.8-linux32.tar.bz2", Some("bea4b275decd492af6462157d293dd6fcf08a949859f8aec0959537b40afd032")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 8, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.8-linux64.tar.bz2", Some("089f8e3e357d6130815964ddd3507c13bd53e4976ccf0a89b5c36a9a6775a188")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 8, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.8-osx64.tar.bz2", Some("de1b283ff112d76395c0162a1cf11528e192bdc230ee3f1b237f7694c7518dee")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 8, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.8-v7.3.8-win64.zip", Some("0894c468e7de758c509a602a28ef0ba4fbf197ccdf946c7853a7283d9bb2a345")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-aarch64.tar.bz2", Some("dfc62f2c453fb851d10a1879c6e75c31ffebbf2a44d181bb06fcac4750d023fc")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux32.tar.bz2", Some("3398cece0167b81baa219c9cd54a549443d8c0a6b553ec8ec13236281e0d86cd")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-linux64.tar.bz2", Some("c58195124d807ecc527499ee19bc511ed753f4f2e418203ca51bc7e3b124d5d1")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2", Some("12d92f578a200d50959e55074b20f29f93c538943e9a6e6522df1a1cc9cef542")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 7, patch: 13, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip", Some("8acb184b48fb3c854de0662e4d23a66b90e73b1ab73a86695022c12c745d8b00")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.8-aarch64-portable.tar.bz2", Some("639c76f128a856747aee23a34276fa101a7a157ea81e76394fbaf80b97dcf2f2")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.8-linux32.tar.bz2", Some("38429ec6ea1aca391821ee4fbda7358ae86de4600146643f2af2fe2c085af839")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.8-linux64.tar.bz2", Some("409085db79a6d90bfcf4f576dca1538498e65937acfbe03bd4909bdc262ff378")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 7, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.8-osx64.tar.bz2", Some("76b8eef5b059a7e478f525615482d2a6e9feb83375e3f63c16381d80521a693f")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 7, patch: 12, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.8-win64.zip", Some("96df67492bc8d62b2e71dddf5f6c58965a26cac9799c5f4081401af0494b3bcc")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.5-aarch64.tar.bz2", Some("85d83093b3ef5b863f641bc4073d057cc98bb821e16aa9361a5ff4898e70e8ee")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.5-linux32.tar.bz2", Some("3dd8b565203d372829e53945c599296fa961895130342ea13791b17c84ed06c4")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.5-linux64.tar.bz2", Some("9000db3e87b54638e55177e68cbeb30a30fe5d17b6be48a9eb43d65b3ebcfc26")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 7, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.5-osx64.tar.bz2", Some("b3a7d3099ad83de7c267bb79ae609d5ce73b01800578ffd91ba7e221b13f80db")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("windows"), major: 3, minor: 7, patch: 10, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.5-win64.zip", Some("072bd22427178dc4e65d961f50281bd2f56e11c4e4d9f16311c703f69f46ae24")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 9, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.3-aarch64.tar.bz2", Some("ee4aa041558b58de6063dd6df93b3def221c4ca4c900d6a9db5b1b52135703a8")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 9, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.3-linux32.tar.bz2", Some("7d81b8e9fcd07c067cfe2f519ab770ec62928ee8787f952cadf2d2786246efc8")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("linux"), major: 3, minor: 7, patch: 9, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.3-linux64.tar.bz2", Some("37e2804c4661c86c857d709d28c7de716b000d31e89766599fdf5a98928b7096")), + (PythonVersion { name: Cow::Borrowed("pypy"), arch: Cow::Borrowed("x86_64"), os: Cow::Borrowed("macos"), major: 3, minor: 7, patch: 9, suffix: None }, "https://downloads.python.org/pypy/pypy3.7-v7.3.3-osx64.tar.bz2", Some("d72b27d5bb60813273f14f07378a08822186a66e216c5d1a768ad295b582438d")), (PythonVersion { name: Cow::Borrowed("cpython"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("linux"), major: 3, minor: 12, patch: 1, suffix: None }, "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-lto-full.tar.zst", Some("3621be2cd8b5686e10a022f04869911cad9197a3ef77b30879fe25e792d7c249")), (PythonVersion { name: Cow::Borrowed("cpython"), arch: Cow::Borrowed("aarch64"), os: Cow::Borrowed("macos"), major: 3, minor: 12, patch: 1, suffix: None }, "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-aarch64-apple-darwin-pgo%2Blto-full.tar.zst", Some("61e51e3490537b800fcefad718157cf775de41044e95aa538b63ab599f66f3a9")), (PythonVersion { name: Cow::Borrowed("cpython"), arch: Cow::Borrowed("x86"), os: Cow::Borrowed("windows"), major: 3, minor: 12, patch: 1, suffix: None }, "https://github.com/indygreg/python-build-standalone/releases/download/20240107/cpython-3.12.1%2B20240107-i686-pc-windows-msvc-shared-pgo-full.tar.zst", Some("22866d35fdf58e90e75d6ba9aa78c288b452ea7041fa9bc5549eca9daa431883")), From bd2bfa5c77b0fb6844c0a5201b9f03a8ab9c7a1e Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:00:25 +0800 Subject: [PATCH 18/34] Prevent removal of an active toolchain (#693) --- CHANGELOG.md | 2 ++ docs/guide/commands/toolchain/remove.md | 1 + rye/src/bootstrap.rs | 4 ++- rye/src/cli/toolchain.rs | 39 ++++++++++++++++++++++++- rye/src/cli/tools.rs | 8 +++-- rye/src/installer.rs | 23 +++++++++++---- rye/src/pyproject.rs | 13 +++++++++ rye/src/sync.rs | 14 +++------ 8 files changed, 84 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adb4593d72..515e7c4f50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ _Unreleased_ - Bumped `ruff` to 0.2.2. #700 +- Prevent `rye toolchain remove` from removing the currently active toolchain. #693 + - Sync latest PyPy releases. #683 diff --git a/docs/guide/commands/toolchain/remove.md b/docs/guide/commands/toolchain/remove.md index 56dca8755c..ff0ea52de2 100644 --- a/docs/guide/commands/toolchain/remove.md +++ b/docs/guide/commands/toolchain/remove.md @@ -15,4 +15,5 @@ Removed installed toolchain cpython@3.9.5 ## Options +* `-f, --force`: Force removal even if the toolchain is in use * `-h, --help`: Print help (see a summary with '-h') \ No newline at end of file diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index c8b6ccae05..e0d1540ef5 100644 --- a/rye/src/bootstrap.rs +++ b/rye/src/bootstrap.rs @@ -19,7 +19,7 @@ use crate::platform::{ get_app_dir, get_canonical_py_path, get_toolchain_python_bin, list_known_toolchains, symlinks_supported, }; -use crate::pyproject::latest_available_python_version; +use crate::pyproject::{latest_available_python_version, write_venv_marker}; use crate::sources::{get_download_url, PythonVersion, PythonVersionRequest}; use crate::utils::{ check_checksum, get_venv_python_bin, set_proxy_variables, symlink_file, unpack_archive, @@ -155,6 +155,8 @@ pub fn ensure_self_venv_with_toolchain( bail!("failed to initialize virtualenv in {}", venv_dir.display()); } + write_venv_marker(&venv_dir, &version)?; + do_update(output, &venv_dir, app_dir)?; fs::write(venv_dir.join("tool-version.txt"), SELF_VERSION.to_string())?; diff --git a/rye/src/cli/toolchain.rs b/rye/src/cli/toolchain.rs index 6979bdfda7..6c009731ad 100644 --- a/rye/src/cli/toolchain.rs +++ b/rye/src/cli/toolchain.rs @@ -5,6 +5,8 @@ use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; +use crate::installer::list_installed_tools; +use crate::piptools::get_pip_tools_venv_path; use anyhow::{anyhow, bail, Context, Error}; use clap::Parser; use clap::ValueEnum; @@ -12,7 +14,8 @@ use console::style; use serde::Deserialize; use serde::Serialize; -use crate::platform::{get_canonical_py_path, list_known_toolchains}; +use crate::platform::{get_app_dir, get_canonical_py_path, list_known_toolchains}; +use crate::pyproject::read_venv_marker; use crate::sources::{iter_downloadable, PythonVersion}; use crate::utils::symlink_file; @@ -60,6 +63,9 @@ pub struct RegisterCommand { pub struct RemoveCommand { /// Name and version of the toolchain. version: String, + /// Force removal even if the toolchain is in use. + #[arg(short, long)] + force: bool, } /// List all registered toolchains @@ -103,9 +109,40 @@ fn register(cmd: RegisterCommand) -> Result<(), Error> { Ok(()) } +/// Checks if a toolchain is still in use. +fn check_in_use(ver: &PythonVersion) -> Result<(), Error> { + // Check if used by rye itself. + let app_dir = get_app_dir(); + for venv in &[app_dir.join("self"), get_pip_tools_venv_path(ver)] { + let venv_marker = read_venv_marker(venv); + if let Some(ref venv_marker) = venv_marker { + if &venv_marker.python == ver { + bail!("toolchain {} is still in use by rye itself", ver); + } + } + } + + // Check if used by any tool. + let installed_tools = list_installed_tools()?; + for (tool, info) in &installed_tools { + if let Some(ref venv_marker) = info.venv_marker { + if &venv_marker.python == ver { + bail!("toolchain {} is still in use by tool {}", ver, tool); + } + } + } + + Ok(()) +} + pub fn remove(cmd: RemoveCommand) -> Result<(), Error> { let ver: PythonVersion = cmd.version.parse()?; let path = get_canonical_py_path(&ver)?; + + if !cmd.force && path.exists() { + check_in_use(&ver)?; + } + if path.is_file() { fs::remove_file(&path)?; echo!("Removed toolchain link {}", &ver); diff --git a/rye/src/cli/tools.rs b/rye/src/cli/tools.rs index a426e981ac..812071b5d4 100644 --- a/rye/src/cli/tools.rs +++ b/rye/src/cli/tools.rs @@ -40,7 +40,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { fn list_tools(cmd: ListCommand) -> Result<(), Error> { let mut tools = list_installed_tools()?.into_iter().collect::>(); - tools.sort(); + tools.sort_by_key(|(tool, _)| tool.clone()); for (tool, mut info) in tools { if !info.valid { @@ -48,7 +48,11 @@ fn list_tools(cmd: ListCommand) -> Result<(), Error> { continue; } if cmd.version_show { - echo!("{} {}", style(tool).cyan(), style(info.version).cyan()); + if let Some(ref venv) = info.venv_marker { + echo!("{} {} ({})", style(tool).cyan(), info.version, venv.python); + } else { + echo!("{} {}", style(tool).cyan(), info.version); + } } else { echo!("{}", style(tool).cyan()); } diff --git a/rye/src/installer.rs b/rye/src/installer.rs index 73f0d31a63..84c2a8a48e 100644 --- a/rye/src/installer.rs +++ b/rye/src/installer.rs @@ -15,9 +15,9 @@ use crate::bootstrap::{ensure_self_venv, fetch}; use crate::config::Config; use crate::consts::VENV_BIN; use crate::platform::get_app_dir; -use crate::pyproject::{normalize_package_name, ExpandedSources}; +use crate::pyproject::{normalize_package_name, read_venv_marker, ExpandedSources}; use crate::sources::PythonVersionRequest; -use crate::sync::create_virtualenv; +use crate::sync::{create_virtualenv, VenvMarker}; use crate::utils::{ get_short_executable_name, get_venv_python_bin, is_executable, symlink_file, CommandOutput, }; @@ -68,18 +68,25 @@ print(json.dumps(result)) static SUCCESSFULLY_DOWNLOADED_RE: Lazy = Lazy::new(|| Regex::new("(?m)^Successfully downloaded (.*?)$").unwrap()); -#[derive(Ord, PartialOrd, Eq, PartialEq)] +#[derive(Eq, PartialEq)] pub struct ToolInfo { pub version: String, pub scripts: Vec, + pub venv_marker: Option, pub valid: bool, } impl ToolInfo { - pub fn new(version: String, scripts: Vec, valid: bool) -> Self { + pub fn new( + version: String, + scripts: Vec, + venv_marker: Option, + valid: bool, + ) -> Self { Self { version, scripts, + venv_marker, valid, } } @@ -340,8 +347,9 @@ pub fn list_installed_tools() -> Result, Error> { } let tool_name = folder.file_name().to_string_lossy().to_string(); let target_venv_bin_path = folder.path().join(VENV_BIN); - let mut scripts = Vec::new(); + let venv_marker = read_venv_marker(&folder.path()); + let mut scripts = Vec::new(); for script in fs::read_dir(target_venv_bin_path.clone())? { let script = script?; let script_path = script.path(); @@ -365,7 +373,10 @@ pub fn list_installed_tools() -> Result, Error> { Err(_) => String::new(), }; - rv.insert(tool_name, ToolInfo::new(tool_version, scripts, valid)); + rv.insert( + tool_name, + ToolInfo::new(tool_version, scripts, venv_marker, valid), + ); } Ok(rv) diff --git a/rye/src/pyproject.rs b/rye/src/pyproject.rs index 06398f41e3..5bedfa47a8 100644 --- a/rye/src/pyproject.rs +++ b/rye/src/pyproject.rs @@ -1068,6 +1068,19 @@ pub fn read_venv_marker(venv_path: &Path) -> Option { serde_json::from_slice(&contents).ok() } +pub fn write_venv_marker(venv_path: &Path, py_ver: &PythonVersion) -> Result<(), Error> { + fs::write( + venv_path.join("rye-venv.json"), + serde_json::to_string_pretty(&VenvMarker { + python: py_ver.clone(), + venv_path: Some(venv_path.into()), + })?, + ) + .context("failed writing venv marker file")?; + + Ok(()) +} + pub fn get_current_venv_python_version(venv_path: &Path) -> Option { read_venv_marker(venv_path).map(|x| x.python) } diff --git a/rye/src/sync.rs b/rye/src/sync.rs index c899c5b93a..f8fc3e17b8 100644 --- a/rye/src/sync.rs +++ b/rye/src/sync.rs @@ -17,7 +17,7 @@ use crate::lock::{ }; use crate::piptools::{get_pip_sync, get_pip_tools_venv_path}; use crate::platform::get_toolchain_python_bin; -use crate::pyproject::{read_venv_marker, ExpandedSources, PyProject}; +use crate::pyproject::{read_venv_marker, write_venv_marker, ExpandedSources, PyProject}; use crate::sources::PythonVersion; use crate::utils::{ get_venv_python_bin, mark_path_sync_ignore, set_proxy_variables, symlink_dir, CommandOutput, @@ -72,7 +72,7 @@ impl SyncOptions { } /// Config written into the virtualenv for sync purposes. -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] pub struct VenvMarker { pub python: PythonVersion, pub venv_path: Option, @@ -170,14 +170,6 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> { let prompt = pyproject.name().unwrap_or("venv"); create_virtualenv(output, &self_venv, &py_ver, &venv, prompt) .context("failed creating virtualenv ahead of sync")?; - fs::write( - venv.join("rye-venv.json"), - serde_json::to_string_pretty(&VenvMarker { - python: py_ver.clone(), - venv_path: Some(venv.clone().into()), - })?, - ) - .context("failed writing venv marker file")?; } // prepare necessary utilities for pip-sync. This is a super crude @@ -370,6 +362,8 @@ pub fn create_virtualenv( bail!("failed to initialize virtualenv"); } + write_venv_marker(venv, py_ver)?; + // uv can only do it now if Config::current().use_uv() { update_venv_sync_marker(output, venv); From 02a5e09dfd769de298ab2df1a421ae2d0c4ef1c7 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:04:52 +0800 Subject: [PATCH 19/34] Add a workflow to sync Python releases automatically (#704) --- .github/workflows/docs.yml | 2 +- .github/workflows/sync-python-releases.yml | 35 +++++++++++++++++++ Makefile | 4 +++ .../src/rye_devtools/find_downloads.py | 13 ++++++- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/sync-python-releases.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 85aeee34bc..4e722d9042 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,6 +10,7 @@ on: jobs: build: + if: github.repository == 'mitsuhiko/rye' name: Deploy docs runs-on: ubuntu-latest steps: @@ -18,7 +19,6 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Deploy docs uses: mhausenblas/mkdocs-deploy-gh-pages@master - if: github.repository == 'mitsuhiko/rye' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CONFIG_FILE: mkdocs.yml diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml new file mode 100644 index 0000000000..b49136f104 --- /dev/null +++ b/.github/workflows/sync-python-releases.yml @@ -0,0 +1,35 @@ +# For this action to work you must explicitly allow GitHub Actions to create pull requests. +# This setting can be found in a repository's settings under Actions > General > Workflow permissions. +# For repositories belonging to an organization, this setting can be managed by +# admins in organization settings under Actions > General > Workflow permissions. +name: Sync Python Releases +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +jobs: + sync: + if: github.repository == 'mitsuhiko/rye' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rye + uses: eifinger/setup-rye@v1 + with: + enable-cache: true + - name: Sync Python Releases + run: make sync-python-releases + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create PR + uses: peter-evans/create-pull-request@v6 + with: + commit-message: "Sync latest Python releases" + add-paths: "src/rye/downloads.inc" + branch: "sync-python-releases" + title: "Sync Python Releases" + body: | + - Synced latest Python releases + + Auto-generated by [sync-python-releases.yml](.github/workflows/sync-python-releases.yml) diff --git a/Makefile b/Makefile index aa9a29fffc..82dfb76f71 100644 --- a/Makefile +++ b/Makefile @@ -31,3 +31,7 @@ lint: .venv: @rye sync + +.PHONY: sync-python-releases +sync-python-releases: .venv + @rye run find-downloads > rye/src/downloads.inc diff --git a/rye-devtools/src/rye_devtools/find_downloads.py b/rye-devtools/src/rye_devtools/find_downloads.py index 8b618d9ad3..fd4442d8b7 100644 --- a/rye-devtools/src/rye_devtools/find_downloads.py +++ b/rye-devtools/src/rye_devtools/find_downloads.py @@ -7,6 +7,7 @@ import abc import asyncio import itertools +import os import re import sys import time @@ -447,7 +448,17 @@ def sort_key(download: PythonDownload) -> tuple[int, PythonVersion, PlatformTrip async def async_main(): - token = open("token.txt").read().strip() + token = os.environ.get("GITHUB_TOKEN") + if not token: + try: + token = open("token.txt").read().strip() + except Exception: + pass + + if not token: + log("Please set GITHUB_TOKEN environment variable or create a token.txt file.") + sys.exit(1) + headers = { "X-GitHub-Api-Version": "2022-11-28", "Authorization": "Bearer " + token, From 216eb80838af6be4503af0a0c61d6e6a88f2031c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 20 Feb 2024 10:29:08 +0100 Subject: [PATCH 20/34] Added basic tools behavior test (#707) --- rye/tests/common/mod.rs | 1 + rye/tests/test_tools.rs | 96 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 rye/tests/test_tools.rs diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index 0833a7e3d8..1239e8579f 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -143,6 +143,7 @@ impl Space { fs::read_to_string(p).unwrap() } + #[allow(unused)] pub fn init(&self, name: &str) { let status = self .cmd(get_bin()) diff --git a/rye/tests/test_tools.rs b/rye/tests/test_tools.rs new file mode 100644 index 0000000000..2b2c831677 --- /dev/null +++ b/rye/tests/test_tools.rs @@ -0,0 +1,96 @@ +use std::env::consts::EXE_EXTENSION; +use std::fs; + +use crate::common::{rye_cmd_snapshot, Space}; + +mod common; + +#[test] +fn test_basic_tool_behavior() { + let space = Space::new(); + + // in case we left things behind from last run. + fs::remove_dir_all(space.rye_home().join("tools")).ok(); + fs::remove_file( + space + .rye_home() + .join("shims") + .join("pycowsay") + .with_extension(EXE_EXTENSION), + ) + .ok(); + + rye_cmd_snapshot!( + space.rye_cmd() + .arg("tools") + .arg("install") + .arg("pycowsay") + .arg("-p") + .arg("cpython@3.11"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + Installed scripts: + - pycowsay + + ----- stderr ----- + Resolved 1 package in [EXECUTION_TIME] + Downloaded 1 package in [EXECUTION_TIME] + Installed 1 package in [EXECUTION_TIME] + + pycowsay==0.0.0.2 + "###); + + rye_cmd_snapshot!( + space.rye_cmd() + .arg("tools") + .arg("list"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + pycowsay + + ----- stderr ----- + "###); + + rye_cmd_snapshot!( + space.rye_cmd() + .arg("tools") + .arg("list") + .arg("--version-show"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + pycowsay 0.0.0.2 (cpython@3.11.7) + + ----- stderr ----- + "###); + + rye_cmd_snapshot!( + space.rye_cmd() + .arg("toolchain") + .arg("remove") + .arg("cpython@3.11.7"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + error: toolchain cpython@3.11.7 is still in use by tool pycowsay + "###); + + rye_cmd_snapshot!( + space.rye_cmd() + .arg("tools") + .arg("uninstall") + .arg("pycowsay"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Uninstalled pycowsay + + ----- stderr ----- + "###); + + assert!(!space.rye_home().join("tools").join("pycowsay").is_dir()); +} From 979c6c0badf6b72f7c2fdf19af830b9e21012fc9 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:00:14 +0800 Subject: [PATCH 21/34] Fix sync-python-release workflow (#708) --- .github/workflows/sync-python-releases.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-python-releases.yml b/.github/workflows/sync-python-releases.yml index b49136f104..4a182a1649 100644 --- a/.github/workflows/sync-python-releases.yml +++ b/.github/workflows/sync-python-releases.yml @@ -8,6 +8,10 @@ on: schedule: - cron: '0 0 * * *' +permissions: + contents: write + pull-requests: write + jobs: sync: if: github.repository == 'mitsuhiko/rye' @@ -26,10 +30,10 @@ jobs: uses: peter-evans/create-pull-request@v6 with: commit-message: "Sync latest Python releases" - add-paths: "src/rye/downloads.inc" + add-paths: "rye/src/downloads.inc" branch: "sync-python-releases" title: "Sync Python Releases" body: | - Synced latest Python releases - Auto-generated by [sync-python-releases.yml](.github/workflows/sync-python-releases.yml) + Auto-generated by [sync-python-releases.yml](https://github.com/mitsuhiko/rye/blob/main/.github/workflows/sync-python-releases.yml) From e0c48896bd07a3f835f1030eacc64db528f7f23a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 20 Feb 2024 12:13:12 +0100 Subject: [PATCH 22/34] Add autosync support (#677) --- CHANGELOG.md | 2 + docs/guide/commands/add.md | 14 ++++-- docs/guide/commands/remove.md | 12 ++++++ docs/guide/config.md | 4 ++ docs/guide/sync.md | 20 ++++++--- rye/src/cli/add.rs | 15 ++++++- rye/src/cli/lock.rs | 4 ++ rye/src/cli/remove.rs | 12 ++++++ rye/src/cli/sync.rs | 4 ++ rye/src/config.rs | 9 ++++ rye/src/lock.rs | 81 +++++++++++++++++++++++++++++------ rye/src/sync.rs | 13 ++++++ rye/tests/test_sync.rs | 47 +++++++++++++++++++- 13 files changed, 211 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 515e7c4f50..f9c53ccd3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ _Unreleased_ - Sync latest PyPy releases. #683 +- When `uv` is enabled, rye will now automatically sync on `add` and `remove`. #677 + ## 0.25.0 diff --git a/docs/guide/commands/add.md b/docs/guide/commands/add.md index 23cbb99aa8..42b5bbccad 100644 --- a/docs/guide/commands/add.md +++ b/docs/guide/commands/add.md @@ -5,9 +5,13 @@ but provides additional helper arguments to make this process more user friendly instance instead of passing git references within the requiement string, the `--git` parameter can be used. -After a dependency is added it's not automatically installed. To do that, you need to -invoke the [`sync`](sync.md) command. To remove a dependency again use the [`remove`](remove.md) -command. +If auto sync is disabled, after a dependency is added it's not automatically +installed. To do that, you need to invoke the [`sync`](sync.md) command or pass +`--sync`. To remove a dependency again use the [`remove`](remove.md) command. + ++++ 0.26.0 + + Added support for auto-sync and the `--sync` / `--no-sync` flags. ## Example @@ -64,6 +68,10 @@ Added flask @ git+https://github.com/pallets/flask as regular dependency * `--pin `: Overrides the pin operator [possible values: `equal`, `tilde-equal``, `greater-than-equal``] +* `--sync`: Runs `sync` automatically even if auto-sync is disabled. + +* `--no-sync`: Does not run `sync` automatically even if auto-sync is enabled. + * `-v, --verbose`: Enables verbose diagnostics * `-q, --quiet`: Turns off all output diff --git a/docs/guide/commands/remove.md b/docs/guide/commands/remove.md index 055f3980e0..8e10d7c003 100644 --- a/docs/guide/commands/remove.md +++ b/docs/guide/commands/remove.md @@ -3,6 +3,14 @@ Removes a package from this project. This removes a package from the `pyproject.toml` dependency list. +If auto sync is disabled, after a dependency is removed it's not automatically +uninstalled. To do that, you need to invoke the [`sync`](sync.md) command or pass +`--sync`. + ++++ 0.26.0 + + Added support for auto-sync and the `--sync` / `--no-sync` flags. + ## Example ``` @@ -20,6 +28,10 @@ Removed flask>=3.0.1 * `--optional `: Remove this from the optional dependency group +* `--sync`: Runs `sync` automatically even if auto-sync is disabled. + +* `--no-sync`: Does not run `sync` automatically even if auto-sync is enabled. + * `-v, --verbose`: Enables verbose diagnostics * `-q, --quiet`: Turns off all output diff --git a/docs/guide/config.md b/docs/guide/config.md index 933532f1a2..93ad8aae0b 100644 --- a/docs/guide/config.md +++ b/docs/guide/config.md @@ -85,6 +85,10 @@ global-python = false # for pip-tools. Learn more about uv here: https://github.com/astral-sh/uv use-uv = false +# Enable or disable automatic `sync` after `add` and `remove`. This defaults +# to `true` when uv is enabled and `false` otherwise. +autosync = true + # Marks the managed .venv in a way that cloud based synchronization systems # like Dropbox and iCloud Files will not upload it. This defaults to true # as a .venv in cloud storage typically does not make sense. Set this to diff --git a/docs/guide/sync.md b/docs/guide/sync.md index 2e08bace51..74b45f7952 100644 --- a/docs/guide/sync.md +++ b/docs/guide/sync.md @@ -1,12 +1,18 @@ # Syncing and Locking -Rye currently uses [pip-tools](https://github.com/jazzband/pip-tools) to download and install -dependencies. For this purpose it creates two "lockfiles" (called `requirements.lock` and -`requirements-dev.lock`). These are not real lockfiles but they fulfill a similar purpose -until a better solution has been implemented. - -Whenever `rye sync` is called, it will update lockfiles as well as the virtualenv. If you only -want to update the lockfiles, then `rye lock` can be used. +Rye supports two systems to manage dependencies: +[uv](https://github.com/astral-sh/uv) and +[pip-tools](https://github.com/jazzband/pip-tools). It currently defaults to +`pip-tools` but will offer you the option to use `uv` instead. `uv` will become +the default choice once it stabilzes as it offers significantly better performance. + +In order to download dependencies rye creates two "lockfiles" (called +`requirements.lock` and `requirements-dev.lock`). These are not real lockfiles +but they fulfill a similar purpose until a better solution has been implemented. + +Whenever `rye sync` is called, it will update lockfiles as well as the +virtualenv. If you only want to update the lockfiles, then `rye lock` can be +used. ## Lock diff --git a/rye/src/cli/add.rs b/rye/src/cli/add.rs index 8c8787258c..1b2668410b 100644 --- a/rye/src/cli/add.rs +++ b/rye/src/cli/add.rs @@ -16,7 +16,7 @@ use crate::config::Config; use crate::consts::VENV_BIN; use crate::pyproject::{BuildSystem, DependencyKind, ExpandedSources, PyProject}; use crate::sources::PythonVersion; -use crate::sync::{sync, SyncOptions}; +use crate::sync::{autosync, sync, SyncOptions}; use crate::utils::{format_requirement, set_proxy_variables, CommandOutput}; const PACKAGE_FINDER_SCRIPT: &str = r#" @@ -210,6 +210,12 @@ pub struct Args { /// Overrides the pin operator #[arg(long)] pin: Option, + /// Runs `sync` even if auto-sync is disabled. + #[arg(long)] + sync: bool, + /// Does not run `sync` even if auto-sync is enabled. + #[arg(long, conflicts_with = "sync")] + no_sync: bool, /// Enables verbose diagnostics. #[arg(short, long)] verbose: bool, @@ -222,6 +228,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); let self_venv = ensure_self_venv(output).context("error bootstrapping venv")?; let python_path = self_venv.join(VENV_BIN).join("python"); + let cfg = Config::current(); let mut pyproject_toml = PyProject::discover()?; let py_ver = pyproject_toml.venv_python_version()?; @@ -251,7 +258,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { } if !cmd.excluded { - if Config::current().use_uv() { + if cfg.use_uv() { sync(SyncOptions::python_only().pyproject(None)) .context("failed to sync ahead of add")?; resolve_requirements_with_uv( @@ -294,6 +301,10 @@ pub fn execute(cmd: Args) -> Result<(), Error> { } } + if (cfg.autosync() && !cmd.no_sync) || cmd.sync { + autosync(&pyproject_toml, output)?; + } + Ok(()) } diff --git a/rye/src/cli/lock.rs b/rye/src/cli/lock.rs index aa63432de2..e12e0b6ccb 100644 --- a/rye/src/cli/lock.rs +++ b/rye/src/cli/lock.rs @@ -34,6 +34,9 @@ pub struct Args { /// Set to true to lock with sources in the lockfile. #[arg(long)] with_sources: bool, + /// Reset prior lock options. + #[arg(long)] + reset: bool, /// Use this pyproject.toml file #[arg(long, value_name = "PYPROJECT_TOML")] pyproject: Option, @@ -51,6 +54,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { features: cmd.features, all_features: cmd.all_features, with_sources: cmd.with_sources, + reset: cmd.reset, }, pyproject: cmd.pyproject, ..SyncOptions::default() diff --git a/rye/src/cli/remove.rs b/rye/src/cli/remove.rs index 17a5d1e9c2..73adaa2632 100644 --- a/rye/src/cli/remove.rs +++ b/rye/src/cli/remove.rs @@ -4,7 +4,9 @@ use anyhow::Error; use clap::Parser; use pep508_rs::Requirement; +use crate::config::Config; use crate::pyproject::{DependencyKind, PyProject}; +use crate::sync::autosync; use crate::utils::{format_requirement, CommandOutput}; /// Removes a package from this project. @@ -19,6 +21,12 @@ pub struct Args { /// Remove this from an optional dependency group. #[arg(long, conflicts_with = "dev")] optional: Option, + /// Runs `sync` even if auto-sync is disabled. + #[arg(long)] + sync: bool, + /// Does not run `sync` even if auto-sync is enabled. + #[arg(long, conflicts_with = "sync")] + no_sync: bool, /// Enables verbose diagnostics. #[arg(short, long)] verbose: bool, @@ -56,5 +64,9 @@ pub fn execute(cmd: Args) -> Result<(), Error> { } } + if (Config::current().autosync() && !cmd.no_sync) || cmd.sync { + autosync(&pyproject_toml, output)?; + } + Ok(()) } diff --git a/rye/src/cli/sync.rs b/rye/src/cli/sync.rs index 17f5a06970..aaaea1cc82 100644 --- a/rye/src/cli/sync.rs +++ b/rye/src/cli/sync.rs @@ -46,6 +46,9 @@ pub struct Args { /// Use this pyproject.toml file #[arg(long, value_name = "PYPROJECT_TOML")] pyproject: Option, + /// Do not reuse (reset) prior lock options. + #[arg(long)] + reset: bool, } pub fn execute(cmd: Args) -> Result<(), Error> { @@ -67,6 +70,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { features: cmd.features, all_features: cmd.all_features, with_sources: cmd.with_sources, + reset: cmd.reset, }, pyproject: cmd.pyproject, })?; diff --git a/rye/src/config.rs b/rye/src/config.rs index dcb8a49768..f0c31d5842 100644 --- a/rye/src/config.rs +++ b/rye/src/config.rs @@ -247,6 +247,15 @@ impl Config { Ok(rv) } + /// Enable autosync. + pub fn autosync(&self) -> bool { + self.doc + .get("behavior") + .and_then(|x| x.get("autosync")) + .and_then(|x| x.as_bool()) + .unwrap_or_else(|| self.use_uv()) + } + /// Indicates if the experimental uv support should be used. pub fn use_uv(&self) -> bool { self.doc diff --git a/rye/src/lock.rs b/rye/src/lock.rs index 92f760cec4..1922546125 100644 --- a/rye/src/lock.rs +++ b/rye/src/lock.rs @@ -32,12 +32,14 @@ static REQUIREMENTS_HEADER: &str = r#"# generated by rye # use `rye lock` or `rye sync` to update this lockfile # # last locked with the following flags: -# pre: {{ lock_options.pre }} -# features: {{ lock_options.features }} -# all-features: {{ lock_options.all_features }} -# with-sources: {{ lock_options.with_sources }} +# pre: {{ lock_options.pre|tojson }} +# features: {{ lock_options.features|tojson }} +# all-features: {{ lock_options.all_features|tojson }} +# with-sources: {{ lock_options.with_sources|tojson }} "#; +static PARAM_RE: Lazy = + Lazy::new(|| Regex::new(r"^# (pre|features|all-features|with_sources):\s*(.*?)$").unwrap()); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum LockMode { @@ -73,6 +75,55 @@ pub struct LockOptions { pub all_features: bool, /// Should locking happen with sources? pub with_sources: bool, + /// Do not reuse (reset) prior lock options. + pub reset: bool, +} + +impl LockOptions { + /// Writes the lock options as header. + pub fn write_header(&self, mut w: W) -> Result<(), Error> { + writeln!(w, "{}", render!(REQUIREMENTS_HEADER, lock_options => self))?; + Ok(()) + } + + /// Restores lock options from a requirements file. + /// + /// This also applies overrides from the command line. + pub fn restore<'o>(s: &str, opts: &'o LockOptions) -> Result, Error> { + // nothing to do here + if opts.reset { + return Ok(Cow::Borrowed(opts)); + } + + let mut rv = opts.clone(); + for line in s + .lines() + .skip_while(|x| *x != "# last locked with the following flags:") + { + if let Some(m) = PARAM_RE.captures(line) { + let value = &m[2]; + match &m[1] { + "pre" => rv.pre = rv.pre || serde_json::from_str(value)?, + "features" => { + if rv.features.is_empty() { + rv.features = serde_json::from_str(value)?; + } + } + "all-features" => { + rv.all_features = rv.all_features || serde_json::from_str(value)? + } + "with-sources" => rv.with_sources = serde_json::from_str(value)?, + _ => unreachable!(), + } + } + } + + if rv.all_features { + rv.features = Vec::new(); + } + + Ok(Cow::Owned(rv)) + } } /// Creates lockfiles for all projects in the workspace. @@ -310,11 +361,14 @@ fn generate_lockfile( ) -> Result<(), Error> { let scratch = tempfile::tempdir()?; let requirements_file = scratch.path().join("requirements.txt"); - if lockfile.is_file() { - fs::copy(lockfile, &requirements_file)?; + let lock_options = if lockfile.is_file() { + let requirements = fs::read_to_string(lockfile)?; + fs::write(&requirements_file, &requirements)?; + LockOptions::restore(&requirements, lock_options)? } else { fs::write(&requirements_file, b"")?; - } + Cow::Borrowed(lock_options) + }; let mut cmd = if Config::current().use_uv() { let self_venv = ensure_self_venv(output)?; @@ -331,6 +385,9 @@ fn generate_lockfile( } else if output == CommandOutput::Quiet { cmd.arg("-q"); } + if lock_options.pre { + cmd.arg("--prerelease=allow"); + } // this primarily exists for testing if let Ok(dt) = env::var("__RYE_UV_EXCLUDE_NEWER") { cmd.arg("--exclude-newer").arg(dt); @@ -359,6 +416,9 @@ fn generate_lockfile( } else { "-q" }); + if lock_options.pre { + cmd.arg("--pre"); + } cmd }; @@ -376,9 +436,6 @@ fn generate_lockfile( if lock_options.update_all { cmd.arg("--upgrade"); } - if lock_options.pre { - cmd.arg("--pre"); - } sources.add_as_pip_args(&mut cmd); set_proxy_variables(&mut cmd); let status = cmd.status().context("unable to run pip-compile")?; @@ -392,7 +449,7 @@ fn generate_lockfile( workspace_path, exclusions, sources, - lock_options, + &lock_options, )?; Ok(()) @@ -407,7 +464,7 @@ fn finalize_lockfile( lock_options: &LockOptions, ) -> Result<(), Error> { let mut rv = BufWriter::new(fs::File::create(out)?); - writeln!(rv, "{}", render!(REQUIREMENTS_HEADER, lock_options))?; + lock_options.write_header(&mut rv)?; // only if we are asked to include sources we do that. if lock_options.with_sources { diff --git a/rye/src/sync.rs b/rye/src/sync.rs index f8fc3e17b8..67a337f0c7 100644 --- a/rye/src/sync.rs +++ b/rye/src/sync.rs @@ -310,6 +310,19 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> { Ok(()) } +/// Performs an autosync. +pub fn autosync(pyproject: &PyProject, output: CommandOutput) -> Result<(), Error> { + sync(SyncOptions { + output, + dev: true, + mode: SyncMode::Regular, + force: false, + no_lock: false, + lock_options: LockOptions::default(), + pyproject: Some(pyproject.toml_path().to_path_buf()), + }) +} + pub fn create_virtualenv( output: CommandOutput, self_venv: &Path, diff --git a/rye/tests/test_sync.rs b/rye/tests/test_sync.rs index ebd404c545..19372f8ae4 100644 --- a/rye/tests/test_sync.rs +++ b/rye/tests/test_sync.rs @@ -31,11 +31,12 @@ fn test_empty_sync() { } #[test] -fn test_add_and_sync() { +fn test_add_and_sync_no_auto_sync() { let space = Space::new(); space.init("my-project"); + // add colorama to ensure we have this as a dependency on all platforms - rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("flask==3.0.0").arg("colorama"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("flask==3.0.0").arg("colorama").arg("--no-sync"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -78,3 +79,45 @@ fn test_add_and_sync() { + werkzeug==3.0.1 "###); } + +#[test] +fn test_add_autosync() { + let space = Space::new(); + space.init("my-project"); + // add colorama to ensure we have this as a dependency on all platforms + rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("flask==3.0.0").arg("colorama"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.1 + Added colorama>=0.4.6 as regular dependency + Added flask>=3.0.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 ----- + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 9 packages in [EXECUTION_TIME] + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 9 packages in [EXECUTION_TIME] + Built 1 editable in [EXECUTION_TIME] + Resolved 8 packages in [EXECUTION_TIME] + Downloaded 8 packages 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) + + werkzeug==3.0.1 + "###); +} From 503f706db7b80fd851ee1d6da3a0b1da441fc8e3 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:13:33 +0800 Subject: [PATCH 23/34] Improve the consistency of installation messages (#702) --- rye/src/bootstrap.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index e0d1540ef5..2d809d90dd 100644 --- a/rye/src/bootstrap.rs +++ b/rye/src/bootstrap.rs @@ -90,7 +90,7 @@ pub fn ensure_self_venv_with_toolchain( return Ok(venv_dir); } else { if output != CommandOutput::Quiet { - echo!("detected outdated rye internals. Refreshing"); + echo!("Detected outdated rye internals. Refreshing"); } fs::remove_dir_all(&venv_dir).context("could not remove self-venv for update")?; if pip_tools_dir.is_dir() { @@ -388,6 +388,7 @@ fn ensure_specific_self_toolchain( Ok(toolchain_version) } } + /// Fetches a version if missing. pub fn fetch( version: &PythonVersionRequest, @@ -433,19 +434,22 @@ pub fn fetch( if let Some(sha256) = sha256 { if output != CommandOutput::Quiet { - echo!("{}", style("Checking checksum").cyan()); + echo!("{} {}", style("Checking").cyan(), "checksum"); } check_checksum(&archive_buffer, sha256) - .with_context(|| format!("hash check of {} failed", &url))?; + .with_context(|| format!("Checksum check of {} failed", &url))?; } else if output != CommandOutput::Quiet { echo!("Checksum check skipped (no hash available)"); } + if output != CommandOutput::Quiet { + echo!("{}", style("Unpacking").cyan()); + } unpack_archive(&archive_buffer, &target_dir, 1) .with_context(|| format!("unpacking of downloaded tarball {} failed", &url))?; if output != CommandOutput::Quiet { - echo!("{} Downloaded {}", style("success:").green(), version); + echo!("{} {}", style("Downloaded").green(), version); } Ok(version) From 57d8fc3ce7d7102fcd21a9c881b42195d5e59103 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:45:38 +0800 Subject: [PATCH 24/34] docs: add document for `rye tools list` (#710) --- docs/guide/commands/tools/index.md | 2 ++ docs/guide/commands/tools/list.md | 33 ++++++++++++++++++++++++++++++ rye/src/cli/tools.rs | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 docs/guide/commands/tools/list.md diff --git a/docs/guide/commands/tools/index.md b/docs/guide/commands/tools/index.md index cb8ff88144..2c097c7bc0 100644 --- a/docs/guide/commands/tools/index.md +++ b/docs/guide/commands/tools/index.md @@ -5,3 +5,5 @@ Helper utility to manage global tool installations. * [`install`](install.md): installs a tool globally. * [`uninstall`](uninstall.md): uninstalls a globally installed tool. + +* [`list`](list.md): lists all globally installed tools. diff --git a/docs/guide/commands/tools/list.md b/docs/guide/commands/tools/list.md new file mode 100644 index 0000000000..316ed6a7de --- /dev/null +++ b/docs/guide/commands/tools/list.md @@ -0,0 +1,33 @@ +# `list` + +Lists all already installed global tools. + +For more information see [Tools](/guide/tools/). + +## Example + +List installed tools: + +``` +$ rye tools list +pycowsay +``` + +List installed tools with version: + +``` +$ rye tools list --version-show +pycowsay 0.0.0.2 (cpython@3.12.1) +``` + +## Arguments + +*no arguments* + +## Options + +* `-i, --include-scripts`: Also show all the scripts installed by the tools + +* `-v, --version-show`: Show the version of tools + +* `-h, --help`: Print help diff --git a/rye/src/cli/tools.rs b/rye/src/cli/tools.rs index 812071b5d4..a49c7a2016 100644 --- a/rye/src/cli/tools.rs +++ b/rye/src/cli/tools.rs @@ -14,7 +14,7 @@ pub struct Args { /// List all registered tools #[derive(Parser, Debug)] pub struct ListCommand { - /// Also how all the scripts installed by the tools. + /// Also show all the scripts installed by the tools. #[arg(short, long)] include_scripts: bool, /// Show the version of tools. From 7fd61de3583e81788ef20ce3975ed1374cc8f920 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:00:10 +0800 Subject: [PATCH 25/34] Remove `tools` directory during self uninstallation (#713) * Remove `tools` directory during self uninstallation Also included a test, however, this test is self-destructive and might interfere with other tests, not sure it is the right thing to test. * Fmt * Allow unused * Keep stdout and stderr, add ignore marker --- rye/src/cli/rye.rs | 1 + rye/tests/common/mod.rs | 10 +++++++++- rye/tests/test_self.rs | 44 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 rye/tests/test_self.rs diff --git a/rye/src/cli/rye.rs b/rye/src/cli/rye.rs index e5e3d00750..2431ed8199 100644 --- a/rye/src/cli/rye.rs +++ b/rye/src/cli/rye.rs @@ -344,6 +344,7 @@ fn uninstall(args: UninstallCommand) -> Result<(), Error> { remove_dir_all_if_exists(&app_dir.join("self"))?; remove_dir_all_if_exists(&app_dir.join("py"))?; remove_dir_all_if_exists(&app_dir.join("pip-tools"))?; + remove_dir_all_if_exists(&app_dir.join("tools"))?; // special deleting logic if we are placed in the app dir and the shim deletion // did not succeed. This is likely the case on windows where we then use the diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index 1239e8579f..53631faa0c 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -9,6 +9,7 @@ use tempfile::TempDir; // Exclude any packages uploaded after this date. pub static EXCLUDE_NEWER: &str = "2023-11-18T12:00:00Z"; +#[allow(unused)] pub const INSTA_FILTERS: &[(&str, &str)] = &[ // general temp folders ( @@ -31,7 +32,7 @@ fn marked_tempdir() -> TempDir { } fn bootstrap_test_rye() -> PathBuf { - let home = get_cargo_bin("rye").parent().unwrap().join("rye-test-home"); + let home = get_bin().parent().unwrap().join("rye-test-home"); fs::create_dir_all(&home).ok(); let lock_path = home.join("lock"); let mut lock = fslock::LockFile::open(&lock_path).unwrap(); @@ -164,6 +165,13 @@ impl Space { pub fn project_path(&self) -> &Path { &self.project_dir } + + #[allow(unused)] + pub fn lock_rye_home(&self) -> fslock::LockFile { + let mut lock = fslock::LockFile::open(&self.rye_home().join("lock")).unwrap(); + lock.lock().unwrap(); + lock + } } #[allow(unused_macros)] diff --git a/rye/tests/test_self.rs b/rye/tests/test_self.rs new file mode 100644 index 0000000000..7fcd228a49 --- /dev/null +++ b/rye/tests/test_self.rs @@ -0,0 +1,44 @@ +use crate::common::Space; +mod common; + +// This test is self-destructive, making other tests slow, ignore it by default. +#[test] +#[ignore] +fn test_self_uninstall() { + let space = Space::new(); + let _guard = space.lock_rye_home(); + + // install a global tool to ensure tools directory is created + space + .rye_cmd() + .arg("install") + .arg("pycowsay") + .arg("-f") + .status() + .unwrap(); + + assert!(space.rye_home().join("self").is_dir()); + assert!(space.rye_home().join("py").is_dir()); + assert!(space.rye_home().join("tools").is_dir()); + + let status = space + .rye_cmd() + .arg("self") + .arg("uninstall") + .arg("--yes") + .status() + .unwrap(); + assert!(status.success()); + + let may_left = &["env", "config.toml", "lock"]; + let leftovers: Vec<_> = space + .rye_home() + .read_dir() + .unwrap() + .filter(|x| { + let x = x.as_ref().unwrap(); + !may_left.contains(&x.file_name().to_str().unwrap()) + }) + .collect(); + assert!(leftovers.is_empty(), "leftovers: {:?}", leftovers); +} From ac850bcb6383947cd41afc38b1601bae58a713df Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Tue, 20 Feb 2024 22:01:46 +0800 Subject: [PATCH 26/34] docs: add `tools list` to navigation (#716) --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 2949d291ab..6b68ea9442 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,6 +49,7 @@ nav: - Overview: guide/commands/tools/index.md - install: guide/commands/tools/install.md - uninstall: guide/commands/tools/uninstall.md + - list: guide/commands/tools/list.md - self: - Overview: guide/commands/self/index.md - completion: guide/commands/self/completion.md From 40e90ad3d408da1978d8165ceeb0d4a0be14daf3 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Wed, 21 Feb 2024 00:23:12 +0800 Subject: [PATCH 27/34] Detect `mkdocs.yml` changes for website (#718) --- .github/workflows/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4e722d9042..882a6e97de 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,6 +4,7 @@ on: paths: - 'docs/**' - 'CHANGELOG.md' + - 'mkdocs.yml' branches: - main workflow_dispatch: From 72376360ea072e96f816a7dc527703e891b836c1 Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Tue, 20 Feb 2024 20:00:18 +0000 Subject: [PATCH 28/34] bump uv to 0.1.6 (#719) --- CHANGELOG.md | 2 ++ rye/src/bootstrap.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9c53ccd3a..c70541b95c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ that were not yet released. _Unreleased_ +- Bumped `uv` to 0.1.6. #719 + - Bumped `ruff` to 0.2.2. #700 - Prevent `rye toolchain remove` from removing the currently active toolchain. #693 diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index 2d809d90dd..f3000a783b 100644 --- a/rye/src/bootstrap.rs +++ b/rye/src/bootstrap.rs @@ -37,7 +37,7 @@ pub const SELF_PYTHON_TARGET_VERSION: PythonVersionRequest = PythonVersionReques suffix: None, }; -const SELF_VERSION: u64 = 13; +const SELF_VERSION: u64 = 14; const SELF_REQUIREMENTS: &str = r#" build==1.0.3 @@ -57,7 +57,7 @@ unearth==0.14.0 urllib3==2.0.7 virtualenv==20.25.0 ruff==0.2.2 -uv==0.1.5 +uv==0.1.6 "#; static FORCED_TO_UPDATE: AtomicBool = AtomicBool::new(false); From 3209a6e63aea3345edf3d1ce6ef9371eaa187caf Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 20 Feb 2024 21:08:19 +0100 Subject: [PATCH 29/34] Fix custom sources not working with add (#720) --- CHANGELOG.md | 2 + rye/src/cli/add.rs | 6 +++ rye/tests/common/mod.rs | 18 ++++++++ rye/tests/test_add.rs | 92 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 rye/tests/test_add.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c70541b95c..bbc742a297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ _Unreleased_ - Sync latest PyPy releases. #683 +- Fixes an issue where when `uv` is enabled, `add` did not honor custom sources. #720 + - When `uv` is enabled, rye will now automatically sync on `add` and `remove`. #677 diff --git a/rye/src/cli/add.rs b/rye/src/cli/add.rs index 1b2668410b..1cf8edff70 100644 --- a/rye/src/cli/add.rs +++ b/rye/src/cli/add.rs @@ -464,6 +464,12 @@ fn resolve_requirements_with_uv( if output == CommandOutput::Quiet { cmd.arg("-q"); } + // this primarily exists for testing + if let Ok(dt) = env::var("__RYE_UV_EXCLUDE_NEWER") { + cmd.arg("--exclude-newer").arg(dt); + } + let sources = ExpandedSources::from_sources(&pyproject_toml.sources()?)?; + sources.add_as_pip_args(&mut cmd); let mut child = cmd .stdin(Stdio::piped()) .stdout(Stdio::piped()) diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index 53631faa0c..67178c7391 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -131,6 +131,24 @@ impl Space { self.cmd(get_bin()) } + #[allow(unused)] + pub fn edit_toml, R, F: FnOnce(&mut toml_edit::Document) -> R>( + &self, + path: P, + f: F, + ) -> R { + let p = self.project_path().join(path.as_ref()); + let mut doc = if p.is_file() { + std::fs::read_to_string(&p).unwrap().parse().unwrap() + } else { + toml_edit::Document::default() + }; + let rv = f(&mut doc); + fs::create_dir_all(p.parent().unwrap()).ok(); + fs::write(p, doc.to_string()).unwrap(); + rv + } + #[allow(unused)] pub fn write, B: AsRef<[u8]>>(&self, path: P, contents: B) { let p = self.project_path().join(path.as_ref()); diff --git a/rye/tests/test_add.rs b/rye/tests/test_add.rs new file mode 100644 index 0000000000..1b388ffbb2 --- /dev/null +++ b/rye/tests/test_add.rs @@ -0,0 +1,92 @@ +use toml_edit::{value, ArrayOfTables, Table}; + +use crate::common::{rye_cmd_snapshot, Space}; + +mod common; + +#[test] +fn test_add_flask() { + let space = Space::new(); + space.init("my-project"); + // add colorama to ensure we have this as a dependency on all platforms + rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("flask==3.0.0").arg("colorama"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.1 + Added colorama>=0.4.6 as regular dependency + Added flask>=3.0.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 ----- + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 9 packages in [EXECUTION_TIME] + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 9 packages in [EXECUTION_TIME] + Built 1 editable in [EXECUTION_TIME] + Resolved 8 packages in [EXECUTION_TIME] + Downloaded 8 packages 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) + + werkzeug==3.0.1 + "###); +} + +#[test] +fn test_add_from_find_links() { + let space = Space::new(); + space.init("my-project"); + space.edit_toml("pyproject.toml", |doc| { + let mut source = Table::new(); + source["name"] = value("extra"); + source["type"] = value("find-links"); + source["url"] = value("https://download.pytorch.org/whl/torch_stable.html"); + let mut sources = ArrayOfTables::new(); + sources.push(source); + doc["tool"]["rye"]["sources"] = value(sources.into_array()); + }); + + rye_cmd_snapshot!(space.rye_cmd().arg("add").arg("tqdm").arg("colorama"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.1 + Added colorama>=0.4.6 as regular dependency + Added tqdm>=4.66.1 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 ----- + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 3 packages in [EXECUTION_TIME] + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 3 packages in [EXECUTION_TIME] + Built 1 editable in [EXECUTION_TIME] + Resolved 2 packages in [EXECUTION_TIME] + Downloaded 2 packages in [EXECUTION_TIME] + Installed 3 packages in [EXECUTION_TIME] + + colorama==0.4.6 + + my-project==0.1.0 (from file:[TEMP_PATH]/project) + + tqdm==4.66.1 + "###); +} From 299b49109f0785346d989b9604a4af44e8979753 Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Wed, 21 Feb 2024 07:41:25 +0000 Subject: [PATCH 30/34] document lockfile limitations (#721) * docs: document lockfile limitations * Update docs/guide/sync.md Co-authored-by: Jo <10510431+j178@users.noreply.github.com> --------- Co-authored-by: Armin Ronacher Co-authored-by: Jo <10510431+j178@users.noreply.github.com> --- docs/guide/sync.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/guide/sync.md b/docs/guide/sync.md index 74b45f7952..6d100253ce 100644 --- a/docs/guide/sync.md +++ b/docs/guide/sync.md @@ -99,3 +99,12 @@ lockfile (`requirements-dev.lock`). ``` rye sync --no-dev ``` + +## Limitations + +Lockfiles depend on the platform they were generated on. This is a known limitation +in pip-tools. + +For example, if your project relies on platform-specific packages and you generate +lockfiles on Windows, these lockfiles will include Windows-specific projects. +Consequently, they won't be compatible with other platforms like Linux or macOS. From 60d465be24c8db4f2f4b3f32cc3543422b001040 Mon Sep 17 00:00:00 2001 From: Jochen Kupperschmidt Date: Wed, 21 Feb 2024 08:41:42 +0100 Subject: [PATCH 31/34] Remove redundant occurrences of "instead" (#724) --- docs/guide/installation.md | 8 ++++---- docs/guide/pyproject.md | 2 +- notes/metasrv.md | 2 +- rye/src/cli/mod.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/guide/installation.md b/docs/guide/installation.md index aaa2a43bbf..36b4d0b6b6 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -66,7 +66,7 @@ opt-out, or you run a custom shell you will need to do this manually. ``` In some setups `.profile` is not sourced, in which case you can add it to your - `.bashrc` instead: + `.bashrc`: ```bash echo 'source "$HOME/.rye/env"' >> ~/.bashrc @@ -81,7 +81,7 @@ opt-out, or you run a custom shell you will need to do this manually. ``` In some setups `.profile` is not sourced, in which case you can add it to your - `.zprofile` instead: + `.zprofile`: ```bash echo 'source "$HOME/.rye/env"' >> ~/.zprofile @@ -89,7 +89,7 @@ opt-out, or you run a custom shell you will need to do this manually. === "Fish" - Since fish does not support `env` files, you instead need to add + Since fish does not support `env` files, you need to add the shims directly. This can be accomplished by running this command once: @@ -99,7 +99,7 @@ opt-out, or you run a custom shell you will need to do this manually. === "Nushell" - Since nushell does not support `env` files, you instead need to add + Since nushell does not support `env` files, you need to add the shims directly. This can be accomplished by adding this to your `env.nu` file: diff --git a/docs/guide/pyproject.md b/docs/guide/pyproject.md index 21ea362cdd..832b6b0309 100644 --- a/docs/guide/pyproject.md +++ b/docs/guide/pyproject.md @@ -162,7 +162,7 @@ devserver = { cmd = "flask run --debug", env = { FLASK_APP = "./hello.py" } } This is a special key that can be set instead of `cmd` to make a command invoke multiple other commands. Each command will be executed one after another. If any of the commands -fails the rest of the commands won't be executed and instead the chain fails. +fails, the rest of the commands won't be executed and the chain fails. ```toml [tool.rye.scripts] diff --git a/notes/metasrv.md b/notes/metasrv.md index 2a05aae99e..d036e0131f 100644 --- a/notes/metasrv.md +++ b/notes/metasrv.md @@ -18,7 +18,7 @@ If you have ever hit that URL you will have realized that it's an enormous HTML package very uploaded to PyPI. Yet this is still in some sense the canonical way to install packages. If you for instance use `Rye` today you configure the index by pointing to that URL. -With the use of a **meta server**, one would instead point it to a meta server instead. So for instance +With the use of a **meta server**, one would instead point it to a meta server. So for instance the meta server for `pypi.org` could be hosted at a different URL, say `https://meta.pypi.org/`. That meta server URL fully replaces the existing index URL. Each meta server is supposed to target a single index only. A package manager _only_ interfaces with the meta server and it's the meta diff --git a/rye/src/cli/mod.rs b/rye/src/cli/mod.rs index 9c66b5bded..c34a163de3 100644 --- a/rye/src/cli/mod.rs +++ b/rye/src/cli/mod.rs @@ -132,7 +132,7 @@ pub fn execute() -> Result<(), Error> { Command::List(cmd) => list::execute(cmd), Command::Shell(..) => { bail!( - "unknown command. The shell command was removed. Activate the virtualenv instead with '{}' instead.", + "unknown command. The shell command was removed. Activate the virtualenv with '{}' instead.", if cfg!(windows) { ".venv\\Scripts\\activate" } else { From 3e51b20607013d45254b2ea97120c8c03fec44a4 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:48:25 +0800 Subject: [PATCH 32/34] Rename `rye tools list` flags (#722) --- .github/workflows/docs.yml | 1 + CHANGELOG.md | 2 ++ docs/guide/commands/tools/list.md | 14 +++++++++++--- rye/src/cli/tools.rs | 10 +++++----- rye/tests/test_tools.rs | 2 +- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 882a6e97de..d228e96d03 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,7 @@ on: - 'docs/**' - 'CHANGELOG.md' - 'mkdocs.yml' + - 'scripts/install.sh' branches: - main workflow_dispatch: diff --git a/CHANGELOG.md b/CHANGELOG.md index bbc742a297..63a5e48b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ _Unreleased_ - When `uv` is enabled, rye will now automatically sync on `add` and `remove`. #677 +- Rename `rye tools list` flags: `-i, --include-scripts` to `-s, --include-scripts` and `-v, --version-show` to `-v, --include-version`. #722 + ## 0.25.0 diff --git a/docs/guide/commands/tools/list.md b/docs/guide/commands/tools/list.md index 316ed6a7de..3b266e9179 100644 --- a/docs/guide/commands/tools/list.md +++ b/docs/guide/commands/tools/list.md @@ -16,7 +16,7 @@ pycowsay List installed tools with version: ``` -$ rye tools list --version-show +$ rye tools list --include-version pycowsay 0.0.0.2 (cpython@3.12.1) ``` @@ -26,8 +26,16 @@ pycowsay 0.0.0.2 (cpython@3.12.1) ## Options -* `-i, --include-scripts`: Also show all the scripts installed by the tools +* `-s, --include-scripts`: Show all the scripts installed by the tools -* `-v, --version-show`: Show the version of tools ++/- 0.26.0 + + Renamed from `-i, --include-scripts` to `-s, --include-scripts`. + +* `-v, --include-version`: Show the version of tools + ++/- 0.26.0 + + Renamed from `-v, --version-show` to `-v, --include-version`. * `-h, --help`: Print help diff --git a/rye/src/cli/tools.rs b/rye/src/cli/tools.rs index a49c7a2016..4122ca45a7 100644 --- a/rye/src/cli/tools.rs +++ b/rye/src/cli/tools.rs @@ -14,12 +14,12 @@ pub struct Args { /// List all registered tools #[derive(Parser, Debug)] pub struct ListCommand { - /// Also show all the scripts installed by the tools. - #[arg(short, long)] + /// Show all the scripts installed by the tools. + #[arg(short = 's', long)] include_scripts: bool, /// Show the version of tools. - #[arg(short, long)] - version_show: bool, + #[arg(short = 'v', long)] + include_version: bool, } #[derive(Parser, Debug)] @@ -47,7 +47,7 @@ fn list_tools(cmd: ListCommand) -> Result<(), Error> { echo!("{} ({})", style(tool).red(), style("seems broken").red()); continue; } - if cmd.version_show { + if cmd.include_version { if let Some(ref venv) = info.venv_marker { echo!("{} {} ({})", style(tool).cyan(), info.version, venv.python); } else { diff --git a/rye/tests/test_tools.rs b/rye/tests/test_tools.rs index 2b2c831677..789a0c06ee 100644 --- a/rye/tests/test_tools.rs +++ b/rye/tests/test_tools.rs @@ -57,7 +57,7 @@ fn test_basic_tool_behavior() { space.rye_cmd() .arg("tools") .arg("list") - .arg("--version-show"), @r###" + .arg("--include-version"), @r###" success: true exit_code: 0 ----- stdout ----- From cae57866ad24003a2e2c20abf2f1a249b4c6f231 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:48:52 +0800 Subject: [PATCH 33/34] find-downloads: prefer `pgo` over `lto` builds (#725) --- rye-devtools/src/rye_devtools/find_downloads.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rye-devtools/src/rye_devtools/find_downloads.py b/rye-devtools/src/rye_devtools/find_downloads.py index fd4442d8b7..4f4a5e68e6 100644 --- a/rye-devtools/src/rye_devtools/find_downloads.py +++ b/rye-devtools/src/rye_devtools/find_downloads.py @@ -122,8 +122,8 @@ class CPythonFinder(Finder): "shared-noopt", "shared-noopt", "pgo+lto", - "lto", "pgo", + "lto", ] HIDDEN_FLAVORS = [ "debug", From 2b6c2700d9d0530ae7c1f0f0f9a7e2180e2edcca Mon Sep 17 00:00:00 2001 From: Gunung Pambudi Wibisono <55311527+gunungpw@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:40:01 +0700 Subject: [PATCH 34/34] add example for local path dependancy (#732) --- docs/guide/commands/add.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/guide/commands/add.md b/docs/guide/commands/add.md index 42b5bbccad..816cd154a3 100644 --- a/docs/guide/commands/add.md +++ b/docs/guide/commands/add.md @@ -36,6 +36,13 @@ $ rye add flask --git https://github.com/pallets/flask Added flask @ git+https://github.com/pallets/flask as regular dependency ``` +Add a local dependency: + +``` +$ rye add packagename --path path/to/packagename +Added packagename @ file:///path/to/packagename as regular dependency +``` + ## Arguments * `...`: The package to add as PEP 508 requirement string. e.g. 'flask==2.2.3'