use crate::Feature::*;
use std::{fs::read_dir, io::Error, path::Path, process::Command};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
enum Feature {
ControlMessage,
SocketMessage,
SocketMultiMessage,
MtuDiscovery,
GenericSegmentationOffload,
GenericReceiveOffload,
PacketInfo,
TypeOfService,
}
impl Feature {
fn name(&self) -> &str {
match self {
ControlMessage => "cmsg",
SocketMessage => "socket_msg",
SocketMultiMessage => "socket_mmsg",
MtuDiscovery => "mtu_disc",
GenericSegmentationOffload => "gso",
GenericReceiveOffload => "gro",
PacketInfo => "pktinfo",
TypeOfService => "tos",
}
}
}
impl From<&str> for Feature {
fn from(value: &str) -> Self {
for feature in ALL_FEATURES {
if feature.name() == value {
return feature;
}
}
panic!("unsupported feature: {value}");
}
}
const ALL_FEATURES: [Feature; 8] = [
ControlMessage,
SocketMessage,
SocketMultiMessage,
MtuDiscovery,
GenericSegmentationOffload,
GenericReceiveOffload,
PacketInfo,
TypeOfService,
];
fn main() -> Result<(), Error> {
let mut features = Features::default();
if let Some(list) = option_env("S2N_QUIC_PLATFORM_FEATURES_OVERRIDE") {
for _ in 0..2 {
for feature in list.split(',').filter(|&s| !s.is_empty()) {
features.insert(feature.trim().into());
}
}
return Ok(());
}
let env = Env::new();
for feature in read_dir("features")? {
let path = feature?.path();
if let Some(name) = path.file_stem() {
println!("cargo:rerun-if-changed={}", path.display());
if env.check(&path)? {
features.insert(name.to_str().expect("valid feature name").into());
}
}
}
let is_miri = std::env::var("CARGO_CFG_MIRI").is_ok();
match env.target_os.as_str() {
"linux" => {
if is_miri {
features.insert(SocketMessage);
features.insert(SocketMultiMessage);
}
features.insert(MtuDiscovery);
features.insert(GenericSegmentationOffload);
features.insert(GenericReceiveOffload);
features.insert(PacketInfo);
features.insert(TypeOfService);
}
"macos" => {
if is_miri {
features.insert(SocketMessage);
}
features.insert(PacketInfo);
features.insert(TypeOfService);
}
"android" => {
features.insert(MtuDiscovery);
features.insert(PacketInfo);
features.insert(TypeOfService);
}
_ => {
}
}
Ok(())
}
#[derive(Debug, Default)]
struct Features {
features: std::collections::HashSet<Feature>,
}
impl Features {
fn insert(&mut self, feature: Feature) {
if matches!(feature, SocketMessage | SocketMultiMessage) {
self.insert(ControlMessage);
}
if [
GenericSegmentationOffload,
GenericReceiveOffload,
PacketInfo,
TypeOfService,
]
.contains(&feature)
&& !self.supports(ControlMessage)
{
return;
}
let newly_inserted = self.features.insert(feature);
if newly_inserted {
println!("cargo:rustc-cfg=s2n_quic_platform_{}", feature.name());
}
}
fn supports(&self, feature: Feature) -> bool {
self.features.contains(&feature)
}
}
struct Env {
rustc: String,
out_dir: String,
target: String,
target_os: String,
rustc_linker: Option<String>,
}
impl Env {
fn new() -> Self {
Self {
rustc: env("RUSTC"),
out_dir: env("OUT_DIR"),
target: env("TARGET"),
target_os: env("CARGO_CFG_TARGET_OS"),
rustc_linker: option_env("RUSTC_LINKER"),
}
}
fn check(&self, path: &Path) -> Result<bool, Error> {
let mut command = Command::new(&self.rustc);
command
.arg("--out-dir")
.arg(&self.out_dir)
.arg("--target")
.arg(&self.target)
.arg("--crate-type")
.arg("bin")
.arg("--codegen")
.arg("opt-level=0")
.arg(path);
if let Some(linker) = self.rustc_linker.as_ref() {
command.arg(format!("-Clinker={linker}"));
}
for (key, _) in std::env::vars() {
const CARGO_FEATURE: &str = "CARGO_FEATURE_";
if key.starts_with(CARGO_FEATURE) {
command.arg("--cfg").arg(format!(
"feature=\"{}\"",
key.trim_start_matches(CARGO_FEATURE)
.to_lowercase()
.replace('_', "-")
));
}
}
Ok(command.spawn()?.wait()?.success())
}
}
fn env(name: &str) -> String {
option_env(name).unwrap_or_else(|| panic!("build script missing {name:?} environment variable"))
}
fn option_env(name: &str) -> Option<String> {
println!("cargo:rerun-if-env-changed={name}");
std::env::var(name).ok()
}