mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Perform lock in uv sync
by default (#4839)
## Summary - `uv sync` will now lock by default. - `uv sync --locked` will lock, and error if the generated lock does not match `uv.lock` on-disk. - `uv sync --frozen` will skip locking and just use `uv.lock`. Closes https://github.com/astral-sh/uv/issues/4812. Closes https://github.com/astral-sh/uv/issues/4803.
This commit is contained in:
parent
8c9bd70c71
commit
540ff24302
16 changed files with 401 additions and 214 deletions
|
@ -1784,6 +1784,14 @@ pub struct RunArgs {
|
|||
#[derive(Args)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct SyncArgs {
|
||||
/// Assert that the `uv.lock` will remain unchanged.
|
||||
#[arg(long, conflicts_with = "frozen")]
|
||||
pub locked: bool,
|
||||
|
||||
/// Install without updating the `uv.lock` file.
|
||||
#[arg(long, conflicts_with = "locked")]
|
||||
pub frozen: bool,
|
||||
|
||||
/// Include optional dependencies from the extra group name; may be provided more than once.
|
||||
/// Only applies to `pyproject.toml`, `setup.py`, and `setup.cfg` sources.
|
||||
#[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)]
|
||||
|
@ -1811,7 +1819,7 @@ pub struct SyncArgs {
|
|||
pub no_clean: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
pub installer: InstallerArgs,
|
||||
pub installer: ResolverInstallerArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub build: BuildArgs,
|
||||
|
|
|
@ -29,7 +29,6 @@ uv-resolver = { workspace = true, features = ["clap"] }
|
|||
uv-types = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
configparser = { workspace = true }
|
||||
console = { workspace = true }
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use std::path::Path;
|
||||
|
||||
use anstream::eprint;
|
||||
use anyhow::Result;
|
||||
|
||||
use requirements_txt::RequirementsTxt;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
use uv_configuration::Upgrade;
|
||||
use uv_distribution::Workspace;
|
||||
use uv_git::ResolvedRepositoryReference;
|
||||
use uv_resolver::{Lock, Preference, PreferenceError};
|
||||
|
||||
|
@ -63,34 +61,6 @@ pub async fn read_requirements_txt(
|
|||
})
|
||||
}
|
||||
|
||||
/// Load the lockfile from the workspace.
|
||||
///
|
||||
/// Returns `Ok(None)` if the lockfile does not exist, is invalid, or is not required for the given upgrade strategy.
|
||||
pub async fn read_lockfile(workspace: &Workspace, upgrade: &Upgrade) -> Result<Option<Lock>> {
|
||||
// As an optimization, skip reading the lockfile is we're upgrading all packages anyway.
|
||||
if upgrade.is_all() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// If an existing lockfile exists, build up a set of preferences.
|
||||
let lockfile = workspace.install_path().join("uv.lock");
|
||||
let lock = match fs_err::tokio::read_to_string(&lockfile).await {
|
||||
Ok(encoded) => match toml::from_str::<Lock>(&encoded) {
|
||||
Ok(lock) => lock,
|
||||
Err(err) => {
|
||||
eprint!("Failed to parse lockfile; ignoring locked requirements: {err}");
|
||||
return Ok(None);
|
||||
}
|
||||
},
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Ok(None);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
Ok(Some(lock))
|
||||
}
|
||||
|
||||
/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy.
|
||||
pub fn read_lock_requirements(lock: &Lock, upgrade: &Upgrade) -> LockedRequirements {
|
||||
let mut preferences = Vec::new();
|
||||
|
|
|
@ -10,6 +10,7 @@ use uv_distribution::pyproject_mut::PyProjectTomlMut;
|
|||
use uv_distribution::{DistributionDatabase, ProjectWorkspace, VirtualProject, Workspace};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_python::{PythonFetch, PythonPreference, PythonRequest};
|
||||
|
||||
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::FlatIndex;
|
||||
use uv_types::{BuildIsolation, HashStrategy};
|
||||
|
@ -17,6 +18,7 @@ use uv_warnings::warn_user_once;
|
|||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::pip::resolution_environment;
|
||||
use crate::commands::project::lock::commit;
|
||||
use crate::commands::reporters::ResolverReporter;
|
||||
use crate::commands::{project, ExitStatus, SharedState};
|
||||
use crate::printer::Printer;
|
||||
|
@ -204,10 +206,14 @@ pub(crate) async fn add(
|
|||
// Initialize any shared state.
|
||||
let state = SharedState::default();
|
||||
|
||||
// Lock and sync the environment.
|
||||
// Read the existing lockfile.
|
||||
let existing = project::lock::read(project.workspace()).await?;
|
||||
|
||||
// Lock and sync the environment, if necessary.
|
||||
let lock = project::lock::do_lock(
|
||||
project.workspace(),
|
||||
venv.interpreter(),
|
||||
existing.as_ref(),
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
preview,
|
||||
|
@ -218,6 +224,9 @@ pub(crate) async fn add(
|
|||
printer,
|
||||
)
|
||||
.await?;
|
||||
if !existing.is_some_and(|existing| existing == lock) {
|
||||
commit(&lock, project.workspace()).await?;
|
||||
}
|
||||
|
||||
// Perform a full sync, because we don't know what exactly is affected by the removal.
|
||||
// TODO(ibraheem): Should we accept CLI overrides for this? Should we even sync here?
|
||||
|
|
|
@ -8,7 +8,7 @@ use uv_dispatch::BuildDispatch;
|
|||
use uv_distribution::{Workspace, DEV_DEPENDENCIES};
|
||||
use uv_git::ResolvedRepositoryReference;
|
||||
use uv_python::{Interpreter, PythonFetch, PythonPreference, PythonRequest};
|
||||
use uv_requirements::upgrade::{read_lock_requirements, read_lockfile, LockedRequirements};
|
||||
use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements};
|
||||
use uv_resolver::{FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython};
|
||||
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
|
||||
use uv_warnings::{warn_user, warn_user_once};
|
||||
|
@ -52,10 +52,14 @@ pub(crate) async fn lock(
|
|||
.await?
|
||||
.into_interpreter();
|
||||
|
||||
// Read the existing lockfile.
|
||||
let existing = read(&workspace).await?;
|
||||
|
||||
// Perform the lock operation.
|
||||
match do_lock(
|
||||
&workspace,
|
||||
&interpreter,
|
||||
existing.as_ref(),
|
||||
settings.as_ref(),
|
||||
&SharedState::default(),
|
||||
preview,
|
||||
|
@ -67,7 +71,12 @@ pub(crate) async fn lock(
|
|||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => Ok(ExitStatus::Success),
|
||||
Ok(lock) => {
|
||||
if !existing.is_some_and(|existing| existing == lock) {
|
||||
commit(&lock, &workspace).await?;
|
||||
}
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
Err(ProjectError::Operation(pip::operations::Error::Resolve(
|
||||
uv_resolver::ResolveError::NoSolution(err),
|
||||
))) => {
|
||||
|
@ -84,6 +93,7 @@ pub(crate) async fn lock(
|
|||
pub(super) async fn do_lock(
|
||||
workspace: &Workspace,
|
||||
interpreter: &Interpreter,
|
||||
existing: Option<&Lock>,
|
||||
settings: ResolverSettingsRef<'_>,
|
||||
state: &SharedState,
|
||||
preview: PreviewMode,
|
||||
|
@ -175,9 +185,7 @@ pub(super) async fn do_lock(
|
|||
};
|
||||
|
||||
// If an existing lockfile exists, build up a set of preferences.
|
||||
let existing_lock = read_lockfile(workspace, upgrade).await?;
|
||||
let LockedRequirements { preferences, git } = existing_lock
|
||||
.as_ref()
|
||||
let LockedRequirements { preferences, git } = existing
|
||||
.map(|lock| read_lock_requirements(lock, upgrade))
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -238,15 +246,29 @@ pub(super) async fn do_lock(
|
|||
// Notify the user of any resolution diagnostics.
|
||||
pip::operations::diagnose_resolution(resolution.diagnostics(), printer)?;
|
||||
|
||||
// Avoid serializing and writing to disk if the lock hasn't changed.
|
||||
let lock = Lock::from_resolution_graph(&resolution)?;
|
||||
if existing_lock.is_some_and(|existing_lock| existing_lock == lock) {
|
||||
return Ok(lock);
|
||||
}
|
||||
|
||||
// Write the lockfile to disk.
|
||||
let encoded = lock.to_toml()?;
|
||||
fs_err::tokio::write(workspace.install_path().join("uv.lock"), encoded.as_bytes()).await?;
|
||||
|
||||
Ok(lock)
|
||||
Ok(Lock::from_resolution_graph(&resolution)?)
|
||||
}
|
||||
|
||||
/// Write the lockfile to disk.
|
||||
pub(crate) async fn commit(lock: &Lock, workspace: &Workspace) -> Result<(), ProjectError> {
|
||||
let encoded = lock.to_toml()?;
|
||||
fs_err::tokio::write(workspace.install_path().join("uv.lock"), encoded).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read the lockfile from the workspace.
|
||||
///
|
||||
/// Returns `Ok(None)` if the lockfile does not exist.
|
||||
pub(crate) async fn read(workspace: &Workspace) -> Result<Option<Lock>, ProjectError> {
|
||||
match fs_err::tokio::read_to_string(&workspace.install_path().join("uv.lock")).await {
|
||||
Ok(encoded) => match toml::from_str::<Lock>(&encoded) {
|
||||
Ok(lock) => Ok(Some(lock)),
|
||||
Err(err) => {
|
||||
eprint!("Failed to parse lockfile; ignoring locked requirements: {err}");
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,14 @@ pub(crate) mod tree;
|
|||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub(crate) enum ProjectError {
|
||||
#[error("The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.")]
|
||||
LockMismatch,
|
||||
|
||||
#[error(
|
||||
"Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`."
|
||||
)]
|
||||
MissingLockfile,
|
||||
|
||||
#[error("The current Python version ({0}) is not compatible with the locked Python requirement: `{1}`")]
|
||||
LockedPythonIncompatibility(Version, RequiresPython),
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ use uv_python::{PythonFetch, PythonPreference, PythonRequest};
|
|||
use uv_warnings::{warn_user, warn_user_once};
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::project::lock::commit;
|
||||
use crate::commands::{project, ExitStatus, SharedState};
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::ResolverInstallerSettings;
|
||||
|
@ -98,10 +99,14 @@ pub(crate) async fn remove(
|
|||
// Initialize any shared state.
|
||||
let state = SharedState::default();
|
||||
|
||||
// Lock and sync the environment.
|
||||
// Read the existing lockfile.
|
||||
let existing = project::lock::read(project.workspace()).await?;
|
||||
|
||||
// Lock and sync the environment, if necessary.
|
||||
let lock = project::lock::do_lock(
|
||||
project.workspace(),
|
||||
venv.interpreter(),
|
||||
existing.as_ref(),
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
preview,
|
||||
|
@ -112,6 +117,9 @@ pub(crate) async fn remove(
|
|||
printer,
|
||||
)
|
||||
.await?;
|
||||
if !existing.is_some_and(|existing| existing == lock) {
|
||||
commit(&lock, project.workspace()).await?;
|
||||
}
|
||||
|
||||
// Perform a full sync, because we don't know what exactly is affected by the removal.
|
||||
// TODO(ibraheem): Should we accept CLI overrides for this? Should we even sync here?
|
||||
|
|
|
@ -22,6 +22,7 @@ use uv_python::{
|
|||
request_from_version_file, EnvironmentPreference, Interpreter, PythonEnvironment, PythonFetch,
|
||||
PythonInstallation, PythonPreference, PythonRequest, VersionRequest,
|
||||
};
|
||||
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
|
@ -179,10 +180,14 @@ pub(crate) async fn run(
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Read the existing lockfile.
|
||||
let existing = project::lock::read(project.workspace()).await?;
|
||||
|
||||
// Lock and sync the environment.
|
||||
let lock = project::lock::do_lock(
|
||||
project.workspace(),
|
||||
venv.interpreter(),
|
||||
existing.as_ref(),
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
preview,
|
||||
|
@ -193,6 +198,11 @@ pub(crate) async fn run(
|
|||
printer,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if !existing.is_some_and(|existing| existing == lock) {
|
||||
project::lock::commit(&lock, project.workspace()).await?;
|
||||
}
|
||||
|
||||
project::sync::do_sync(
|
||||
&project,
|
||||
&venv,
|
||||
|
|
|
@ -7,25 +7,30 @@ use uv_dispatch::BuildDispatch;
|
|||
use uv_distribution::{VirtualProject, DEV_DEPENDENCIES};
|
||||
use uv_installer::SitePackages;
|
||||
use uv_python::{PythonEnvironment, PythonFetch, PythonPreference, PythonRequest};
|
||||
|
||||
use uv_resolver::{FlatIndex, Lock};
|
||||
use uv_types::{BuildIsolation, HashStrategy};
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
use crate::commands::project::lock::do_lock;
|
||||
use crate::commands::project::{ProjectError, SharedState};
|
||||
use crate::commands::{pip, project, ExitStatus};
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::{InstallerSettings, InstallerSettingsRef};
|
||||
use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings};
|
||||
|
||||
/// Sync the project environment.
|
||||
#[allow(clippy::fn_params_excessive_bools)]
|
||||
pub(crate) async fn sync(
|
||||
locked: bool,
|
||||
frozen: bool,
|
||||
extras: ExtrasSpecification,
|
||||
dev: bool,
|
||||
modifications: Modifications,
|
||||
python: Option<String>,
|
||||
python_preference: PythonPreference,
|
||||
python_fetch: PythonFetch,
|
||||
settings: InstallerSettings,
|
||||
settings: ResolverInstallerSettings,
|
||||
preview: PreviewMode,
|
||||
connectivity: Connectivity,
|
||||
concurrency: Concurrency,
|
||||
|
@ -53,12 +58,88 @@ pub(crate) async fn sync(
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Read the lockfile.
|
||||
let lock: Lock = {
|
||||
let encoded =
|
||||
fs_err::tokio::read_to_string(project.workspace().install_path().join("uv.lock"))
|
||||
.await?;
|
||||
toml::from_str(&encoded)?
|
||||
// Initialize any shared state.
|
||||
let state = SharedState::default();
|
||||
|
||||
let lock = if frozen {
|
||||
// Read the existing lockfile.
|
||||
project::lock::read(project.workspace())
|
||||
.await?
|
||||
.ok_or_else(|| ProjectError::MissingLockfile)?
|
||||
} else if locked {
|
||||
// Read the existing lockfile.
|
||||
let existing = project::lock::read(project.workspace())
|
||||
.await?
|
||||
.ok_or_else(|| ProjectError::MissingLockfile)?;
|
||||
|
||||
// Perform the lock operation, but don't write the lockfile to disk.
|
||||
let lock = match do_lock(
|
||||
project.workspace(),
|
||||
venv.interpreter(),
|
||||
Some(&existing),
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
preview,
|
||||
connectivity,
|
||||
concurrency,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(lock) => lock,
|
||||
Err(ProjectError::Operation(pip::operations::Error::Resolve(
|
||||
uv_resolver::ResolveError::NoSolution(err),
|
||||
))) => {
|
||||
let report = miette::Report::msg(format!("{err}"))
|
||||
.context("No solution found when resolving dependencies:");
|
||||
anstream::eprint!("{report:?}");
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
// If the locks disagree, return an error.
|
||||
if lock != existing {
|
||||
return Err(ProjectError::LockMismatch.into());
|
||||
}
|
||||
|
||||
lock
|
||||
} else {
|
||||
// Read the existing lockfile.
|
||||
let existing = project::lock::read(project.workspace()).await?;
|
||||
|
||||
// Perform the lock operation.
|
||||
match do_lock(
|
||||
project.workspace(),
|
||||
venv.interpreter(),
|
||||
existing.as_ref(),
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
preview,
|
||||
connectivity,
|
||||
concurrency,
|
||||
native_tls,
|
||||
cache,
|
||||
printer,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(lock) => {
|
||||
project::lock::commit(&lock, project.workspace()).await?;
|
||||
lock
|
||||
}
|
||||
Err(ProjectError::Operation(pip::operations::Error::Resolve(
|
||||
uv_resolver::ResolveError::NoSolution(err),
|
||||
))) => {
|
||||
let report = miette::Report::msg(format!("{err}"))
|
||||
.context("No solution found when resolving dependencies:");
|
||||
anstream::eprint!("{report:?}");
|
||||
return Ok(ExitStatus::Failure);
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
};
|
||||
|
||||
// Perform the sync operation.
|
||||
|
@ -69,8 +150,8 @@ pub(crate) async fn sync(
|
|||
extras,
|
||||
dev,
|
||||
modifications,
|
||||
settings.as_ref(),
|
||||
&SharedState::default(),
|
||||
settings.as_ref().into(),
|
||||
&state,
|
||||
preview,
|
||||
connectivity,
|
||||
concurrency,
|
||||
|
|
|
@ -10,15 +10,15 @@ use uv_client::Connectivity;
|
|||
use uv_configuration::{Concurrency, PreviewMode};
|
||||
use uv_distribution::Workspace;
|
||||
use uv_python::{PythonFetch, PythonPreference, PythonRequest};
|
||||
|
||||
use uv_warnings::warn_user_once;
|
||||
|
||||
use crate::commands::pip::tree::DisplayDependencyGraph;
|
||||
use crate::commands::project::FoundInterpreter;
|
||||
use crate::commands::ExitStatus;
|
||||
use crate::commands::{project, ExitStatus};
|
||||
use crate::printer::Printer;
|
||||
use crate::settings::ResolverSettings;
|
||||
|
||||
use super::lock::do_lock;
|
||||
use super::SharedState;
|
||||
|
||||
/// Run a command.
|
||||
|
@ -60,10 +60,14 @@ pub(crate) async fn tree(
|
|||
.await?
|
||||
.into_interpreter();
|
||||
|
||||
// Update the lock file.
|
||||
let lock = do_lock(
|
||||
// Read the existing lockfile.
|
||||
let existing = project::lock::read(&workspace).await?;
|
||||
|
||||
// Update the lock file, if necessary.
|
||||
let lock = project::lock::do_lock(
|
||||
&workspace,
|
||||
&interpreter,
|
||||
existing.as_ref(),
|
||||
settings.as_ref(),
|
||||
&SharedState::default(),
|
||||
preview,
|
||||
|
@ -74,6 +78,9 @@ pub(crate) async fn tree(
|
|||
printer,
|
||||
)
|
||||
.await?;
|
||||
if !existing.is_some_and(|existing| existing == lock) {
|
||||
project::lock::commit(&lock, &workspace).await?;
|
||||
}
|
||||
|
||||
// Read packages from the lockfile.
|
||||
let mut packages: IndexMap<_, Vec<_>> = IndexMap::new();
|
||||
|
|
|
@ -884,6 +884,8 @@ async fn run_project(
|
|||
let cache = cache.init()?.with_refresh(args.refresh);
|
||||
|
||||
commands::sync(
|
||||
args.locked,
|
||||
args.frozen,
|
||||
args.extras,
|
||||
args.dev,
|
||||
args.modifications,
|
||||
|
|
|
@ -9,7 +9,7 @@ use install_wheel_rs::linker::LinkMode;
|
|||
use pep508_rs::{ExtraName, RequirementOrigin};
|
||||
use pypi_types::Requirement;
|
||||
use uv_cache::{CacheArgs, Refresh};
|
||||
use uv_cli::options::{flag, installer_options, resolver_installer_options, resolver_options};
|
||||
use uv_cli::options::{flag, resolver_installer_options, resolver_options};
|
||||
use uv_cli::{
|
||||
AddArgs, ColorChoice, Commands, ExternalCommand, GlobalArgs, ListFormat, LockArgs, Maybe,
|
||||
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
|
||||
|
@ -29,8 +29,7 @@ use uv_python::{Prefix, PythonFetch, PythonPreference, PythonVersion, Target};
|
|||
use uv_requirements::RequirementsSource;
|
||||
use uv_resolver::{AnnotationStyle, DependencyMode, ExcludeNewer, PreReleaseMode, ResolutionMode};
|
||||
use uv_settings::{
|
||||
Combine, FilesystemOptions, InstallerOptions, Options, PipOptions, ResolverInstallerOptions,
|
||||
ResolverOptions,
|
||||
Combine, FilesystemOptions, Options, PipOptions, ResolverInstallerOptions, ResolverOptions,
|
||||
};
|
||||
|
||||
use crate::commands::pip::operations::Modifications;
|
||||
|
@ -415,12 +414,14 @@ impl PythonFindSettings {
|
|||
#[allow(clippy::struct_excessive_bools, dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SyncSettings {
|
||||
pub(crate) locked: bool,
|
||||
pub(crate) frozen: bool,
|
||||
pub(crate) extras: ExtrasSpecification,
|
||||
pub(crate) dev: bool,
|
||||
pub(crate) modifications: Modifications,
|
||||
pub(crate) python: Option<String>,
|
||||
pub(crate) refresh: Refresh,
|
||||
pub(crate) settings: InstallerSettings,
|
||||
pub(crate) settings: ResolverInstallerSettings,
|
||||
}
|
||||
|
||||
impl SyncSettings {
|
||||
|
@ -428,6 +429,8 @@ impl SyncSettings {
|
|||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub(crate) fn resolve(args: SyncArgs, filesystem: Option<FilesystemOptions>) -> Self {
|
||||
let SyncArgs {
|
||||
locked,
|
||||
frozen,
|
||||
extra,
|
||||
all_extras,
|
||||
no_all_extras,
|
||||
|
@ -447,6 +450,8 @@ impl SyncSettings {
|
|||
};
|
||||
|
||||
Self {
|
||||
locked,
|
||||
frozen,
|
||||
extras: ExtrasSpecification::from_args(
|
||||
flag(all_extras, no_all_extras).unwrap_or_default(),
|
||||
extra.unwrap_or_default(),
|
||||
|
@ -455,7 +460,10 @@ impl SyncSettings {
|
|||
modifications,
|
||||
python,
|
||||
refresh: Refresh::from(refresh),
|
||||
settings: InstallerSettings::combine(installer_options(installer, build), filesystem),
|
||||
settings: ResolverInstallerSettings::combine(
|
||||
resolver_installer_options(installer, build),
|
||||
filesystem,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1272,20 +1280,6 @@ impl VenvSettings {
|
|||
///
|
||||
/// Combines the `[tool.uv]` persistent configuration with the command-line arguments
|
||||
/// ([`InstallerArgs`], represented as [`InstallerOptions`]).
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct InstallerSettings {
|
||||
pub(crate) index_locations: IndexLocations,
|
||||
pub(crate) index_strategy: IndexStrategy,
|
||||
pub(crate) keyring_provider: KeyringProviderType,
|
||||
pub(crate) config_setting: ConfigSettings,
|
||||
pub(crate) exclude_newer: Option<ExcludeNewer>,
|
||||
pub(crate) link_mode: LinkMode,
|
||||
pub(crate) compile_bytecode: bool,
|
||||
pub(crate) reinstall: Reinstall,
|
||||
pub(crate) build_options: BuildOptions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InstallerSettingsRef<'a> {
|
||||
pub(crate) index_locations: &'a IndexLocations,
|
||||
|
@ -1299,100 +1293,6 @@ pub(crate) struct InstallerSettingsRef<'a> {
|
|||
pub(crate) build_options: &'a BuildOptions,
|
||||
}
|
||||
|
||||
impl InstallerSettings {
|
||||
/// Resolve the [`InstallerSettings`] from the CLI and filesystem configuration.
|
||||
pub(crate) fn combine(args: InstallerOptions, filesystem: Option<FilesystemOptions>) -> Self {
|
||||
let ResolverInstallerOptions {
|
||||
index_url,
|
||||
extra_index_url,
|
||||
no_index,
|
||||
find_links,
|
||||
index_strategy,
|
||||
keyring_provider,
|
||||
resolution: _,
|
||||
prerelease: _,
|
||||
config_settings,
|
||||
exclude_newer,
|
||||
link_mode,
|
||||
compile_bytecode,
|
||||
upgrade: _,
|
||||
upgrade_package: _,
|
||||
reinstall,
|
||||
reinstall_package,
|
||||
no_build,
|
||||
no_build_package,
|
||||
no_binary,
|
||||
no_binary_package,
|
||||
} = filesystem
|
||||
.map(FilesystemOptions::into_options)
|
||||
.map(|options| options.top_level)
|
||||
.unwrap_or_default();
|
||||
|
||||
Self {
|
||||
index_locations: IndexLocations::new(
|
||||
args.index_url.combine(index_url),
|
||||
args.extra_index_url
|
||||
.combine(extra_index_url)
|
||||
.unwrap_or_default(),
|
||||
args.find_links.combine(find_links).unwrap_or_default(),
|
||||
args.no_index.combine(no_index).unwrap_or_default(),
|
||||
),
|
||||
index_strategy: args
|
||||
.index_strategy
|
||||
.combine(index_strategy)
|
||||
.unwrap_or_default(),
|
||||
keyring_provider: args
|
||||
.keyring_provider
|
||||
.combine(keyring_provider)
|
||||
.unwrap_or_default(),
|
||||
config_setting: args
|
||||
.config_settings
|
||||
.combine(config_settings)
|
||||
.unwrap_or_default(),
|
||||
exclude_newer: args.exclude_newer.combine(exclude_newer),
|
||||
link_mode: args.link_mode.combine(link_mode).unwrap_or_default(),
|
||||
compile_bytecode: args
|
||||
.compile_bytecode
|
||||
.combine(compile_bytecode)
|
||||
.unwrap_or_default(),
|
||||
reinstall: Reinstall::from_args(
|
||||
args.reinstall.combine(reinstall),
|
||||
args.reinstall_package
|
||||
.combine(reinstall_package)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
build_options: BuildOptions::new(
|
||||
NoBinary::from_args(
|
||||
args.no_binary.combine(no_binary),
|
||||
args.no_binary_package
|
||||
.combine(no_binary_package)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
NoBuild::from_args(
|
||||
args.no_build.combine(no_build),
|
||||
args.no_build_package
|
||||
.combine(no_build_package)
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_ref(&self) -> InstallerSettingsRef {
|
||||
InstallerSettingsRef {
|
||||
index_locations: &self.index_locations,
|
||||
index_strategy: self.index_strategy,
|
||||
keyring_provider: self.keyring_provider,
|
||||
config_setting: &self.config_setting,
|
||||
exclude_newer: self.exclude_newer,
|
||||
link_mode: self.link_mode,
|
||||
compile_bytecode: self.compile_bytecode,
|
||||
reinstall: &self.reinstall,
|
||||
build_options: &self.build_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The resolved settings to use for an invocation of the `uv` CLI when resolving dependencies.
|
||||
///
|
||||
/// Combines the `[tool.uv]` persistent configuration with the command-line arguments
|
||||
|
|
|
@ -112,7 +112,7 @@ fn add_registry() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -149,7 +149,7 @@ fn add_git() -> Result<()> {
|
|||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -270,7 +270,7 @@ fn add_git() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -307,7 +307,7 @@ fn add_git_raw() -> Result<()> {
|
|||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -416,7 +416,7 @@ fn add_git_raw() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -506,7 +506,7 @@ fn add_unnamed() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -626,7 +626,7 @@ fn add_remove_dev() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -705,7 +705,7 @@ fn add_remove_dev() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -786,7 +786,7 @@ fn add_remove_optional() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -862,7 +862,7 @@ fn add_remove_optional() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -993,7 +993,7 @@ fn add_remove_workspace() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().current_dir(&child1), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(&child1), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1062,7 +1062,7 @@ fn add_remove_workspace() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().current_dir(&child1), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(&child1), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1173,7 +1173,7 @@ fn add_workspace_editable() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().current_dir(&child1), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").current_dir(&child1), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1212,7 +1212,7 @@ fn update() -> Result<()> {
|
|||
Resolved 6 packages in [TIME]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1451,7 +1451,7 @@ fn update() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1490,7 +1490,7 @@ fn add_no_clean() -> Result<()> {
|
|||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1579,7 +1579,7 @@ fn add_no_clean() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile without cleaning the environment.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-clean"), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--no-clean"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1590,7 +1590,7 @@ fn add_no_clean() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install from the lockfile, cleaning the environment.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1630,7 +1630,7 @@ fn remove_registry() -> Result<()> {
|
|||
Resolved 4 packages in [TIME]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1698,7 +1698,7 @@ fn remove_registry() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
|
@ -88,7 +88,7 @@ fn lock_wheel_registry() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -160,7 +160,7 @@ fn lock_sdist_registry() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().env_remove("UV_EXCLUDE_NEWER"), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").env_remove("UV_EXCLUDE_NEWER"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -229,7 +229,7 @@ fn lock_sdist_git() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -323,7 +323,7 @@ fn lock_wheel_url() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -417,7 +417,7 @@ fn lock_sdist_url() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -531,7 +531,7 @@ fn lock_project_extra() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install the base dependencies from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -547,7 +547,7 @@ fn lock_project_extra() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install the extras from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("test"), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra").arg("test"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -591,7 +591,7 @@ fn lock_project_with_overrides() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install the base dependencies from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -772,7 +772,7 @@ fn lock_dependency_extra() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -998,7 +998,7 @@ fn lock_conditional_dependency_extra() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1022,7 +1022,7 @@ fn lock_conditional_dependency_extra() -> Result<()> {
|
|||
fs_err::copy(lockfile, context_38.temp_dir.join("uv.lock"))?;
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context_38.filters(), context_38.sync(), @r###"
|
||||
uv_snapshot!(context_38.filters(), context_38.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1189,7 +1189,7 @@ fn lock_dependency_non_existent_extra() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -1943,7 +1943,7 @@ fn lock_requires_python() -> Result<()> {
|
|||
|
||||
// Install from the lockfile.
|
||||
// Note we need to disable Python fetches or we'll just download 3.12
|
||||
uv_snapshot!(filters, context38.sync().arg("--python-fetch").arg("manual"), @r###"
|
||||
uv_snapshot!(filters, context38.sync().arg("--frozen").arg("--python-fetch").arg("manual"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
@ -2398,7 +2398,7 @@ fn lock_dev() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile, excluding development dependencies.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--no-dev"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -2412,7 +2412,7 @@ fn lock_dev() -> Result<()> {
|
|||
"###);
|
||||
|
||||
// Install from the lockfile, including development dependencies (the default).
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -2806,7 +2806,7 @@ fn lock_cycles() -> Result<()> {
|
|||
});
|
||||
|
||||
// Install from the lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
164
crates/uv/tests/sync.rs
Normal file
164
crates/uv/tests/sync.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||
|
||||
use anyhow::Result;
|
||||
use assert_cmd::prelude::*;
|
||||
use assert_fs::prelude::*;
|
||||
|
||||
use common::{uv_snapshot, TestContext};
|
||||
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
fn sync() -> 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` should generate a lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
"###);
|
||||
|
||||
assert!(context.temp_dir.child("uv.lock").exists());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locked() -> 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 = ["anyio==3.7.0"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Running with `--locked` should error, if no lockfile is present.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`.
|
||||
"###);
|
||||
|
||||
// Lock the initial requirements.
|
||||
context.lock().assert().success();
|
||||
|
||||
let existing = fs_err::read_to_string(context.temp_dir.child("uv.lock"))?;
|
||||
|
||||
// Update the requirements.
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Running with `--locked` should error.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Resolved 2 packages in [TIME]
|
||||
error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.
|
||||
"###);
|
||||
|
||||
let updated = fs_err::read_to_string(context.temp_dir.child("uv.lock"))?;
|
||||
|
||||
// And the lockfile should be unchanged.
|
||||
assert_eq!(existing, updated);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen() -> 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 = ["anyio==3.7.0"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Running with `--frozen` should error, if no lockfile is present.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`.
|
||||
"###);
|
||||
|
||||
context.lock().assert().success();
|
||||
|
||||
// Update the requirements.
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// Running with `--frozen` should install the stale lockfile.
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv sync` is experimental and may change without warning.
|
||||
Prepared 4 packages in [TIME]
|
||||
Installed 4 packages in [TIME]
|
||||
+ anyio==3.7.0
|
||||
+ idna==3.6
|
||||
+ project==0.1.0 (from file://[TEMP_DIR]/)
|
||||
+ sniffio==1.3.1
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue