mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00

In *some* places in our crates, `serde` (and `rkyv`) are optional dependencies. I believe this was done out of reasons of "good sense," that is, it follows a Rust ecosystem pattern where serde integration tends to be an opt-in crate feature. (And similarly for `rkyv`.) However, ultimately, `uv` itself requires `serde` and `rkyv` to function. Since our crates are strictly internal, there are limited consumers for our crates without `serde` (and `rkyv`) enabled. I think one possibility is that optional `serde` (and `rkyv`) integration means that someone can do this: cargo test -p pep440_rs And this will run tests _without_ `serde` or `rkyv` enabled. That in turn could lead to faster iteration time by reducing compile times. But, I'm not sure this is worth supporting. The iterative compilation times of individual crates are probably fast enough in debug mode, even with `serde` and `rkyv` enabled. Namely, `serde` and `rkyv` themselves shouldn't need to be re-compiled in most cases. On `main`: ``` from-scratch: `cargo test -p pep440_rs --lib` 0.685 incremental: `cargo test -p pep440_rs --lib` 0.278s from-scratch: `cargo test -p pep440_rs --features serde,rkyv --lib` 3.948s incremental: `cargo test -p pep440_rs --features serde,rkyv --lib` 0.321s ``` So while a from-scratch build does take significantly longer, an incremental build is about the same. The benefit of doing this change is two-fold: 1. It brings out crates into alignment with "reality." In particular, some crates were _implicitly_ relying on `serde` being enabled without explicitly declaring it. This technically means that our `Cargo.toml`s were wrong in some cases, but it is hard to observe it because of feature unification in a Cargo workspace. 2. We no longer need to deal with the cognitive burden of writing `#[cfg_attr(feature = "serde", ...)]` everywhere.
99 lines
3.2 KiB
Rust
99 lines
3.2 KiB
Rust
use std::fmt::{Display, Formatter};
|
|
use std::path::Path;
|
|
use std::str::FromStr;
|
|
|
|
#[cfg(feature = "pyo3")]
|
|
use pyo3::pyclass;
|
|
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
|
|
|
use uv_normalize::ExtraName;
|
|
|
|
use crate::{Cursor, MarkerEnvironment, MarkerTree, Pep508Error, VerbatimUrl};
|
|
|
|
/// A PEP 508-like, direct URL dependency specifier without a package name.
|
|
///
|
|
/// In a `requirements.txt` file, the name of the package is optional for direct URL
|
|
/// dependencies. This isn't compliant with PEP 508, but is common in `requirements.txt`, which
|
|
/// is implementation-defined.
|
|
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
|
|
#[cfg_attr(feature = "pyo3", pyclass(module = "pep508"))]
|
|
pub struct UnnamedRequirement {
|
|
/// The direct URL that defines the version specifier.
|
|
pub url: VerbatimUrl,
|
|
/// The list of extras such as `security`, `tests` in
|
|
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
|
|
pub extras: Vec<ExtraName>,
|
|
/// The markers such as `python_version > "3.8"` in
|
|
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
|
|
/// Those are a nested and/or tree.
|
|
pub marker: Option<MarkerTree>,
|
|
}
|
|
|
|
impl UnnamedRequirement {
|
|
/// Returns whether the markers apply for the given environment
|
|
pub fn evaluate_markers(&self, env: &MarkerEnvironment, extras: &[ExtraName]) -> bool {
|
|
if let Some(marker) = &self.marker {
|
|
marker.evaluate(env, extras)
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for UnnamedRequirement {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.url)?;
|
|
if !self.extras.is_empty() {
|
|
write!(
|
|
f,
|
|
"[{}]",
|
|
self.extras
|
|
.iter()
|
|
.map(ToString::to_string)
|
|
.collect::<Vec<_>>()
|
|
.join(",")
|
|
)?;
|
|
}
|
|
if let Some(marker) = &self.marker {
|
|
write!(f, " ; {}", marker)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// <https://github.com/serde-rs/serde/issues/908#issuecomment-298027413>
|
|
impl<'de> Deserialize<'de> for UnnamedRequirement {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s = String::deserialize(deserializer)?;
|
|
FromStr::from_str(&s).map_err(de::Error::custom)
|
|
}
|
|
}
|
|
|
|
/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
|
|
impl Serialize for UnnamedRequirement {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.collect_str(self)
|
|
}
|
|
}
|
|
|
|
impl FromStr for UnnamedRequirement {
|
|
type Err = Pep508Error;
|
|
|
|
/// Parse a PEP 508-like direct URL requirement without a package name.
|
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
|
crate::parse_unnamed_requirement(&mut Cursor::new(input), None)
|
|
}
|
|
}
|
|
|
|
impl UnnamedRequirement {
|
|
/// Parse a PEP 508-like direct URL requirement without a package name.
|
|
pub fn parse(input: &str, working_dir: impl AsRef<Path>) -> Result<Self, Pep508Error> {
|
|
crate::parse_unnamed_requirement(&mut Cursor::new(input), Some(working_dir.as_ref()))
|
|
}
|
|
}
|