+ use std::num::NonZeroUsize;
+
+ use camino::{Utf8Path, Utf8PathBuf};
+ use libpijul::pristine::sanakirja::Pristine;
+ use libpijul::{Base32, RecordBuilder, TxnT, TxnTExt};
+ use quote::quote;
+
+ #[derive(Debug)]
+ pub struct Build {
+ pub package: Package,
+ pub toolchain: Toolchain,
+ pub compilation: Compilation,
+ pub repository: Repository,
+ pub timestamp: jiff::Timestamp,
+ }
+
+ #[derive(Debug)]
+ pub struct Package {
+ pub package_name: &'static str,
+ pub package_version: &'static str,
+ }
+
+ impl Package {
+ fn export() -> Result<proc_macro2::TokenStream, anyhow::Error> {
+ let package_name = std::env::var("CARGO_PKG_NAME")?;
+ let package_version = &std::env::var("CARGO_PKG_VERSION")?;
+
+ Ok(quote! {
+ extension_build_info::Package {
+ package_name: #package_name,
+ package_version: #package_version,
+ }
+ })
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct Toolchain {
+ pub cargo_version: &'static str,
+ pub rustc_version: &'static str,
+ }
+
+ impl Toolchain {
+ fn get_version(program_location: String) -> Result<String, anyhow::Error> {
+ let version_output = std::process::Command::new(program_location)
+ .arg("--version")
+ .output()?;
+ let version_string = String::from_utf8(version_output.stdout)?;
+
+ Ok(match version_string.strip_suffix('\n') {
+ Some(version) => version.to_string(),
+ None => version_string,
+ })
+ }
+ fn export() -> Result<proc_macro2::TokenStream, anyhow::Error> {
+ let cargo_version = Self::get_version(std::env::var("CARGO")?)?;
+ let rustc_version = Self::get_version(std::env::var("RUSTC")?)?;
+
+ Ok(quote! {
+ extension_build_info::Toolchain {
+ cargo_version: #cargo_version,
+ rustc_version: #rustc_version,
+ }
+ })
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct Compilation {
+ pub target: &'static str,
+ pub profile: &'static str,
+ pub opt_level: &'static str,
+ pub debug: &'static str,
+ pub rustflags: &'static [&'static str],
+ }
+
+ impl Compilation {
+ fn export() -> Result<proc_macro2::TokenStream, anyhow::Error> {
+ let target = std::env::var("TARGET")?;
+ let profile = std::env::var("PROFILE")?;
+ let opt_level = std::env::var("OPT_LEVEL")?;
+ let debug = std::env::var("DEBUG")?;
+ let encoded_rustflags = std::env::var("CARGO_ENCODED_RUSTFLAGS")?;
+ let rustflags = encoded_rustflags.split('\u{1f}');
+
+ Ok(quote! {
+ extension_build_info::Compilation {
+ target: #target,
+ profile: #profile,
+ opt_level: #opt_level,
+ debug: #debug,
+ rustflags: &[#(#rustflags),*],
+ }
+ })
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct Repository {
+ pub channel_name: &'static str,
+ pub channel_state: &'static str,
+ pub has_unrecorded_changes: bool,
+ }
+
+ impl Repository {
+ fn export() -> Result<proc_macro2::TokenStream, anyhow::Error> {
+ let extension_manifest_directory = Utf8PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
+ let repository_root = extension_manifest_directory
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap();
+ let pristine_db = repository_root
+ .join(libpijul::DOT_DIR)
+ .join("pristine")
+ .join("db");
+
+ let pristine = Pristine::new(pristine_db.as_str())?;
+ let change_store =
+ libpijul::changestore::filesystem::FileSystem::from_root(repository_root.as_str(), 256);
+ let working_copy = libpijul::working_copy::FileSystem::from_root(repository_root.as_str());
+
+ // Recorded changes
+ let transaction = pristine.arc_txn_begin()?;
+ let read_transaction = transaction.read();
+ let channel_name = read_transaction
+ .current_channel()
+ .unwrap_or(libpijul::DEFAULT_CHANNEL);
+ let channel = read_transaction.load_channel(channel_name)?.unwrap();
+ let channel_merkle = read_transaction.current_state(&*channel.read())?;
+ let channel_state = channel_merkle.to_base32();
+
+ // Unrecorded changes
+ let mut record_builder = RecordBuilder::new();
+ record_builder.record(
+ transaction.clone(),
+ libpijul::Algorithm::default(),
+ true,
+ &libpijul::DEFAULT_SEPARATOR,
+ channel.clone(),
+ &working_copy,
+ &change_store,
+ "",
+ std::thread::available_parallelism()
+ .unwrap_or(NonZeroUsize::MIN)
+ .get(),
+ )?;
+ let unrecorded_changes = record_builder.finish();
+ let has_unrecorded_changes = !unrecorded_changes.actions.is_empty();
+
+ Ok(quote! {
+ extension_build_info::Repository {
+ channel_name: #channel_name,
+ channel_state: #channel_state,
+ has_unrecorded_changes: #has_unrecorded_changes,
+ }
+ })
+ }
+ }
+
+ #[macro_export]
+ macro_rules! include_build_info {
+ () => {
+ include!(concat!(env!("OUT_DIR"), "/build_info.rs"))
+ };
+ }
+
+ pub fn export() -> Result<(), anyhow::Error> {
+ let timestamp = jiff::Timestamp::now();
+ let timestamp_seconds = timestamp.as_second();
+ let timestamp_nanoseconds = timestamp.subsec_nanosecond();
+
+ let package = Package::export()?;
+ let toolchain = Toolchain::export()?;
+ let compilation = Compilation::export()?;
+ let repository = Repository::export()?;
+
+ let token_stream = quote! {
+ extension_build_info::Build {
+ package: #package,
+ toolchain: #toolchain,
+ compilation: #compilation,
+ repository: #repository,
+ timestamp: jiff::Timestamp::constant(#timestamp_seconds, #timestamp_nanoseconds),
+ }
+ };
+
+ let out_dir = std::env::var("OUT_DIR")?;
+ let file_path = Utf8Path::new(&out_dir).join("build_info.rs");
+ std::fs::write(file_path, token_stream.to_string().as_bytes())?;
+
+ Ok(())
+ }