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 uv sync --check flag #12342

Merged
merged 5 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3283,6 +3283,15 @@ pub struct SyncArgs {
value_parser = parse_maybe_string,
)]
pub python: Option<Maybe<String>>,

/// Check if the Python environment is synchronized with the project.
///
/// If the environment is not up to date, uv will exit with an error.
#[arg(long, overrides_with("no_check"))]
pub check: bool,

#[arg(long, overrides_with("check"), hide = true)]
pub no_check: bool,
}

#[derive(Args)]
Expand Down
5 changes: 4 additions & 1 deletion crates/uv-configuration/src/dry_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
pub enum DryRun {
/// The operation should execute in dry run mode.
Enabled,
/// The operation should execute in dry run mode and check if the current environment is
/// synced.
Check,
/// The operation should execute in normal mode.
#[default]
Disabled,
Expand All @@ -19,6 +22,6 @@ impl DryRun {

/// Returns `true` if dry run mode is enabled.
pub const fn enabled(&self) -> bool {
matches!(self, DryRun::Enabled)
matches!(self, DryRun::Enabled) || matches!(self, DryRun::Check)
}
}
10 changes: 9 additions & 1 deletion crates/uv/src/commands/pip/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ pub(crate) async fn install(
.context("Failed to determine installation plan")?;

if dry_run.enabled() {
report_dry_run(resolution, plan, modifications, start, printer)?;
report_dry_run(dry_run, resolution, plan, modifications, start, printer)?;
return Ok(Changelog::default());
}

Expand Down Expand Up @@ -665,6 +665,7 @@ pub(crate) fn report_target_environment(

/// Report on the results of a dry-run installation.
fn report_dry_run(
dry_run: DryRun,
resolution: &Resolution,
plan: Plan,
modifications: Modifications,
Expand Down Expand Up @@ -788,6 +789,10 @@ fn report_dry_run(
}
}

if matches!(dry_run, DryRun::Check) {
return Err(Error::OutdatedEnvironment);
}

Ok(())
}

Expand Down Expand Up @@ -859,4 +864,7 @@ pub(crate) enum Error {

#[error(transparent)]
Anyhow(#[from] anyhow::Error),

#[error("The environment is outdated; run `{}` to update the environment", "uv sync".cyan())]
OutdatedEnvironment,
}
11 changes: 10 additions & 1 deletion crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,8 @@ impl SyncSettings {
package,
script,
python,
check,
no_check,
} = args;
let install_mirrors = filesystem
.clone()
Expand All @@ -1085,10 +1087,17 @@ impl SyncSettings {
filesystem,
);

let check = flag(check, no_check).unwrap_or_default();
let dry_run = if check {
DryRun::Check
} else {
DryRun::from_args(dry_run)
};

Self {
locked,
frozen,
dry_run: DryRun::from_args(dry_run),
dry_run,
script,
active: flag(active, no_active),
extras: ExtrasSpecification::from_args(
Expand Down
62 changes: 62 additions & 0 deletions crates/uv/tests/it/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,68 @@ fn mixed_requires_python() -> Result<()> {
Ok(())
}

#[test]
fn check() -> Result<()> {
let context = TestContext::new("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig"]
"#,
)?;

// Running `uv sync --check` should fail.
uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###"
success: false
exit_code: 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be exit code 2 or 1? I think we probably should use 1.

----- stdout -----

----- stderr -----
Discovered existing environment at: .venv
Resolved 2 packages in [TIME]
Would create lockfile at: uv.lock
Would download 1 package
Would install 1 package
+ iniconfig==2.0.0
error: The environment is outdated; run `uv sync` to update the environment
"###);

// Sync the environment.
uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 2 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
"###);

assert!(context.temp_dir.child("uv.lock").exists());

// Running `uv sync --check` should pass now that the environment is up to date.
uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Discovered existing environment at: .venv
Resolved 2 packages in [TIME]
Found up-to-date lockfile at: uv.lock
Audited 1 package in [TIME]
Would make no changes
"###);
Ok(())
}

/// Sync development dependencies in a (legacy) non-project workspace root.
#[test]
fn sync_legacy_non_project_dev_dependencies() -> Result<()> {
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,10 @@ uv sync [OPTIONS]
<p>To view the location of the cache directory, run <code>uv cache dir</code>.</p>

<p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p>
</dd><dt id="uv-sync--check"><a href="#uv-sync--check"><code>--check</code></a></dt><dd><p>Check if the Python environment is synchronized with the project.</p>

<p>If the environment is not up to date, uv will exit with an error.</p>

</dd><dt id="uv-sync--color"><a href="#uv-sync--color"><code>--color</code></a> <i>color-choice</i></dt><dd><p>Control the use of color in output.</p>

<p>By default, uv will automatically detect support for colors when writing to a terminal.</p>
Expand Down
Loading