Skip to main content

compile_time_macros/
lib.rs

1//! This crate provides macros for getting compile time information.
2//!
3//! This crate should not be used directly; use [`compile-time`](https://docs.rs/compile-time) instead.
4
5extern crate proc_macro;
6
7#[cfg(any(feature = "time", feature = "version"))]
8use once_cell::sync::Lazy;
9use proc_macro::TokenStream;
10use quote::{format_ident, quote, ToTokens};
11#[cfg(feature = "time")]
12use time::{macros::format_description, OffsetDateTime};
13
14mod command;
15
16#[cfg(feature = "time")]
17static COMPILE_TIME: Lazy<OffsetDateTime> = Lazy::new(OffsetDateTime::now_utc);
18#[cfg(feature = "version")]
19static RUSTC_VERSION: Lazy<rustc_version::Result<rustc_version::Version>> = Lazy::new(rustc_version::version);
20
21/// Compile date as `time::Date`.
22///
23/// # Example
24///
25/// ```
26/// const COMPILE_DATE: time::Date = compile_time::date!();
27///
28/// let year = COMPILE_DATE.year();
29/// let month = COMPILE_DATE.month();
30/// let day = COMPILE_DATE.day();
31/// println!("Compiled on {month} {day}, {year}.");
32/// ```
33#[cfg(feature = "time")]
34#[proc_macro]
35pub fn date(_input: TokenStream) -> TokenStream {
36  let date = COMPILE_TIME.date();
37
38  let year = date.year();
39  let month = format_ident!("{}", format!("{:?}", date.month()));
40  let day = date.day();
41
42  let time_prefix = quote! { ::compile_time::__re_exports::time };
43  quote! {
44    const {
45      match #time_prefix::Date::from_calendar_date(#year, #time_prefix::Month::#month, #day) {
46        Ok(date) => date,
47        _ => ::core::unreachable!(),
48      }
49    }
50  }
51  .into()
52}
53
54/// Compile date as `&'static str` in `yyyy-MM-dd` format.
55///
56/// # Example
57///
58/// ```
59/// const COMPILE_DATE: time::Date = compile_time::date!();
60///
61/// let year = COMPILE_DATE.year();
62/// let month: u8 = COMPILE_DATE.month().into();
63/// let day = COMPILE_DATE.day();
64/// let date_string = format!("{year:04}-{month:02}-{day:02}");
65///
66/// assert_eq!(compile_time::date_str!(), date_string);
67/// ```
68#[cfg(feature = "time")]
69#[proc_macro]
70pub fn date_str(_input: TokenStream) -> TokenStream {
71  let date = COMPILE_TIME.date();
72
73  let fmt = format_description!("[year]-[month]-[day]");
74  let date_str = date.format(&fmt).unwrap();
75
76  quote! { #date_str }.into()
77}
78
79/// Compile time as `time::Time`.
80///
81/// # Example
82///
83/// ```
84/// const COMPILE_TIME: time::Time = compile_time::time!();
85///
86/// let hour = COMPILE_TIME.hour();
87/// let minute = COMPILE_TIME.minute();
88/// let second = COMPILE_TIME.second();
89/// println!("Compiled at {hour:02}:{minute:02}:{second:02}.");
90/// ```
91#[cfg(feature = "time")]
92#[proc_macro]
93pub fn time(_input: TokenStream) -> TokenStream {
94  let time = COMPILE_TIME.time();
95
96  let hour = time.hour();
97  let minute = time.minute();
98  let second = time.second();
99
100  let time_prefix = quote! { ::compile_time::__re_exports::time };
101  quote! {
102    const {
103      match #time_prefix::Time::from_hms(#hour, #minute, #second) {
104        Ok(time) => time,
105        _ => ::core::unreachable!(),
106      }
107    }
108  }
109  .into()
110}
111
112/// Compile time as `&'static str` in `hh:mm:ss` format.
113///
114/// # Example
115///
116/// ```
117/// const COMPILE_TIME: time::Time = compile_time::time!();
118///
119/// let hour = COMPILE_TIME.hour();
120/// let minute = COMPILE_TIME.minute();
121/// let second = COMPILE_TIME.second();
122/// let time_string = format!("{hour:02}:{minute:02}:{second:02}");
123///
124/// assert_eq!(compile_time::time_str!(), time_string);
125/// ```
126#[cfg(feature = "time")]
127#[proc_macro]
128pub fn time_str(_input: TokenStream) -> TokenStream {
129  let time = COMPILE_TIME.time();
130
131  let fmt = format_description!("[hour]:[minute]:[second]");
132  let time_str = time.format(&fmt).unwrap();
133
134  quote! { #time_str }.into()
135}
136
137/// Compile date and time as `time::OffsetDateTime`.
138///
139/// # Example
140///
141/// ```
142/// const COMPILE_DATETIME: time::OffsetDateTime = compile_time::datetime!();
143///
144/// let year = COMPILE_DATETIME.year();
145/// let month = COMPILE_DATETIME.month();
146/// let day = COMPILE_DATETIME.day();
147/// let hour = COMPILE_DATETIME.hour();
148/// let minute = COMPILE_DATETIME.minute();
149/// let second = COMPILE_DATETIME.second();
150/// println!("Compiled at {hour:02}:{minute:02}:{second:02} on {month} {day}, {year}.");
151/// #
152/// # // Evaluation is only done once.
153/// # std::thread::sleep(std::time::Duration::from_secs(1));
154/// # assert_eq!(COMPILE_DATETIME, compile_time::datetime!());
155/// #
156/// # // Additional sanity check.
157/// # let now = time::OffsetDateTime::now_utc();
158/// # let yesterday = now.saturating_sub(time::Duration::days(1));
159/// # assert!(COMPILE_DATETIME > yesterday);
160/// # assert!(COMPILE_DATETIME < now);
161/// ```
162#[cfg(feature = "time")]
163#[proc_macro]
164pub fn datetime(_input: TokenStream) -> TokenStream {
165  let datetime = *COMPILE_TIME;
166
167  let year = datetime.year();
168  let month = format_ident!("{}", format!("{:?}", datetime.month()));
169  let day = datetime.day();
170
171  let hour = datetime.hour();
172  let minute = datetime.minute();
173  let second = datetime.second();
174
175  let time_prefix = quote! { ::compile_time::__re_exports::time };
176  let date = quote! {
177    match #time_prefix::Date::from_calendar_date(#year, #time_prefix::Month::#month, #day) {
178      Ok(date) => date,
179      _ => ::core::unreachable!(),
180    }
181  };
182
183  let time = quote! {
184    match #time_prefix::Time::from_hms(#hour, #minute, #second) {
185      Ok(time) => time,
186      _ => ::core::unreachable!(),
187    }
188  };
189
190  quote! {
191    const { #time_prefix::PrimitiveDateTime::new(#date, #time).assume_utc() }
192  }
193  .into()
194}
195
196/// Compile time as `&'static str` in `yyyy-MM-ddThh:mm:ssZ` format.
197///
198/// # Example
199///
200/// ```
201/// const COMPILE_DATE_STRING: &str = compile_time::date_str!();
202/// const COMPILE_TIME_STRING: &str = compile_time::time_str!();
203///
204/// let datetime_string = format!("{COMPILE_DATE_STRING}T{COMPILE_TIME_STRING}Z");
205/// assert_eq!(compile_time::datetime_str!(), datetime_string);
206/// ```
207#[cfg(feature = "time")]
208#[proc_macro]
209pub fn datetime_str(_input: TokenStream) -> TokenStream {
210  let datetime = *COMPILE_TIME;
211
212  let fmt = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]Z");
213  let datetime_str = datetime.format(&fmt).unwrap();
214
215  quote! { #datetime_str }.into()
216}
217
218/// Compile date and time as UNIX timestamp in seconds.
219///
220/// # Example
221///
222/// ```
223/// const COMPILE_DATETIME: time::OffsetDateTime = compile_time::datetime!();
224///
225/// assert_eq!(compile_time::unix!(), COMPILE_DATETIME.unix_timestamp());
226/// ```
227#[cfg(feature = "time")]
228#[proc_macro]
229pub fn unix(_input: TokenStream) -> TokenStream {
230  let datetime = *COMPILE_TIME;
231
232  let unix_timestamp = proc_macro2::Literal::i64_unsuffixed(datetime.unix_timestamp());
233
234  quote! {
235    #unix_timestamp
236  }
237  .into()
238}
239
240/// Rust compiler version as `semver::Version`.
241///
242/// # Example
243///
244/// ```
245/// let rustc_version: semver::Version = compile_time::rustc_version!();
246/// assert_eq!(rustc_version, rustc_version::version().unwrap());
247/// ```
248#[cfg(feature = "version")]
249#[proc_macro]
250pub fn rustc_version(_item: TokenStream) -> TokenStream {
251  let rustc_version::Version { major, minor, patch, pre, build } = match &*RUSTC_VERSION {
252    Ok(rustc_version) => rustc_version,
253    Err(err) => panic!("Failed to get version: {}", err),
254  };
255
256  let semver_prefix = quote! { ::compile_time::__re_exports::semver };
257  let pre = if pre.is_empty() {
258    quote! { #semver_prefix::Prerelease::EMPTY }
259  } else {
260    let pre = pre.as_str();
261    quote! {
262      if let Ok(pre) = #semver_prefix::Prerelease::new(#pre) {
263        pre
264      } else {
265        ::core::unreachable!()
266      }
267    }
268  };
269
270  let build = if build.is_empty() {
271    quote! { #semver_prefix::BuildMetadata::EMPTY }
272  } else {
273    let build = build.as_str();
274    quote! {
275      if let Ok(build) = #semver_prefix::BuildMetadata::new(#build) {
276        build
277      } else {
278        ::core::unreachable!()
279      }
280    }
281  };
282
283  quote! {
284    #semver_prefix::Version {
285      major: #major,
286      minor: #minor,
287      patch: #patch,
288      pre: #pre,
289      build: #build,
290    }
291  }
292  .into()
293}
294
295/// Rust compiler version as `&'static str`.
296///
297/// # Example
298///
299/// ```
300/// const RUSTC_VERSION_STRING: &str = compile_time::rustc_version_str!();
301/// assert_eq!(RUSTC_VERSION_STRING, compile_time::rustc_version_str!());
302/// ```
303#[cfg(feature = "version")]
304#[proc_macro]
305pub fn rustc_version_str(_item: TokenStream) -> TokenStream {
306  let rustc_version = match &*RUSTC_VERSION {
307    Ok(rustc_version) => rustc_version,
308    Err(err) => panic!("Failed to get version: {}", err),
309  };
310
311  let rustc_version_string = rustc_version.to_string();
312  quote! { #rustc_version_string }.into()
313}
314
315/// Rust compiler major version as integer literal.
316///
317/// # Example
318///
319/// ```
320/// let rustc_version: semver::Version = compile_time::rustc_version!();
321/// assert_eq!(rustc_version.major, compile_time::rustc_version_major!());
322/// ```
323#[cfg(feature = "version")]
324#[proc_macro]
325pub fn rustc_version_major(_item: TokenStream) -> TokenStream {
326  let major = match &*RUSTC_VERSION {
327    Ok(rustc_version) => rustc_version.major,
328    Err(err) => panic!("Failed to get version: {}", err),
329  };
330
331  proc_macro2::Literal::u64_unsuffixed(major).to_token_stream().into()
332}
333
334/// Rust compiler minor version as integer literal.
335///
336/// # Example
337///
338/// ```
339/// let rustc_version: semver::Version = compile_time::rustc_version!();
340/// assert_eq!(rustc_version.minor, compile_time::rustc_version_minor!());
341/// ```
342#[cfg(feature = "version")]
343#[proc_macro]
344pub fn rustc_version_minor(_item: TokenStream) -> TokenStream {
345  let minor = match &*RUSTC_VERSION {
346    Ok(rustc_version) => rustc_version.minor,
347    Err(err) => panic!("Failed to get version: {}", err),
348  };
349
350  proc_macro2::Literal::u64_unsuffixed(minor).to_token_stream().into()
351}
352
353/// Rust compiler patch version as integer literal.
354///
355/// # Example
356///
357/// ```
358/// let rustc_version: semver::Version = compile_time::rustc_version!();
359/// assert_eq!(rustc_version.minor, compile_time::rustc_version_minor!());
360/// ```
361#[cfg(feature = "version")]
362#[proc_macro]
363pub fn rustc_version_patch(_item: TokenStream) -> TokenStream {
364  let patch = match &*RUSTC_VERSION {
365    Ok(rustc_version) => rustc_version.patch,
366    Err(err) => panic!("Failed to get version: {}", err),
367  };
368
369  proc_macro2::Literal::u64_unsuffixed(patch).to_token_stream().into()
370}
371
372/// Rust compiler pre version as `&'static str`.
373///
374/// # Example
375///
376/// ```
377/// let rustc_version: semver::Version = compile_time::rustc_version!();
378/// assert_eq!(rustc_version.pre.as_str(), compile_time::rustc_version_pre!());
379/// ```
380#[cfg(feature = "version")]
381#[proc_macro]
382pub fn rustc_version_pre(_item: TokenStream) -> TokenStream {
383  let pre = match &*RUSTC_VERSION {
384    Ok(rustc_version) => rustc_version.pre.as_str(),
385    Err(err) => panic!("Failed to get version: {}", err),
386  };
387
388  quote! { #pre }.into()
389}
390
391/// Rust compiler build version as `&'static str`.
392///
393/// # Example
394///
395/// ```
396/// let rustc_version: semver::Version = compile_time::rustc_version!();
397/// assert_eq!(rustc_version.build.as_str(), compile_time::rustc_version_build!());
398/// ```
399#[cfg(feature = "version")]
400#[proc_macro]
401pub fn rustc_version_build(_item: TokenStream) -> TokenStream {
402  let build = match &*RUSTC_VERSION {
403    Ok(rustc_version) => rustc_version.build.as_str(),
404    Err(err) => panic!("Failed to get version: {}", err),
405  };
406
407  quote! { #build }.into()
408}
409
410/// Rust compiler build version as `&'static str`.
411///
412/// # Example
413///
414/// ```
415/// let magic_number: &[u8] = compile_time::command_bytes!("echo", "42");
416/// assert_eq!(magic_number, b"42\n");
417/// ```
418#[proc_macro]
419#[cfg(feature = "version")]
420pub fn command_bytes(input: TokenStream) -> TokenStream {
421  use proc_macro2::Span;
422  use syn::{parse_macro_input, LitByteStr};
423
424  use command::CommandInput;
425
426  let input = parse_macro_input!(input as CommandInput);
427  match command::output(input) {
428    Ok(output) => {
429      let output = LitByteStr::new(&output, Span::call_site());
430      quote! { #output }.into()
431    },
432    Err(e) => e,
433  }
434}
435
436/// Rust compiler build version as `&'static str`.
437///
438/// # Example
439///
440/// ```
441/// let magic_number: &str = compile_time::command_str!("echo", "42");
442/// assert_eq!(magic_number, "42\n");
443/// ```
444#[proc_macro]
445#[cfg(feature = "version")]
446pub fn command_str(input: TokenStream) -> TokenStream {
447  use proc_macro2::Span;
448  use syn::{parse_macro_input, LitStr};
449
450  use command::CommandInput;
451
452  let input = parse_macro_input!(input as CommandInput);
453  match command::output(input) {
454    Ok(output) => {
455      let output_string = match String::from_utf8(output) {
456        Ok(string) => string,
457        Err(_) => {
458          return quote! {
459            ::core::compile_error!("Command output is not valid UTF-8.")
460          }
461          .into()
462        },
463      };
464
465      let output = LitStr::new(&output_string, Span::call_site());
466      quote! { #output }.into()
467    },
468    Err(e) => e,
469  }
470}