1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{quote, ToTokens};
4
5macro_rules! error {
6 ($($args:tt)*) => {
7 syn::Error::new(proc_macro2::Span::call_site(), format!($($args)*))
8 };
9}
10
11mod args;
12mod utils;
13
14#[proc_macro]
50pub fn git_version(input: TokenStream) -> TokenStream {
51 let args = syn::parse_macro_input!(input as args::Args);
52
53 let tokens = match git_version_impl(args) {
54 Ok(x) => x,
55 Err(e) => e.to_compile_error(),
56 };
57
58 TokenStream::from(tokens)
59}
60
61fn git_version_impl(args: args::Args) -> syn::Result<TokenStream2> {
62 let git_args = args.git_args.map_or_else(
63 || vec!["--always".to_string(), "--dirty=-modified".to_string()],
64 |list| list.iter().map(|x| x.value()).collect(),
65 );
66
67 let cargo_fallback = args.cargo_prefix.is_some() || args.cargo_suffix.is_some();
68
69 let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR")
70 .ok_or_else(|| error!("CARGO_MANIFEST_DIR is not set"))?;
71
72 match utils::describe(manifest_dir, git_args) {
73 Ok(version) => {
74 let dependencies = utils::git_dependencies()?;
75 let prefix = args.prefix.iter();
76 let suffix = args.suffix;
77 Ok(quote!({
78 #dependencies;
79 concat!(#(#prefix,)* #version, #suffix)
80 }))
81 }
82 Err(_) if cargo_fallback => {
83 if let Ok(version) = std::env::var("CARGO_PKG_VERSION") {
84 let prefix = args.cargo_prefix.iter();
85 let suffix = args.cargo_suffix;
86 Ok(quote!(concat!(#(#prefix,)* #version, #suffix)))
87 } else if let Some(fallback) = args.fallback {
88 Ok(fallback.to_token_stream())
89 } else {
90 Err(error!("Unable to get git or cargo version"))
91 }
92 }
93 Err(_) if args.fallback.is_some() => Ok(args.fallback.to_token_stream()),
94 Err(e) => Err(error!("{}", e)),
95 }
96}
97
98#[proc_macro]
142pub fn git_submodule_versions(input: TokenStream) -> TokenStream {
143 let args = syn::parse_macro_input!(input as args::Args);
144
145 let tokens = match git_submodule_versions_impl(args) {
146 Ok(x) => x,
147 Err(e) => e.to_compile_error(),
148 };
149
150 TokenStream::from(tokens)
151}
152
153fn git_submodule_versions_impl(args: args::Args) -> syn::Result<TokenStream2> {
154 if let Some(cargo_prefix) = &args.cargo_prefix {
155 return Err(syn::Error::new_spanned(cargo_prefix, "invalid argument `cargo_prefix` for `git_submodule_versions!()`"));
156 }
157 if let Some(cargo_suffix) = &args.cargo_suffix {
158 return Err(syn::Error::new_spanned(cargo_suffix, "invalid argument `cargo_suffix` for `git_submodule_versions!()`"));
159 }
160
161 let manifest_dir = std::env::var_os("CARGO_MANIFEST_DIR")
162 .ok_or_else(|| error!("CARGO_MANIFEST_DIR is not set"))?;
163 let git_dir = crate::utils::git_dir(&manifest_dir)
164 .map_err(|e| error!("failed to determine .git directory: {}", e))?;
165
166 let modules = match crate::utils::get_submodules(&manifest_dir) {
167 Ok(x) => x,
168 Err(err) => return Err(error!("{}", err)),
169 };
170
171 if modules.is_empty() {
173 return Ok(quote!([("", ""); 0]));
174 }
175
176 let git_args = args.git_args.as_ref().map_or_else(
177 || vec!["--always".to_string(), "--dirty=-modified".to_string()],
178 |list| list.iter().map(|x| x.value()).collect(),
179 );
180
181 let root_dir = git_dir.join("..");
182 let mut versions = Vec::new();
183 for submodule in &modules {
184 let path = root_dir.join(submodule);
185 let version = match crate::utils::describe(path, &git_args) {
187 Ok(version) => {
188 let prefix = args.prefix.iter();
189 let suffix = args.suffix.iter();
190 quote!{
191 ::core::concat!(#(#prefix,)* #version #(, #suffix)*)
192 }
193 }
194 Err(e) => {
195 if let Some(fallback) = &args.fallback {
196 quote!( #fallback )
197 } else {
198 return Err(error!("{}", e));
199 }
200 },
201 };
202 versions.push(version);
203 }
204
205 Ok(quote!({
206 [#((#modules, #versions)),*]
207 }))
208}