mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-26 18:06:45 +00:00
Add uv-workspace crate with settings discovery and deserialization (#3007)
## Summary This PR adds basic struct definitions along with a "workspace" concept for discovering settings. (The "workspace" terminology is used to match Ruff; I did not invent it.) A few notes: - We discover any `pyproject.toml` or `uv.toml` file in any parent directory of the current working directory. (We could adjust this to look at the directories of the input files.) - We don't actually do anything with the configuration yet; but those PRs are large and I want this to be reviewed in isolation.
This commit is contained in:
parent
c0efeeddf6
commit
295b58ad37
21 changed files with 433 additions and 20 deletions
|
|
@ -8,6 +8,7 @@ use std::time::SystemTime;
|
|||
use fs_err as fs;
|
||||
use fs_err::{DirEntry, File};
|
||||
use reflink_copy as reflink;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tempfile::tempdir_in;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
|
|
@ -201,7 +202,7 @@ fn parse_scripts(
|
|||
scripts_from_ini(extras, python_minor, ini)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
pub enum LinkMode {
|
||||
/// Clone (i.e., copy-on-write) packages from the wheel into the site packages.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ once_cell = { workspace = true }
|
|||
reqwest = { workspace = true }
|
||||
reqwest-middleware = { workspace = true }
|
||||
rust-netrc = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use uv_auth::{self, KeyringProvider};
|
|||
/// Keyring provider type to use for credential lookup.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub enum KeyringProviderType {
|
||||
/// Do not use keyring for credential lookup.
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ impl NoBuild {
|
|||
|
||||
#[derive(Debug, Default, Clone, Hash, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub enum IndexStrategy {
|
||||
/// Only use results from the first index that returns a match for a given package name.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use serde::ser::SerializeMap;
|
||||
use std::{
|
||||
collections::{btree_map::Entry, BTreeMap},
|
||||
str::FromStr,
|
||||
|
|
@ -35,12 +36,53 @@ enum ConfigSettingValue {
|
|||
List(Vec<String>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConfigSettingValue {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
match self {
|
||||
ConfigSettingValue::String(value) => serializer.serialize_str(value),
|
||||
ConfigSettingValue::List(values) => serializer.collect_seq(values.iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for ConfigSettingValue {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = ConfigSettingValue;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a string or list of strings")
|
||||
}
|
||||
|
||||
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
Ok(ConfigSettingValue::String(value.to_string()))
|
||||
}
|
||||
|
||||
fn visit_seq<A: serde::de::SeqAccess<'de>>(
|
||||
self,
|
||||
mut seq: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
let mut values = Vec::new();
|
||||
while let Some(value) = seq.next_element()? {
|
||||
values.push(value);
|
||||
}
|
||||
Ok(ConfigSettingValue::List(values))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings to pass to a PEP 517 build backend, structured as a map from (string) key to string or
|
||||
/// list of strings.
|
||||
///
|
||||
/// See: <https://peps.python.org/pep-0517/#config-settings>
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[cfg_attr(not(feature = "serde"), allow(dead_code))]
|
||||
pub struct ConfigSettings(BTreeMap<String, ConfigSettingValue>);
|
||||
|
||||
impl FromIterator<ConfigSettingEntry> for ConfigSettings {
|
||||
|
|
@ -77,23 +119,42 @@ impl ConfigSettings {
|
|||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for ConfigSettings {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
use serde::ser::SerializeMap;
|
||||
|
||||
let mut map = serializer.serialize_map(Some(self.0.len()))?;
|
||||
for (key, value) in &self.0 {
|
||||
match value {
|
||||
ConfigSettingValue::String(value) => {
|
||||
map.serialize_entry(&key, &value)?;
|
||||
}
|
||||
ConfigSettingValue::List(values) => {
|
||||
map.serialize_entry(&key, &values)?;
|
||||
}
|
||||
}
|
||||
map.serialize_entry(key, value)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for ConfigSettings {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = ConfigSettings;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a map from string to string or list of strings")
|
||||
}
|
||||
|
||||
fn visit_map<A: serde::de::MapAccess<'de>>(
|
||||
self,
|
||||
mut map: A,
|
||||
) -> Result<Self::Value, A::Error> {
|
||||
let mut config = BTreeMap::default();
|
||||
while let Some((key, value)) = map.next_entry()? {
|
||||
config.insert(key, value);
|
||||
}
|
||||
Ok(ConfigSettings(config))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_map(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,44 @@ impl FromStr for PackageNameSpecifier {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for PackageNameSpecifier {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = PackageNameSpecifier;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a package name or `:all:` or `:none:`")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
// Accept the special values `:all:` and `:none:`.
|
||||
match value {
|
||||
":all:" => Ok(PackageNameSpecifier::All),
|
||||
":none:" => Ok(PackageNameSpecifier::None),
|
||||
_ => {
|
||||
// Otherwise, parse the value as a package name.
|
||||
match PackageName::from_str(value) {
|
||||
Ok(name) => Ok(PackageNameSpecifier::Package(name)),
|
||||
Err(err) => Err(E::custom(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Package name specification.
|
||||
///
|
||||
/// Consumes both package names and selection directives for compatibility with pip flags
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ petgraph = { workspace = true }
|
|||
pubgrub = { workspace = true }
|
||||
rkyv = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
textwrap = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use chrono::{DateTime, Days, NaiveDate, NaiveTime, Utc};
|
|||
|
||||
/// A timestamp that excludes files newer than it.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ExcludeNewer(DateTime<Utc>);
|
||||
|
||||
impl ExcludeNewer {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::Manifest;
|
|||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub enum PreReleaseMode {
|
||||
/// Disallow all pre-release versions.
|
||||
Disallow,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ use crate::{Manifest, ResolveError};
|
|||
/// package.
|
||||
#[derive(Debug, Default, Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub enum AnnotationStyle {
|
||||
/// Render the annotations on a single, comma-separated line.
|
||||
Line,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use crate::Manifest;
|
|||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
|
||||
pub enum ResolutionMode {
|
||||
/// Resolve the highest compatible version of each package.
|
||||
#[default]
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ authors.workspace = true
|
|||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pep440_rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
uv-client = { workspace = true }
|
||||
uv-extract = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
pep440_rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
fs-err = { workspace = true }
|
||||
|
|
@ -22,6 +22,7 @@ futures = { workspace = true }
|
|||
once_cell = {workspace = true}
|
||||
reqwest = { workspace = true }
|
||||
reqwest-middleware = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ impl FromStr for PythonVersion {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for PythonVersion {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let s = String::deserialize(deserializer)?;
|
||||
PythonVersion::from_str(&s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PythonVersion {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
|
|
|
|||
36
crates/uv-workspace/Cargo.toml
Normal file
36
crates/uv-workspace/Cargo.toml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
[package]
|
||||
name = "uv-workspace"
|
||||
version = "0.0.1"
|
||||
edition = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
documentation = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
distribution-types = { workspace = true }
|
||||
install-wheel-rs = { workspace = true }
|
||||
pep508_rs = { workspace = true }
|
||||
uv-auth = { workspace = true, features = ["serde"] }
|
||||
uv-configuration = { workspace = true, features = ["serde"] }
|
||||
uv-fs = { workspace = true }
|
||||
uv-normalize = { workspace = true }
|
||||
uv-resolver = { workspace = true, features = ["serde"] }
|
||||
uv-toolchain = { workspace = true, features = ["serde"] }
|
||||
uv-warnings = { workspace = true }
|
||||
|
||||
fs-err = { workspace = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serde = ["dep:serde", "dep:serde_json"]
|
||||
5
crates/uv-workspace/src/lib.rs
Normal file
5
crates/uv-workspace/src/lib.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub use crate::settings::*;
|
||||
pub use crate::workspace::*;
|
||||
|
||||
mod settings;
|
||||
mod workspace;
|
||||
88
crates/uv-workspace/src/settings.rs
Normal file
88
crates/uv-workspace/src/settings.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use distribution_types::{FlatIndexLocation, IndexUrl};
|
||||
use install_wheel_rs::linker::LinkMode;
|
||||
use uv_configuration::{ConfigSettings, IndexStrategy, KeyringProviderType, PackageNameSpecifier};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
|
||||
use uv_toolchain::PythonVersion;
|
||||
|
||||
/// A `pyproject.toml` with an (optional) `[tool.uv]` section.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
pub(crate) struct PyProjectToml {
|
||||
pub(crate) tool: Option<Tools>,
|
||||
}
|
||||
|
||||
/// A `[tool]` section.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
pub(crate) struct Tools {
|
||||
pub(crate) uv: Option<Options>,
|
||||
}
|
||||
|
||||
/// A `[tool.uv]` section.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
pub quiet: Option<bool>,
|
||||
pub verbose: Option<bool>,
|
||||
pub native_tls: Option<bool>,
|
||||
pub no_cache: bool,
|
||||
pub cache_dir: Option<PathBuf>,
|
||||
pub pip: Option<PipOptions>,
|
||||
}
|
||||
|
||||
/// A `[tool.uv.pip]` section.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct PipOptions {
|
||||
pub system: Option<bool>,
|
||||
pub offline: Option<bool>,
|
||||
pub index_url: Option<IndexUrl>,
|
||||
pub extra_index_url: Option<IndexUrl>,
|
||||
pub no_index: Option<bool>,
|
||||
pub find_links: Option<Vec<FlatIndexLocation>>,
|
||||
pub index_strategy: Option<IndexStrategy>,
|
||||
pub keyring_provider: Option<KeyringProviderType>,
|
||||
pub no_build: Option<bool>,
|
||||
pub no_binary: Option<Vec<PackageNameSpecifier>>,
|
||||
pub only_binary: Option<Vec<PackageNameSpecifier>>,
|
||||
pub no_build_isolation: Option<bool>,
|
||||
pub resolver: Option<ResolverOptions>,
|
||||
pub installer: Option<InstallerOptions>,
|
||||
}
|
||||
|
||||
/// A `[tool.uv.pip.resolver]` section.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct ResolverOptions {
|
||||
pub resolution: Option<ResolutionMode>,
|
||||
pub prerelease: Option<PreReleaseMode>,
|
||||
pub no_strip_extras: Option<bool>,
|
||||
pub no_annotate: Option<bool>,
|
||||
pub no_header: Option<bool>,
|
||||
pub generate_hashes: Option<bool>,
|
||||
pub legacy_setup_py: Option<bool>,
|
||||
pub config_setting: Option<ConfigSettings>,
|
||||
pub python_version: Option<PythonVersion>,
|
||||
pub exclude_newer: Option<ExcludeNewer>,
|
||||
pub no_emit_package: Option<Vec<PackageName>>,
|
||||
pub emit_index_url: Option<bool>,
|
||||
pub emit_find_links: Option<bool>,
|
||||
pub annotation_style: Option<AnnotationStyle>,
|
||||
}
|
||||
|
||||
/// A `[tool.uv.pip.installer]` section.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct InstallerOptions {
|
||||
pub link_mode: Option<LinkMode>,
|
||||
pub compile_bytecode: Option<bool>,
|
||||
}
|
||||
94
crates/uv-workspace/src/workspace.rs
Normal file
94
crates/uv-workspace/src/workspace.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use uv_fs::Simplified;
|
||||
use uv_warnings::warn_user;
|
||||
|
||||
use crate::{Options, PyProjectToml};
|
||||
|
||||
/// Represents a project workspace that contains a set of options and a root path.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Workspace {
|
||||
options: Options,
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
/// Find the [`Workspace`] for the given path.
|
||||
///
|
||||
/// The search starts at the given path and goes up the directory tree until a workspace is
|
||||
/// found.
|
||||
pub fn find(path: impl AsRef<Path>) -> Result<Option<Self>, WorkspaceError> {
|
||||
for ancestor in path.as_ref().ancestors() {
|
||||
match read_options(ancestor) {
|
||||
Ok(Some(options)) => {
|
||||
return Ok(Some(Self {
|
||||
options,
|
||||
root: ancestor.to_path_buf(),
|
||||
}))
|
||||
}
|
||||
Ok(None) => {
|
||||
// Continue traversing the directory tree.
|
||||
}
|
||||
Err(err @ WorkspaceError::PyprojectToml(..)) => {
|
||||
// If we see an invalid `pyproject.toml`, warn but continue.
|
||||
warn_user!("{err}");
|
||||
}
|
||||
Err(err) => {
|
||||
// Otherwise, warn and stop.
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a `uv.toml` or `pyproject.toml` file in the given directory.
|
||||
fn read_options(dir: &Path) -> Result<Option<Options>, WorkspaceError> {
|
||||
// Read a `uv.toml` file in the current directory.
|
||||
let path = dir.join("uv.toml");
|
||||
match fs_err::read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
let options: Options = toml::from_str(&content)
|
||||
.map_err(|err| WorkspaceError::UvToml(path.user_display().to_string(), err))?;
|
||||
return Ok(Some(options));
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
|
||||
// Read a `pyproject.toml` file in the current directory.
|
||||
let path = path.join("pyproject.toml");
|
||||
match fs_err::read_to_string(&path) {
|
||||
Ok(content) => {
|
||||
// Parse, but skip any `pyproject.toml` that doesn't have a `[tool.uv]` section.
|
||||
let pyproject: PyProjectToml = toml::from_str(&content).map_err(|err| {
|
||||
WorkspaceError::PyprojectToml(path.user_display().to_string(), err)
|
||||
})?;
|
||||
let Some(tool) = pyproject.tool else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(options) = tool.uv else {
|
||||
return Ok(None);
|
||||
};
|
||||
return Ok(Some(options));
|
||||
}
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum WorkspaceError {
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
#[error("Failed to parse `{0}`")]
|
||||
PyprojectToml(String, #[source] toml::de::Error),
|
||||
|
||||
#[error("Failed to parse `{0}`")]
|
||||
UvToml(String, #[source] toml::de::Error),
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ requirements-txt = { workspace = true, features = ["http"] }
|
|||
uv-auth = { workspace = true }
|
||||
uv-cache = { workspace = true, features = ["clap"] }
|
||||
uv-client = { workspace = true }
|
||||
uv-configuration = { workspace = true, features = ["clap"] }
|
||||
uv-dispatch = { workspace = true }
|
||||
uv-distribution = { workspace = true }
|
||||
uv-fs = { workspace = true }
|
||||
|
|
@ -31,11 +32,11 @@ uv-interpreter = { workspace = true }
|
|||
uv-normalize = { workspace = true }
|
||||
uv-requirements = { workspace = true }
|
||||
uv-resolver = { workspace = true, features = ["clap"] }
|
||||
uv-types = { workspace = true, features = ["clap"] }
|
||||
uv-configuration = { workspace = true, features = ["clap"] }
|
||||
uv-virtualenv = { workspace = true }
|
||||
uv-toolchain = { workspace = true }
|
||||
uv-types = { workspace = true, features = ["clap"] }
|
||||
uv-virtualenv = { workspace = true }
|
||||
uv-warnings = { workspace = true }
|
||||
uv-workspace = { workspace = true, features = ["serde", "schemars"] }
|
||||
|
||||
anstream = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -104,6 +104,9 @@ async fn run() -> Result<ExitStatus> {
|
|||
}
|
||||
};
|
||||
|
||||
// Load the workspace settings.
|
||||
let _ = uv_workspace::Workspace::find(env::current_dir()?)?;
|
||||
|
||||
let globals = cli.global_args;
|
||||
|
||||
// Configure the `tracing` crate, which controls internal logging.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue