From 802171af4ecba372a55e2333b1eead173ea794b5 Mon Sep 17 00:00:00 2001 From: Kim Gustyr Date: Tue, 9 Apr 2024 18:23:19 +0100 Subject: [PATCH] Add autosync to `run`, `OneOff` sync mode --- docs/guide/config.md | 5 +++++ rye/src/cli/add.rs | 2 +- rye/src/cli/remove.rs | 2 +- rye/src/cli/run.rs | 28 +++++++++++++++++++++++----- rye/src/cli/test.rs | 10 ++++++++-- rye/src/config.rs | 11 ++++++++++- rye/src/sync.rs | 11 ++++++++--- rye/tests/test_cli.rs | 4 ++++ rye/tests/test_init.rs | 16 ++++++++++++++++ 9 files changed, 76 insertions(+), 13 deletions(-) diff --git a/docs/guide/config.md b/docs/guide/config.md index 46500da6ee..90da5d1c60 100644 --- a/docs/guide/config.md +++ b/docs/guide/config.md @@ -89,6 +89,11 @@ use-uv = false # to `true` when uv is enabled and `false` otherwise. autosync = true +# Enable or disable automatic `sync` ahead of `run` and `test`. This defaults +# to `true` when uv is enabled and `false` otherwise. Note that autosync invoked +# before `run` and `test` will never update your lockfiles. +autosync-before-run = 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/rye/src/cli/add.rs b/rye/src/cli/add.rs index 13a0f281e0..06ada84b31 100644 --- a/rye/src/cli/add.rs +++ b/rye/src/cli/add.rs @@ -303,7 +303,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { } if (cfg.autosync() && !cmd.no_sync) || cmd.sync { - autosync(&pyproject_toml, output)?; + autosync(&pyproject_toml, output, false)?; } Ok(()) diff --git a/rye/src/cli/remove.rs b/rye/src/cli/remove.rs index 73adaa2632..8519019e8c 100644 --- a/rye/src/cli/remove.rs +++ b/rye/src/cli/remove.rs @@ -65,7 +65,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { } if (Config::current().autosync() && !cmd.no_sync) || cmd.sync { - autosync(&pyproject_toml, output)?; + autosync(&pyproject_toml, output, false)?; } Ok(()) diff --git a/rye/src/cli/run.rs b/rye/src/cli/run.rs index 7db917d72b..cdd66a17ef 100644 --- a/rye/src/cli/run.rs +++ b/rye/src/cli/run.rs @@ -8,10 +8,11 @@ use anyhow::{bail, Context, Error}; use clap::Parser; use console::style; +use crate::config::Config; use crate::pyproject::{PyProject, Script}; -use crate::sync::{sync, SyncOptions}; +use crate::sync::{autosync, sync, SyncOptions}; use crate::tui::redirect_to_stderr; -use crate::utils::{exec_spawn, get_venv_python_bin, success_status, IoPathContext}; +use crate::utils::{exec_spawn, get_venv_python_bin, success_status, CommandOutput, IoPathContext}; /// Runs a command installed into this package. #[derive(Parser, Debug)] @@ -26,6 +27,18 @@ pub struct Args { /// Use this pyproject.toml file #[arg(long, value_name = "PYPROJECT_TOML")] pyproject: 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, + /// Turns off all output. + #[arg(short, long, conflicts_with = "verbose")] + quiet: bool, } #[derive(Parser, Debug)] @@ -36,11 +49,16 @@ enum Cmd { pub fn execute(cmd: Args) -> Result<(), Error> { let _guard = redirect_to_stderr(true); + let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); let pyproject = PyProject::load_or_discover(cmd.pyproject.as_deref())?; - // make sure we have the minimal virtualenv. - sync(SyncOptions::python_only().pyproject(cmd.pyproject)) - .context("failed to sync ahead of run")?; + if (Config::current().autosync_before_run() && !cmd.no_sync) || cmd.sync { + autosync(&pyproject, output, true)?; + } else { + // make sure we have the minimal virtualenv. + sync(SyncOptions::python_only().pyproject(cmd.pyproject)) + .context("failed to sync ahead of run")?; + } if cmd.list || cmd.cmd.is_none() { return list_scripts(&pyproject); diff --git a/rye/src/cli/test.rs b/rye/src/cli/test.rs index ff81f820a0..81401c4356 100644 --- a/rye/src/cli/test.rs +++ b/rye/src/cli/test.rs @@ -31,6 +31,12 @@ pub struct Args { // Disable test output capture to stdout #[arg(long = "no-capture", short = 's')] no_capture: bool, + /// 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, @@ -72,8 +78,8 @@ pub fn execute(cmd: Args) -> Result<(), Error> { if !pytest.is_file() { let has_pytest = has_pytest_dependency(&projects)?; if has_pytest { - if Config::current().autosync() { - autosync(&projects[0], output)?; + if (Config::current().autosync_before_run() && !cmd.no_sync) || cmd.sync { + autosync(&projects[0], output, true)?; } else { bail!("pytest not installed but in dependencies. Run `rye sync`.") } diff --git a/rye/src/config.rs b/rye/src/config.rs index 1f578fca80..17ddc931f6 100644 --- a/rye/src/config.rs +++ b/rye/src/config.rs @@ -251,7 +251,7 @@ impl Config { Ok(rv) } - /// Enable autosync. + /// Enable autosync for `add` and `remove`. pub fn autosync(&self) -> bool { self.doc .get("behavior") @@ -259,6 +259,15 @@ impl Config { .and_then(|x| x.as_bool()) .unwrap_or_else(|| self.use_uv()) } + + /// Enable autosync for `run` and `test`. + pub fn autosync_before_run(&self) -> bool { + self.doc + .get("behavior") + .and_then(|x| x.get("autosync-before-run")) + .and_then(|x| x.as_bool()) + .unwrap_or_else(|| self.use_uv()) + } /// Indicates if uv should be used instead of pip-tools. pub fn use_uv(&self) -> bool { diff --git a/rye/src/sync.rs b/rye/src/sync.rs index 2acf751a15..6bcd72867d 100644 --- a/rye/src/sync.rs +++ b/rye/src/sync.rs @@ -37,6 +37,8 @@ pub enum SyncMode { Regular, /// recreate everything Full, + /// Recreate if no lock file present, otherwise install without updating + OneOff, } /// Updates the virtualenv based on the pyproject.toml @@ -177,16 +179,19 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> { // hack to make this work for now. We basically sym-link pip itself // into a folder all by itself and place a second file in there which we // can pass to pip-sync to install the local package. + let has_lock = (if cmd.dev { &dev_lockfile } else { &lockfile }).is_file(); if recreate || cmd.mode != SyncMode::PythonOnly { let sources = ExpandedSources::from_sources(&pyproject.sources()?)?; if cmd.no_lock { let lockfile = if cmd.dev { &dev_lockfile } else { &lockfile }; - if !lockfile.is_file() { + if !has_lock { bail!( "Locking is disabled but lockfile '{}' does not exist", lockfile.display() ); } + } else if cmd.mode == SyncMode::OneOff && has_lock { + // do nothing } else if let Some(workspace) = pyproject.workspace() { // make sure we have an up-to-date lockfile update_workspace_lockfile( @@ -310,11 +315,11 @@ pub fn sync(mut cmd: SyncOptions) -> Result<(), Error> { } /// Performs an autosync. -pub fn autosync(pyproject: &PyProject, output: CommandOutput) -> Result<(), Error> { +pub fn autosync(pyproject: &PyProject, output: CommandOutput, one_off: bool) -> Result<(), Error> { sync(SyncOptions { output, dev: true, - mode: SyncMode::Regular, + mode: if one_off {SyncMode::OneOff} else {SyncMode::Regular}, force: false, no_lock: false, lock_options: LockOptions::default(), diff --git a/rye/tests/test_cli.rs b/rye/tests/test_cli.rs index 10c6765d6a..c3cf08e477 100644 --- a/rye/tests/test_cli.rs +++ b/rye/tests/test_cli.rs @@ -47,5 +47,9 @@ fn test_dotenv() { 42 23 ----- stderr ----- + Reusing already existing virtualenv + Installing dependencies + Audited 1 package in [EXECUTION_TIME] + Done! "###); } diff --git a/rye/tests/test_init.rs b/rye/tests/test_init.rs index a5ce194501..5a576f1654 100644 --- a/rye/tests/test_init.rs +++ b/rye/tests/test_init.rs @@ -41,6 +41,10 @@ fn test_init_lib() { Hello from my-project! ----- stderr ----- + Reusing already existing virtualenv + Installing dependencies + Audited 1 package in [EXECUTION_TIME] + Done! "###); assert!( @@ -89,6 +93,10 @@ fn test_init_default() { Hello from my-project! ----- stderr ----- + Reusing already existing virtualenv + Installing dependencies + Audited 1 package in [EXECUTION_TIME] + Done! "###); assert!( @@ -138,6 +146,10 @@ fn test_init_script() { Hello from my-project! ----- stderr ----- + Reusing already existing virtualenv + Installing dependencies + Audited 1 package in [EXECUTION_TIME] + Done! "###); rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("python").arg("-mmy_project"), @r###" @@ -147,6 +159,10 @@ fn test_init_script() { Hello from my-project! ----- stderr ----- + Reusing already existing virtualenv + Installing dependencies + Audited 1 package in [EXECUTION_TIME] + Done! "###); }