use crate::{
errors::RustBuildMetaParseError,
helpers::convert_rel_path_to_main_sep,
list::{BinaryListState, TestListState},
platform::{BuildPlatforms, TargetPlatform},
reuse_build::PathMapper,
};
use camino::Utf8PathBuf;
use itertools::Itertools;
use nextest_metadata::{BuildPlatformsSummary, RustBuildMetaSummary, RustNonTestBinarySummary};
use std::{
collections::{BTreeMap, BTreeSet},
marker::PhantomData,
};
use tracing::warn;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RustBuildMeta<State> {
pub target_directory: Utf8PathBuf,
pub base_output_directories: BTreeSet<Utf8PathBuf>,
pub non_test_binaries: BTreeMap<String, BTreeSet<RustNonTestBinarySummary>>,
pub build_script_out_dirs: BTreeMap<String, Utf8PathBuf>,
pub linked_paths: BTreeMap<Utf8PathBuf, BTreeSet<String>>,
pub build_platforms: BuildPlatforms,
state: PhantomData<State>,
}
impl RustBuildMeta<BinaryListState> {
pub fn new(target_directory: impl Into<Utf8PathBuf>, build_platforms: BuildPlatforms) -> Self {
Self {
target_directory: target_directory.into(),
base_output_directories: BTreeSet::new(),
non_test_binaries: BTreeMap::new(),
build_script_out_dirs: BTreeMap::new(),
linked_paths: BTreeMap::new(),
state: PhantomData,
build_platforms,
}
}
pub fn map_paths(&self, path_mapper: &PathMapper) -> RustBuildMeta<TestListState> {
RustBuildMeta {
target_directory: path_mapper
.new_target_dir()
.unwrap_or(&self.target_directory)
.to_path_buf(),
base_output_directories: self.base_output_directories.clone(),
non_test_binaries: self.non_test_binaries.clone(),
build_script_out_dirs: self.build_script_out_dirs.clone(),
linked_paths: self.linked_paths.clone(),
state: PhantomData,
build_platforms: self.build_platforms.map_libdir(path_mapper.libdir_mapper()),
}
}
}
impl RustBuildMeta<TestListState> {
#[cfg(test)]
pub(crate) fn empty() -> Self {
Self {
target_directory: Utf8PathBuf::new(),
base_output_directories: BTreeSet::new(),
non_test_binaries: BTreeMap::new(),
build_script_out_dirs: BTreeMap::new(),
linked_paths: BTreeMap::new(),
state: PhantomData,
build_platforms: BuildPlatforms::new_with_no_target().unwrap(),
}
}
pub fn dylib_paths(&self) -> Vec<Utf8PathBuf> {
let libdirs = self
.build_platforms
.host
.libdir
.as_path()
.into_iter()
.chain(
self.build_platforms
.target
.as_ref()
.and_then(|target| target.libdir.as_path()),
)
.map(|libdir| libdir.to_path_buf())
.collect::<Vec<_>>();
if libdirs.is_empty() {
warn!("failed to detect the rustc libdir, may fail to list or run tests");
}
self.linked_paths
.keys()
.filter_map(|rel_path| {
let join_path = self
.target_directory
.join(convert_rel_path_to_main_sep(rel_path));
join_path.exists().then_some(join_path)
})
.chain(self.base_output_directories.iter().flat_map(|base_output| {
let abs_base = self
.target_directory
.join(convert_rel_path_to_main_sep(base_output));
let with_deps = abs_base.join("deps");
[with_deps, abs_base]
}))
.chain(libdirs)
.unique()
.collect()
}
}
impl<State> RustBuildMeta<State> {
pub fn from_summary(summary: RustBuildMetaSummary) -> Result<Self, RustBuildMetaParseError> {
let build_platforms = if let Some(summary) = summary.platforms {
BuildPlatforms::from_summary(summary.clone())?
} else if let Some(summary) = summary.target_platforms.first() {
BuildPlatforms::from_target_summary(summary.clone())?
} else {
BuildPlatforms::from_summary_str(summary.target_platform.clone())?
};
Ok(Self {
target_directory: summary.target_directory,
base_output_directories: summary.base_output_directories,
build_script_out_dirs: summary.build_script_out_dirs,
non_test_binaries: summary.non_test_binaries,
linked_paths: summary
.linked_paths
.into_iter()
.map(|linked_path| (linked_path, BTreeSet::new()))
.collect(),
state: PhantomData,
build_platforms,
})
}
pub fn to_summary(&self) -> RustBuildMetaSummary {
RustBuildMetaSummary {
target_directory: self.target_directory.clone(),
base_output_directories: self.base_output_directories.clone(),
non_test_binaries: self.non_test_binaries.clone(),
build_script_out_dirs: self.build_script_out_dirs.clone(),
linked_paths: self.linked_paths.keys().cloned().collect(),
target_platform: self.build_platforms.to_summary_str(),
target_platforms: vec![self.build_platforms.to_target_or_host_summary()],
platforms: Some(BuildPlatformsSummary {
host: self.build_platforms.host.to_summary(),
targets: self
.build_platforms
.target
.as_ref()
.into_iter()
.map(TargetPlatform::to_summary)
.collect(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cargo_config::TargetTriple,
platform::{BuildPlatforms, HostPlatform, PlatformLibdir, TargetPlatform},
};
use nextest_metadata::{
BuildPlatformsSummary, HostPlatformSummary, PlatformLibdirSummary,
PlatformLibdirUnavailable,
};
use target_spec::summaries::PlatformSummary;
use test_case::test_case;
impl Default for RustBuildMeta<BinaryListState> {
fn default() -> Self {
RustBuildMeta::<BinaryListState>::new(
Utf8PathBuf::default(),
BuildPlatforms::new_with_no_target()
.expect("creating BuildPlatforms without target triple should succeed"),
)
}
}
fn x86_64_pc_windows_msvc_triple() -> TargetTriple {
TargetTriple::deserialize_str(Some("x86_64-pc-windows-msvc".to_owned()))
.expect("creating TargetTriple should succeed")
.expect("the output of deserialize_str shouldn't be None")
}
fn host_current() -> HostPlatform {
HostPlatform::current(PlatformLibdir::Unavailable(
PlatformLibdirUnavailable::OLD_SUMMARY,
))
.expect("should detect the host platform successfully")
}
fn host_current_with_libdir(libdir: &str) -> HostPlatform {
HostPlatform::current(PlatformLibdir::Available(libdir.into()))
.expect("should detect the host platform successfully")
}
fn host_not_current_with_libdir(libdir: &str) -> HostPlatform {
cfg_if::cfg_if! {
if #[cfg(windows)] {
let triple = TargetTriple::x86_64_unknown_linux_gnu();
} else {
let triple = x86_64_pc_windows_msvc_triple();
}
};
HostPlatform {
platform: triple.platform,
libdir: PlatformLibdir::Available(libdir.into()),
}
}
fn target_linux() -> TargetPlatform {
TargetPlatform::new(
TargetTriple::x86_64_unknown_linux_gnu(),
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::OLD_SUMMARY),
)
}
fn target_linux_with_libdir(libdir: &str) -> TargetPlatform {
TargetPlatform::new(
TargetTriple::x86_64_unknown_linux_gnu(),
PlatformLibdir::Available(libdir.into()),
)
}
fn target_windows() -> TargetPlatform {
TargetPlatform::new(
x86_64_pc_windows_msvc_triple(),
PlatformLibdir::Unavailable(PlatformLibdirUnavailable::OLD_SUMMARY),
)
}
#[test_case(RustBuildMetaSummary {
..Default::default()
}, RustBuildMeta::<BinaryListState> {
build_platforms: BuildPlatforms {
host: host_current(),
target: None,
},
..Default::default()
}; "no target platforms")]
#[test_case(RustBuildMetaSummary {
target_platform: Some("x86_64-unknown-linux-gnu".to_owned()),
..Default::default()
}, RustBuildMeta::<BinaryListState> {
build_platforms: BuildPlatforms {
host: host_current(),
target: Some(target_linux()),
},
..Default::default()
}; "only target platform field")]
#[test_case(RustBuildMetaSummary {
target_platform: Some("x86_64-unknown-linux-gnu".to_owned()),
target_platforms: vec![PlatformSummary::new("x86_64-pc-windows-msvc")],
..Default::default()
}, RustBuildMeta::<BinaryListState> {
build_platforms: BuildPlatforms {
host: host_current(),
target: Some(target_windows()),
},
..Default::default()
}; "target platform and target platforms field")]
#[test_case(RustBuildMetaSummary {
target_platform: Some("aarch64-unknown-linux-gnu".to_owned()),
target_platforms: vec![PlatformSummary::new("x86_64-pc-windows-msvc")],
platforms: Some(BuildPlatformsSummary {
host: host_not_current_with_libdir("/fake/test/libdir/281").to_summary(),
targets: vec![target_linux_with_libdir("/fake/test/libdir/837").to_summary()],
}),
..Default::default()
}, RustBuildMeta::<BinaryListState> {
build_platforms: BuildPlatforms {
host: host_not_current_with_libdir("/fake/test/libdir/281"),
target: Some(target_linux_with_libdir("/fake/test/libdir/837")),
},
..Default::default()
}; "target platform and target platforms and platforms field")]
#[test_case(RustBuildMetaSummary {
platforms: Some(BuildPlatformsSummary {
host: host_current().to_summary(),
targets: vec![],
}),
..Default::default()
}, RustBuildMeta::<BinaryListState> {
build_platforms: BuildPlatforms {
host: host_current(),
target: None,
},
..Default::default()
}; "platforms with zero targets")]
fn test_from_summary(summary: RustBuildMetaSummary, expected: RustBuildMeta<BinaryListState>) {
let actual = RustBuildMeta::<BinaryListState>::from_summary(summary)
.expect("RustBuildMeta should deserialize from summary with success.");
assert_eq!(actual, expected);
}
#[test]
fn test_from_summary_error_multiple_targets() {
let summary = RustBuildMetaSummary {
platforms: Some(BuildPlatformsSummary {
host: host_current().to_summary(),
targets: vec![target_linux().to_summary(), target_windows().to_summary()],
}),
..Default::default()
};
let actual = RustBuildMeta::<BinaryListState>::from_summary(summary);
assert!(matches!(actual, Err(RustBuildMetaParseError::Unsupported { .. })), "Expect the parse result to be an error of RustBuildMetaParseError::Unsupported, actual {actual:?}");
}
#[test]
fn test_from_summary_error_invalid_host_platform_summary() {
let summary = RustBuildMetaSummary {
platforms: Some(BuildPlatformsSummary {
host: HostPlatformSummary {
platform: PlatformSummary::new("invalid-platform-triple"),
libdir: PlatformLibdirSummary::Unavailable {
reason: PlatformLibdirUnavailable::RUSTC_FAILED,
},
},
targets: vec![],
}),
..Default::default()
};
let actual = RustBuildMeta::<BinaryListState>::from_summary(summary);
actual.expect_err("parse result should be an error");
}
#[test_case(RustBuildMeta::<BinaryListState> {
build_platforms: BuildPlatforms {
host: host_current(),
target: None,
},
..Default::default()
}, RustBuildMetaSummary {
target_platform: None,
target_platforms: vec![host_current().to_summary().platform],
platforms: Some(BuildPlatformsSummary {
host: host_current().to_summary(),
targets: vec![],
}),
..Default::default()
}; "build platforms without target")]
#[test_case(RustBuildMeta::<BinaryListState> {
build_platforms: BuildPlatforms {
host: host_current_with_libdir("/fake/test/libdir/736"),
target: Some(target_linux_with_libdir("/fake/test/libdir/873")),
},
..Default::default()
}, RustBuildMetaSummary {
target_platform: Some(
target_linux_with_libdir("/fake/test/libdir/873")
.triple
.platform
.triple_str()
.to_owned(),
),
target_platforms: vec![target_linux_with_libdir("/fake/test/libdir/873").triple.platform.to_summary()],
platforms: Some(BuildPlatformsSummary {
host: host_current_with_libdir("/fake/test/libdir/736").to_summary(),
targets: vec![target_linux_with_libdir("/fake/test/libdir/873").to_summary()],
}),
..Default::default()
}; "build platforms with target")]
fn test_to_summary(meta: RustBuildMeta<BinaryListState>, expected: RustBuildMetaSummary) {
let actual = meta.to_summary();
assert_eq!(actual, expected);
}
#[test]
fn test_dylib_paths_should_include_rustc_dir() {
let host_libdir = Utf8PathBuf::from("/fake/rustc/host/libdir");
let target_libdir = Utf8PathBuf::from("/fake/rustc/target/libdir");
let rust_build_meta = RustBuildMeta {
build_platforms: BuildPlatforms {
host: HostPlatform::current(PlatformLibdir::Available(host_libdir.clone()))
.expect("should detect the host platform successfully"),
target: Some(TargetPlatform::new(
TargetTriple::x86_64_unknown_linux_gnu(),
PlatformLibdir::Available(target_libdir.clone()),
)),
},
..RustBuildMeta::empty()
};
let dylib_paths = rust_build_meta.dylib_paths();
assert!(
dylib_paths.contains(&host_libdir),
"{dylib_paths:?} should contain {host_libdir}"
);
assert!(
dylib_paths.contains(&target_libdir),
"{dylib_paths:?} should contain {target_libdir}"
);
}
#[test]
fn test_dylib_paths_should_not_contain_duplicate_paths() {
let tmpdir = camino_tempfile::tempdir().expect("should create temp dir successfully");
let host_libdir = tmpdir.path().to_path_buf();
let target_libdir = host_libdir.clone();
let fake_target_dir = tmpdir
.path()
.parent()
.expect("tmp directory should have a parent");
let tmpdir_dirname = tmpdir
.path()
.file_name()
.expect("tmp directory should have a file name");
let rust_build_meta = RustBuildMeta {
target_directory: fake_target_dir.to_path_buf(),
linked_paths: [(Utf8PathBuf::from(tmpdir_dirname), Default::default())].into(),
base_output_directories: [Utf8PathBuf::from(tmpdir_dirname)].into(),
build_platforms: BuildPlatforms {
host: HostPlatform::current(PlatformLibdir::Available(host_libdir.clone()))
.expect("should detect the host platform successfully"),
target: Some(TargetPlatform::new(
TargetTriple::x86_64_unknown_linux_gnu(),
PlatformLibdir::Available(target_libdir.clone()),
)),
},
..RustBuildMeta::empty()
};
let dylib_paths = rust_build_meta.dylib_paths();
assert!(
dylib_paths.clone().into_iter().all_unique(),
"{dylib_paths:?} should not contain duplicate paths"
);
}
}