Skip to content

Commit

Permalink
Use CARGO_MANIFEST_DIR to discover git repo.
Browse files Browse the repository at this point in the history
This is needed when a crate is part of a git repo, which isn't the same
git repo as the workspace root.
  • Loading branch information
de-vri-es committed Dec 13, 2023
1 parent 49737a6 commit d32f924
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 81 deletions.
119 changes: 53 additions & 66 deletions git-version-macro/src/describe_submodules.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)*))
Expand Down Expand Up @@ -76,19 +75,19 @@ impl Parse for GitModArgs {
}

pub(crate) fn git_submodule_versions_impl(args: GitModArgs) -> syn::Result<TokenStream2> {
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(
Expand All @@ -106,77 +105,65 @@ pub(crate) fn git_submodule_versions_impl(args: GitModArgs) -> syn::Result<Token
};
let fallback = args.fallback.map(|x| x.value());

match describe_submodules(describe_paths, &git_describe_args, prefix, suffix, fallback) {
Ok(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<Vec<String>, String> {
let mut args: Vec<String> = "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<Path>) -> Result<Vec<String>, 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<I, S>(
paths: Vec<(String, String)>,
root: &Path,
submodules: &[String],
describe_args: I,
prefix: String,
suffix: String,
fallback: Option<String>,
) -> Result<(Vec<String>, Vec<String>), String>
prefix: &str,
suffix: &str,
fallback: Option<&str>,
) -> Result<Vec<String>, String>
where
I: IntoIterator<Item = S> + Clone,
S: AsRef<OsStr>,
{
let mut paths_out: Vec<String> = vec![];
let mut versions: Vec<String> = 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)
}
14 changes: 10 additions & 4 deletions git-version-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)*) => {
Expand All @@ -26,7 +27,9 @@ fn canonicalize_path(path: &Path) -> syn::Result<String> {

/// Create a token stream representing dependencies on the git state.
fn git_dependencies() -> syn::Result<TokenStream2> {
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()
Expand Down Expand Up @@ -165,7 +168,10 @@ fn git_version_impl(args: Args) -> syn::Result<TokenStream2> {

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();
Expand Down
30 changes: 19 additions & 11 deletions git-version-macro/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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<I, S>(args: I) -> Result<String, String>
pub fn describe<I, S>(dir: impl AsRef<Path>, args: I) -> Result<String, String>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
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<PathBuf, String> {
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<Path>) -> Result<PathBuf, String> {
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<String, String> {
pub fn run_git(program: &str, command: &mut std::process::Command) -> Result<String, String> {
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()
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit d32f924

Please sign in to comment.