manifest.rs (88731B)
1 #![expect(clippy::std_instead_of_core, reason = "false positive")] 2 use super::{ 3 args::NonZeroUsizePlus1, 4 cargo::{Toolchain, ToolchainErr}, 5 }; 6 use alloc::borrow::Cow; 7 use core::cmp::Ordering; 8 use std::{ 9 fs::{File, TryLockError}, 10 io::{Error, ErrorKind, Read as _, StderrLock, Write as _}, 11 path::{Path, PathBuf}, 12 }; 13 use toml::{ 14 Spanned, 15 de::{DeArray, DeValue, Error as TomlErr}, 16 map::Map, 17 }; 18 /// Unit tests. 19 #[cfg(test)] 20 mod tests; 21 /// `"workspace"`. 22 const WORKSPACE: &str = "workspace"; 23 /// `"package"`. 24 const PACKAGE: &str = "package"; 25 /// `"rust-version"`. 26 const RUST_VERSION: &str = "rust-version"; 27 /// Error returned from extracting `"workspace"`. 28 #[cfg_attr(test, derive(Debug, PartialEq))] 29 #[derive(Clone, Copy)] 30 pub(crate) enum WorkspaceErr { 31 /// Variant returned when there is no `"workspace"` key. 32 Missing, 33 /// Variant returned when `"workspace"` is not a table. 34 InvalidType, 35 /// Variant returned when `workspace.package` does not exist. 36 MissingPackage, 37 /// Variant returned when `workspace.package` is not a table. 38 InvalidPackageType, 39 /// Variant returned when `workspace.package.rust-version` does not exist. 40 MissingPackageMsrv, 41 /// Variant returned when `workspace.package.rust-version` is not a string. 42 InvalidPackageMsrvType, 43 /// Variant returned when `workspace.package.rust-version` is not a valid MSRV. 44 Msrv, 45 } 46 impl WorkspaceErr { 47 /// Writes `self` to `stderr`. 48 fn write(self, mut stderr: StderrLock<'_>, file: &Path) -> Result<(), Error> { 49 match self { 50 Self::Missing => writeln!( 51 stderr, 52 "'{WORKSPACE}' does not exist in {}.", 53 file.display() 54 ), 55 Self::InvalidType => writeln!( 56 stderr, 57 "'{WORKSPACE}' exists but is not a table in {}.", 58 file.display() 59 ), 60 Self::MissingPackage => writeln!( 61 stderr, 62 "'{WORKSPACE}.{PACKAGE}' does not exist in {}.", 63 file.display() 64 ), 65 Self::InvalidPackageType => writeln!( 66 stderr, 67 "'{WORKSPACE}.{PACKAGE}' exists but is not a table in {}.", 68 file.display() 69 ), 70 Self::MissingPackageMsrv => writeln!( 71 stderr, 72 "'{WORKSPACE}.{PACKAGE}.{RUST_VERSION}' does not exist in {}.", 73 file.display() 74 ), 75 Self::InvalidPackageMsrvType => writeln!( 76 stderr, 77 "'{WORKSPACE}.{PACKAGE}.{RUST_VERSION}' exists but is not a string in {}.", 78 file.display() 79 ), 80 Self::Msrv => writeln!( 81 stderr, 82 "'{WORKSPACE}.{PACKAGE}.{RUST_VERSION}' exists but is not a valid MSRV in {}.", 83 file.display() 84 ), 85 } 86 } 87 } 88 /// `"name"`. 89 const NAME: &str = "name"; 90 /// Error returned from extracting `"package"`. 91 #[cfg_attr(test, derive(Debug))] 92 pub(crate) enum PackageErr { 93 /// Variant returned when there is no `"package"` key. 94 Missing, 95 /// Variant returned when `"package"` is not a table. 96 InvalidType, 97 /// Variant returned when `packagen.name` does not exist. 98 MissingName, 99 /// Variant returned when `package.name` is not a string. 100 InvalidNameType, 101 /// Variant returned when `package.rust-version` is not a string nor table. 102 InvalidMsrvType, 103 /// Variant returned when `package.rust-version` is not a valid MSRV. 104 Msrv, 105 /// Variant returned when `package.rust-version` is table that doesn't contain the `"workspace"` key. 106 MsrvWorkspaceMissing, 107 /// Variant returned when `package.rust-version.workspace` is not a Boolean with value `true`. 108 MsrvWorkspaceVal, 109 /// Variant returned when `package.workspace` is not a string. 110 InvalidWorkspaceType, 111 /// Variant returned when searching for the workspace file errors. 112 WorkspaceIo(Error), 113 /// Variant when locking the workspace file errors. 114 WorkspaceLock(TryLockError), 115 /// Variant when the length of the workspace file does not match the length reported from the file system. 116 WorkspaceLenMismatch, 117 /// Variant returned when there is no workspace `Cargo.toml`. 118 /// 119 /// This is only returned if the package's MSRV is inherited from the workspace, there is no 120 /// `workspace` key in the package's `Cargo.toml`, and there is no `workspace` key in the table 121 /// `package` (i.e., this is only returned when we must search for it by crawling up the directory). 122 WorkspaceDoesNotExist, 123 /// Variant returned when the file located at `package.workspace` could not be read. 124 /// 125 /// This is only returned if the table `package` had a key `workspace` that was a string, or we searched 126 /// for the workspace and found a `Cargo.toml`. 127 WorkspaceRead(Error, PathBuf), 128 /// Variant returned when the file located at `package.workspace` could not be locked. 129 /// 130 /// This is only returned if the table `package` had a key `workspace` that was a string, or we searched 131 /// for the workspace and found a `Cargo.toml`. 132 WorkspaceReadLock(TryLockError, PathBuf), 133 /// Variant returned when the length of the file located at `package.workspace` does not match the length 134 /// reported by the file system. 135 /// 136 /// This is only returned if the table `package` had a key `workspace` that was a string, or we searched 137 /// for the workspace and found a `Cargo.toml`. 138 WorkspaceReadLenMismatch(PathBuf), 139 /// Variant returned when the file located at `package.workspace` is not valid TOML. 140 /// 141 /// This is only returned if the table `package` had a key `workspace` that was a string, or we searched 142 /// for the workspace and found a `Cargo.toml`. 143 WorkspaceToml(TomlErr, PathBuf), 144 /// Variant returned when `package.rust-version` defers to the workspace MSRV, but there was an error 145 /// extracting the workspace MSRV from the file located at the contained `PathBuf`. 146 Workspace(WorkspaceErr, PathBuf), 147 } 148 impl PackageErr { 149 /// Writes `self` to `stderr`. 150 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 151 fn write(self, mut stderr: StderrLock<'_>, file: &Path) -> Result<(), Error> { 152 match self { 153 Self::Missing => writeln!(stderr, "'{PACKAGE}' does not exist in {}.", file.display()), 154 Self::InvalidType => writeln!( 155 stderr, 156 "'{PACKAGE}' exists but is not a table in {}.", 157 file.display() 158 ), 159 Self::MissingName => writeln!(stderr, "'{PACKAGE}.{NAME}' does not exist in {}.", file.display()), 160 Self::InvalidNameType => writeln!( 161 stderr, 162 "'{PACKAGE}.{NAME}' exists but is not a string in {}.", 163 file.display() 164 ), 165 Self::InvalidMsrvType => writeln!( 166 stderr, 167 "'{PACKAGE}.{RUST_VERSION}' exists but is not a string nor table in {}.", 168 file.display() 169 ), 170 Self::Msrv => writeln!( 171 stderr, 172 "'{PACKAGE}.{RUST_VERSION}' is a string but is not a valid MSRV in {}.", 173 file.display() 174 ), 175 Self::MsrvWorkspaceMissing => writeln!( 176 stderr, 177 "'{PACKAGE}.{RUST_VERSION}' is a table but does not contain the key '{WORKSPACE}' in {}.", 178 file.display() 179 ), 180 Self::MsrvWorkspaceVal => writeln!( 181 stderr, 182 "'{PACKAGE}.{RUST_VERSION}.{WORKSPACE}' exists but is not a Boolean or is not true in {}.", 183 file.display() 184 ), 185 Self::WorkspaceIo(e) => writeln!( 186 stderr, 187 "There was an error looking for the workspace Cargo.toml in {} and its ancestor directories: {e}.", 188 file.parent().unwrap_or_else(|| unreachable!("there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml.")).display(), 189 ), 190 Self::WorkspaceLock(e) => writeln!( 191 stderr, 192 "There was an error locking the workspace Cargo.toml in {} and its ancestor directories: {e}.", 193 file.parent().unwrap_or_else(|| unreachable!("there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml.")).display(), 194 ), 195 Self::WorkspaceLenMismatch=> writeln!( 196 stderr, 197 "The length of the workspace Cargo.toml in {} does not match the length reported by the file systemn.", 198 file.parent().unwrap_or_else(|| unreachable!("there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml.")).display(), 199 ), 200 Self::WorkspaceDoesNotExist => writeln!( 201 stderr, 202 "There is no workspace Cargo.toml in {} nor its ancestor directories.", 203 file.parent().unwrap_or_else(|| unreachable!("there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml.")).display(), 204 ), 205 Self::InvalidWorkspaceType => writeln!( 206 stderr, 207 "'{PACKAGE}.{WORKSPACE}' exists but is not a string in {}.", 208 file.display() 209 ), 210 Self::WorkspaceRead(e, p) => { 211 writeln!(stderr, "There was an issue reading the workspace file {}: {e}.", p.display()) 212 } 213 Self::WorkspaceReadLock(e, p) => { 214 writeln!(stderr, "There was an issue locking the workspace file {}: {e}.", p.display()) 215 } 216 Self::WorkspaceReadLenMismatch(p) => { 217 writeln!(stderr, "The length of the workspace file {} does not match the length reported by the file system.", p.display()) 218 } 219 Self::WorkspaceToml(e, p) => write!( 220 stderr, 221 "Error parsing workspace file {} as TOML: {e}.", 222 p.display() 223 ), 224 Self::Workspace(e, p) => e.write(stderr, &p), 225 } 226 } 227 } 228 /// `"features"`. 229 const FEATURES: &str = "features"; 230 /// Error returned from extracting feature dependencies. 231 #[cfg_attr(test, derive(Debug, PartialEq))] 232 pub(crate) enum FeatureDependenciesErr { 233 /// Variant returned when a feature is not an array. 234 /// 235 /// The contained `String` is the name of the feature. 236 InvalidFeatureType(String), 237 /// Variant returned when a feature dependency is not a string. 238 /// 239 /// The contained `String` is the name of the feature. 240 InvalidDependencyType(String), 241 /// Variant returned when a feature dependency is not a feature nor dependency. 242 /// 243 /// The first contained `String` is the name of the feature, and the second `String` is the name of the invalid 244 /// feature dependency. 245 /// 246 /// Note this is only possible when `allow_implied_features` is `false` when passed to 247 /// [`Features::validate_dependencies`]. 248 InvalidDependency(String, String), 249 /// Variant returned when a feature is cyclic. 250 /// 251 /// The contained `String` is the name of the feature. 252 CyclicFeature(String), 253 /// Variant returned when a feature dependency is redundant. 254 /// 255 /// The first contained `String` is the name of the feature, and the second `String` is the name of the 256 /// redundant feature dependency. 257 RedundantDependency(String, String), 258 } 259 impl FeatureDependenciesErr { 260 /// Writes `self` to `stderr`. 261 fn write(self, mut stderr: StderrLock<'_>, file: &Path) -> Result<(), Error> { 262 match self { 263 Self::InvalidFeatureType(name) => { 264 writeln!( 265 stderr, 266 "'{FEATURES}.{name}' is not an array in {}.", 267 file.display() 268 ) 269 } 270 Self::InvalidDependencyType(name) => writeln!( 271 stderr, 272 "'{FEATURES}.{name}' contains a value that is not a string in {}.", 273 file.display() 274 ), 275 Self::InvalidDependency(name, dep_name) => writeln!( 276 stderr, 277 "'{FEATURES}.{name}' contains '{dep_name}' which is neither a feature nor dependency in {}. It may be an implied feature from an optional dependency, but --allow-implied-features was not passed.", 278 file.display() 279 ), 280 Self::CyclicFeature(name) => writeln!( 281 stderr, 282 "'{FEATURES}.{name}' is a cyclic feature in {}.", 283 file.display() 284 ), 285 Self::RedundantDependency(name, dep_name) => writeln!( 286 stderr, 287 "'{FEATURES}.{name}' contains the redundant dependency '{dep_name}' in {}.", 288 file.display() 289 ), 290 } 291 } 292 } 293 /// Error returned from extracting `"features"`. 294 #[cfg_attr(test, derive(Debug, PartialEq))] 295 pub(crate) enum FeaturesErr { 296 /// Variant returned when `features` is not a table in Cargo.toml. 297 InvalidType, 298 /// Variant returned when `features` contains a feature with an invalid name. 299 /// 300 /// The contained `String` is the name of the feature. 301 InvalidName(String), 302 /// Variant returned when there is an issue with a feature's dependencies. 303 FeatureDependencies(FeatureDependenciesErr), 304 } 305 impl FeaturesErr { 306 /// Writes `self` to `stderr`. 307 fn write(self, mut stderr: StderrLock<'_>, file: &Path) -> Result<(), Error> { 308 match self { 309 Self::InvalidType => { 310 writeln!( 311 stderr, 312 "'{FEATURES}' exists but is not a table in {}.", 313 file.display() 314 ) 315 } 316 Self::InvalidName(name) => { 317 writeln!( 318 stderr, 319 "'{FEATURES}.{name}' is not a valid feature name in {}.", 320 file.display() 321 ) 322 } 323 Self::FeatureDependencies(e) => e.write(stderr, file), 324 } 325 } 326 } 327 /// Error returned from extracting dependencies. 328 #[cfg_attr(test, derive(Debug, PartialEq))] 329 pub(crate) enum DependenciesErr { 330 /// Variant returned when the dependencies is not a table. 331 /// 332 /// The contained `str` is the name of the dependencies (e.g., `"dependencies"`). 333 Type(&'static str), 334 /// Variant returned when a dependency has an invalid name. 335 /// 336 /// The contained `str` is the name of dependencies (e.g., `"dependencies"`), and the `String` is the 337 /// name of the dependency. 338 Name(&'static str, String), 339 /// Variant returned when a dependency is not a string or table. 340 /// 341 /// The contained `str` is the name of dependencies (e.g., `"dependencies"`), and the `String` is the 342 /// name of the dependency. 343 DependencyType(&'static str, String), 344 /// Variant returned when a dependency contains an `"optional"` key whose value is not a Boolean. 345 /// 346 /// The contained `str` is the name of dependencies (e.g., `"dependencies"`), and the `String` is the 347 /// name of the dependency. 348 OptionalType(&'static str, String), 349 /// Variant returned when an optional dependency would cause an implied feature to be created. 350 /// 351 /// Note this is only possible when `allow_implied_features` is `false` when passed to 352 /// [`Features::add_optional_dependencies`]. 353 /// 354 /// The contained `str` is the name of dependencies (e.g., `"dependencies"`), and the `String` is the 355 /// name of the dependency. 356 ImpliedFeature(&'static str, String), 357 } 358 /// Error returned from extracting dependencies to add implied features. 359 #[cfg_attr(test, derive(Debug, PartialEq))] 360 pub(crate) enum ImpliedFeaturesErr { 361 /// Variant returned from extracting dependencies. 362 Dependencies(DependenciesErr), 363 /// Variant returned when `target` is not a table in Cargo.toml. 364 TargetType, 365 /// Variant returned when `target` contains a key whose value is not a table. 366 /// 367 /// The contained `String` is the name of the key. 368 TargetPlatformType(String), 369 /// Variant returned when a target platform contains an issue with dependencies. 370 /// 371 /// The contained `String` is the name of the target platform. 372 TagetPlatformDependencies(String, DependenciesErr), 373 /// Variant returned when a feature dependency is not a feature nor dependency. 374 /// 375 /// The first contained `String` is the name of the feature, and the second `String` is the name of the invalid 376 /// feature dependency. 377 /// 378 /// Note this is only possible when `allow_implied_features` is `true` when passed to 379 /// [`Features::validate_dependencies`] since when `false` we verify the the dependency 380 /// is defined as a feature. 381 InvalidDependency(String, String), 382 } 383 /// `"optional"`. 384 const OPTIONAL: &str = "optional"; 385 /// `"target"`. 386 const TARGET: &str = "target"; 387 impl ImpliedFeaturesErr { 388 /// Writes `self` to `stderr`. 389 fn write(self, mut stderr: StderrLock<'_>, file: &Path) -> Result<(), Error> { 390 match self { 391 Self::Dependencies(e) => match e { 392 DependenciesErr::Type(name) => { 393 writeln!( 394 stderr, 395 "'{name}' exists but is not a table in {}.", 396 file.display() 397 ) 398 } 399 DependenciesErr::Name(name, dep_name) => { 400 writeln!( 401 stderr, 402 "'{name}.{dep_name}' is not a valid dependency name in {}.", 403 file.display() 404 ) 405 } 406 DependenciesErr::DependencyType(name, dep_name) => { 407 writeln!( 408 stderr, 409 "'{name}.{dep_name}' exists but is not a string nor table in {}.", 410 file.display() 411 ) 412 } 413 DependenciesErr::OptionalType(name, dep_name) => { 414 writeln!( 415 stderr, 416 "'{name}.{dep_name}.{OPTIONAL}' exists but is not a Boolean in {}.", 417 file.display() 418 ) 419 } 420 DependenciesErr::ImpliedFeature(name, dep_name) => { 421 writeln!( 422 stderr, 423 "'{name}.{dep_name}' causes an implied feature to be defined in {}, but implied features were forbidden. --allow-implied-features can be passed to allow it.", 424 file.display() 425 ) 426 } 427 }, 428 Self::TargetType => { 429 writeln!( 430 stderr, 431 "'{TARGET}' exists but is not a table in {}.", 432 file.display() 433 ) 434 } 435 Self::TargetPlatformType(name) => { 436 writeln!( 437 stderr, 438 "'{TARGET}.{name}' exists but is not a table in {}.", 439 file.display() 440 ) 441 } 442 Self::TagetPlatformDependencies(name, e) => match e { 443 DependenciesErr::Type(table_name) => { 444 writeln!( 445 stderr, 446 "'{TARGET}.{name}.{table_name}' exists but is not a table in {}.", 447 file.display() 448 ) 449 } 450 DependenciesErr::Name(table_name, dep_name) => { 451 writeln!( 452 stderr, 453 "'{TARGET}.{name}.{table_name}.{dep_name}' is not a valid dependency name in {}.", 454 file.display() 455 ) 456 } 457 DependenciesErr::DependencyType(table_name, dep_name) => writeln!( 458 stderr, 459 "'{TARGET}.{name}.{table_name}.{dep_name}' exists but is not a string nor table in {}.", 460 file.display() 461 ), 462 DependenciesErr::OptionalType(table_name, dep_name) => writeln!( 463 stderr, 464 "'{TARGET}.{name}.{table_name}.{dep_name}.{OPTIONAL}' exists but is not a Boolean in {}.", 465 file.display() 466 ), 467 DependenciesErr::ImpliedFeature(table_name, dep_name) => { 468 writeln!( 469 stderr, 470 "'{TARGET}.{name}.{table_name}.{dep_name}' causes an implied feature to be defined in {}, but implied features were forbidden. --allow-implied-features can be passed to allow it.", 471 file.display() 472 ) 473 } 474 }, 475 Self::InvalidDependency(name, dep_name) => writeln!( 476 stderr, 477 "'{FEATURES}.{name}' contains '{dep_name}' which is neither a feature nor dependency in {}.", 478 file.display() 479 ), 480 } 481 } 482 } 483 /// Error returned from parsing Cargo.toml. 484 #[cfg_attr(test, derive(Debug, PartialEq))] 485 pub(crate) enum ManifestErr { 486 /// Variant returned when Cargo.toml is not valid TOML. 487 Toml(TomlErr, PathBuf), 488 /// Variant returned when extracting `package`. 489 Package(PackageErr, PathBuf), 490 /// Variant returned when extracting `features`. 491 Features(FeaturesErr, PathBuf), 492 /// Variant returned when extracting dependencies in order to add implied features. 493 ImpliedFeatures(ImpliedFeaturesErr, PathBuf), 494 /// Variant returned when ignoring a feature that does not exist. 495 UndefinedIgnoreFeature(String, PathBuf), 496 } 497 impl ManifestErr { 498 /// Writes `self` to `stderr`. 499 pub(crate) fn write(self, mut stderr: StderrLock<'_>) -> Result<(), Error> { 500 match self { 501 Self::Toml(e, file) => write!( 502 stderr, 503 "Error parsing package file {} as TOML: {e}.", 504 file.display() 505 ), 506 Self::Package(e, file) => e.write(stderr, &file), 507 Self::Features(e, file) => e.write(stderr, &file), 508 Self::ImpliedFeatures(e, file) => e.write(stderr, &file), 509 Self::UndefinedIgnoreFeature(feature, file) => writeln!( 510 stderr, 511 "The feature '{feature}' was requested to be ignored, but it is not a feature in the package file {}.", 512 file.display() 513 ), 514 } 515 } 516 } 517 /// Error when there are too many features to create the power set. 518 #[cfg_attr(test, derive(Debug, PartialEq))] 519 pub(crate) struct TooManyFeaturesErr; 520 /// Parses `val` as a `u64` in decimal notation without leading 0s. 521 /// 522 /// # Errors 523 /// 524 /// Errors iff `val` is not a valid `u64` in decimal notation without leading 0s. 525 pub(crate) fn parse_int(val: &str) -> Result<u64, ()> { 526 val.as_bytes().first().ok_or(()).and_then(|fst| { 527 if *fst == b'0' { 528 if val.len() == 1 { Ok(0) } else { Err(()) } 529 } else { 530 val.parse().map_err(|_e| ()) 531 } 532 }) 533 } 534 /// MSRV in Cargo.toml. 535 #[cfg_attr(test, derive(Debug, PartialEq))] 536 pub(crate) struct Msrv { 537 /// Major version. 538 major: u64, 539 /// Minor version. 540 minor: Option<u64>, 541 /// Patch version. 542 patch: Option<u64>, 543 } 544 impl Msrv { 545 /// Converts `msrv` into `Self` based on a valid MSRV string. 546 #[expect(unsafe_code, reason = "comments justify their correctness")] 547 fn extract_msrv(msrv: &str) -> Result<Self, ()> { 548 let mut iter = msrv.as_bytes().split(|b| *b == b'.'); 549 iter.next().ok_or(()).and_then(|fst| { 550 // SAFETY: 551 // The original input is a `str` and we split on `b'.'` which is a single-byte 552 // UTF-8 code unit; thus we don't have to worry about splitting a multi-byte 553 // UTF-8 code unit. 554 let major_utf8 = unsafe { str::from_utf8_unchecked(fst) }; 555 parse_int(major_utf8).and_then(|major| { 556 iter.next().map_or_else( 557 || { 558 Ok(Self { 559 major, 560 minor: None, 561 patch: None, 562 }) 563 }, 564 |snd| { 565 // SAFETY: 566 // The original input is a `str` and we split on `b'.'` which is 567 // a single-byte UTF-8 code unit; thus we don't have to worry 568 // about splitting a multi-byte UTF-8 code unit. 569 let minor_utf8 = unsafe { str::from_utf8_unchecked(snd) }; 570 parse_int(minor_utf8).and_then(|minor_val| { 571 iter.next().map_or_else( 572 || { 573 Ok(Self { 574 major, 575 minor: Some(minor_val), 576 patch: None, 577 }) 578 }, 579 |lst| { 580 // SAFETY: 581 // The original input is a `str` and we split on 582 // `b'.'` which is a single-byte UTF-8 code 583 // unit; thus we don't have to worry about 584 // splitting a multi-byte UTF-8 code unit. 585 let patch_utf8 = unsafe { str::from_utf8_unchecked(lst) }; 586 parse_int(patch_utf8).and_then(|patch_val| { 587 iter.next().map_or_else( 588 || { 589 Ok(Self { 590 major, 591 minor: Some(minor_val), 592 patch: Some(patch_val), 593 }) 594 }, 595 |_| Err(()), 596 ) 597 }) 598 }, 599 ) 600 }) 601 }, 602 ) 603 }) 604 }) 605 } 606 /// Reads `workspace` from `toml` extracting the MSRV. 607 fn extract_workspace( 608 toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 609 ) -> Result<Self, WorkspaceErr> { 610 toml.get(WORKSPACE) 611 .ok_or(WorkspaceErr::Missing) 612 .and_then(|work_span| { 613 if let DeValue::Table(ref workspace) = *work_span.get_ref() { 614 workspace 615 .get(PACKAGE) 616 .ok_or(WorkspaceErr::MissingPackage) 617 .and_then(|pack_span| { 618 if let DeValue::Table(ref package) = *pack_span.get_ref() { 619 package 620 .get(RUST_VERSION) 621 .ok_or(WorkspaceErr::MissingPackageMsrv) 622 .and_then(|msrv_span| { 623 if let DeValue::String(ref msrv) = *msrv_span.get_ref() { 624 Self::extract_msrv(msrv) 625 .map_err(|()| WorkspaceErr::Msrv) 626 } else { 627 Err(WorkspaceErr::InvalidPackageMsrvType) 628 } 629 }) 630 } else { 631 Err(WorkspaceErr::InvalidPackageType) 632 } 633 }) 634 } else { 635 Err(WorkspaceErr::InvalidType) 636 } 637 }) 638 } 639 /// Recursively looks for `Cargo.toml` in `cur_dir` and ancestor directories until one is found 640 /// that contains a key named [`WORKSPACE`]. Once found, it's MSRV will be parsed and returned. 641 /// 642 /// We make this recursive in case the (impossible?) path traversal becomes cyclic; in which 643 /// we want a stack overflow to occur. 644 /// 645 /// Note if any error occurs not related to a not found file error, then this will error. 646 #[expect( 647 clippy::verbose_file_reads, 648 reason = "false positive since we want to lock the file" 649 )] 650 fn get_workspace_toml(mut cur_dir: PathBuf) -> Result<Self, PackageErr> { 651 cur_dir.push(super::cargo_toml()); 652 match File::options() 653 .read(true) 654 .open(&cur_dir) 655 { 656 Ok(mut file) => { 657 file.try_lock_shared() 658 .map_err(PackageErr::WorkspaceLock) 659 .and_then(|()| { 660 file.metadata() 661 .map_err(PackageErr::WorkspaceIo) 662 .and_then(|meta| { 663 let meta_len = usize::try_from(meta.len()).unwrap_or(usize::MAX); 664 let mut data_utf8 = Vec::with_capacity(meta_len); 665 file.read_to_end(&mut data_utf8) 666 .map_err(PackageErr::WorkspaceIo) 667 .and_then(|len| { 668 drop(file); 669 if meta_len == len { 670 String::from_utf8(data_utf8) 671 .map_err(|e| PackageErr::WorkspaceIo(Error::other(e))) 672 .and_then(|data| { 673 Map::parse(&data) 674 .map_err(|e| PackageErr::WorkspaceToml(e, cur_dir.clone())) 675 .and_then(|toml| { 676 let t = toml.into_inner(); 677 if t.contains_key(WORKSPACE) { 678 Self::extract_workspace(&t) 679 .map_err(|e| PackageErr::Workspace(e, cur_dir)) 680 } else { 681 _ = cur_dir.pop(); 682 if cur_dir.pop() { 683 Self::get_workspace_toml(cur_dir) 684 } else { 685 Err(PackageErr::WorkspaceDoesNotExist) 686 } 687 } 688 }) 689 }) 690 } else { 691 Err(PackageErr::WorkspaceLenMismatch) 692 } 693 }) 694 }) 695 }) 696 } 697 Err(e) => { 698 if matches!(e.kind(), ErrorKind::NotFound) { 699 _ = cur_dir.pop(); 700 if cur_dir.pop() { 701 Self::get_workspace_toml(cur_dir) 702 } else { 703 Err(PackageErr::WorkspaceIo(e)) 704 } 705 } else { 706 Err(PackageErr::WorkspaceIo(e)) 707 } 708 } 709 } 710 } 711 /// Extracts `"rust-version"` from `"package"` or `toml` in the case it's defined in a workspace. 712 #[expect( 713 clippy::panic_in_result_fn, 714 reason = "want to crash when there is a bug" 715 )] 716 #[expect( 717 clippy::verbose_file_reads, 718 reason = "false positive since we want to lock the file" 719 )] 720 fn extract_from_toml( 721 toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 722 package: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 723 cargo_toml: &Path, 724 ) -> Result<Option<Self>, PackageErr> { 725 package.get(RUST_VERSION).map_or(Ok(None), |msrv_span| { 726 match *msrv_span.get_ref() { 727 DeValue::String(ref msrv) => Self::extract_msrv(msrv) 728 .map_err(|()| PackageErr::Msrv) 729 .map(Some), 730 DeValue::Table(ref msrv) => msrv 731 .get(WORKSPACE) 732 .ok_or(PackageErr::MsrvWorkspaceMissing) 733 .and_then(|work| { 734 if matches!(*work.get_ref(), DeValue::Boolean(b) if b) { 735 package.get(WORKSPACE).map_or_else( 736 || if toml.contains_key(WORKSPACE) { 737 Self::extract_workspace(toml).map_err(|e| PackageErr::Workspace(e, cargo_toml.to_path_buf())).map(Some) 738 } else { 739 let mut search_path = cargo_toml.to_path_buf(); 740 assert!(search_path.pop(), "there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml."); 741 if search_path.pop() { 742 Self::get_workspace_toml(search_path).map(Some) 743 } else { 744 Err(PackageErr::WorkspaceDoesNotExist) 745 } 746 }, 747 |path_span| { 748 if let DeValue::String(ref workspace_path) = *path_span.get_ref() { 749 let mut path = cargo_toml.to_path_buf(); 750 assert!(path.pop(), "there is a bug in main. manifest::Manifest::from_toml must be passed the absolute path to the package's Cargo.toml."); 751 path.push(workspace_path.as_ref()); 752 path.push(super::cargo_toml()); 753 File::options().read(true).open(&path).map_err(|e| PackageErr::WorkspaceRead(e, path.clone())).and_then(|mut workspace_file| { 754 workspace_file.try_lock_shared().map_err(|e| PackageErr::WorkspaceReadLock(e, path.clone())).and_then(|()| { 755 workspace_file.metadata().map_err(|e| PackageErr::WorkspaceRead(e, path.clone())).and_then(|meta| { 756 let meta_len = usize::try_from(meta.len()).unwrap_or(usize::MAX); 757 let mut workspace_utf8 = Vec::with_capacity(meta_len); 758 workspace_file.read_to_end(&mut workspace_utf8).map_err(|e| PackageErr::WorkspaceRead(e, path.clone())).and_then(|len| { 759 drop(workspace_file); 760 if meta_len == len { 761 String::from_utf8(workspace_utf8).map_err(|e| PackageErr::WorkspaceRead(Error::other(e), path.clone())).and_then(|workspace_data| { 762 Map::parse(&workspace_data).map_err(|e| PackageErr::WorkspaceToml(e, path.clone())).and_then(|workspace_toml| Self::extract_workspace(workspace_toml.get_ref()).map_err(|e| PackageErr::Workspace(e, path)).map(Some)) 763 }) 764 } else { 765 Err(PackageErr::WorkspaceReadLenMismatch(path)) 766 } 767 }) 768 }) 769 }) 770 }) 771 } else { 772 Err(PackageErr::InvalidWorkspaceType) 773 } 774 }, 775 ) 776 } else { 777 Err(PackageErr::MsrvWorkspaceVal) 778 } 779 }), 780 DeValue::Integer(_) 781 | DeValue::Float(_) 782 | DeValue::Boolean(_) 783 | DeValue::Datetime(_) 784 | DeValue::Array(_) => Err(PackageErr::InvalidMsrvType), 785 } 786 }) 787 } 788 /// Returns `Some` containing the MSRV with `'+'` prepended iff `stable` is semantically greater than `self` 789 /// or the default toolchan is semantically not equivalent to `self`; otherwise returns `None`. 790 /// 791 /// When `stable` is semantically less than the MSRV, an error is returned. 792 pub(crate) fn compare_to_other( 793 &self, 794 default: bool, 795 rustup_home: Option<&Path>, 796 cargo_path: &Path, 797 cargo_home: Option<&Path>, 798 ) -> Result<Option<String>, Box<ToolchainErr>> { 799 if default { 800 Toolchain::Default(false) 801 } else { 802 Toolchain::Stable 803 } 804 .get_version(rustup_home, cargo_path, cargo_home) 805 .and_then(|stable_dflt_version| { 806 match self.major.cmp(&stable_dflt_version.major) { 807 Ordering::Less => Ok(true), 808 Ordering::Equal => self.minor.map_or_else( 809 || Ok(false), 810 |min| match min.cmp(&stable_dflt_version.minor) { 811 Ordering::Less => Ok(true), 812 Ordering::Equal => self.patch.map_or_else( 813 || Ok(false), 814 |pat| match pat.cmp(&stable_dflt_version.patch) { 815 Ordering::Less => Ok(true), 816 Ordering::Equal => Ok(false), 817 Ordering::Greater => { 818 if default { 819 Ok(true) 820 } else { 821 Err(Box::new(ToolchainErr::MsrvTooHigh)) 822 } 823 } 824 }, 825 ), 826 Ordering::Greater => { 827 if default { 828 Ok(true) 829 } else { 830 Err(Box::new(ToolchainErr::MsrvTooHigh)) 831 } 832 } 833 }, 834 ), 835 Ordering::Greater => { 836 if default { 837 Ok(true) 838 } else { 839 Err(Box::new(ToolchainErr::MsrvTooHigh)) 840 } 841 } 842 } 843 .and_then(|get_msrv| { 844 if get_msrv { 845 Toolchain::Msrv(&self.minor.map_or_else( 846 || format!("+{}", self.major), 847 |min| { 848 self.patch.map_or_else( 849 || format!("+{}.{min}", self.major), 850 |pat| format!("+{}.{min}.{pat}", self.major), 851 ) 852 }, 853 )) 854 .get_version(rustup_home, cargo_path, cargo_home) 855 .and_then(|msrv_version| { 856 if msrv_version.major == self.major 857 && self.minor.is_none_or(|minor| { 858 msrv_version.minor == minor 859 && self.patch.is_none_or(|patch| msrv_version.patch == patch) 860 }) 861 { 862 Ok(Some(format!( 863 "+{}.{}.{}", 864 msrv_version.major, msrv_version.minor, msrv_version.patch 865 ))) 866 } else { 867 Err(Box::new(ToolchainErr::MsrvNotCompatibleWithInstalledMsrv( 868 msrv_version, 869 ))) 870 } 871 }) 872 } else { 873 Ok(None) 874 } 875 }) 876 }) 877 } 878 } 879 /// `package` info. 880 #[cfg_attr(test, derive(Debug, PartialEq))] 881 pub(crate) struct Package { 882 /// `rust-version`. 883 msrv: Option<Msrv>, 884 /// `name`. 885 name: String, 886 } 887 impl Package { 888 /// MSRV. 889 pub(crate) const fn msrv(&self) -> Option<&Msrv> { 890 self.msrv.as_ref() 891 } 892 /// Name. 893 pub(crate) const fn name(&self) -> &str { 894 self.name.as_str() 895 } 896 /// Extracts `"package"` from `toml`. 897 fn extract_from_toml( 898 toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 899 cargo_toml: &Path, 900 ) -> Result<Self, PackageErr> { 901 toml.get(PACKAGE) 902 .ok_or(PackageErr::Missing) 903 .and_then(|pack_span| { 904 if let DeValue::Table(ref package) = *pack_span.get_ref() { 905 package 906 .get(NAME) 907 .ok_or(PackageErr::MissingName) 908 .and_then(|name_span| { 909 if let DeValue::String(ref name) = *name_span.get_ref() { 910 Msrv::extract_from_toml(toml, package, cargo_toml).map(|msrv| { 911 Self { 912 msrv, 913 name: name.clone().into_owned(), 914 } 915 }) 916 } else { 917 Err(PackageErr::InvalidNameType) 918 } 919 }) 920 } else { 921 Err(PackageErr::InvalidType) 922 } 923 }) 924 } 925 } 926 /// Returns `true` iff `nodes` is pairwise disconnected. 927 #[expect( 928 clippy::arithmetic_side_effects, 929 clippy::indexing_slicing, 930 reason = "comment justifies correctness" 931 )] 932 fn pairwise_disconnected(nodes: &[&str], features: &[(String, Vec<String>)]) -> bool { 933 /// `panic`s with a static message about the existence of a bug in [`Manifest::deserialize`]. 934 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 935 fn impossible<T>() -> T { 936 unreachable!( 937 "there is a bug in manifest::Manifest::deserialize where feature dependencies are not only features." 938 ) 939 } 940 /// Returns `true` iff `feature_deps` directly contains `feature` or indirectly in the `Vec<String>` 941 /// associated with it. 942 fn contains( 943 feature_deps: &[String], 944 feature: &str, 945 features: &[(String, Vec<String>)], 946 ) -> bool { 947 feature_deps.iter().any(|feat| { 948 feat == feature 949 || contains( 950 &features 951 .iter() 952 .find(|val| val.0 == *feat) 953 .unwrap_or_else(impossible) 954 .1, 955 feature, 956 features, 957 ) 958 }) 959 } 960 !nodes.iter().enumerate().any(|(idx, feature)| { 961 let feature_info = &features 962 .iter() 963 .find(|val| val.0 == **feature) 964 .unwrap_or_else(impossible) 965 .1; 966 // `idx < nodes.len()`, so overflow is not possible and indexing is fine. 967 nodes[idx + 1..].iter().any(|feat| { 968 contains(feature_info, feat, features) 969 || contains( 970 features 971 .iter() 972 .find(|val| val.0 == **feat) 973 .unwrap_or_else(impossible) 974 .1 975 .as_slice(), 976 feature, 977 features, 978 ) 979 }) 980 }) 981 } 982 /// Power set of [`Features`] returned from [`Features::power_set`]. 983 /// 984 /// Note this is technically not the power set of features since semantically equivalent sets are ignored. 985 /// 986 /// The last set iterated will always be the empty set. If no features in [`Features`] depend on another 987 /// feature, then this will always return the entire set of features first; otherwise the entire set will 988 /// never be returned. The expected cardinality of a set iterated decreases; thus while it will almost always 989 /// be possible for a set _A_ to have larger cardinality than a set _B_ even when _A_ is iterated before _B_, 990 /// the expected value is smaller. 991 /// 992 /// The reason we attempt, but don't guarantee, that the first set iterated corresponds to a semantically 993 /// equivalent set as the original set is to take advantage of the parallel compiliation that occurs. Typically 994 /// the more features one enables, the more dependencies and functionality is added. By compiling code that 995 /// maximizes this first, we take better advantage of how code is compiled; in contrast if we compiled fewer 996 /// features first, subsequent compilations with more features will have to happen. We don't guarantee this 997 /// though since it slightly complicates code; instead we iterate based on the _expected_ cardinality in 998 /// descending order. 999 /// 1000 /// We don't implement `Iterator` since we want to re-use the same `String` that represents the set we are 1001 /// returning. 1002 #[cfg_attr(test, derive(Debug, PartialEq))] 1003 pub(crate) struct PowerSet<'a> { 1004 /// The set of features. 1005 feats: &'a [(String, Vec<String>)], 1006 /// `true` iff there are more sets to iterate. 1007 has_remaining: bool, 1008 /// `true` iff `feats` has redundant features; thus requiring us to check if a given set should be returned. 1009 check_overlap: bool, 1010 /// The current element of the power set that we are to return. 1011 /// 1012 /// This gets decremented as we iterate sets. 1013 idx: usize, 1014 /// Intermediate buffer we use to check if a set contains redundant features. 1015 buffer: Vec<&'a str>, 1016 /// The set we return. 1017 /// 1018 /// This is of the form `"<feat_1>,<feat_2>,...,<feat_n>"`. 1019 set: String, 1020 /// Number of sets skipped due to an equivalence with a smaller set. 1021 skipped_sets_counter: usize, 1022 /// `true` iff they empty set should be skipped. 1023 /// 1024 /// This doesn't contribute to [`Self::skipped_sets_counter`]. 1025 skip_empty_set: bool, 1026 } 1027 impl<'a> PowerSet<'a> { 1028 /// Max cardinality of a set we allow to take the power set of. 1029 // usize::MAX = 2^usize::BITS - 1 >= usize::BITS since usize::MAX >= 0; 1030 // thus `usize::BITS as usize` is free from truncation. 1031 #[expect(clippy::as_conversions, reason = "comment justifies correctness")] 1032 const MAX_SET_LEN: usize = usize::BITS as usize; 1033 /// Returns the cardinality less one of the power set of a set 1034 /// whose cardinality is `set_len`. 1035 /// 1036 /// `set_len` MUST not be greater than [`Self::MAX_SET_LEN`]. 1037 #[expect( 1038 clippy::arithmetic_side_effects, 1039 reason = "comment justifies correctness" 1040 )] 1041 const fn len_minus_one(set_len: usize) -> usize { 1042 assert!( 1043 set_len <= Self::MAX_SET_LEN, 1044 "manifest::PowerSet::len_minus_one must be passed a `usize` no larger than PowerSet::MAX_SET_LEN" 1045 ); 1046 if set_len == Self::MAX_SET_LEN { 1047 usize::MAX 1048 } else { 1049 // We verified that `set_len <= usize::BITS`; thus 1050 // this won't overflow nor underflow since 2^0 = 1. 1051 (1 << set_len) - 1 1052 } 1053 } 1054 /// Returns the cardinality of `self`. 1055 /// 1056 /// Note this is constant and is not affected by iteration of 1057 /// `self`. This isn't the remaining length. [`Self::skip_empty_set`] 1058 /// contributes towards this value. 1059 #[expect( 1060 clippy::arithmetic_side_effects, 1061 reason = "comment justifies correctness" 1062 )] 1063 pub(crate) fn len(&self) -> NonZeroUsizePlus1 { 1064 // We don't allow construction of `PowerSet` unless the set of features 1065 // is no more than [`Self::MAX_SET_LEN`]; thus this won't `panic`. 1066 // Underflow won't occur either since we don't allow construction when the 1067 // set of features is empty and when we must skip the empty set. 1068 // `NonZeroUsizePlus1` treats `0` as `usize::MAX + 1`, so we use wrapping addition. 1069 // This will only wrapp when `self.features.len() == Self::MAX_SET_LEN` and `!self.skip_empty_set`. 1070 NonZeroUsizePlus1::new( 1071 (Self::len_minus_one(self.feats.len()) - usize::from(self.skip_empty_set)) 1072 .wrapping_add(1), 1073 ) 1074 } 1075 /// Contructs `Self` based on `features`. 1076 /// 1077 /// When iterating the sets, the empty set will be skipped iff `skip_empty_set`; 1078 /// but this _won't_ contribute to `skipped_sets_counter`. 1079 /// 1080 /// Returns `None` iff `features` is empty and `skip_empty_set`. 1081 fn new( 1082 features: &'a Features, 1083 skip_empty_set: bool, 1084 ) -> Result<Option<Self>, TooManyFeaturesErr> { 1085 let len = features.0.len(); 1086 match len { 1087 0 if skip_empty_set => Ok(None), 1088 ..=Self::MAX_SET_LEN => { 1089 let mut buffer = Vec::with_capacity(len); 1090 features.0.iter().fold((), |(), key| { 1091 buffer.push(key.0.as_ref()); 1092 }); 1093 let check_overlap = !pairwise_disconnected(buffer.as_slice(), &features.0); 1094 Ok(Some(Self { 1095 feats: &features.0, 1096 has_remaining: true, 1097 check_overlap, 1098 // We verified `len <= Self::MAX_SET_LEN`, so this won't `panic`. 1099 idx: Self::len_minus_one(len), 1100 buffer, 1101 // This won't overflow since `usize::MAX = 2^usize::BITS - 1`, `usize::BITS >= 16`, the max 1102 // value of `len` is `usize::BITS`. 1103 // 16 * usize::BITS < 2^usize::BITS for `usize::BITS > 6`. 1104 set: String::with_capacity(len << 4), 1105 skipped_sets_counter: 0, 1106 skip_empty_set, 1107 })) 1108 } 1109 _ => Err(TooManyFeaturesErr), 1110 } 1111 } 1112 /// Resets `self` such that iteration returns to the beginning. 1113 pub(crate) const fn reset(&mut self) { 1114 // We don't allow construction of `PowerSet` when the number of 1115 // features exceeds [`Self::MAX_SET_LEN`], so this won't `panic`. 1116 self.idx = Self::len_minus_one(self.feats.len()); 1117 self.has_remaining = true; 1118 self.skipped_sets_counter = 0; 1119 } 1120 /// Writes the next element into `self.buffer` even if the set contains overlapping features. 1121 #[expect( 1122 clippy::arithmetic_side_effects, 1123 reason = "comment justifies correctness" 1124 )] 1125 fn inner_next_set(&mut self) { 1126 self.buffer.clear(); 1127 self.feats.iter().enumerate().fold((), |(), (i, feat)| { 1128 if self.idx & (1 << i) != 0 { 1129 self.buffer.push(feat.0.as_str()); 1130 } 1131 }); 1132 if self.idx == 0 { 1133 self.has_remaining = false; 1134 // The empty set is always the last set. 1135 } else if self.idx == 1 && self.skip_empty_set { 1136 self.idx = 0; 1137 self.has_remaining = false; 1138 } else { 1139 // This won't underflow since `idx > 0`. 1140 self.idx -= 1; 1141 } 1142 } 1143 /// Transforms the current element into its string form. 1144 fn current_set(&mut self) { 1145 self.set.clear(); 1146 self.buffer.iter().fold((), |(), s| { 1147 self.set.push_str(s); 1148 self.set.push(','); 1149 }); 1150 // We remove the trailing comma. In the event `self.set` is empty, this does nothing. 1151 _ = self.set.pop(); 1152 } 1153 /// Returns the next set. 1154 /// 1155 /// This returns `None` iff there are no more sets to return. It will continue to return `None` 1156 /// unless [`Self::reset`] is called. 1157 pub(crate) fn next_set(&mut self) -> Option<&str> { 1158 self.next_set_with_skip_count().map(|tup| tup.0) 1159 } 1160 /// Returns the next set along with the number of set skipped thus far. 1161 /// 1162 /// This returns `None` iff there are no more sets to return. It will continue to return `None` 1163 /// unless [`Self::reset`] is called. 1164 #[expect( 1165 clippy::arithmetic_side_effects, 1166 reason = "comment justifies correctness" 1167 )] 1168 pub(crate) fn next_set_with_skip_count(&mut self) -> Option<(&str, usize)> { 1169 if self.has_remaining { 1170 if self.check_overlap { 1171 while self.has_remaining { 1172 self.inner_next_set(); 1173 if pairwise_disconnected(self.buffer.as_slice(), self.feats) { 1174 self.current_set(); 1175 return Some((&self.set, self.skipped_sets_counter)); 1176 } 1177 // This maxes at `usize::MAX` since we ensure a power set is not created on a 1178 // set with more than `usize::BITS` elements. 1179 // We set to set every time [`Self::reset`] is called as well. 1180 self.skipped_sets_counter += 1; 1181 } 1182 None 1183 } else { 1184 self.inner_next_set(); 1185 self.current_set(); 1186 Some((&self.set, self.skipped_sets_counter)) 1187 } 1188 } else { 1189 None 1190 } 1191 } 1192 } 1193 /// Dependency table. 1194 enum DepTable { 1195 /// Library dependencies. 1196 Dependencies, 1197 /// Build dependencies. 1198 BuildDependencies, 1199 } 1200 impl DepTable { 1201 /// Returns the string representation of `self`. 1202 const fn into_str(self) -> &'static str { 1203 match self { 1204 Self::Dependencies => "dependencies", 1205 Self::BuildDependencies => "build-dependencies", 1206 } 1207 } 1208 } 1209 /// `"dep:"`. 1210 const DEP: &[u8; 4] = b"dep:"; 1211 /// Returns `true` iff `utf8` begins with [`DEP`]. 1212 fn is_feature_dependency_a_dependency(utf8: &[u8]) -> bool { 1213 utf8.starts_with(DEP) 1214 } 1215 /// Returns `true` iff `name` does not contain a `'/'` nor begins with [`DEP`]. 1216 fn is_feature_dependency_a_feature(name: &str) -> bool { 1217 let utf8 = name.as_bytes(); 1218 !(utf8.contains(&b'/') || is_feature_dependency_a_dependency(utf8)) 1219 } 1220 /// Features in Cargo.toml. 1221 /// 1222 /// Note this contains a `Vec` instead of a `HashMap` or `BTreeMap` since we enforce that very few entries exist 1223 /// due to the exponential nature of generating the power set; thus making a `Vec` more efficient and faster. 1224 /// This size is so small that we don't even sort and perform a binary search. 1225 /// 1226 /// The `String` in the tuple represents the name of the feature, and the `Vec` in the tuple represents the 1227 /// feature dependencies. The feature dependencies will not contain any dependency that contains a `'/'` 1228 /// but will contain all others. The name of each feature will not contain a `'/'` nor begin with [`DEP`]. 1229 /// Each dependency that is a feature (i.e., does not begin with [`DEP`]) is well-defined (i.e., is a feature 1230 /// itself) when `false` is passed to [`Self::extract_from_toml`]; when `true` is passed, then feature dependencies 1231 /// that are features are not verified to be defined in `self` since implied features still have to be added. 1232 /// Each feature does not contain any cycles nor redundant dependencies. 1233 /// 1234 /// One must still add implied features caused from any optional dependencies iff `true` is passed to 1235 /// [`Self::extract_from_toml`]; after which, one needs to remove all dependencies that are not features and verify 1236 /// all dependencies that are features are defined in `self` in order for [`PowerSet`] to work correctly. A feature 1237 /// with name `<dependency>` needs to be added with an empty `Vec` of dependencies iff no features exist that have a 1238 /// dependency whose name is `"dep:<dependency>"` _and_ there doesn't already exist a feature with that name. 1239 /// An error MUST NOT happen if there is such a feature since different kinds of dependencies can have the 1240 /// same name. While this will allow for the situation where a feature is defined with the same name as 1241 /// an implied feature, that won't matter once `cargo` is run since it will error anyway. 1242 #[cfg_attr(test, derive(Debug, PartialEq))] 1243 pub(crate) struct Features(Vec<(String, Vec<String>)>); 1244 impl Features { 1245 /// Returns `true` iff `self` contains a feature named `"default"`. 1246 pub(crate) fn contains_default(&self) -> bool { 1247 self.0.iter().any(|f| f.0 == "default") 1248 } 1249 /// `panic`s with a static message about the existence of a bug in `validate_dependencies`. 1250 /// 1251 /// This is used in lieu of `unreachable` in `validate_dependencies`, `check_redundant_features` 1252 /// and `extract_feature_dependencies`. 1253 #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] 1254 fn impossible<T>() -> T { 1255 unreachable!("there is a bug in manifest::Features::validate_dependencies.") 1256 } 1257 /// Verifies dependencies associated with `feature` is valid. 1258 /// 1259 /// `dependencies` are assumed to be associated with `feature` which the pair of is checked to be 1260 /// a key-value pair from `features` iff `allow_implied_features`. This verifies the following: 1261 /// 1262 /// * `dependencies` is an array that only contains strings. 1263 /// * Each string in `dependencies` that does not contain a `'/'` nor begins with [`DEP`] is a key 1264 /// in `features` iff `allow_implied_features`. 1265 /// * `feature` nor any dependency that is a feature in `dependencies` is cyclic. 1266 /// 1267 /// `cycle_detection` MUST contain only `feature` when this is called externally. 1268 /// 1269 /// This MUST only be called by itself or [`Self::extract_feature_dependencies`]. 1270 fn validate_dependencies<'a>( 1271 feature: &str, 1272 dependencies: &'a DeValue<'_>, 1273 features: &'a Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 1274 cycle_detection: &mut Vec<&'a str>, 1275 allow_implied_features: bool, 1276 ) -> Result<(), FeatureDependenciesErr> { 1277 if let DeValue::Array(ref info) = *dependencies { 1278 info.iter().try_fold((), |(), dep_span| { 1279 if let DeValue::String(ref dep_name) = *dep_span.get_ref() { 1280 if is_feature_dependency_a_feature(dep_name) { 1281 if cycle_detection.contains(&dep_name.as_ref()) { 1282 Err(FeatureDependenciesErr::CyclicFeature( 1283 dep_name.clone().into_owned(), 1284 )) 1285 } else if let Some(next_feature) = features.get(dep_name.as_ref()) { 1286 cycle_detection.push(dep_name); 1287 Self::validate_dependencies( 1288 dep_name, 1289 next_feature.get_ref(), 1290 features, 1291 cycle_detection, 1292 allow_implied_features, 1293 ) 1294 .map(|()| { 1295 // We require calling code to add `feature` before calling this function. We 1296 // always add the most recent feature dependency. Therefore this is not empty. 1297 _ = cycle_detection.pop().unwrap_or_else(Self::impossible); 1298 }) 1299 } else if allow_implied_features { 1300 // `dep_name` may be an implied feature which we have yet to add. 1301 Ok(()) 1302 } else { 1303 // We require calling code to add `feature` before calling this function. We always 1304 // add the most recent feature dependency. Therefore this is not empty. 1305 Err(FeatureDependenciesErr::InvalidDependency( 1306 cycle_detection 1307 .pop() 1308 .unwrap_or_else(Self::impossible) 1309 .to_owned(), 1310 dep_name.clone().into_owned(), 1311 )) 1312 } 1313 } else { 1314 Ok(()) 1315 } 1316 } else { 1317 Err(FeatureDependenciesErr::InvalidDependencyType( 1318 feature.to_owned(), 1319 )) 1320 } 1321 }) 1322 } else { 1323 Err(FeatureDependenciesErr::InvalidFeatureType( 1324 feature.to_owned(), 1325 )) 1326 } 1327 } 1328 /// Verifies there are no redundant dependencies that are features in `dependencies`. 1329 /// 1330 /// Returns `true` iff there is a redundant dependency. 1331 /// 1332 /// This must be called _after_ `validate_dependencies` is called on the same arguments. 1333 fn check_redundant_dependencies( 1334 feature: &str, 1335 dependencies: &DeArray<'_>, 1336 features: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 1337 allow_implied_features: bool, 1338 ) -> bool { 1339 dependencies.iter().any(|dep_span| { 1340 if let DeValue::String(ref dep_name) = *dep_span.get_ref() { 1341 is_feature_dependency_a_feature(dep_name) 1342 && (feature == dep_name 1343 || features.get(dep_name.as_ref()).map_or_else( 1344 || { 1345 if allow_implied_features { 1346 false 1347 } else { 1348 // We require `validate_dependencies` to be called before this function which 1349 // ensures all features recursively in the `dependencies` are defined as features iff `!allow_implied_features`. 1350 Self::impossible() 1351 } 1352 }, 1353 |next_feature_span| { 1354 Self::check_redundant_dependencies( 1355 feature, 1356 next_feature_span 1357 .get_ref() 1358 .as_array() 1359 // We require `validate_dependencies` to be called before this function 1360 // which ensures all feature dependencies recursively are arrays. 1361 .unwrap_or_else(Self::impossible), 1362 features, 1363 allow_implied_features, 1364 ) 1365 }, 1366 )) 1367 } else { 1368 // We require `validate_dependencies` to be called before this function which ensures all 1369 // dependencies recursivley in `dependencies` are strings. 1370 Self::impossible() 1371 } 1372 }) 1373 } 1374 /// Extracts the feature dependencies associated with `feature`. 1375 /// 1376 /// `dependencies` are assumed to be associated with `feature` which the pair of is checked to be 1377 /// a key-value pair from `features` iff `allow_implied_features`. This verifies the following: 1378 /// 1379 /// * `dependencies` is an array that only contains strings. 1380 /// * Each string in `dependencies` that does not contain a `'/'` nor begins with [`DEP`] is a key 1381 /// in `features` iff `allow_implied_features`. 1382 /// * There is no redundant feature in `dependencies` where "redundant" means the following: 1383 /// * There are no cycles (e.g., feature = \["feature"] or feature = \["a"], a = \["b"], b = \["a"]). 1384 /// * No unnecessary dependencies that are features (e.g., feature = \["a", "a"] or feature = \["a", "b"] 1385 /// a = \["b"], b = \[]). 1386 /// * There are no duplicate dependencies in `dependencies` that beging with [`DEP`]. 1387 /// 1388 /// Note since all dependencies that contain a `'/'` are ignored, there may be duplicates of them. 1389 /// Also when checking for redundant features in `dependencies`, _only_ features are considered; thus 1390 /// something like the following is allowed: feature = \["dep:a", "a"], a = \["dep:a"]. 1391 /// 1392 /// This must only be called from [`Self::extract_from_toml`]. 1393 #[expect( 1394 clippy::arithmetic_side_effects, 1395 reason = "comment justifies correctness" 1396 )] 1397 fn extract_feature_dependencies<'a>( 1398 feature: &'a str, 1399 dependencies: &'a DeValue<'_>, 1400 features: &'a Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 1401 cycle_buffer: &mut Vec<&'a str>, 1402 allow_implied_features: bool, 1403 ) -> Result<Vec<String>, FeatureDependenciesErr> { 1404 // `Self::validate_dependencies` requires `cycle_buffer` to contain, and only contain, `feature`. 1405 cycle_buffer.clear(); 1406 cycle_buffer.push(feature); 1407 Self::validate_dependencies(feature, dependencies, features, cycle_buffer, allow_implied_features).and_then(|()| { 1408 // `validate_dependencies` ensures `dependencies` is an array. 1409 let deps = dependencies.as_array().unwrap_or_else(Self::impossible); 1410 let mut vec_deps = Vec::with_capacity(deps.len()); 1411 deps.iter().enumerate().try_fold((), |(), (idx, dep_span)| if let DeValue::String(ref dep_name) = *dep_span.get_ref() { 1412 let dep_utf8 = dep_name.as_bytes(); 1413 if dep_utf8.contains(&b'/') { 1414 Ok(()) 1415 } else if is_feature_dependency_a_dependency(dep_utf8) { 1416 if vec_deps.iter().any(|d| d == dep_name) { 1417 Err(FeatureDependenciesErr::RedundantDependency(feature.to_owned(), dep_name.clone().into_owned())) 1418 } else { 1419 vec_deps.push(dep_name.clone().into_owned()); 1420 Ok(()) 1421 } 1422 } else if let Some(next_feature_span) = features.get(dep_name.as_ref()) { 1423 // `validate_dependencies` ensures all feature 1424 // dependencies recursively are arrays. 1425 let feat_info = next_feature_span.get_ref().as_array().unwrap_or_else(Self::impossible); 1426 // `idx < deps.iter().len()`; thus this won't overflow. 1427 deps.iter().skip(idx + 1).try_fold((), |(), next_dep_span| if let DeValue::String(ref next_dep_name) = *next_dep_span.get_ref() { 1428 if is_feature_dependency_a_feature(next_dep_name) { 1429 if dep_name == next_dep_name { 1430 Err(FeatureDependenciesErr::RedundantDependency(feature.to_owned(), dep_name.clone().into_owned())) 1431 } else if Self::check_redundant_dependencies(next_dep_name, feat_info, features, allow_implied_features) { 1432 Err(FeatureDependenciesErr::RedundantDependency(feature.to_owned(), next_dep_name.clone().into_owned())) 1433 } else { 1434 features.get(next_dep_name.as_ref()).map_or_else( 1435 || { 1436 if allow_implied_features { 1437 Ok(()) 1438 } else { 1439 // `validate_dependencies` ensures all features 1440 // recursively in the feature dependencies are defined 1441 // as features iff `!allow_implied_features`. 1442 Self::impossible() 1443 } 1444 }, 1445 |next_dep_feature_span| { 1446 // `validate_dependencies` ensures all feature 1447 // dependencies recursively are arrays. 1448 if Self::check_redundant_dependencies(dep_name, next_dep_feature_span.get_ref().as_array().unwrap_or_else(Self::impossible), features, allow_implied_features) { 1449 Err(FeatureDependenciesErr::RedundantDependency(feature.to_owned(), dep_name.clone().into_owned())) 1450 } else { 1451 Ok(()) 1452 } 1453 } 1454 ) 1455 } 1456 } else { 1457 Ok(()) 1458 } 1459 } else { 1460 // `validate_dependencies` ensures all dependencies recursively in `dependencies` are 1461 // strings. 1462 Self::impossible() 1463 }).map(|()| vec_deps.push(dep_name.clone().into_owned())) 1464 } else if allow_implied_features { 1465 vec_deps.push(dep_name.clone().into_owned()); 1466 Ok(()) 1467 } else { 1468 // `validate_dependencies` ensures all features 1469 // recursively in `dependencies` are defined as features 1470 // iff `!allow_implied_features`. 1471 Self::impossible() 1472 } 1473 } else { 1474 // `validate_dependencies` ensures all dependencies recursively in `dependencies` are strings. 1475 Self::impossible() 1476 }).map(|()| vec_deps) 1477 }) 1478 } 1479 /// Extracts `"features"` from `toml`. 1480 fn extract_from_toml( 1481 toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 1482 allow_implied_features: bool, 1483 ) -> Result<Self, FeaturesErr> { 1484 toml.get(FEATURES).map_or_else( 1485 || Ok(Self(Vec::new())), 1486 |features_span| { 1487 if let DeValue::Table(ref features) = *features_span.get_ref() { 1488 let mut cycle_buffer = Vec::with_capacity(features.len()); 1489 let mut feats = Vec::with_capacity(features.len()); 1490 features 1491 .iter() 1492 .try_fold((), |(), (name_span, feature_span)| { 1493 let name = name_span.get_ref(); 1494 if is_feature_dependency_a_feature(name) { 1495 Self::extract_feature_dependencies( 1496 name, 1497 feature_span.get_ref(), 1498 features, 1499 &mut cycle_buffer, 1500 allow_implied_features, 1501 ) 1502 .map_err(FeaturesErr::FeatureDependencies) 1503 .map(|deps| { 1504 feats.push((name.clone().into_owned(), deps)); 1505 }) 1506 } else { 1507 Err(FeaturesErr::InvalidName(name.clone().into_owned())) 1508 } 1509 }) 1510 .map(|()| Self(feats)) 1511 } else { 1512 Err(FeaturesErr::InvalidType) 1513 } 1514 }, 1515 ) 1516 } 1517 /// Extracts optional dependencies and adds their corresponding implied feature to `self` iff 1518 /// `allow_implied_features` and it's appropriate to do so. 1519 /// 1520 /// This must only be called from [`Self::add_implied_features`]. 1521 fn add_optional_dependencies( 1522 &mut self, 1523 toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 1524 table: DepTable, 1525 allow_implied_features: bool, 1526 ) -> Result<(), DependenciesErr> { 1527 let table_name = table.into_str(); 1528 toml.get(table_name).map_or(Ok(()), |deps_span| { 1529 if let DeValue::Table(ref deps) = *deps_span.get_ref() { 1530 deps.iter().try_fold((), |(), dep_span| { 1531 let dep_name = dep_span.0.get_ref(); 1532 if is_feature_dependency_a_feature(dep_name) { 1533 match *dep_span.1.get_ref() { 1534 DeValue::String(_) => Ok(()), 1535 DeValue::Table(ref dep_info) => { 1536 dep_info.get(OPTIONAL).map_or(Ok(()), |opt_span| { 1537 if let DeValue::Boolean(ref optional) = *opt_span.get_ref() { 1538 if *optional { 1539 self.0 1540 .iter() 1541 .try_fold((), |(), feat| { 1542 if feat.0 == *dep_name { 1543 // We already have a feature with the same name, 1544 // so we don't need to continue. 1545 Err(()) 1546 } else if feat.1.iter().any(|feat_dep| { 1547 feat_dep 1548 .as_bytes() 1549 .split_at_checked(DEP.len()) 1550 .is_some_and(|(pref, rem)| { 1551 pref == DEP 1552 && dep_name.as_bytes() == rem 1553 }) 1554 }) { 1555 // The feature dependencies contain `"dep:<dep_name>"`, 1556 // so we don't need to add an implied feature. 1557 Err(()) 1558 } else { 1559 // The feature name is not `<dep_name>` and all of 1560 // feature dependencies of all features are not named 1561 // `"dep:<dep_name>"`; thus we need to continue our 1562 // search. 1563 Ok(()) 1564 } 1565 }) 1566 .map_or(Ok(()), |()| { 1567 if allow_implied_features { 1568 // There is no feature with the name `<dep_name>` nor 1569 // are there any features that contain a feature 1570 // dependency named `"dep:<dep_name>"`; thus we must 1571 // insert an implied feature. 1572 self.0.push(( 1573 dep_name.clone().into_owned(), 1574 Vec::new(), 1575 )); 1576 Ok(()) 1577 } else { 1578 Err(DependenciesErr::ImpliedFeature( 1579 table_name, 1580 dep_name.clone().into_owned(), 1581 )) 1582 } 1583 }) 1584 } else { 1585 Ok(()) 1586 } 1587 } else { 1588 Err(DependenciesErr::OptionalType( 1589 table_name, 1590 dep_name.clone().into_owned(), 1591 )) 1592 } 1593 }) 1594 } 1595 DeValue::Integer(_) 1596 | DeValue::Float(_) 1597 | DeValue::Boolean(_) 1598 | DeValue::Datetime(_) 1599 | DeValue::Array(_) => Err(DependenciesErr::DependencyType( 1600 table_name, 1601 dep_name.clone().into_owned(), 1602 )), 1603 } 1604 } else { 1605 Err(DependenciesErr::Name( 1606 table_name, 1607 dep_name.clone().into_owned(), 1608 )) 1609 } 1610 }) 1611 } else { 1612 Err(DependenciesErr::Type(table_name)) 1613 } 1614 }) 1615 } 1616 /// Adds implied features to `self` based on the optional dependencies in `toml` iff `allow_implied_features`. 1617 fn add_implied_features( 1618 &mut self, 1619 toml: &Map<Spanned<Cow<'_, str>>, Spanned<DeValue<'_>>>, 1620 allow_implied_features: bool, 1621 ) -> Result<(), ImpliedFeaturesErr> { 1622 self.add_optional_dependencies(toml, DepTable::Dependencies, allow_implied_features).map_err(ImpliedFeaturesErr::Dependencies).and_then(|()| self.add_optional_dependencies(toml, DepTable::BuildDependencies, allow_implied_features).map_err(ImpliedFeaturesErr::Dependencies).and_then(|()| toml.get(TARGET).map_or_else( 1623 || Ok(()), 1624 |target_span| { 1625 if let DeValue::Table(ref target) = *target_span.get_ref() { 1626 target.iter().try_fold((), |(), target_platform_span| { 1627 if let DeValue::Table(ref target_platform) = *target_platform_span.1.get_ref() { 1628 self.add_optional_dependencies(target_platform, DepTable::Dependencies, allow_implied_features).map_err(|e| ImpliedFeaturesErr::TagetPlatformDependencies(target_platform_span.0.get_ref().clone().into_owned(), e)).and_then(|()| self.add_optional_dependencies(target_platform, DepTable::BuildDependencies, allow_implied_features).map_err(|e| ImpliedFeaturesErr::TagetPlatformDependencies(target_platform_span.0.get_ref().clone().into_owned(), e))) 1629 } else { 1630 Err(ImpliedFeaturesErr::TargetPlatformType(target_platform_span.0.get_ref().clone().into_owned())) 1631 } 1632 }) 1633 } else { 1634 Err(ImpliedFeaturesErr::TargetType) 1635 } 1636 } 1637 ).and_then(|()| { 1638 if allow_implied_features { 1639 // We don't have to worry about cyclic features or anything other than the lack of a feature with 1640 // the name of the feature dependency. 1641 self.0.iter().try_fold((), |(), feature| feature.1.iter().try_fold((), |(), dep| { 1642 // We didn't save any feature dependencies that contain `'/'`, so we simply have to check if 1643 // a dependency begins with [`DEP`] to skip it. 1644 if is_feature_dependency_a_dependency(dep.as_bytes()) || self.0.iter().any(|other_feature| other_feature.0 == *dep) { 1645 Ok(()) 1646 } else { 1647 Err(ImpliedFeaturesErr::InvalidDependency(feature.0.clone(), dep.clone())) 1648 } 1649 })) 1650 } else { 1651 // When `!allowed_implied_features`, [`Self::validate_dependencies`] verifies non-dependency 1652 // feature dependencies are defined as features. 1653 Ok(()) 1654 } 1655 }))) 1656 } 1657 /// Returns the power set of `self` with semantically equivalent sets removed. 1658 /// 1659 /// The empty set of features is skipped iff `skip_no_feats`. None is only 1660 /// returned if there are no sets of features. This is only possible iff 1661 /// `self` contains no features and `skip_no_feats`. 1662 pub(crate) fn power_set( 1663 &self, 1664 skip_no_feats: bool, 1665 ) -> Result<Option<PowerSet<'_>>, TooManyFeaturesErr> { 1666 PowerSet::new(self, skip_no_feats) 1667 } 1668 } 1669 /// Package and features in `Cargo.toml`. 1670 #[cfg_attr(test, derive(Debug, PartialEq))] 1671 pub(crate) struct Manifest { 1672 /// The package. 1673 package: Package, 1674 /// The features. 1675 features: Features, 1676 } 1677 impl Manifest { 1678 /// Returns the defined MSRV iff there was one defined. 1679 pub(crate) const fn package(&self) -> &Package { 1680 &self.package 1681 } 1682 /// Returns the defined features. 1683 /// 1684 /// Note the returned `Features` doesn't have any cyclic features, each feature dependency for a given 1685 /// feature is a feature itself, and there are no redundant feature dependencies for a given feature. 1686 pub(crate) const fn features(&self) -> &Features { 1687 &self.features 1688 } 1689 /// Returns the data needed from `Cargo.toml`. 1690 /// 1691 /// Note `ignore_features` MUST not contain an empty `String`. 1692 #[expect( 1693 clippy::arithmetic_side_effects, 1694 reason = "comments justify correctness" 1695 )] 1696 #[expect( 1697 clippy::needless_pass_by_value, 1698 reason = "want to drop `val` as soon as possible" 1699 )] 1700 pub(crate) fn from_toml( 1701 val: String, 1702 allow_implied_features: bool, 1703 cargo_toml: &Path, 1704 ignore_features: &[String], 1705 ) -> Result<Self, Box<ManifestErr>> { 1706 Map::parse(val.as_str()) 1707 .map_err(|e| Box::new(ManifestErr::Toml(e, cargo_toml.to_path_buf()))) 1708 .and_then(|span| { 1709 let cargo = span.get_ref(); 1710 Package::extract_from_toml(cargo, cargo_toml) 1711 .map_err(|e| Box::new(ManifestErr::Package(e, cargo_toml.to_path_buf()))) 1712 .and_then(|package| { 1713 Features::extract_from_toml(cargo, allow_implied_features) 1714 .map_err(|e| { 1715 Box::new(ManifestErr::Features(e, cargo_toml.to_path_buf())) 1716 }) 1717 .and_then(|mut features| { 1718 features 1719 .add_implied_features(cargo, allow_implied_features) 1720 .map_err(|e| { 1721 Box::new(ManifestErr::ImpliedFeatures( 1722 e, 1723 cargo_toml.to_path_buf(), 1724 )) 1725 }) 1726 .and_then(|()| { 1727 features.0.iter_mut().fold( 1728 (), 1729 |(), &mut (_, ref mut deps)| { 1730 deps.retain(|d| { 1731 // We retain only features. Since we didn't save any 1732 // dependencies that contain `'/'`, it's slightly faster to just 1733 // check that a feature dependency is not a dependency. 1734 !is_feature_dependency_a_dependency( 1735 d.as_bytes(), 1736 ) 1737 }); 1738 }, 1739 ); 1740 // First we ensure all features we are to ignore are defined; 1741 // while doing this, we remove the feature. Note `ignore_features` 1742 // and `features` only contain distinct features, so we simply 1743 // have to check for the first occurrence. 1744 // Note calling code is required to have removed the empty 1745 // string if it had existed. 1746 ignore_features 1747 .iter() 1748 .try_fold((), |(), ig_feat| { 1749 features 1750 .0 1751 .iter() 1752 .try_fold(0, |idx, info| { 1753 if info.0 == *ig_feat { 1754 Err(idx) 1755 } else { 1756 // Clearly free from overflow. 1757 Ok(idx + 1) 1758 } 1759 }) 1760 .map_or_else( 1761 |idx| { 1762 drop(features.0.swap_remove(idx)); 1763 Ok(()) 1764 }, 1765 |_| { 1766 Err(Box::new( 1767 ManifestErr::UndefinedIgnoreFeature( 1768 ig_feat.clone(), 1769 cargo_toml.to_path_buf(), 1770 ), 1771 )) 1772 }, 1773 ) 1774 }) 1775 .map(|()| { 1776 // Now we remove all features that depend on the 1777 // features we are to ignore. 1778 ignore_features.iter().fold((), |(), ig_feat| { 1779 features.0.retain(|info| { 1780 !info.1.iter().any(|d| d == ig_feat) 1781 }); 1782 }); 1783 Self { package, features } 1784 }) 1785 }) 1786 }) 1787 }) 1788 }) 1789 } 1790 }