#![allow(clippy::pedantic, clippy::panic)]
use serde::Deserialize;
use std::env;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
const DEFAULT_DAP_FEATURES: &[&str] = &["dap.breakpoints.basic", "dap.core", "dap.inline_values"];
#[derive(Debug, Deserialize)]
struct FeatureCatalog {
feature: Vec<Feature>,
}
#[derive(Debug, Deserialize)]
struct Feature {
id: String,
#[serde(default)]
area: String,
#[serde(default)]
advertised: bool,
}
#[derive(Debug)]
struct CatalogSource {
path: PathBuf,
}
fn resolve_catalog_source(manifest_dir: &Path) -> Result<CatalogSource, Box<dyn Error>> {
if let Ok(override_path) = env::var("FEATURES_TOML_OVERRIDE") {
let override_path = PathBuf::from(override_path);
if override_path.exists() {
return Ok(CatalogSource { path: override_path });
}
}
let local = manifest_dir.join("features.toml");
if local.exists() {
return Ok(CatalogSource { path: local });
}
let workspace = manifest_dir
.parent()
.and_then(Path::parent)
.map(|parent| parent.join("features.toml"))
.filter(|path| path.exists());
if let Some(path) = workspace {
return Ok(CatalogSource { path });
}
let vendored = manifest_dir.join("features_sot.toml");
if vendored.exists() {
return Ok(CatalogSource { path: vendored });
}
Err(format!("features catalog not found for manifest dir: {}", manifest_dir.display()).into())
}
fn load_catalog_for_build(
manifest_dir: &Path,
) -> Result<(FeatureCatalog, CatalogSource), Box<dyn Error>> {
let source = resolve_catalog_source(manifest_dir)?;
let content = fs::read_to_string(&source.path)?;
let catalog = toml::from_str(&content)?;
Ok((catalog, source))
}
fn render_dap_feature_catalog_module(ids: &[String]) -> String {
let mut sorted = ids.to_vec();
sorted.sort_unstable();
sorted.dedup();
let mut code = String::new();
code.push_str("// @generated by build.rs; DO NOT EDIT.\n\n");
code.push_str("pub const ADVERTISED_DAP_FEATURES: &[&str] = &[\n");
for id in &sorted {
code.push_str(&format!(" {:?},\n", id));
}
code.push_str("];\n\n");
code.push_str(
"pub fn advertised_features() -> &'static [&'static str] { ADVERTISED_DAP_FEATURES }\n\n",
);
code.push_str(
"pub fn has_feature(id: &str) -> bool { ADVERTISED_DAP_FEATURES.contains(&id) }\n",
);
code
}
fn render_dap_fallback_module(default_features: &[&str]) -> String {
let features =
default_features.iter().map(|feature| (*feature).to_string()).collect::<Vec<_>>();
render_dap_feature_catalog_module(&features)
}
fn generate_catalog_module() -> Result<(), Box<dyn Error>> {
let out_dir = std::env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("dap_feature_catalog.rs");
println!("cargo:rerun-if-env-changed=FEATURES_TOML_OVERRIDE");
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?;
let code = match load_catalog_for_build(Path::new(&manifest_dir)) {
Ok((catalog, source)) => {
println!("cargo:rerun-if-changed={}", source.path.display());
let mut source_features = catalog
.feature
.iter()
.filter(|feature| feature.area == "debug" && feature.advertised)
.map(|feature| feature.id.clone())
.collect::<Vec<_>>();
source_features.sort_unstable();
source_features.dedup();
render_dap_feature_catalog_module(&source_features)
}
Err(error) => {
eprintln!("Warning: failed to load DAP feature catalog from features.toml: {error}");
render_dap_fallback_module(DEFAULT_DAP_FEATURES)
}
};
fs::write(dest_path, code)?;
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
generate_catalog_module()
}