diff --git a/git-version-macro/src/describe_submodules.rs b/git-version-macro/src/describe_submodules.rs index ac6b8af..a327372 100644 --- a/git-version-macro/src/describe_submodules.rs +++ b/git-version-macro/src/describe_submodules.rs @@ -1,7 +1,4 @@ extern crate proc_macro; -use crate::canonicalize_path; -use crate::git_dependencies; -use crate::utils::run_git; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use std::ffi::OsStr; @@ -15,6 +12,8 @@ use syn::{ Ident, LitStr, }; +use crate::utils::{git_dir, run_git}; + macro_rules! error { ($($args:tt)*) => { syn::Error::new(Span::call_site(), format!($($args)*)) @@ -76,19 +75,19 @@ impl Parse for GitModArgs { } pub(crate) fn git_submodule_versions_impl(args: GitModArgs) -> syn::Result { - let mut modules = match get_submodules() { + 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 modules = match get_submodules(&manifest_dir) { Ok(x) => x, Err(err) => return Err(error!("{}", err)), }; - modules.retain(|path| !path.is_empty()); - - let mut describe_paths: Vec<(String, String)> = vec![]; - - for path in modules { - let path_obj = Path::new(&path); - let path_obj = canonicalize_path(path_obj)?; - describe_paths.push((path, path_obj)); + // Ensure that the type of the empty array is still known to the compiler. + if modules.is_empty() { + return Ok(quote!([("", ""); 0])); } let git_describe_args = args.args.map_or_else( @@ -106,77 +105,65 @@ pub(crate) fn git_submodule_versions_impl(args: GitModArgs) -> syn::Result { - let dependencies = git_dependencies()?; - let (paths, versions) = result; - - // Ensure that the type of the empty array is still known to the compiler. - if paths.is_empty() { - Ok(quote!({ - #dependencies; - [("", ""); 0] - })) - } else { - Ok(quote!({ - #dependencies; - [#((#paths, #versions)),*] - })) - } - } - Err(e) => Err(error!("{}", e)), - } + let versions = describe_submodules(&git_dir.join(".."), &modules, &git_describe_args, &prefix, &suffix, fallback.as_deref()) + .map_err(|e| error!("{}", e))?; + + Ok(quote!({ + [#((#modules, #versions)),*] + })) } /// Run `git submodule foreach` command to discover submodules in the project. -fn get_submodules() -> Result, String> { - let mut args: Vec = "submodule foreach --quiet --recursive" - .to_string() - .split(' ') - .map(|x| x.to_string()) - .collect(); - - args.push("echo $displaypath".to_string()); - - let result = run_git("git submodule", Command::new("git").args(args))?; - - Ok(result.split('\n').map(|x| x.to_string()).collect()) +fn get_submodules(dir: impl AsRef) -> Result, String> { + let dir = dir.as_ref(); + let result = run_git("git submodule", + Command::new("git") + .arg("-C") + .arg(dir) + .arg("submodule") + .arg("foreach") + .arg("--quiet") + .arg("--recursive") + .arg("echo $displaypath"), + )?; + + Ok(result.lines() + .filter(|x| !x.is_empty()) + .map(|x| x.to_owned()) + .collect() + ) } /// Run `git describe` for each submodule to get the git version with the specified args. fn describe_submodules( - paths: Vec<(String, String)>, + root: &Path, + submodules: &[String], describe_args: I, - prefix: String, - suffix: String, - fallback: Option, -) -> Result<(Vec, Vec), String> + prefix: &str, + suffix: &str, + fallback: Option<&str>, +) -> Result, String> where I: IntoIterator + Clone, S: AsRef, { - let mut paths_out: Vec = vec![]; let mut versions: Vec = vec![]; - for (rel_path, abs_path) in paths.into_iter() { + for submodule in submodules { + let path = root.join(submodule); // Get the submodule version or fallback. - let result = match run_git( - "git describe", - Command::new("git") - .current_dir(abs_path) - .arg("describe") - .args(describe_args.clone()), - ) { + let version = match crate::utils::describe(path, describe_args.clone()) { Ok(version) => version, - Err(_git_err) if fallback.is_some() => fallback.clone().unwrap(), - Err(git_err) => { - // If git error and no fallback provided, return error. - return Err(git_err); - } + Err(e) => { + if let Some(fallback) = fallback { + fallback.to_owned() + } else { + return Err(e) + } + }, }; - paths_out.push(rel_path); - versions.push(format!("{}{}{}", prefix, result, suffix)) + versions.push(format!("{}{}{}", prefix, version, suffix)) } - Ok((paths_out, versions)) + Ok(versions) } diff --git a/git-version-macro/src/lib.rs b/git-version-macro/src/lib.rs index 1323147..844a7be 100644 --- a/git-version-macro/src/lib.rs +++ b/git-version-macro/src/lib.rs @@ -6,9 +6,10 @@ use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::token::{Comma, Eq}; use syn::{Expr, Ident, LitStr}; -pub(crate) mod describe_submodules; +use self::utils::{describe, git_dir}; + +mod describe_submodules; mod utils; -use self::utils::{describe_cwd, git_dir_cwd}; macro_rules! error { ($($args:tt)*) => { @@ -26,7 +27,9 @@ fn canonicalize_path(path: &Path) -> syn::Result { /// Create a token stream representing dependencies on the git state. fn git_dependencies() -> syn::Result { - let git_dir = git_dir_cwd().map_err(|e| error!("failed to determine .git directory: {}", e))?; + 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() @@ -165,7 +168,10 @@ fn git_version_impl(args: Args) -> syn::Result { let cargo_fallback = args.cargo_prefix.is_some() || args.cargo_suffix.is_some(); - match describe_cwd(&git_args) { + 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) { Ok(version) => { let dependencies = git_dependencies()?; let prefix = args.prefix.iter(); diff --git a/git-version-macro/src/utils.rs b/git-version-macro/src/utils.rs index 5aa3271..f1fd9da 100644 --- a/git-version-macro/src/utils.rs +++ b/git-version-macro/src/utils.rs @@ -1,32 +1,40 @@ use std::ffi::OsStr; -use std::path::PathBuf; +use std::path::{PathBuf, Path}; use std::process::Command; /// Run `git describe` for the current working directory with custom flags to get version information from git. -pub fn describe_cwd(args: I) -> Result +pub fn describe(dir: impl AsRef, args: I) -> Result where I: IntoIterator, S: AsRef, { - run_git("git describe", Command::new("git").arg("describe").args(args)) + let dir = dir.as_ref(); + run_git("git describe", Command::new("git") + .arg("-C") + .arg(dir) + .arg("describe").args(args)) } -/// Get the git directory for the current working directory. -pub fn git_dir_cwd() -> Result { - let path = run_git("git rev-parse", Command::new("git").args(["rev-parse", "--git-dir"]))?; - Ok(PathBuf::from(path)) +/// Get the git directory for the given directory. +pub fn git_dir(dir: impl AsRef) -> Result { + let dir = dir.as_ref(); + let path = run_git("git rev-parse", Command::new("git") + .arg("-C") + .arg(dir) + .args(["rev-parse", "--git-dir"]))?; + Ok(dir.join(path)) } -pub(crate) fn run_git(program: &str, command: &mut std::process::Command) -> Result { +pub fn run_git(program: &str, command: &mut std::process::Command) -> Result { let output = command .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn() .map_err(|e| { if e.kind() == std::io::ErrorKind::NotFound { - "Command `git` not found: is git installed?".to_string() + format!("Command `{}` not found: is git installed?", command.get_program().to_string_lossy()) } else { - format!("Failed to run `{}`: {}", program, e) + format!("Failed to run `{}`: {}", command.get_program().to_string_lossy(), e) } })? .wait_with_output() @@ -87,7 +95,7 @@ fn test_git_dir() { use assert2::{assert, let_assert}; use std::path::Path; - let_assert!(Ok(git_dir) = git_dir_cwd()); + let_assert!(Ok(git_dir) = git_dir(".")); let_assert!(Ok(git_dir) = git_dir.canonicalize()); let_assert!(Ok(expected) = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.git").canonicalize()); assert!(git_dir == expected);