From 936d2915e2257be20213e3501e96be3df10c5f49 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Wed, 24 Jul 2024 09:10:01 +0000 Subject: [PATCH] add cargo manifest reflection Signed-off-by: Jason Volk --- Cargo.lock | 12 ++++++++ Cargo.toml | 4 +++ src/core/Cargo.toml | 2 ++ src/core/error/mod.rs | 2 ++ src/core/info/cargo.rs | 66 ++++++++++++++++++++++++++++++++++++++++++ src/core/info/mod.rs | 1 + src/macros/cargo.rs | 51 ++++++++++++++++++++++++++++++++ src/macros/mod.rs | 4 +++ 8 files changed, 142 insertions(+) create mode 100644 src/core/info/cargo.rs create mode 100644 src/macros/cargo.rs diff --git a/Cargo.lock b/Cargo.lock index 71d79adb..d9af38d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -472,6 +472,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cargo_toml" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad639525b1c67b6a298f378417b060fbc04618bea559482a8484381cce27d965" +dependencies = [ + "serde", + "toml", +] + [[package]] name = "cc" version = "1.1.6" @@ -666,8 +676,10 @@ dependencies = [ "argon2", "axum 0.7.5", "bytes", + "cargo_toml", "checked_ops", "chrono", + "conduit_macros", "const-str", "either", "figment", diff --git a/Cargo.toml b/Cargo.toml index 5fcb03ef..acaed701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,10 @@ name = "conduit" [workspace.dependencies.const-str] version = "0.5.7" +[workspace.dependencies.cargo_toml] +version = "0.20" +features = ["features"] + [workspace.dependencies.sanitize-filename] version = "0.5.0" diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index 453d7b13..620aad02 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -53,8 +53,10 @@ sha256_media = [] argon2.workspace = true axum.workspace = true bytes.workspace = true +cargo_toml.workspace = true checked_ops.workspace = true chrono.workspace = true +conduit-macros.workspace = true const-str.workspace = true either.workspace = true figment.workspace = true diff --git a/src/core/error/mod.rs b/src/core/error/mod.rs index 9439261e..8664d740 100644 --- a/src/core/error/mod.rs +++ b/src/core/error/mod.rs @@ -55,6 +55,8 @@ pub enum Error { Http(#[from] http::Error), #[error("{0}")] HttpHeader(#[from] http::header::InvalidHeaderValue), + #[error("{0}")] + CargoToml(#[from] cargo_toml::Error), // ruma #[error("{0}")] diff --git a/src/core/info/cargo.rs b/src/core/info/cargo.rs new file mode 100644 index 00000000..0d2db1ad --- /dev/null +++ b/src/core/info/cargo.rs @@ -0,0 +1,66 @@ +//! Information about the build related to Cargo. This is a frontend interface +//! informed by proc-macros that capture raw information at build time which is +//! further processed at runtime either during static initialization or as +//! necessary. + +use std::sync::OnceLock; + +use cargo_toml::Manifest; +use conduit_macros::cargo_manifest; + +use crate::Result; + +// Raw captures of the cargo manifest for each crate. This is provided by a +// proc-macro at build time since the source directory and the cargo toml's may +// not be present during execution. + +#[cargo_manifest] +const WORKSPACE_MANIFEST: &'static str = (); +#[cargo_manifest("macros")] +const MACROS_MANIFEST: &'static str = (); +#[cargo_manifest("core")] +const CORE_MANIFEST: &'static str = (); +#[cargo_manifest("database")] +const DATABASE_MANIFEST: &'static str = (); +#[cargo_manifest("service")] +const SERVICE_MANIFEST: &'static str = (); +#[cargo_manifest("admin")] +const ADMIN_MANIFEST: &'static str = (); +#[cargo_manifest("router")] +const ROUTER_MANIFEST: &'static str = (); +#[cargo_manifest("main")] +const MAIN_MANIFEST: &'static str = (); + +/// Processed list of features access all project crates. This is generated from +/// the data in the MANIFEST strings and contains all possible project features. +/// For *enabled* features see the info::rustc module instead. +static FEATURES: OnceLock> = OnceLock::new(); + +/// List of all possible features for the project. For *enabled* features in +/// this build see the companion function in info::rustc. +pub fn features() -> &'static Vec { + FEATURES.get_or_init(|| init_features().unwrap_or_else(|e| panic!("Failed initialize features: {e}"))) +} + +fn init_features() -> Result> { + let mut features = Vec::new(); + append_features(&mut features, WORKSPACE_MANIFEST)?; + append_features(&mut features, MACROS_MANIFEST)?; + append_features(&mut features, CORE_MANIFEST)?; + append_features(&mut features, DATABASE_MANIFEST)?; + append_features(&mut features, SERVICE_MANIFEST)?; + append_features(&mut features, ADMIN_MANIFEST)?; + append_features(&mut features, ROUTER_MANIFEST)?; + append_features(&mut features, MAIN_MANIFEST)?; + features.sort(); + features.dedup(); + + Ok(features) +} + +fn append_features(features: &mut Vec, manifest: &str) -> Result<()> { + let manifest = Manifest::from_str(manifest)?; + features.extend(manifest.features.keys().cloned()); + + Ok(()) +} diff --git a/src/core/info/mod.rs b/src/core/info/mod.rs index 42ec971e..7749bbdc 100644 --- a/src/core/info/mod.rs +++ b/src/core/info/mod.rs @@ -1,4 +1,5 @@ //! Information about the project. This module contains version, build, system, //! etc information which can be queried by admins or used by developers. +pub mod cargo; pub mod version; diff --git a/src/macros/cargo.rs b/src/macros/cargo.rs new file mode 100644 index 00000000..17132a6c --- /dev/null +++ b/src/macros/cargo.rs @@ -0,0 +1,51 @@ +use std::{fs::read_to_string, path::PathBuf}; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, ItemConst, Lit, NestedMeta}; + +pub(super) fn manifest(args: TokenStream, item: TokenStream) -> TokenStream { + let item = parse_macro_input!(item as ItemConst); + let args = parse_macro_input!(args as AttributeArgs); + let member = args.into_iter().find_map(|arg| { + let NestedMeta::Lit(arg) = arg else { + return None; + }; + let Lit::Str(arg) = arg else { + return None; + }; + Some(arg.value()) + }); + + let path = manifest_path(member.as_deref()); + let manifest = read_to_string(&path).unwrap_or_default(); + + let name = item.ident; + let val = manifest.as_str(); + let ret = quote! { + const #name: &'static str = #val; + }; + + ret.into() +} + +#[allow(clippy::option_env_unwrap)] +fn manifest_path(member: Option<&str>) -> PathBuf { + let mut path: PathBuf = option_env!("CARGO_MANIFEST_DIR") + .expect("missing CARGO_MANIFEST_DIR in environment") + .into(); + + // conduwuit/src/macros/ -> conduwuit/src/ + path.pop(); + + if let Some(member) = member { + // conduwuit/$member/Cargo.toml + path.push(member); + } else { + // conduwuit/src/ -> conduwuit/ + path.pop(); + } + + path.push("Cargo.toml"); + path +} diff --git a/src/macros/mod.rs b/src/macros/mod.rs index 6f286d66..718583a4 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -1,4 +1,5 @@ mod admin; +mod cargo; mod utils; use proc_macro::TokenStream; @@ -7,3 +8,6 @@ use proc_macro::TokenStream; pub fn admin_command_dispatch(args: TokenStream, input: TokenStream) -> TokenStream { admin::command_dispatch(args, input) } + +#[proc_macro_attribute] +pub fn cargo_manifest(args: TokenStream, input: TokenStream) -> TokenStream { cargo::manifest(args, input) }