use crate::{
cargo_config::{CargoTargetArg, TargetTriple},
errors::{RustBuildMetaParseError, TargetTripleError, UnknownHostPlatform},
reuse_build::{LibdirMapper, PlatformLibdirMapper},
};
use camino::{Utf8Path, Utf8PathBuf};
use nextest_metadata::{
BuildPlatformsSummary, HostPlatformSummary, PlatformLibdirSummary, PlatformLibdirUnavailable,
TargetPlatformSummary,
};
use target_spec::summaries::PlatformSummary;
pub use target_spec::Platform;
use tracing::debug;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BuildPlatforms {
pub host: HostPlatform,
pub target: Option<TargetPlatform>,
}
impl BuildPlatforms {
pub fn new_with_no_target() -> Result<Self, UnknownHostPlatform> {
Ok(Self {
host: HostPlatform::current(PlatformLibdir::Unavailable(
PlatformLibdirUnavailable::new_const("test"),
))?,
target: None,
})
}
pub fn map_libdir(&self, mapper: &LibdirMapper) -> Self {
Self {
host: self.host.map_libdir(&mapper.host),
target: self
.target
.as_ref()
.map(|target| target.map_libdir(&mapper.target)),
}
}
pub fn to_cargo_target_arg(&self) -> Result<CargoTargetArg, TargetTripleError> {
match &self.target {
Some(target) => target.triple.to_cargo_target_arg(),
None => {
Ok(CargoTargetArg::Builtin(
self.host.platform.triple_str().to_owned(),
))
}
}
}
pub fn to_summary(&self) -> BuildPlatformsSummary {
BuildPlatformsSummary {
host: self.host.to_summary(),
targets: self
.target
.as_ref()
.map(|target| vec![target.to_summary()])
.unwrap_or_default(),
}
}
pub fn to_target_or_host_summary(&self) -> PlatformSummary {
if let Some(target) = &self.target {
target.triple.platform.to_summary()
} else {
self.host.platform.to_summary()
}
}
pub fn to_summary_str(&self) -> Option<String> {
self.target
.as_ref()
.map(|triple| triple.triple.platform.triple_str().to_owned())
}
pub fn from_summary(summary: BuildPlatformsSummary) -> Result<Self, RustBuildMetaParseError> {
Ok(BuildPlatforms {
host: HostPlatform::from_summary(summary.host)?,
target: {
if summary.targets.len() > 1 {
return Err(RustBuildMetaParseError::Unsupported {
message: "multiple build targets is not supported".to_owned(),
});
}
summary
.targets
.first()
.map(|target| TargetPlatform::from_summary(target.clone()))
.transpose()?
},
})
}
pub fn from_target_summary(summary: PlatformSummary) -> Result<Self, RustBuildMetaParseError> {
let host = HostPlatform::current(PlatformLibdir::Unavailable(
PlatformLibdirUnavailable::OLD_SUMMARY,
))
.map_err(|error| RustBuildMetaParseError::UnknownHostPlatform(error.error))?;
let target = TargetTriple::deserialize(Some(summary))?.map(|triple| {
TargetPlatform::new(
triple,
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::OLD_SUMMARY),
)
});
Ok(Self { host, target })
}
pub fn from_summary_str(summary: Option<String>) -> Result<Self, RustBuildMetaParseError> {
let host = HostPlatform::current(PlatformLibdir::Unavailable(
PlatformLibdirUnavailable::OLD_SUMMARY,
))
.map_err(|error| RustBuildMetaParseError::UnknownHostPlatform(error.error))?;
let target = TargetTriple::deserialize_str(summary)?.map(|triple| {
TargetPlatform::new(
triple,
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::OLD_SUMMARY),
)
});
Ok(Self { host, target })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HostPlatform {
pub platform: Platform,
pub libdir: PlatformLibdir,
}
impl HostPlatform {
pub fn current(libdir: PlatformLibdir) -> Result<Self, UnknownHostPlatform> {
let platform = Platform::current().map_err(|error| UnknownHostPlatform { error })?;
Ok(Self { platform, libdir })
}
pub fn to_summary(&self) -> HostPlatformSummary {
HostPlatformSummary {
platform: self.platform.to_summary(),
libdir: self.libdir.to_summary(),
}
}
pub fn from_summary(summary: HostPlatformSummary) -> Result<Self, RustBuildMetaParseError> {
let platform = summary
.platform
.to_platform()
.map_err(RustBuildMetaParseError::PlatformDeserializeError)?;
Ok(Self {
platform,
libdir: PlatformLibdir::from_summary(summary.libdir),
})
}
fn map_libdir(&self, mapper: &PlatformLibdirMapper) -> Self {
Self {
platform: self.platform.clone(),
libdir: mapper.map(&self.libdir),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TargetPlatform {
pub triple: TargetTriple,
pub libdir: PlatformLibdir,
}
impl TargetPlatform {
pub fn new(triple: TargetTriple, libdir: PlatformLibdir) -> Self {
Self { triple, libdir }
}
pub fn to_summary(&self) -> TargetPlatformSummary {
TargetPlatformSummary {
platform: self.triple.platform.to_summary(),
libdir: self.libdir.to_summary(),
}
}
pub fn from_summary(summary: TargetPlatformSummary) -> Result<Self, RustBuildMetaParseError> {
Ok(Self {
triple: TargetTriple::deserialize(Some(summary.platform))
.map_err(RustBuildMetaParseError::PlatformDeserializeError)?
.expect("the input is not None, so the output must not be None"),
libdir: PlatformLibdir::from_summary(summary.libdir),
})
}
fn map_libdir(&self, mapper: &PlatformLibdirMapper) -> Self {
Self {
triple: self.triple.clone(),
libdir: mapper.map(&self.libdir),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PlatformLibdir {
Available(Utf8PathBuf),
Unavailable(PlatformLibdirUnavailable),
}
impl PlatformLibdir {
pub fn from_path(path: Utf8PathBuf) -> Self {
Self::Available(path)
}
pub fn from_rustc_stdout(rustc_output: Option<Vec<u8>>) -> Self {
fn inner(v: Option<Vec<u8>>) -> Result<Utf8PathBuf, PlatformLibdirUnavailable> {
let v = v.ok_or(PlatformLibdirUnavailable::RUSTC_FAILED)?;
let s = String::from_utf8(v).map_err(|e| {
debug!("failed to convert the output to a string: {e}");
PlatformLibdirUnavailable::RUSTC_OUTPUT_ERROR
})?;
let mut lines = s.lines();
let Some(out) = lines.next() else {
debug!("empty output");
return Err(PlatformLibdirUnavailable::RUSTC_OUTPUT_ERROR);
};
let trimmed = out.trim();
if trimmed.is_empty() {
debug!("empty output");
return Err(PlatformLibdirUnavailable::RUSTC_OUTPUT_ERROR);
}
for line in lines {
if !line.trim().is_empty() {
debug!("unexpected additional output: {line}");
return Err(PlatformLibdirUnavailable::RUSTC_OUTPUT_ERROR);
}
}
Ok(Utf8PathBuf::from(trimmed))
}
match inner(rustc_output) {
Ok(path) => Self::Available(path),
Err(error) => Self::Unavailable(error),
}
}
pub fn from_unavailable(error: PlatformLibdirUnavailable) -> Self {
Self::Unavailable(error)
}
pub fn as_path(&self) -> Option<&Utf8Path> {
match self {
Self::Available(path) => Some(path),
Self::Unavailable(_) => None,
}
}
pub fn to_summary(&self) -> PlatformLibdirSummary {
match self {
Self::Available(path) => PlatformLibdirSummary::Available { path: path.clone() },
Self::Unavailable(reason) => PlatformLibdirSummary::Unavailable {
reason: reason.clone(),
},
}
}
pub fn from_summary(summary: PlatformLibdirSummary) -> Self {
match summary {
PlatformLibdirSummary::Available { path: libdir } => Self::Available(libdir),
PlatformLibdirSummary::Unavailable { reason } => Self::Unavailable(reason),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test]
fn test_from_rustc_output_invalid() {
assert_eq!(
PlatformLibdir::from_rustc_stdout(None),
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::RUSTC_FAILED),
);
assert_eq!(
PlatformLibdir::from_rustc_stdout(Some(Vec::new())),
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::RUSTC_OUTPUT_ERROR),
);
assert_eq!(
PlatformLibdir::from_rustc_stdout(Some(b"\n".to_vec())),
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::RUSTC_OUTPUT_ERROR),
);
assert_eq!(
PlatformLibdir::from_rustc_stdout(Some(b"/fake/libdir/1\n/fake/libdir/2\n".to_vec())),
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::RUSTC_OUTPUT_ERROR),
);
}
#[test_case(b"/fake/libdir/22548", "/fake/libdir/22548"; "single line")]
#[test_case(
b"\t /fake/libdir\t \n\r",
"/fake/libdir";
"with leading or trailing whitespace"
)]
#[test_case(
b"/fake/libdir/1\n\n",
"/fake/libdir/1";
"trailing newlines"
)]
fn test_read_from_rustc_output_valid(input: &[u8], actual: &str) {
assert_eq!(
PlatformLibdir::from_rustc_stdout(Some(input.to_vec())),
PlatformLibdir::Available(actual.into()),
);
}
}