22
33extern crate proc_macro;
44use proc_macro:: TokenStream ;
5- use std:: hash:: { Hash , Hasher } ;
5+
6+ use std:: hash:: { BuildHasher , RandomState } ;
7+ use std:: io:: Write ;
8+
9+ /// Properly passes an error message to the compiler without crashing macro engines.
10+ macro_rules! build_error {
11+ ( $( $arg: tt) * ) => {
12+ format!( "compile_error!(r#\" {}\" #)" , format!( $( $arg) * ) )
13+ . parse:: <TokenStream >( )
14+ . unwrap( )
15+ } ;
16+ }
617
718#[ proc_macro]
819#[ doc = include_str ! ( "../README.md" ) ]
920pub fn comptime ( code : TokenStream ) -> TokenStream {
10- let ( mut externs, mut out_dir, mut args) = ( vec ! [ ] , None , std:: env:: args ( ) ) ;
21+ let mut out_dir = None ;
22+ let mut externs = vec ! [ ] ;
23+
24+ let mut args = std:: env:: args ( ) ;
1125 while let Some ( arg) = args. next ( ) {
12- if arg == "--out-dir" {
13- out_dir = args. next ( ) ;
14- } else if arg == "--extern" {
15- externs. push ( "--extern" . to_owned ( ) ) ;
26+ // Push deps to rustc so you don't need to explicitly link with 'extern crate'
27+ if arg == "--extern" {
1628 externs. push ( args. next ( ) . unwrap ( ) ) ;
29+ } else if arg == "--out-dir" {
30+ out_dir = args. next ( ) . map ( std:: path:: PathBuf :: from) ;
31+ }
32+ }
33+
34+ if out_dir. is_none ( ) {
35+ let out = std:: env:: current_dir ( ) . unwrap ( ) . join ( "target" ) . join ( "debug" ) . join ( "deps" ) ;
36+ if out. exists ( ) {
37+ out_dir = Some ( out) ;
1738 }
1839 }
1940
2041 let Some ( out_dir) = out_dir else {
21- return "compile_error!( \ " Could not find output directory.\" )" . parse ( ) . unwrap ( )
42+ return build_error ! ( "Could not find output directory." ) ;
2243 } ;
2344
24- let code = format ! ( "fn main(){{ println!(\" {{:?}}\" , {{ {code} }}) }}" ) ;
25-
26- let mut hash = std:: collections:: hash_map:: DefaultHasher :: new ( ) ;
27- code. hash ( & mut hash) ;
28- let hash = hash. finish ( ) ;
29-
30- let output_file = format ! ( "{out_dir}{}constime-{hash}.exe" , std:: path:: MAIN_SEPARATOR ) ;
31- let out_path = std:: path:: Path :: new ( & output_file) ;
32- if out_path. exists ( ) {
33- let ext = out_path. with_extension ( "err" ) ;
34- if ext. exists ( ) {
35- return format ! (
36- "compile_error!(r#\" {}\" #)" ,
37- std:: fs:: read_to_string( ext) . expect( "Error when compiling" )
38- )
39- . parse ( )
40- . unwrap ( ) ;
41- }
42- } else {
43- let input_file = format ! ( "{out_dir}{}constime-{hash}.rs" , std:: path:: MAIN_SEPARATOR ) ;
44- std:: fs:: write ( & input_file, code) . expect ( "Failed to write temporary file to output" ) ;
45+ let wrapped_code = format ! ( r#"
46+ fn main() {{
47+ println!("{{:?}}", {{ {code} }});
48+ }}
49+ "# ) ;
50+
51+ let hash = RandomState :: new ( ) . hash_one ( & wrapped_code) ;
52+
53+ let constime_base = out_dir. join ( "constime" ) ;
54+ if !constime_base. exists ( ) {
55+ std:: fs:: create_dir ( & constime_base) . unwrap ( ) ;
56+ }
4557
46- let rustc = std:: process:: Command :: new ( "rustc" )
58+ let evaluator_base = constime_base
59+ . join ( hash. to_string ( ) ) ;
60+
61+ if !evaluator_base. exists ( ) { // This hasn't been compiled yet.
62+ let mut rustc = std:: process:: Command :: new ( "rustc" ) ;
63+ rustc
4764 . stderr ( std:: process:: Stdio :: piped ( ) )
48- . args ( [ & input_file, "-o" , & output_file] )
49- . args ( [ "-L" , & out_dir] )
50- . output ( ) ;
51-
52- match rustc {
53- Err ( why) => return format ! ( "compile_error!(r#\" {}\" #)" , why) . parse ( ) . unwrap ( ) ,
54- Ok ( output) if !output. status . success ( ) => {
55- return format ! (
56- "compile_error!(r#\" {}\" #)" ,
57- std:: str :: from_utf8( & output. stderr) . unwrap( )
58- )
59- . parse ( )
60- . unwrap ( )
61- }
62- _ => ( ) ,
65+ . stdin ( std:: process:: Stdio :: piped ( ) )
66+ . current_dir ( constime_base)
67+ . arg ( "-L" )
68+ . arg ( out_dir)
69+ . arg ( "-o" )
70+ . arg ( & evaluator_base)
71+ . arg ( "-" ) ;
72+
73+ for ext in & externs {
74+ rustc. arg ( "--extern" ) . arg ( ext) ;
75+ }
76+
77+ let Ok ( mut rustc) = rustc. spawn ( ) else {
78+ return build_error ! ( "Failed to spawn rustc" ) ;
79+ } ;
80+
81+ // Avoid deadlock by containing stdin handling in its own scope
82+ if let Some ( mut stdin) = rustc. stdin . take ( ) {
83+ if stdin. write_all ( wrapped_code. as_bytes ( ) ) . is_err ( ) {
84+ return build_error ! ( "Failed to write to rustc stdin" ) ;
85+ } ;
86+ } else {
87+ return build_error ! ( "Failed to open stdin for rustc" ) ;
88+ }
89+
90+ let Ok ( output) = rustc. wait_with_output ( ) else {
91+ return build_error ! ( "Failed to wait for rustc" ) ;
92+ } ;
93+
94+ if !output. status . success ( ) {
95+ return build_error ! ( "{}" , String :: from_utf8_lossy( & output. stderr) ) ;
6396 }
6497 }
6598
66- let out = std:: process:: Command :: new ( & output_file )
99+ let out = std:: process:: Command :: new ( & evaluator_base )
67100 . stdout ( std:: process:: Stdio :: piped ( ) )
68101 . output ( ) ;
69102
70103 match out {
71- Err ( why) => {
72- std:: fs:: write ( out_path. with_extension ( "err" ) , why. to_string ( ) ) . unwrap ( ) ;
73- format ! ( "compile_error!(r#\" Failed to execute code: {why}\" #)" )
74- . parse ( )
75- . unwrap ( )
104+ Err ( why) => return build_error ! ( "Failed to execute code: {why}" ) ,
105+ Ok ( out) => {
106+ let out = String :: from_utf8_lossy ( & out. stdout ) ;
107+
108+ let Ok ( out) = out. parse ( ) else {
109+ return build_error ! ( "Failed to parse output into a TokenStream" ) ;
110+ } ;
111+
112+ return out;
76113 }
77- Ok ( out) => std:: str:: from_utf8 ( & out. stdout ) . unwrap ( ) . parse ( ) . unwrap ( ) ,
78114 }
79- }
115+ }
0 commit comments