From e2f90d33006e774cef8192c7df686c649dd75471 Mon Sep 17 00:00:00 2001 From: Jo <10510431+j178@users.noreply.github.com> Date: Fri, 23 Feb 2024 17:11:39 +0800 Subject: [PATCH 1/7] Bump `uv` to 0.1.9 (#746) --- CHANGELOG.md | 2 +- rye/src/bootstrap.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c618c54685..60bc1afda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ _Unreleased_ - Fixed `rye config --show-path` abort with an error. #706 -- Bumped `uv` to 0.1.7. #719, #740 +- Bumped `uv` to 0.1.9. #719, #740, #746 - Bumped `ruff` to 0.2.2. #700 diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index 8c388fc99e..0543f66279 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.2.2 -uv==0.1.7 +uv==0.1.9 "#; static FORCED_TO_UPDATE: AtomicBool = AtomicBool::new(false); From da13dfd8d1f5c02b05f9f1e9c896908cfa202e99 Mon Sep 17 00:00:00 2001 From: Chaojie Date: Fri, 23 Feb 2024 17:11:48 +0800 Subject: [PATCH 2/7] Better badge display (#747) --- artwork/badge.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artwork/badge.json b/artwork/badge.json index 146f74fb11..77a49460bc 100644 --- a/artwork/badge.json +++ b/artwork/badge.json @@ -3,6 +3,6 @@ "message": "Rye", "logoSvg": "", "logoWidth": 12, - "labelColor": "grey", - "color": "#261230" + "labelColor": "white", + "color": "#ADC541" } \ No newline at end of file From 4cbb1d44ff8d6603b058a594831fe5e9a3615e08 Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Fri, 23 Feb 2024 09:12:33 +0000 Subject: [PATCH 3/7] --script and --lib flags (#738) --- CHANGELOG.md | 2 + docs/guide/basics.md | 35 +++ rye/src/cli/init.rs | 297 +++++++----------- rye/src/templates/LICENSE.txt.j2 | 1 + rye/src/templates/README.md.j2 | 7 + rye/src/templates/gitignore.j2 | 15 + rye/src/templates/lib/default/__init__.py.j2 | 2 + rye/src/templates/lib/maturin/Cargo.toml.j2 | 12 + rye/src/templates/lib/maturin/__init__.py.j2 | 3 + rye/src/templates/lib/maturin/lib.rs.j2 | 14 + rye/src/templates/pyproject.toml.j2 | 86 +++++ .../templates/script/default/__init__.py.j2 | 3 + .../templates/script/default/__main__.py.j2 | 4 + rye/src/templates/setuptools.py.j2 | 15 + rye/tests/common/mod.rs | 6 + rye/tests/test_init.rs | 183 +++++++++++ 16 files changed, 496 insertions(+), 189 deletions(-) create mode 100644 rye/src/templates/LICENSE.txt.j2 create mode 100644 rye/src/templates/README.md.j2 create mode 100644 rye/src/templates/gitignore.j2 create mode 100644 rye/src/templates/lib/default/__init__.py.j2 create mode 100644 rye/src/templates/lib/maturin/Cargo.toml.j2 create mode 100644 rye/src/templates/lib/maturin/__init__.py.j2 create mode 100644 rye/src/templates/lib/maturin/lib.rs.j2 create mode 100644 rye/src/templates/pyproject.toml.j2 create mode 100644 rye/src/templates/script/default/__init__.py.j2 create mode 100644 rye/src/templates/script/default/__main__.py.j2 create mode 100644 rye/src/templates/setuptools.py.j2 create mode 100644 rye/tests/test_init.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 60bc1afda3..2953505bba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ that were not yet released. _Unreleased_ +- `init` now supports `--script` and `--lib` to generate a script or library project. #738 + - Fixed `rye config --show-path` abort with an error. #706 - Bumped `uv` to 0.1.9. #719, #740, #746 diff --git a/docs/guide/basics.md b/docs/guide/basics.md index 62099c82a2..e30dd25559 100644 --- a/docs/guide/basics.md +++ b/docs/guide/basics.md @@ -136,3 +136,38 @@ virtualenv is located and more. ``` rye show ``` + +## Executable projects + +To generate a project that is aimed to provide an executable +script, use `rye init --script`: + +```shell +rye init --script my-project +cd my-project +``` + +The following structure will be created: + +``` +. +├── .git +├── .gitignore +├── .python-version +├── README.md +├── pyproject.toml +└── src + └── my_project + └── __init__.py + └── __main__.py +``` + +The [`pyproject.toml`](pyproject.md) will be generated with a +[`[project.scripts]`](pyproject.md#projectscripts) section named `hello` +that points to the `main()` function of `__init__.py`. After you +synchronized your changes, you can run the script with `rye run my-project`. + +```shell +rye sync +rye run hello +``` diff --git a/rye/src/cli/init.rs b/rye/src/cli/init.rs index a456c314d0..3b907beb10 100644 --- a/rye/src/cli/init.rs +++ b/rye/src/cli/init.rs @@ -35,6 +35,9 @@ pub struct Args { /// Where to place the project (defaults to current path) #[arg(default_value = ".")] path: PathBuf, + /// Initialization type + #[command(flatten)] + init_type: ArgTemplateChoice, /// Minimal Python version supported by this project. #[arg(long)] min_py: Option, @@ -82,174 +85,51 @@ pub struct Args { quiet: bool, } -/// The pyproject.toml template -/// -/// This uses a template just to simplify the flexibility of emitting it. -const TOML_TEMPLATE: &str = r#"[project] -name = {{ name }} -version = {{ version }} -description = {{ description }} -{%- if author %} -authors = [ - { name = {{ author[0] }}, email = {{ author[1] }} } -] -{%- endif %} -{%- if dependencies %} -dependencies = [ -{%- for dependency in dependencies %} - {{ dependency }}, -{%- endfor %} -] -{%- else %} -dependencies = [] -{%- endif %} -{%- if with_readme %} -readme = "README.md" -{%- endif %} -requires-python = {{ requires_python }} -{%- if license %} -license = { text = {{ license }} } -{%- endif %} -{%- if private %} -classifiers = ["Private :: Do Not Upload"] -{%- endif %} - -[project.scripts] -hello = {{ name_safe ~ ":hello"}} - -{%- if not is_virtual %} - -[build-system] -{%- if build_system == "hatchling" %} -requires = ["hatchling"] -build-backend = "hatchling.build" -{%- elif build_system == "setuptools" %} -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" -{%- elif build_system == "flit" %} -requires = ["flit_core>=3.4"] -build-backend = "flit_core.buildapi" -{%- elif build_system == "pdm" %} -requires = ["pdm-backend"] -build-backend = "pdm.backend" -{%- elif build_system == "maturin" %} -requires = ["maturin>=1.2,<2.0"] -build-backend = "maturin" -{%- endif %} -{%- endif %} - -[tool.rye] -managed = true -{%- if is_virtual %} -virtual = true -{%- endif %} -{%- if dev_dependencies %} -dev-dependencies = [ -{%- for dependency in dev_dependencies %} - {{ dependency }}, -{%- endfor %} -] -{%- else %} -dev-dependencies = [] -{%- endif %} - -{%- if not is_virtual %} -{%- if build_system == "hatchling" %} - -[tool.hatch.metadata] -allow-direct-references = true - -[tool.hatch.build.targets.wheel] -packages = [{{ "src/" ~ name_safe }}] -{%- elif build_system == "maturin" %} - -[tool.maturin] -python-source = "python" -module-name = {{ name_safe ~ "._lowlevel" }} -features = ["pyo3/extension-module"] -{%- endif %} -{%- endif %} - -"#; - -/// The template for the readme file. -const README_TEMPLATE: &str = r#"# {{ name }} - -Describe your project here. - -{%- if license %} -* License: {{ license }} -{%- endif %} - -"#; - -const LICENSE_TEMPLATE: &str = r#" -{{ license_text }} -"#; - -/// Template for the __init__.py -const INIT_PY_TEMPLATE: &str = r#"def hello(): - return "Hello from {{ name }}!" - -"#; - -/// Template for the lib.rs -const LIB_RS_TEMPLATE: &str = r#"use pyo3::prelude::*; +#[derive(Parser, Debug)] +#[group(multiple = false)] +struct ArgTemplateChoice { + /// Generate a library project (default). + #[arg(long)] + lib: bool, -/// Prints a message. -#[pyfunction] -fn hello() -> PyResult { - Ok("Hello from {{ name }}!".into()) + /// Generate an executable project. + #[arg(long)] + script: bool, } -/// A Python module implemented in Rust. -#[pymodule] -fn _lowlevel(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(hello, m)?)?; - Ok(()) +enum TemplateChoice { + Lib, + Script, } -"#; -/// Template for the __init__.py -const RUST_INIT_PY_TEMPLATE: &str = r#"from {{ name_safe }}._lowlevel import hello -__all__ = ["hello"] +/// The pyproject.toml template +const TOML_TEMPLATE: &str = include_str!("../templates/pyproject.toml.j2"); -"#; +/// The template for the README.md. +const README_TEMPLATE: &str = include_str!("../templates/README.md.j2"); -/// Template for the Cargo.toml -const CARGO_TOML_TEMPLATE: &str = r#"[package] -name = {{ name }} -version = "0.1.0" -edition = "2021" +/// The template for the LICENSE.txt. +const LICENSE_TEMPLATE: &str = include_str!("../templates/LICENSE.txt.j2"); -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = {{ name_safe }} -crate-type = ["cdylib"] +/// Template for the __init__.py when --lib is specified. +const INIT_PY_LIB_TEMPLATE: &str = include_str!("../templates/lib/default/__init__.py.j2"); -[dependencies] -pyo3 = "0.19.0" +/// Template for the __init__.py when --script is specified. +const INIT_PY_BIN_TEMPLATE: &str = include_str!("../templates/script/default/__init__.py.j2"); -"#; +const INIT_PY_BIN_MAIN_TEMPLATE: &str = include_str!("../templates/script/default/__main__.py.j2"); -/// Template for fresh gitignore files -const GITIGNORE_TEMPLATE: &str = r#"# python generated files -__pycache__/ -*.py[oc] -build/ -dist/ -wheels/ -*.egg-info +/// Template for the lib.rs when using the maturin build system. +const LIB_RS_TEMPLATE: &str = include_str!("../templates/lib/maturin/lib.rs.j2"); -{%- if is_rust %} -# Rust -target/ -{%- endif %} +/// Template for the __init__.py when using the maturin build system. +const RUST_INIT_PY_TEMPLATE: &str = include_str!("../templates/lib/maturin/__init__.py.j2"); -# venv -.venv +/// Template for the Cargo.toml. +const CARGO_TOML_TEMPLATE: &str = include_str!("../templates/lib/maturin/Cargo.toml.j2"); -"#; +/// Template for fresh gitignore files. +const GITIGNORE_TEMPLATE: &str = include_str!("../templates/gitignore.j2"); /// Script used for setup.py setup proxy. const SETUP_PY_PROXY_SCRIPT: &str = r#" @@ -399,7 +279,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { true } else if !cmd.no_readme { let rv = env.render_named_str( - "README.txt", + "README.md", README_TEMPLATE, context! { name => metadata.name, @@ -419,6 +299,20 @@ pub fn execute(cmd: Args) -> Result<(), Error> { let private = cmd.private; + // What template are we using? + let template = { + if cmd.init_type.script { + TemplateChoice::Script + } else { + // default value + TemplateChoice::Lib + } + }; + + if cmd.init_type.script && build_system == BuildSystem::Maturin { + bail!("--script is not supported when the build-system is maturin"); + } + // crate a python module safe name. This is the name on the metadata with // underscores instead of dashes to form a valid python package name and in // case it starts with a digit, an underscore is prepended. @@ -431,8 +325,6 @@ pub fn execute(cmd: Args) -> Result<(), Error> { name_safe.insert(0, '_'); } - let is_rust = build_system == BuildSystem::Maturin; - // if git init is successful prepare the local git repository if !is_inside_git_work_tree(&dir) && Command::new("git") @@ -452,7 +344,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { "gitignore.txt", GITIGNORE_TEMPLATE, context! { - is_rust + is_rust => matches!(build_system, BuildSystem::Maturin) }, )?; fs::write(&gitignore, rv).context("failed to write .gitignore")?; @@ -478,6 +370,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { license => metadata.license, dependencies => metadata.dependencies, dev_dependencies => metadata.dev_dependencies, + is_script => matches!(template, TemplateChoice::Script), is_virtual, with_readme, build_system, @@ -490,37 +383,63 @@ pub fn execute(cmd: Args) -> Result<(), Error> { let src_dir = dir.join("src"); if !imported_something && !src_dir.is_dir() { let name = metadata.name.expect("project name"); - if is_rust { - fs::create_dir_all(&src_dir).ok(); - let project_dir = dir.join("python").join(&name_safe); - fs::create_dir_all(&project_dir).ok(); - let rv = env.render_named_str("lib.rs", LIB_RS_TEMPLATE, context! { name })?; - fs::write(src_dir.join("lib.rs"), rv).context("failed to write lib.rs")?; - let rv = env.render_named_str( - "Cargo.json", - CARGO_TOML_TEMPLATE, - context! { - name, - name_safe, - }, - )?; - fs::write(dir.join("Cargo.toml"), rv).context("failed to write Cargo.toml")?; - let rv = env.render_named_str( - "__init__.py", - RUST_INIT_PY_TEMPLATE, - context! { - name_safe - }, - )?; - fs::write(project_dir.join("__init__.py"), rv) - .context("failed to write __init__.py")?; - } else { - let project_dir = src_dir.join(&name_safe); - fs::create_dir_all(&project_dir).ok(); - let rv = - env.render_named_str("__init__.py", INIT_PY_TEMPLATE, context! { name })?; - fs::write(project_dir.join("__init__.py"), rv) - .context("failed to write __init__.py")?; + match (template, build_system) { + (TemplateChoice::Lib, BuildSystem::Maturin) => { + fs::create_dir_all(&src_dir).ok(); + let project_dir = dir.join("python").join(&name_safe); + fs::create_dir_all(&project_dir).ok(); + let rv = env.render_named_str("lib.rs", LIB_RS_TEMPLATE, context! { name })?; + fs::write(src_dir.join("lib.rs"), rv).context("failed to write lib.rs")?; + let rv = env.render_named_str( + "Cargo.json", + CARGO_TOML_TEMPLATE, + context! { + name, + name_safe, + }, + )?; + fs::write(dir.join("Cargo.toml"), rv).context("failed to write Cargo.toml")?; + let rv = env.render_named_str( + "__init__.py", + RUST_INIT_PY_TEMPLATE, + context! { + name_safe + }, + )?; + fs::write(project_dir.join("__init__.py"), rv) + .context("failed to write __init__.py")?; + } + (TemplateChoice::Lib, _) => { + let project_dir = src_dir.join(&name_safe); + fs::create_dir_all(&project_dir).ok(); + let rv = env.render_named_str( + "__init__.py", + INIT_PY_LIB_TEMPLATE, + context! { name }, + )?; + fs::write(project_dir.join("__init__.py"), rv) + .context("failed to write __init__.py")?; + } + (TemplateChoice::Script, _) => { + let project_dir = src_dir.join(&name_safe); + fs::create_dir_all(&project_dir).ok(); + + let rv1 = env.render_named_str( + "__init__.py", + INIT_PY_BIN_TEMPLATE, + context! { name }, + )?; + fs::write(project_dir.join("__init__.py"), rv1) + .context("failed to write __init__.py")?; + + let rv2 = env.render_named_str( + "__main__.py", + INIT_PY_BIN_MAIN_TEMPLATE, + context! { name_safe }, + )?; + fs::write(project_dir.join("__main__.py"), rv2) + .context("failed to write __main__.py")?; + } } } } diff --git a/rye/src/templates/LICENSE.txt.j2 b/rye/src/templates/LICENSE.txt.j2 new file mode 100644 index 0000000000..3764068971 --- /dev/null +++ b/rye/src/templates/LICENSE.txt.j2 @@ -0,0 +1 @@ +{{ license_text }} diff --git a/rye/src/templates/README.md.j2 b/rye/src/templates/README.md.j2 new file mode 100644 index 0000000000..51b9e0a3a3 --- /dev/null +++ b/rye/src/templates/README.md.j2 @@ -0,0 +1,7 @@ +# {{ name }} + +Describe your project here. + +{%- if license %} +* License: {{ license }} +{%- endif %} diff --git a/rye/src/templates/gitignore.j2 b/rye/src/templates/gitignore.j2 new file mode 100644 index 0000000000..577f5d621d --- /dev/null +++ b/rye/src/templates/gitignore.j2 @@ -0,0 +1,15 @@ +# python generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +{%- if is_rust %} +# Rust +target/ +{%- endif %} + +# venv +.venv diff --git a/rye/src/templates/lib/default/__init__.py.j2 b/rye/src/templates/lib/default/__init__.py.j2 new file mode 100644 index 0000000000..ade09b2520 --- /dev/null +++ b/rye/src/templates/lib/default/__init__.py.j2 @@ -0,0 +1,2 @@ +def hello() -> str: + return "Hello from {{ name }}!" diff --git a/rye/src/templates/lib/maturin/Cargo.toml.j2 b/rye/src/templates/lib/maturin/Cargo.toml.j2 new file mode 100644 index 0000000000..dd0206f32e --- /dev/null +++ b/rye/src/templates/lib/maturin/Cargo.toml.j2 @@ -0,0 +1,12 @@ +[package] +name = {{ name }} +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = {{ name_safe }} +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.19.0" diff --git a/rye/src/templates/lib/maturin/__init__.py.j2 b/rye/src/templates/lib/maturin/__init__.py.j2 new file mode 100644 index 0000000000..fffd3307ae --- /dev/null +++ b/rye/src/templates/lib/maturin/__init__.py.j2 @@ -0,0 +1,3 @@ +from {{ name_safe }}._lowlevel import hello + +__all__ = ["hello"] diff --git a/rye/src/templates/lib/maturin/lib.rs.j2 b/rye/src/templates/lib/maturin/lib.rs.j2 new file mode 100644 index 0000000000..baf9c5b4fe --- /dev/null +++ b/rye/src/templates/lib/maturin/lib.rs.j2 @@ -0,0 +1,14 @@ +const LIB_RS_TEMPLATE: &str = r#"use pyo3::prelude::*; + +/// Prints a message. +#[pyfunction] +fn hello() -> PyResult { + Ok("Hello from {{ name }}!".into()) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn _lowlevel(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(hello, m)?)?; + Ok(()) +} diff --git a/rye/src/templates/pyproject.toml.j2 b/rye/src/templates/pyproject.toml.j2 new file mode 100644 index 0000000000..b8d4b9d376 --- /dev/null +++ b/rye/src/templates/pyproject.toml.j2 @@ -0,0 +1,86 @@ +[project] +name = {{ name }} +version = {{ version }} +description = {{ description }} +{%- if author %} +authors = [ + { name = {{ author[0] }}, email = {{ author[1] }} } +] +{%- endif %} +{%- if dependencies %} +dependencies = [ +{%- for dependency in dependencies %} + {{ dependency }}, +{%- endfor %} +] +{%- else %} +dependencies = [] +{%- endif %} +{%- if with_readme %} +readme = "README.md" +{%- endif %} +requires-python = {{ requires_python }} +{%- if license %} +license = { text = {{ license }} } +{%- endif %} +{%- if private %} +classifiers = ["Private :: Do Not Upload"] +{%- endif %} +{%- if is_script %} + +[project.scripts] +hello = {{ name_safe ~ ":main"}} +{%- endif %} + +{%- if not is_virtual %} + +[build-system] +{%- if build_system == "hatchling" %} +requires = ["hatchling"] +build-backend = "hatchling.build" +{%- elif build_system == "setuptools" %} +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" +{%- elif build_system == "flit" %} +requires = ["flit_core>=3.4"] +build-backend = "flit_core.buildapi" +{%- elif build_system == "pdm" %} +requires = ["pdm-backend"] +build-backend = "pdm.backend" +{%- elif build_system == "maturin" %} +requires = ["maturin>=1.2,<2.0"] +build-backend = "maturin" +{%- endif %} +{%- endif %} + +[tool.rye] +managed = true +{%- if is_virtual %} +virtual = true +{%- endif %} +{%- if dev_dependencies %} +dev-dependencies = [ +{%- for dependency in dev_dependencies %} + {{ dependency }}, +{%- endfor %} +] +{%- else %} +dev-dependencies = [] +{%- endif %} + +{%- if not is_virtual %} +{%- if build_system == "hatchling" %} + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.hatch.build.targets.wheel] +packages = [{{ "src/" ~ name_safe }}] +{%- elif build_system == "maturin" %} + +[tool.maturin] +python-source = "python" +module-name = {{ name_safe ~ "._lowlevel" }} +features = ["pyo3/extension-module"] +{%- endif %} +{%- endif %} diff --git a/rye/src/templates/script/default/__init__.py.j2 b/rye/src/templates/script/default/__init__.py.j2 new file mode 100644 index 0000000000..a689ebcc16 --- /dev/null +++ b/rye/src/templates/script/default/__init__.py.j2 @@ -0,0 +1,3 @@ +def main() -> int: + print("Hello from {{ name }}!") + return 0 diff --git a/rye/src/templates/script/default/__main__.py.j2 b/rye/src/templates/script/default/__main__.py.j2 new file mode 100644 index 0000000000..3fad4bf7ac --- /dev/null +++ b/rye/src/templates/script/default/__main__.py.j2 @@ -0,0 +1,4 @@ +import {{ name_safe }} +import sys + +sys.exit({{ name_safe }}.main()) diff --git a/rye/src/templates/setuptools.py.j2 b/rye/src/templates/setuptools.py.j2 new file mode 100644 index 0000000000..2fe29626ba --- /dev/null +++ b/rye/src/templates/setuptools.py.j2 @@ -0,0 +1,15 @@ +import json, sys +from pathlib import Path +from tempfile import TemporaryDirectory + +def setup(**kwargs) -> None: + print(json.dumps(kwargs), file=sys.stderr) + +if __name__ == "setuptools": + _setup_proxy_module = sys.modules.pop("setuptools") + _setup_proxy_cwd = sys.path.pop(0) + import setuptools as __setuptools + sys.path.insert(0, _setup_proxy_cwd) + sys.modules["setuptools"] = _setup_proxy_module + def __getattr__(name): + return getattr(__setuptools, name) diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index 0963939102..f441310c26 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -151,6 +151,12 @@ impl Space { rv } + #[allow(unused)] + pub fn read_toml>(&self, path: P) -> toml_edit::Document { + let p = self.project_path().join(path.as_ref()); + std::fs::read_to_string(&p).unwrap().parse().unwrap() + } + #[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_init.rs b/rye/tests/test_init.rs new file mode 100644 index 0000000000..39da78b7d2 --- /dev/null +++ b/rye/tests/test_init.rs @@ -0,0 +1,183 @@ +use crate::common::{get_bin, rye_cmd_snapshot, Space}; + +mod common; + +// Test that init --lib works +#[test] +fn test_init_lib() { + let space = Space::new(); + space + .cmd(get_bin()) + .arg("init") + .arg("--name") + .arg("my-project") + .arg("-q") + .arg("--lib") + .current_dir(space.project_path()) + .status() + .expect("initialization successful"); + + rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.1 + 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 1 package in [EXECUTION_TIME] + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 1 package in [EXECUTION_TIME] + Built 1 editable in [EXECUTION_TIME] + Installed 1 package in [EXECUTION_TIME] + + my-project==0.1.0 (from file:[TEMP_PATH]/project) + "###); + + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("python").arg("-c").arg("import my_project; print(my_project.hello())"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from my-project! + + ----- stderr ----- + "###); + + assert!( + space.read_toml("pyproject.toml")["project"] + .get("scripts") + .is_none(), + "[project.scripts] should not be present" + ) +} + +// The default is the same as --lib +#[test] +fn test_init_default() { + let space = Space::new(); + space + .cmd(get_bin()) + .arg("init") + .arg("--name") + .arg("my-project") + .arg("-q") + .current_dir(space.project_path()) + .status() + .expect("initialization successful"); + + rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.1 + 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 1 package in [EXECUTION_TIME] + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 1 package in [EXECUTION_TIME] + Built 1 editable in [EXECUTION_TIME] + Installed 1 package in [EXECUTION_TIME] + + my-project==0.1.0 (from file:[TEMP_PATH]/project) + "###); + + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("python").arg("-c").arg("import my_project; print(my_project.hello())"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from my-project! + + ----- stderr ----- + "###); + + assert!( + space.read_toml("pyproject.toml")["project"] + .get("scripts") + .is_none(), + "[project.scripts] should not be present" + ) +} + +// Test that init --script works +#[test] +fn test_init_script() { + let space = Space::new(); + space + .cmd(get_bin()) + .arg("init") + .arg("--name") + .arg("my-project") + .arg("-q") + .arg("--script") + .current_dir(space.project_path()) + .status() + .expect("initialization successful"); + + rye_cmd_snapshot!(space.rye_cmd().arg("sync"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Initializing new virtualenv in [TEMP_PATH]/project/.venv + Python version: cpython@3.12.1 + 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 1 package in [EXECUTION_TIME] + warning: Requirements file [TEMP_FILE] does not contain any dependencies + Built 1 editable in [EXECUTION_TIME] + Resolved 1 package in [EXECUTION_TIME] + Built 1 editable in [EXECUTION_TIME] + Installed 1 package in [EXECUTION_TIME] + + my-project==0.1.0 (from file:[TEMP_PATH]/project) + "###); + + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("hello"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from my-project! + + ----- stderr ----- + "###); + + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("python").arg("-mmy_project"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from my-project! + + ----- stderr ----- + "###); +} + +// Test that init --script and --lib are incompatible. +#[test] +fn test_init_lib_and_script_incompatible() { + let space = Space::new(); + rye_cmd_snapshot!(space.cmd(get_bin()).arg("init").arg("--name").arg("my-project").arg("--script").arg("--lib").current_dir(space.project_path()), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: an argument cannot be used with one or more of the other specified arguments + "###); +} From 0d33ecc8f31df0d336f6fba92c514a1f977efec2 Mon Sep 17 00:00:00 2001 From: Daniel Bauer <4690162+dnlbauer@users.noreply.github.com> Date: Fri, 23 Feb 2024 10:13:33 +0100 Subject: [PATCH 4/7] Better shell detection for fish and zsh during installation (#748) --- Cargo.lock | 51 ---------------------------------------------- rye/Cargo.toml | 1 - rye/src/cli/rye.rs | 20 ++++++++++++++---- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d7c369080..ac0412a6df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,25 +402,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -1656,26 +1637,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rayon" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -1882,7 +1843,6 @@ dependencies = [ "toml_edit", "url", "walkdir", - "whattheshell", "which", "winapi", "winreg", @@ -2152,7 +2112,6 @@ dependencies = [ "libc", "ntapi", "once_cell", - "rayon", "winapi", ] @@ -2565,16 +2524,6 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "whattheshell" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c94d2698698cb1322e20292460fd158373af5fb2802afb6bea9ee17628efddb" -dependencies = [ - "sysinfo", - "thiserror", -] - [[package]] name = "which" version = "6.0.0" diff --git a/rye/Cargo.toml b/rye/Cargo.toml index 916733b17c..c7a3c384e5 100644 --- a/rye/Cargo.toml +++ b/rye/Cargo.toml @@ -62,7 +62,6 @@ home = "0.5.9" ctrlc = "3.4.2" [target."cfg(unix)".dependencies] -whattheshell = "1.0.1" xattr = "1.3.1" [target."cfg(windows)".dependencies] diff --git a/rye/src/cli/rye.rs b/rye/src/cli/rye.rs index 2431ed8199..37c45ad0bc 100644 --- a/rye/src/cli/rye.rs +++ b/rye/src/cli/rye.rs @@ -388,9 +388,15 @@ fn uninstall(args: UninstallCommand) -> Result<(), Error> { } #[cfg(unix)] -fn is_fish() -> bool { - use whattheshell::Shell; - Shell::infer().map_or(false, |x| matches!(x, Shell::Fish)) +fn has_fish() -> bool { + use which::which; + which("fish").is_ok() +} + +#[cfg(unix)] +fn has_zsh() -> bool { + use which::which; + which("zsh").is_ok() } fn perform_install( @@ -666,7 +672,13 @@ fn add_rye_to_path(mode: &InstallMode, shims: &Path, ask: bool) -> Result<(), Er echo!(); echo!(" source \"{}/env\"", rye_home.display()); echo!(); - if is_fish() { + if has_zsh() { + echo!("To make it work with zsh, you might need to add this to your .zprofile:"); + echo!(); + echo!(" source \"{}/env\"", rye_home.display()); + echo!(); + } + if has_fish() { echo!("To make it work with fish, run this once instead:"); echo!(); echo!( From ec45d81178abf0cb090f38e57d8b127c661b390f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 23 Feb 2024 11:47:42 +0100 Subject: [PATCH 5/7] Improve the reporting of IO errors (#750) --- rye/src/bootstrap.rs | 76 ++++++++++++++++++++++++---------------- rye/src/cli/build.rs | 6 ++-- rye/src/cli/init.rs | 10 +++--- rye/src/cli/pin.rs | 3 +- rye/src/cli/rye.rs | 15 ++++---- rye/src/cli/toolchain.rs | 12 +++---- rye/src/config.rs | 10 +++--- rye/src/installer.rs | 16 +++++---- rye/src/lock.rs | 20 ++++++++--- rye/src/piptools.rs | 5 +-- rye/src/platform.rs | 10 +++--- rye/src/pyproject.rs | 23 +++++------- rye/src/sync.rs | 8 ++--- rye/src/utils/mod.rs | 46 ++++++++++++++++++------ rye/src/utils/unix.rs | 8 +++-- rye/tests/common/mod.rs | 2 +- 16 files changed, 163 insertions(+), 107 deletions(-) diff --git a/rye/src/bootstrap.rs b/rye/src/bootstrap.rs index 0543f66279..1b96f89a05 100644 --- a/rye/src/bootstrap.rs +++ b/rye/src/bootstrap.rs @@ -23,7 +23,7 @@ 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, - CommandOutput, + CommandOutput, IoPathContext, }; /// this is the target version that we want to fetch @@ -92,10 +92,11 @@ pub fn ensure_self_venv_with_toolchain( if output != CommandOutput::Quiet { echo!("Detected outdated rye internals. Refreshing"); } - fs::remove_dir_all(&venv_dir).context("could not remove self-venv for update")?; + fs::remove_dir_all(&venv_dir) + .path_context(&venv_dir, "could not remove self-venv for update")?; if pip_tools_dir.is_dir() { fs::remove_dir_all(&pip_tools_dir) - .context("could not remove pip-tools for update")?; + .path_context(&pip_tools_dir, "could not remove pip-tools for update")?; } } } @@ -159,7 +160,9 @@ pub fn ensure_self_venv_with_toolchain( do_update(output, &venv_dir, app_dir)?; - fs::write(venv_dir.join("tool-version.txt"), SELF_VERSION.to_string())?; + let tool_version_path = venv_dir.join("tool-version.txt"); + fs::write(&tool_version_path, SELF_VERSION.to_string()) + .path_context(tool_version_path, "could not write tool version")?; FORCED_TO_UPDATE.store(true, atomic::Ordering::Relaxed); Ok(venv_dir) @@ -219,7 +222,7 @@ fn do_update(output: CommandOutput, venv_dir: &Path, app_dir: &Path) -> Result<( } let shims = app_dir.join("shims"); if !shims.is_dir() { - fs::create_dir_all(&shims).context("tried to create shim folder")?; + fs::create_dir_all(&shims).path_context(&shims, "tried to create shim folder")?; } // if rye is itself installed into the shims folder, we want to @@ -237,47 +240,54 @@ fn do_update(output: CommandOutput, venv_dir: &Path, app_dir: &Path) -> Result<( pub fn update_core_shims(shims: &Path, this: &Path) -> Result<(), Error> { #[cfg(unix)] { + let py_shim = shims.join("python"); + let py3_shim = shims.join("python3"); + // on linux we cannot symlink at all, as this will misreport. We will try to do // hardlinks and if that fails, we fall back to copying the entire file over. This // for instance is needed when the rye executable is placed on a different volume // than ~/.rye/shims if cfg!(target_os = "linux") { - fs::remove_file(shims.join("python")).ok(); - if fs::hard_link(this, shims.join("python")).is_err() { - fs::copy(this, shims.join("python")).context("tried to copy python shim")?; + fs::remove_file(&py_shim).ok(); + if fs::hard_link(this, &py_shim).is_err() { + fs::copy(this, &py_shim).path_context(&py_shim, "tried to copy python shim")?; } - fs::remove_file(shims.join("python3")).ok(); - if fs::hard_link(this, shims.join("python3")).is_err() { - fs::copy(this, shims.join("python2")).context("tried to copy python3 shim")?; + fs::remove_file(&py3_shim).ok(); + if fs::hard_link(this, &py3_shim).is_err() { + fs::copy(this, &py3_shim).path_context(&py_shim, "tried to copy python3 shim")?; } // on other unices we always use symlinks } else { - fs::remove_file(shims.join("python")).ok(); - symlink_file(this, shims.join("python")).context("tried to symlink python shim")?; - fs::remove_file(shims.join("python3")).ok(); - symlink_file(this, shims.join("python3")).context("tried to symlink python3 shim")?; + fs::remove_file(&py_shim).ok(); + symlink_file(this, &py_shim).path_context(&py_shim, "tried to symlink python shim")?; + fs::remove_file(&py3_shim).ok(); + symlink_file(this, &py3_shim) + .path_context(&py3_shim, "tried to symlink python3 shim")?; } } #[cfg(windows)] { + let py_shim = shims.join("python.exe"); + let pyw_shim = shims.join("pythonw.exe"); + let py3_shim = shims.join("python3.exe"); + // on windows we need privileges to symlink. Not everyone might have that, so we // fall back to hardlinks. - fs::remove_file(shims.join("python.exe")).ok(); - if symlink_file(this, shims.join("python.exe")).is_err() { - fs::hard_link(this, shims.join("python.exe")) - .context("tried to symlink python shim")?; + fs::remove_file(&py_shim).ok(); + if symlink_file(this, &py_shim).is_err() { + fs::hard_link(this, &py_shim).path_context(&py_shim, "tried to symlink python shim")?; } - fs::remove_file(shims.join("python3.exe")).ok(); - if symlink_file(this, shims.join("python3.exe")).is_err() { - fs::hard_link(this, shims.join("python3.exe")) - .context("tried to symlink python shim")?; + fs::remove_file(&py3_shim).ok(); + if symlink_file(this, &py3_shim).is_err() { + fs::hard_link(this, &py3_shim) + .path_context(&py3_shim, "tried to symlink python3 shim")?; } - fs::remove_file(shims.join("pythonw.exe")).ok(); - if symlink_file(this, shims.join("pythonw.exe")).is_err() { - fs::hard_link(this, shims.join("pythonw.exe")) - .context("tried to symlink pythonw shim")?; + fs::remove_file(&pyw_shim).ok(); + if symlink_file(this, &pyw_shim).is_err() { + fs::hard_link(this, &pyw_shim) + .path_context(&pyw_shim, "tried to symlink pythonw shim")?; } } @@ -421,8 +431,7 @@ pub fn fetch( return Ok(version); } - fs::create_dir_all(&target_dir) - .with_context(|| format!("failed to create target folder {}", target_dir.display()))?; + fs::create_dir_all(&target_dir).path_context(&target_dir, "failed to create target folder")?; if output == CommandOutput::Verbose { echo!("download url: {}", url); @@ -445,8 +454,13 @@ pub fn fetch( if output != CommandOutput::Quiet { echo!("{}", style("Unpacking").cyan()); } - unpack_archive(&archive_buffer, &target_dir, 1) - .with_context(|| format!("unpacking of downloaded tarball {} failed", &url))?; + unpack_archive(&archive_buffer, &target_dir, 1).with_context(|| { + format!( + "unpacking of downloaded tarball {} to '{}' failed", + &url, + target_dir.display() + ) + })?; if output != CommandOutput::Quiet { echo!("{} {}", style("Downloaded").green(), version); diff --git a/rye/src/cli/build.rs b/rye/src/cli/build.rs index 49191c9b18..d1db714fd7 100644 --- a/rye/src/cli/build.rs +++ b/rye/src/cli/build.rs @@ -8,7 +8,7 @@ use console::style; use crate::bootstrap::ensure_self_venv; use crate::pyproject::{locate_projects, PyProject}; -use crate::utils::{get_venv_python_bin, CommandOutput}; +use crate::utils::{get_venv_python_bin, CommandOutput, IoPathContext}; /// Builds a package for distribution. #[derive(Parser, Debug)] @@ -53,10 +53,10 @@ pub fn execute(cmd: Args) -> Result<(), Error> { }; if out.exists() && cmd.clean { - for entry in fs::read_dir(&out)? { + for entry in fs::read_dir(&out).path_context(&out, "enumerate build output")? { let path = entry?.path(); if path.is_file() { - fs::remove_file(path)?; + fs::remove_file(&path).path_context(&path, "clean build artifact")?; } } } diff --git a/rye/src/cli/init.rs b/rye/src/cli/init.rs index 3b907beb10..32ee0b2013 100644 --- a/rye/src/cli/init.rs +++ b/rye/src/cli/init.rs @@ -26,7 +26,7 @@ use crate::pyproject::BuildSystem; use crate::sources::PythonVersionRequest; use crate::utils::{ copy_dir, escape_string, format_requirement, get_venv_python_bin, is_inside_git_work_tree, - CommandOutput, CopyDirOptions, + CommandOutput, CopyDirOptions, IoPathContext, }; /// Initialize a new or existing Python project with Rye. @@ -218,7 +218,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { license_text, }, )?; - fs::write(&license_file, rv)?; + fs::write(&license_file, rv).path_context(&license_file, "create license file")?; } let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); @@ -270,8 +270,8 @@ pub fn execute(cmd: Args) -> Result<(), Error> { // the full version request. This has the disadvantage that we might end up // pinning to an architecture specific version. let to_write = get_pinnable_version(&py, false).unwrap_or_else(|| py.to_string()); - fs::write(python_version_file, format!("{}\n", to_write)) - .context("could not write .python-version file")?; + fs::write(&python_version_file, format!("{}\n", to_write)) + .path_context(&python_version_file, "could not write .python-version file")?; } // create a readme if one is missing @@ -347,7 +347,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { is_rust => matches!(build_system, BuildSystem::Maturin) }, )?; - fs::write(&gitignore, rv).context("failed to write .gitignore")?; + fs::write(&gitignore, rv).path_context(&gitignore, "failed to write .gitignore")?; } if is_metadata_author_none { let new_author = get_default_author_with_fallback(&dir); diff --git a/rye/src/cli/pin.rs b/rye/src/cli/pin.rs index dcd1c19e5d..0c76a38a20 100644 --- a/rye/src/cli/pin.rs +++ b/rye/src/cli/pin.rs @@ -10,6 +10,7 @@ use crate::platform::get_pinnable_version; use crate::pyproject::DiscoveryUnsuccessful; use crate::pyproject::PyProject; use crate::sources::PythonVersionRequest; +use crate::utils::IoPathContext; /// Pins a Python version to this project. /// @@ -57,7 +58,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { None => env::current_dir()?.join(".python-version"), }; fs::write(&version_file, format!("{}\n", to_write)) - .context("failed to write .python-version file")?; + .path_context(&version_file, "failed to write .python-version file")?; if !cmd.no_update_requires_python { if let Some(mut pyproject_toml) = pyproject { diff --git a/rye/src/cli/rye.rs b/rye/src/cli/rye.rs index 37c45ad0bc..003dd78fb8 100644 --- a/rye/src/cli/rye.rs +++ b/rye/src/cli/rye.rs @@ -22,7 +22,7 @@ use crate::cli::toolchain::register_toolchain; use crate::config::Config; use crate::platform::{get_app_dir, symlinks_supported}; use crate::sources::{get_download_url, PythonVersionRequest}; -use crate::utils::{check_checksum, toml, tui_theme, CommandOutput, QuietExit}; +use crate::utils::{check_checksum, toml, tui_theme, CommandOutput, IoPathContext, QuietExit}; #[cfg(windows)] const DEFAULT_HOME: &str = "%USERPROFILE%\\.rye"; @@ -313,7 +313,7 @@ fn install(args: InstallCommand) -> Result<(), Error> { fn remove_dir_all_if_exists(path: &Path) -> Result<(), Error> { if path.is_dir() { - fs::remove_dir_all(path)?; + fs::remove_dir_all(path).path_context(path, "failed to remove directory")?; } Ok(()) } @@ -548,9 +548,9 @@ fn perform_install( // place executable in rye home folder fs::create_dir_all(&shims).ok(); if target.is_file() { - fs::remove_file(&target)?; + fs::remove_file(&target).path_context(&target, "failed to delete old executable")?; } - fs::copy(exe, &target)?; + fs::copy(&exe, &target).path_context(&exe, "failed to copy executable")?; echo!("Installed binary to {}", style(target.display()).cyan()); // write an env file we can source later. Prefer $HOME/.rye over @@ -560,10 +560,9 @@ fn perform_install( .unwrap_or((false, Cow::Borrowed(DEFAULT_HOME))); if cfg!(unix) { - fs::write( - app_dir.join("env"), - render!(UNIX_ENV_FILE, custom_home, rye_home), - )?; + let env_path = app_dir.join("env"); + fs::write(&env_path, render!(UNIX_ENV_FILE, custom_home, rye_home)) + .path_context(&env_path, "failed to write env file")?; } // Register a toolchain if provided. diff --git a/rye/src/cli/toolchain.rs b/rye/src/cli/toolchain.rs index 6c009731ad..3483a9635a 100644 --- a/rye/src/cli/toolchain.rs +++ b/rye/src/cli/toolchain.rs @@ -5,8 +5,6 @@ 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; @@ -14,10 +12,12 @@ use console::style; use serde::Deserialize; use serde::Serialize; +use crate::installer::list_installed_tools; +use crate::piptools::get_pip_tools_venv_path; 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; +use crate::utils::{symlink_file, IoPathContext}; const INSPECT_SCRIPT: &str = r#" import json @@ -144,10 +144,10 @@ pub fn remove(cmd: RemoveCommand) -> Result<(), Error> { } if path.is_file() { - fs::remove_file(&path)?; + fs::remove_file(&path).path_context(&path, "failed to remove toolchain link")?; echo!("Removed toolchain link {}", &ver); } else if path.is_dir() { - fs::remove_dir_all(&path)?; + fs::remove_dir_all(&path).path_context(&path, "failed to remove toolchain")?; echo!("Removed installed toolchain {}", &ver); } else { echo!("Toolchain is not installed"); @@ -286,7 +286,7 @@ where .to_str() .ok_or_else(|| anyhow::anyhow!("non unicode path to interpreter"))?, ) - .context("could not register interpreter")?; + .path_context(&target, "could not register interpreter")?; } } diff --git a/rye/src/config.rs b/rye/src/config.rs index f0c31d5842..7386b903fc 100644 --- a/rye/src/config.rs +++ b/rye/src/config.rs @@ -11,7 +11,7 @@ use toml_edit::Document; use crate::platform::{get_app_dir, get_latest_cpython_version}; use crate::pyproject::{BuildSystem, SourceRef, SourceRefType}; use crate::sources::PythonVersionRequest; -use crate::utils::toml; +use crate::utils::{toml, IoPathContext}; static CONFIG: Mutex>> = Mutex::new(None); static AUTHOR_REGEX: Lazy = @@ -55,7 +55,8 @@ impl Config { /// Saves changes back. pub fn save(&self) -> Result<(), Error> { - fs::write(&self.path, self.doc.to_string())?; + fs::write(&self.path, self.doc.to_string()) + .path_context(&self.path, "failed to save config")?; Ok(()) } @@ -66,12 +67,11 @@ impl Config { /// Loads a config from a path. pub fn from_path(path: &Path) -> Result { - let contents = fs::read_to_string(path) - .with_context(|| format!("failed to read config from '{}'", path.display()))?; + let contents = fs::read_to_string(path).path_context(path, "failed to read config")?; Ok(Config { doc: contents .parse::() - .with_context(|| format!("failed to parse config from '{}'", path.display()))?, + .path_context(path, "failed to parse config")?, path: path.to_path_buf(), }) } diff --git a/rye/src/installer.rs b/rye/src/installer.rs index 84c2a8a48e..17c1bcffc8 100644 --- a/rye/src/installer.rs +++ b/rye/src/installer.rs @@ -20,6 +20,7 @@ use crate::sources::PythonVersionRequest; use crate::sync::{create_virtualenv, VenvMarker}; use crate::utils::{ get_short_executable_name, get_venv_python_bin, is_executable, symlink_file, CommandOutput, + IoPathContext, }; const FIND_SCRIPT_SCRIPT: &str = r#" @@ -299,13 +300,12 @@ fn install_scripts( { if symlink_file(file, &shim_target).is_err() { fs::hard_link(file, &shim_target) - .with_context(|| format!("unable to symlink tool to {}", file.display()))?; + .path_context(file, "unable to symlink tool")?; } } #[cfg(unix)] { - symlink_file(file, shim_target) - .with_context(|| format!("unable to symlink tool to {}", file.display()))?; + symlink_file(file, shim_target).path_context(file, "unable to symlink tool")?; } rv.push(get_short_executable_name(file)); } @@ -340,7 +340,7 @@ pub fn list_installed_tools() -> Result, Error> { } let mut rv = HashMap::new(); - for folder in fs::read_dir(&tool_dir)? { + for folder in fs::read_dir(&tool_dir).path_context(&tool_dir, "unable to enumerate tools")? { let folder = folder?; if !folder.file_type()?.is_dir() { continue; @@ -350,7 +350,9 @@ pub fn list_installed_tools() -> Result, Error> { let venv_marker = read_venv_marker(&folder.path()); let mut scripts = Vec::new(); - for script in fs::read_dir(target_venv_bin_path.clone())? { + for script in fs::read_dir(target_venv_bin_path.clone()) + .path_context(&target_venv_bin_path, "unable to enumerate scripts")? + { let script = script?; let script_path = script.path(); if let Some(base_name) = script_path.file_name() { @@ -388,7 +390,9 @@ fn uninstall_helper(target_venv_path: &Path, shim_dir: &Path) -> Result<(), Erro return Ok(()); } - for script in fs::read_dir(target_venv_bin_path)? { + for script in fs::read_dir(&target_venv_bin_path) + .path_context(&target_venv_bin_path, "unable to enumerate scripts")? + { let script = script?; if let Some(base_name) = script.path().file_name() { let shim_path = shim_dir.join(base_name); diff --git a/rye/src/lock.rs b/rye/src/lock.rs index 1922546125..2c08ace407 100644 --- a/rye/src/lock.rs +++ b/rye/src/lock.rs @@ -23,7 +23,7 @@ use crate::pyproject::{ normalize_package_name, DependencyKind, ExpandedSources, PyProject, Workspace, }; use crate::sources::PythonVersion; -use crate::utils::{set_proxy_variables, CommandOutput}; +use crate::utils::{set_proxy_variables, CommandOutput, IoPathContext}; static FILE_EDITABLE_RE: Lazy = Lazy::new(|| Regex::new(r"^-e (file://.*?)\s*$").unwrap()); static DEP_COMMENT_RE: Lazy = @@ -331,6 +331,8 @@ pub fn update_single_project_lockfile( } } + req_file.flush()?; + let exclusions = find_exclusions(std::slice::from_ref(pyproject))?; generate_lockfile( output, @@ -363,10 +365,14 @@ fn generate_lockfile( let requirements_file = scratch.path().join("requirements.txt"); let lock_options = if lockfile.is_file() { let requirements = fs::read_to_string(lockfile)?; - fs::write(&requirements_file, &requirements)?; + fs::write(&requirements_file, &requirements) + .path_context(&requirements_file, "unable to restore requirements file")?; LockOptions::restore(&requirements, lock_options)? } else { - fs::write(&requirements_file, b"")?; + fs::write(&requirements_file, b"").path_context( + &requirements_file, + "unable to write empty requirements file", + )?; Cow::Borrowed(lock_options) }; @@ -463,7 +469,8 @@ fn finalize_lockfile( sources: &ExpandedSources, lock_options: &LockOptions, ) -> Result<(), Error> { - let mut rv = BufWriter::new(fs::File::create(out)?); + let mut rv = + BufWriter::new(fs::File::create(out).path_context(out, "unable to finalize lockfile")?); lock_options.write_header(&mut rv)?; // only if we are asked to include sources we do that. @@ -472,7 +479,10 @@ fn finalize_lockfile( writeln!(rv)?; } - for line in fs::read_to_string(generated)?.lines() { + for line in fs::read_to_string(generated) + .path_context(generated, "unable to parse resolver output")? + .lines() + { // we deal with this explicitly. if line.trim().is_empty() || line.starts_with("--index-url ") diff --git a/rye/src/piptools.rs b/rye/src/piptools.rs index 38feb57300..f5548da0fc 100644 --- a/rye/src/piptools.rs +++ b/rye/src/piptools.rs @@ -9,7 +9,7 @@ use crate::consts::VENV_BIN; use crate::platform::get_app_dir; use crate::sources::PythonVersion; use crate::sync::create_virtualenv; -use crate::utils::{get_venv_python_bin, CommandOutput}; +use crate::utils::{get_venv_python_bin, CommandOutput, IoPathContext}; // When changing these, also update `SELF_VERSION` in bootstrap.rs to ensure // that the internals are re-created. @@ -50,7 +50,8 @@ fn get_pip_tools_bin(py_ver: &PythonVersion, output: CommandOutput) -> Result> = Mutex::new(None); @@ -78,7 +79,7 @@ pub fn get_toolchain_python_bin(version: &PythonVersion) -> Result Result { let doc = fs::read_to_string(&filepath)? .parse::() - .with_context(|| format!("failed to parse credentials from {}", filepath.display()))?; + .path_context(&filepath, "failed to parse credentials")?; Ok(doc) } pub fn write_credentials(doc: &toml_edit::Document) -> Result<(), Error> { - std::fs::write(get_credentials_filepath()?, doc.to_string()) - .context("unable to write to the credentials file") + let path = get_credentials_filepath()?; + std::fs::write(&path, doc.to_string()) + .path_context(&path, "unable to write to the credentials file") } pub fn get_credentials_filepath() -> Result { diff --git a/rye/src/pyproject.rs b/rye/src/pyproject.rs index 5bedfa47a8..7f295b1936 100644 --- a/rye/src/pyproject.rs +++ b/rye/src/pyproject.rs @@ -17,11 +17,11 @@ use crate::consts::VENV_BIN; use crate::platform::{get_python_version_request_from_pyenv_pin, list_known_toolchains}; use crate::sources::{get_download_url, matches_version, PythonVersion, PythonVersionRequest}; use crate::sync::VenvMarker; -use crate::utils::CommandOutput; use crate::utils::{ escape_string, expand_env_vars, format_requirement, get_short_executable_name, is_executable, toml, }; +use crate::utils::{CommandOutput, IoPathContext}; use anyhow::{anyhow, bail, Context, Error}; use globset::GlobBuilder; use once_cell::sync::Lazy; @@ -577,14 +577,9 @@ impl PyProject { pub fn load(filename: &Path) -> Result { let root = filename.parent().unwrap_or(Path::new(".")); let doc = fs::read_to_string(filename) - .with_context(|| format!("failed to read pyproject.toml from {}", &filename.display()))? + .path_context(filename, "failed to read pyproject.toml")? .parse::() - .with_context(|| { - format!( - "failed to parse pyproject.toml from {}", - &filename.display() - ) - })?; + .path_context(filename, "failed to parse pyproject.toml")?; let mut workspace = Workspace::try_load_from_toml(&doc, root).map(Arc::new); if workspace.is_none() { @@ -626,7 +621,7 @@ impl PyProject { .parse::() .with_context(|| { format!( - "failed to parse pyproject.toml from {} in context of workspace {}", + "failed to parse pyproject.toml from '{}' in context of workspace {}", &filename.display(), workspace.path().display(), ) @@ -996,9 +991,8 @@ impl PyProject { /// Save back changes pub fn save(&self) -> Result<(), Error> { - fs::write(self.toml_path(), self.doc.to_string()).with_context(|| { - format!("unable to write changes to {}", self.toml_path().display()) - })?; + let path = self.toml_path(); + fs::write(&path, self.doc.to_string()).path_context(&path, "unable to write changes")?; Ok(()) } } @@ -1069,14 +1063,15 @@ pub fn read_venv_marker(venv_path: &Path) -> Option { } pub fn write_venv_marker(venv_path: &Path, py_ver: &PythonVersion) -> Result<(), Error> { + let marker = venv_path.join("rye-venv.json"); fs::write( - venv_path.join("rye-venv.json"), + &marker, serde_json::to_string_pretty(&VenvMarker { python: py_ver.clone(), venv_path: Some(venv_path.into()), })?, ) - .context("failed writing venv marker file")?; + .path_context(&marker, "failed writing venv marker file")?; Ok(()) } diff --git a/rye/src/sync.rs b/rye/src/sync.rs index 67a337f0c7..e840b491bf 100644 --- a/rye/src/sync.rs +++ b/rye/src/sync.rs @@ -21,6 +21,7 @@ use crate::pyproject::{read_venv_marker, write_venv_marker, ExpandedSources, PyP use crate::sources::PythonVersion; use crate::utils::{ get_venv_python_bin, mark_path_sync_ignore, set_proxy_variables, symlink_dir, CommandOutput, + IoPathContext, }; /// Controls the sync mode @@ -149,7 +150,7 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> { // kill the virtualenv if it's there and we need to get rid of it. if recreate && venv.is_dir() { - fs::remove_dir_all(&venv).context("failed to delete existing virtualenv")?; + fs::remove_dir_all(&venv).path_context(&venv, "failed to delete existing virtualenv")?; } if venv.is_dir() { @@ -347,8 +348,7 @@ pub fn create_virtualenv( venv_cmd } else { // create the venv folder first so we can manipulate some flags on it. - fs::create_dir_all(venv) - .with_context(|| format!("unable to create virtualenv folder '{}'", venv.display()))?; + fs::create_dir_all(venv).path_context(venv, "unable to create virtualenv folder")?; update_venv_sync_marker(output, venv); let mut venv_cmd = Command::new(self_venv.join(VENV_BIN).join("virtualenv")); @@ -469,7 +469,7 @@ fn inject_tcl_config(venv: &Path, py_bin: &Path) -> Result<(), Error> { // There is only one folder in the venv/lib folder. But in practice, only pypy will use this method in linux #[cfg(unix)] fn get_site_packages(lib_dir: PathBuf) -> Result, Error> { - let entries = fs::read_dir(lib_dir).context("read venv/lib/ path is fail")?; + let entries = fs::read_dir(&lib_dir).path_context(&lib_dir, "read venv/lib/ path failed")?; for entry in entries { let entry = entry?; diff --git a/rye/src/utils/mod.rs b/rye/src/utils/mod.rs index 9d712f192e..0f0e2a15ee 100644 --- a/rye/src/utils/mod.rs +++ b/rye/src/utils/mod.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; use std::{fmt, fs}; -use anyhow::{anyhow, bail, Error}; +use anyhow::{anyhow, bail, Context, Error}; use dialoguer::theme::{ColorfulTheme, Theme}; use once_cell::sync::Lazy; use pep508_rs::{Requirement, VersionOrUrl}; @@ -37,6 +37,21 @@ pub(crate) mod unix; pub(crate) mod ruff; pub(crate) mod toml; +pub trait IoPathContext { + type Out; + + /// Adds path information to an error. + fn path_context, D: fmt::Display>(self, p: P, msg: D) -> Self::Out; +} + +impl IoPathContext for Result { + type Out = Result; + + fn path_context, D: fmt::Display>(self, p: P, msg: D) -> Self::Out { + self.with_context(|| format!("{} (at '{}')", msg, p.as_ref().display())) + } +} + #[cfg(windows)] pub fn symlink_dir(original: P, link: Q) -> Result<(), std::io::Error> where @@ -71,7 +86,8 @@ pub fn mark_path_sync_ignore(venv: &Path, mark_ignore: bool) -> Result<(), Error for flag in ATTRS { if mark_ignore { - xattr::set(venv, flag, b"1")?; + xattr::set(venv, flag, b"1") + .path_context(venv, "failed to write extended attribute")?; } else { xattr::remove(venv, flag).ok(); } @@ -83,7 +99,7 @@ pub fn mark_path_sync_ignore(venv: &Path, mark_ignore: bool) -> Result<(), Error let mut stream_path = venv.as_os_str().to_os_string(); stream_path.push(":com.dropbox.ignored"); if mark_ignore { - fs::write(stream_path, b"1")?; + fs::write(&stream_path, b"1").path_context(&stream_path, "failed to write stream")?; } else { fs::remove_file(stream_path).ok(); } @@ -268,20 +284,25 @@ pub fn unpack_archive(contents: &[u8], dst: &Path, strip_components: usize) -> R let path = dst.join(components.as_path()); if path != Path::new("") && path.strip_prefix(dst).is_ok() { if file.name().ends_with('/') { - fs::create_dir_all(&path)?; + fs::create_dir_all(&path).path_context(&path, "failed to create directory")?; } else { if let Some(p) = path.parent() { if !p.exists() { - fs::create_dir_all(p)?; + fs::create_dir_all(p).path_context(p, "failed to create directory")?; } } - std::io::copy(&mut file, &mut fs::File::create(&path)?)?; + std::io::copy( + &mut file, + &mut fs::File::create(&path) + .path_context(&path, "failed to create file")?, + )?; } #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; if let Some(mode) = file.unix_mode() { - fs::set_permissions(&path, fs::Permissions::from_mode(mode))?; + fs::set_permissions(&path, fs::Permissions::from_mode(mode)) + .path_context(&path, "failed to set permissions")?; } } } @@ -398,7 +419,10 @@ pub fn copy_dir>(from: T, to: T, options: &CopyDirOptions) -> Res let to = to.as_ref(); if from.is_dir() { - for entry in fs::read_dir(from)?.filter_map(|e| e.ok()) { + for entry in fs::read_dir(from) + .path_context(from, "failed to enumerate directory")? + .filter_map(|e| e.ok()) + { let entry_path = entry.path(); if options.exclude.iter().any(|dir| *dir == entry_path) { continue; @@ -406,10 +430,12 @@ pub fn copy_dir>(from: T, to: T, options: &CopyDirOptions) -> Res let destination = to.join(entry.file_name()); if entry.file_type()?.is_dir() { - fs::create_dir_all(&destination)?; + fs::create_dir_all(&destination) + .path_context(&destination, "failed to create directory")?; copy_dir(entry.path(), destination, options)?; } else { - fs::copy(entry.path(), &destination)?; + fs::copy(entry.path(), &destination) + .path_context(entry.path(), "failed to copy file")?; } } } diff --git a/rye/src/utils/unix.rs b/rye/src/utils/unix.rs index 775aa89da6..9e06df961c 100644 --- a/rye/src/utils/unix.rs +++ b/rye/src/utils/unix.rs @@ -3,6 +3,8 @@ use std::{env, fs}; use anyhow::{Context, Error}; +use crate::utils::IoPathContext; + pub(crate) fn add_to_path(rye_home: &Path) -> Result<(), Error> { // for regular shells just add the path to `.profile` add_source_line_to_profile( @@ -19,7 +21,8 @@ pub(crate) fn add_to_path(rye_home: &Path) -> Result<(), Error> { fn add_source_line_to_profile(profile_path: &Path, source_line: &str) -> Result<(), Error> { let mut profile = if profile_path.is_file() { - fs::read_to_string(profile_path)? + fs::read_to_string(profile_path) + .path_context(profile_path, "failed to read profile file")? } else { String::new() }; @@ -27,7 +30,8 @@ fn add_source_line_to_profile(profile_path: &Path, source_line: &str) -> Result< if !profile.lines().any(|x| x.trim() == source_line) { profile.push_str(source_line); profile.push('\n'); - fs::write(profile_path, profile).context("failed to write updated .profile")?; + fs::write(profile_path, profile) + .path_context(profile_path, "failed to write updated .profile")?; } Ok(()) diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index f441310c26..4bab40c824 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -25,7 +25,7 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[ // windows temp folders (r"\b[A-Z]:\\.*\\Local\\Temp\\\S+", "[TEMP_FILE]"), (r" in (\d+\.)?\d+(ms|s)\b", " in [EXECUTION_TIME]"), - (r"\\([\w\d.])", "/$1"), + (r"\\\\?([\w\d.])", "/$1"), (r"rye.exe", "rye"), ]; From d245f625ed1e48b794863cc3a69d0a83daf74c5c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 23 Feb 2024 11:49:08 +0100 Subject: [PATCH 6/7] 0.26.0 --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2953505bba..a4753bef7d 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.26.0 -_Unreleased_ +Released on 2024-02-23 - `init` now supports `--script` and `--lib` to generate a script or library project. #738 @@ -25,8 +27,6 @@ _Unreleased_ - Rename `rye tools list` flags: `-i, --include-scripts` to `-s, --include-scripts` and `-v, --version-show` to `-v, --include-version`. #722 - - ## 0.25.0 Released on 2024-02-19 From 993f42ff1df2407574972d10f30b079c83af10d3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 23 Feb 2024 11:49:42 +0100 Subject: [PATCH 7/7] 0.27 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 a4753bef7d..9148ba36d9 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.27.0 + +_Unreleased_ + ## 0.26.0 diff --git a/Cargo.lock b/Cargo.lock index ac0412a6df..0b4ed0aab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1795,7 +1795,7 @@ dependencies = [ [[package]] name = "rye" -version = "0.26.0" +version = "0.27.0" dependencies = [ "age", "anyhow", diff --git a/rye/Cargo.toml b/rye/Cargo.toml index c7a3c384e5..ad6f37ff23 100644 --- a/rye/Cargo.toml +++ b/rye/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rye" -version = "0.26.0" +version = "0.27.0" edition = "2021" license = "MIT"