Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add autosync support #677

Merged
merged 14 commits into from
Feb 20, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ _Unreleased_

- The installer now has slightly better wording for what the shims are doing. #669

- When `uv` is enabled, rye will now automatically sync on `add` and `remove. #677
mitsuhiko marked this conversation as resolved.
Show resolved Hide resolved

<!-- released start -->

## 0.24.0
Expand Down
11 changes: 10 additions & 1 deletion rye/src/cli/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +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::autosync;
use crate::utils::{format_requirement, set_proxy_variables, CommandOutput};

const PACKAGE_FINDER_SCRIPT: &str = r#"
Expand Down Expand Up @@ -209,6 +210,9 @@ pub struct Args {
/// Overrides the pin operator
#[arg(long)]
pin: Option<Pin>,
/// Runs `sync` even if auto-sync is disabled.
#[arg(long)]
sync: bool,
/// Enables verbose diagnostics.
#[arg(short, long)]
verbose: bool,
Expand All @@ -221,6 +225,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()?;
Expand Down Expand Up @@ -250,7 +255,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
}

if !cmd.excluded {
if Config::current().use_uv() {
if cfg.use_uv() {
resolve_requirements_with_uv(
&pyproject_toml,
&self_venv,
Expand Down Expand Up @@ -291,6 +296,10 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
}
}

if cfg.autosync() || cmd.sync {
autosync(&pyproject_toml, output)?;
}

Ok(())
}

Expand Down
4 changes: 4 additions & 0 deletions rye/src/cli/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf>,
Expand All @@ -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()
Expand Down
9 changes: 9 additions & 0 deletions rye/src/cli/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -19,6 +21,9 @@ pub struct Args {
/// Remove this from an optional dependency group.
#[arg(long, conflicts_with = "dev")]
optional: Option<String>,
/// Runs `sync` even if auto-sync is disabled.
#[arg(long)]
sync: bool,
/// Enables verbose diagnostics.
#[arg(short, long)]
verbose: bool,
Expand Down Expand Up @@ -56,5 +61,9 @@ pub fn execute(cmd: Args) -> Result<(), Error> {
}
}

if Config::current().autosync() || cmd.sync {
autosync(&pyproject_toml, output)?;
}

Ok(())
}
4 changes: 4 additions & 0 deletions rye/src/cli/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub struct Args {
/// Use this pyproject.toml file
#[arg(long, value_name = "PYPROJECT_TOML")]
pyproject: Option<PathBuf>,
/// Do not reuse (reset) prior lock options.
#[arg(long)]
reset: bool,
}

pub fn execute(cmd: Args) -> Result<(), Error> {
Expand All @@ -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,
})?;
Expand Down
9 changes: 9 additions & 0 deletions rye/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
let yes = self
Expand Down
81 changes: 69 additions & 12 deletions rye/src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Regex> =
Lazy::new(|| Regex::new(r"^# (pre|features|all-features|with_sources):\s*(.*?)$").unwrap());

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LockMode {
Expand Down Expand Up @@ -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<W: Write>(&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<Cow<'o, LockOptions>, 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.
Expand Down Expand Up @@ -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)?;
Expand All @@ -331,6 +385,9 @@ fn generate_lockfile(
} else if output == CommandOutput::Quiet {
cmd.arg("-q");
}
if lock_options.pre {
cmd.arg("--prerelease=allow");
}
cmd
} else {
let mut cmd = Command::new(get_pip_compile(py_ver, output)?);
Expand All @@ -355,6 +412,9 @@ fn generate_lockfile(
} else {
"-q"
});
if lock_options.pre {
cmd.arg("--pre");
}
cmd
};

Expand All @@ -372,9 +432,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")?;
Expand All @@ -388,7 +445,7 @@ fn generate_lockfile(
workspace_path,
exclusions,
sources,
lock_options,
&lock_options,
)?;

Ok(())
Expand All @@ -403,7 +460,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 {
Expand Down
13 changes: 13 additions & 0 deletions rye/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,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,
Expand Down