From cbd2106f98572838f9de89714b0ea17ba218b505 Mon Sep 17 00:00:00 2001 From: Maarten de Vries Date: Wed, 13 Dec 2023 17:26:53 +0100 Subject: [PATCH] Shuffle a few more modules around. --- git-version-macro/src/args.rs | 67 +++++++ git-version-macro/src/describe_submodules.rs | 80 -------- git-version-macro/src/lib.rs | 190 +++++++------------ git-version-macro/src/utils.rs | 28 +++ 4 files changed, 167 insertions(+), 198 deletions(-) create mode 100644 git-version-macro/src/args.rs delete mode 100644 git-version-macro/src/describe_submodules.rs diff --git a/git-version-macro/src/args.rs b/git-version-macro/src/args.rs new file mode 100644 index 0000000..f3edf45 --- /dev/null +++ b/git-version-macro/src/args.rs @@ -0,0 +1,67 @@ +use syn::{LitStr, Expr, Ident}; +use syn::punctuated::Punctuated; +use syn::token::Comma; + +#[derive(Default)] +pub struct Args { + pub git_args: Option>, + pub prefix: Option, + pub suffix: Option, + pub cargo_prefix: Option, + pub cargo_suffix: Option, + pub fallback: Option, +} + +impl syn::parse::Parse for Args { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut result = Args::default(); + loop { + if input.is_empty() { + break; + } + let ident: Ident = input.parse()?; + let _: syn::token::Eq = input.parse()?; + let check_dup = |dup: bool| { + if dup { + Err(error!("`{} = ` can only appear once", ident)) + } else { + Ok(()) + } + }; + match ident.to_string().as_str() { + "args" => { + check_dup(result.git_args.is_some())?; + let content; + syn::bracketed!(content in input); + result.git_args = Some(Punctuated::parse_terminated(&content)?); + } + "prefix" => { + check_dup(result.prefix.is_some())?; + result.prefix = Some(input.parse()?); + } + "suffix" => { + check_dup(result.suffix.is_some())?; + result.suffix = Some(input.parse()?); + } + "cargo_prefix" => { + check_dup(result.cargo_prefix.is_some())?; + result.cargo_prefix = Some(input.parse()?); + } + "cargo_suffix" => { + check_dup(result.cargo_suffix.is_some())?; + result.cargo_suffix = Some(input.parse()?); + } + "fallback" => { + check_dup(result.fallback.is_some())?; + result.fallback = Some(input.parse()?); + } + x => Err(error!("Unexpected argument name `{}`", x))?, + } + if input.is_empty() { + break; + } + let _: Comma = input.parse()?; + } + Ok(result) + } +} diff --git a/git-version-macro/src/describe_submodules.rs b/git-version-macro/src/describe_submodules.rs deleted file mode 100644 index 36dc357..0000000 --- a/git-version-macro/src/describe_submodules.rs +++ /dev/null @@ -1,80 +0,0 @@ -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::quote; -use std::path::Path; - -use crate::Args; - -macro_rules! error { - ($($args:tt)*) => { - syn::Error::new(Span::call_site(), format!($($args)*)) - }; -} - -pub fn git_submodule_versions_impl(args: Args) -> syn::Result { - if let Some(cargo_prefix) = &args.cargo_prefix { - return Err(syn::Error::new_spanned(cargo_prefix, "invalid argument `cargo_prefix` for `git_submodule_versions!()`")); - } - if let Some(cargo_suffix) = &args.cargo_suffix { - return Err(syn::Error::new_spanned(cargo_suffix, "invalid argument `cargo_suffix` for `git_submodule_versions!()`")); - } - - let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR") - .ok_or_else(|| error!("CARGO_MANIFEST_DIR is not set"))?; - let git_dir = crate::utils::git_dir(&manifest_dir) - .map_err(|e| error!("failed to determine .git directory: {}", e))?; - - let modules = match crate::utils::get_submodules(&manifest_dir) { - Ok(x) => x, - Err(err) => return Err(error!("{}", err)), - }; - - // Ensure that the type of the empty array is still known to the compiler. - if modules.is_empty() { - return Ok(quote!([("", ""); 0])); - } - - let versions = describe_submodules(&git_dir.join(".."), &modules, &args) - .map_err(|e| error!("{}", e))?; - - Ok(quote!({ - [#((#modules, #versions)),*] - })) -} - -/// Run `git describe` for each submodule to get the git version with the specified args. -fn describe_submodules( - root: &Path, - submodules: &[String], - args: &Args, -) -> Result, String> { - let mut versions = Vec::new(); - - let git_args = args.git_args.as_ref().map_or_else( - || vec!["--always".to_string(), "--dirty=-modified".to_string()], - |list| list.iter().map(|x| x.value()).collect(), - ); - - for submodule in submodules { - let path = root.join(submodule); - // Get the submodule version or fallback. - let version = match crate::utils::describe(path, &git_args) { - Ok(version) => { - let prefix = args.prefix.iter(); - let suffix = args.suffix.iter(); - quote!{ - ::core::concat!(#(#prefix,)* #version #(, #suffix)*) - } - } - Err(e) => { - if let Some(fallback) = &args.fallback { - quote!( #fallback ) - } else { - return Err(e) - } - }, - }; - versions.push(version); - } - - Ok(versions) -} diff --git a/git-version-macro/src/lib.rs b/git-version-macro/src/lib.rs index ff6c123..e78cbe8 100644 --- a/git-version-macro/src/lib.rs +++ b/git-version-macro/src/lib.rs @@ -1,117 +1,15 @@ use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::TokenStream as TokenStream2; use quote::{quote, ToTokens}; -use std::path::{Path, PathBuf}; -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::token::{Comma, Eq}; -use syn::{Expr, Ident, LitStr}; -use self::utils::{describe, git_dir}; - -mod describe_submodules; -mod utils; macro_rules! error { ($($args:tt)*) => { - syn::Error::new(Span::call_site(), format!($($args)*)) + syn::Error::new(proc_macro2::Span::call_site(), format!($($args)*)) }; } -fn canonicalize_path(path: &Path) -> syn::Result { - path.canonicalize() - .map_err(|e| error!("failed to canonicalize {}: {}", path.display(), e))? - .into_os_string() - .into_string() - .map_err(|file| error!("invalid UTF-8 in path to {}", PathBuf::from(file).display())) -} - -/// Create a token stream representing dependencies on the git state. -fn git_dependencies() -> syn::Result { - let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR") - .ok_or_else(|| error!("CARGO_MANIFEST_DIR is not set"))?; - let git_dir = git_dir(manifest_dir).map_err(|e| error!("failed to determine .git directory: {}", e))?; - - let deps: Vec<_> = ["logs/HEAD", "index"] - .iter() - .flat_map(|&file| { - canonicalize_path(&git_dir.join(file)).map(Some).unwrap_or_else(|e| { - eprintln!( - "Failed to add dependency on the git state: {}. Git state changes might not trigger a rebuild.", - e - ); - None - }) - }) - .collect(); - - Ok(quote! { - #( include_bytes!(#deps); )* - }) -} - -#[derive(Default)] -struct Args { - git_args: Option>, - prefix: Option, - suffix: Option, - cargo_prefix: Option, - cargo_suffix: Option, - fallback: Option, -} - -impl Parse for Args { - fn parse(input: ParseStream) -> syn::Result { - let mut result = Args::default(); - loop { - if input.is_empty() { - break; - } - let ident: Ident = input.parse()?; - let _: Eq = input.parse()?; - let check_dup = |dup: bool| { - if dup { - Err(error!("`{} = ` can only appear once", ident)) - } else { - Ok(()) - } - }; - match ident.to_string().as_str() { - "args" => { - check_dup(result.git_args.is_some())?; - let content; - syn::bracketed!(content in input); - result.git_args = Some(Punctuated::parse_terminated(&content)?); - } - "prefix" => { - check_dup(result.prefix.is_some())?; - result.prefix = Some(input.parse()?); - } - "suffix" => { - check_dup(result.suffix.is_some())?; - result.suffix = Some(input.parse()?); - } - "cargo_prefix" => { - check_dup(result.cargo_prefix.is_some())?; - result.cargo_prefix = Some(input.parse()?); - } - "cargo_suffix" => { - check_dup(result.cargo_suffix.is_some())?; - result.cargo_suffix = Some(input.parse()?); - } - "fallback" => { - check_dup(result.fallback.is_some())?; - result.fallback = Some(input.parse()?); - } - x => Err(error!("Unexpected argument name `{}`", x))?, - } - if input.is_empty() { - break; - } - let _: Comma = input.parse()?; - } - Ok(result) - } -} +mod args; +mod utils; /// Get the git version for the source code. /// @@ -150,7 +48,7 @@ impl Parse for Args { /// ``` #[proc_macro] pub fn git_version(input: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(input as Args); + let args = syn::parse_macro_input!(input as args::Args); let tokens = match git_version_impl(args) { Ok(x) => x, @@ -160,7 +58,7 @@ pub fn git_version(input: TokenStream) -> TokenStream { TokenStream::from(tokens) } -fn git_version_impl(args: Args) -> syn::Result { +fn git_version_impl(args: args::Args) -> syn::Result { let git_args = args.git_args.map_or_else( || vec!["--always".to_string(), "--dirty=-modified".to_string()], |list| list.iter().map(|x| x.value()).collect(), @@ -171,9 +69,9 @@ fn git_version_impl(args: Args) -> syn::Result { let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR") .ok_or_else(|| error!("CARGO_MANIFEST_DIR is not set"))?; - match describe(manifest_dir, git_args) { + match utils::describe(manifest_dir, git_args) { Ok(version) => { - let dependencies = git_dependencies()?; + let dependencies = utils::git_dependencies()?; let prefix = args.prefix.iter(); let suffix = args.suffix; Ok(quote!({ @@ -197,16 +95,12 @@ fn git_version_impl(args: Args) -> syn::Result { } } -/// Get the git version for submodules below the cargo project. -/// -/// This macro will not infer type if there are no submodules in the project. +/// Get the git version of all submodules below the cargo project. /// /// This macro expands to `[(&str, &str), N]` where `N` is the total number of /// submodules below the root of the project (evaluated recursively) /// -/// The format of the array is as follows: -/// -/// `[("relative/path/to/submodule", "{prefix}{git_describe_output}{suffix}")]` +/// Each entry in the array is a tuple of the submodule path and the version information. /// /// The following (named) arguments can be given: /// @@ -228,6 +122,9 @@ fn git_version_impl(args: Args) -> syn::Result { /// # use git_version::git_submodule_versions; /// # const N: usize = 0; /// const MODULE_VERSIONS: [(&str, &str); N] = git_submodule_versions!(); +/// for (path, version) in MODULE_VERSIONS { +/// println!("{path}: {version}"); +/// } /// ``` /// /// ``` @@ -243,12 +140,69 @@ fn git_version_impl(args: Args) -> syn::Result { /// ``` #[proc_macro] pub fn git_submodule_versions(input: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(input as Args); + let args = syn::parse_macro_input!(input as args::Args); - let tokens = match describe_submodules::git_submodule_versions_impl(args) { + let tokens = match git_submodule_versions_impl(args) { Ok(x) => x, Err(e) => e.to_compile_error(), }; TokenStream::from(tokens) } + +fn git_submodule_versions_impl(args: args::Args) -> syn::Result { + if let Some(cargo_prefix) = &args.cargo_prefix { + return Err(syn::Error::new_spanned(cargo_prefix, "invalid argument `cargo_prefix` for `git_submodule_versions!()`")); + } + if let Some(cargo_suffix) = &args.cargo_suffix { + return Err(syn::Error::new_spanned(cargo_suffix, "invalid argument `cargo_suffix` for `git_submodule_versions!()`")); + } + + let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR") + .ok_or_else(|| error!("CARGO_MANIFEST_DIR is not set"))?; + let git_dir = crate::utils::git_dir(&manifest_dir) + .map_err(|e| error!("failed to determine .git directory: {}", e))?; + + let modules = match crate::utils::get_submodules(&manifest_dir) { + Ok(x) => x, + Err(err) => return Err(error!("{}", err)), + }; + + // Ensure that the type of the empty array is still known to the compiler. + if modules.is_empty() { + return Ok(quote!([("", ""); 0])); + } + + let git_args = args.git_args.as_ref().map_or_else( + || vec!["--always".to_string(), "--dirty=-modified".to_string()], + |list| list.iter().map(|x| x.value()).collect(), + ); + + let root_dir = git_dir.join(".."); + let mut versions = Vec::new(); + for submodule in &modules { + let path = root_dir.join(submodule); + // Get the submodule version or fallback. + let version = match crate::utils::describe(path, &git_args) { + Ok(version) => { + let prefix = args.prefix.iter(); + let suffix = args.suffix.iter(); + quote!{ + ::core::concat!(#(#prefix,)* #version #(, #suffix)*) + } + } + Err(e) => { + if let Some(fallback) = &args.fallback { + quote!( #fallback ) + } else { + return Err(error!("{}", e)); + } + }, + }; + versions.push(version); + } + + Ok(quote!({ + [#((#modules, #versions)),*] + })) +} diff --git a/git-version-macro/src/utils.rs b/git-version-macro/src/utils.rs index b5f3d39..7368ecc 100644 --- a/git-version-macro/src/utils.rs +++ b/git-version-macro/src/utils.rs @@ -46,6 +46,34 @@ pub fn get_submodules(dir: impl AsRef) -> Result, String> { ) } +pub fn canonicalize_path(path: &Path) -> syn::Result { + path.canonicalize() + .map_err(|e| error!("failed to canonicalize {}: {}", path.display(), e))? + .into_os_string() + .into_string() + .map_err(|file| error!("invalid UTF-8 in path to {}", PathBuf::from(file).display())) +} + +/// Create a token stream representing dependencies on the git state. +pub fn git_dependencies() -> syn::Result { + let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR") + .ok_or_else(|| error!("CARGO_MANIFEST_DIR is not set"))?; + let git_dir = git_dir(manifest_dir).map_err(|e| error!("failed to determine .git directory: {}", e))?; + + let deps: Vec<_> = ["logs/HEAD", "index"] + .iter() + .flat_map(|&file| { + canonicalize_path(&git_dir.join(file)) + .map_err(|e| eprintln!("Failed to add dependency on the git state: {}. Git state changes might not trigger a rebuild.", e)) + .ok() + }) + .collect(); + + Ok(quote::quote! { + #( include_bytes!(#deps); )* + }) +} + fn run_git(program: &str, command: &mut std::process::Command) -> Result { let output = command .stdout(std::process::Stdio::piped())