1
1
// we're already nightly-only so might as well use unstable proc macro APIs.
2
2
#![ feature( proc_macro_span) ]
3
3
4
+ use std:: error:: Error ;
5
+ use std:: fs:: DirBuilder ;
4
6
use std:: path:: PathBuf ;
5
7
use std:: { env, process} ;
6
8
7
9
use litrs:: StringLit ;
10
+ use proc_macro:: TokenStream ;
8
11
use quote:: quote;
9
12
10
13
/// Compiles the given PICA200 shader using [`picasso`](https://github.com/devkitPro/picasso)
@@ -23,69 +26,82 @@ use quote::quote;
23
26
/// # Example
24
27
///
25
28
/// ```no_run
26
- /// # use pica200 ::include_shader;
29
+ /// # use citro3d_macros ::include_shader;
27
30
/// static SHADER_BYTES: &[u8] = include_shader!("assets/vshader.pica");
28
31
/// ```
29
32
#[ proc_macro]
30
33
pub fn include_shader ( input : proc_macro:: TokenStream ) -> proc_macro:: TokenStream {
34
+ match include_shader_impl ( input) {
35
+ Ok ( tokens) => tokens,
36
+ Err ( err) => {
37
+ let err_str = err. to_string ( ) ;
38
+ quote ! { compile_error!( #err_str ) } . into ( )
39
+ }
40
+ }
41
+ }
42
+
43
+ fn include_shader_impl ( input : TokenStream ) -> Result < TokenStream , Box < dyn Error > > {
31
44
let tokens: Vec < _ > = input. into_iter ( ) . collect ( ) ;
32
45
33
46
if tokens. len ( ) != 1 {
34
- let msg = format ! ( "expected exactly one input token, got {}" , tokens. len( ) ) ;
35
- return quote ! { compile_error!( #msg) } . into ( ) ;
47
+ return Err ( format ! ( "expected exactly one input token, got {}" , tokens. len( ) ) . into ( ) ) ;
36
48
}
37
49
38
50
let string_lit = match StringLit :: try_from ( & tokens[ 0 ] ) {
39
- // Error if the token is not a string literal
40
- Err ( e) => return e. to_compile_error ( ) ,
41
51
Ok ( lit) => lit,
52
+ Err ( err) => return Ok ( err. to_compile_error ( ) ) ,
42
53
} ;
43
54
44
55
// The cwd can change depending on whether this is running in a doctest or not:
45
56
// https://users.rust-lang.org/t/which-directory-does-a-proc-macro-run-from/71917
46
57
//
47
58
// But the span's `source_file()` seems to always be relative to the cwd.
48
59
let cwd = env:: current_dir ( ) . expect ( "unable to determine working directory" ) ;
49
- let invoking_source_file = tokens[ 0 ] . span ( ) . source_file ( ) . path ( ) ;
50
- let invoking_source_dir = invoking_source_file
51
- . parent ( )
52
- . expect ( "unable to find parent directory of invoking source file" ) ;
60
+ let span = tokens[ 0 ] . span ( ) ;
61
+ let invoking_source_file = span. source_file ( ) . path ( ) ;
62
+ let Some ( invoking_source_dir) = invoking_source_file. parent ( ) else {
63
+ return Err ( "unable to find parent directory of invoking source file" . into ( ) ) ;
64
+ } ;
53
65
54
66
// By joining these three pieces, we arrive at approximately the same behavior as `include_bytes!`
55
67
let shader_source_file = cwd. join ( invoking_source_dir) . join ( string_lit. value ( ) ) ;
56
- let shader_out_file = shader_source_file. with_extension ( "shbin" ) ;
57
-
58
- let Some ( shader_out_file) = shader_out_file. file_name ( ) else {
59
- let msg = format ! ( "invalid input file name {shader_source_file:?}" ) ;
60
- return quote ! { compile_error!( #msg) } . into ( ) ;
61
- } ;
68
+ let shader_out_file: PathBuf = shader_source_file. with_extension ( "shbin" ) ;
62
69
63
70
let out_dir = PathBuf :: from ( env ! ( "OUT_DIR" ) ) ;
64
- let out_path = out_dir. join ( shader_out_file) ;
71
+ // This might be overkill, but it ensures we get a unique path if different
72
+ // shaders with the same relative path are used within one program
73
+ let relative_out_path = shader_out_file. canonicalize ( ) ?;
74
+
75
+ let out_path = out_dir. join (
76
+ relative_out_path
77
+ . strip_prefix ( "/" )
78
+ . unwrap_or ( & shader_out_file) ,
79
+ ) ;
80
+
81
+ let parent_dir = out_path. parent ( ) . ok_or ( "invalid input filename" ) ?;
82
+ DirBuilder :: new ( ) . recursive ( true ) . create ( parent_dir) ?;
65
83
66
84
let devkitpro = PathBuf :: from ( env ! ( "DEVKITPRO" ) ) ;
67
85
68
86
let output = process:: Command :: new ( devkitpro. join ( "tools/bin/picasso" ) )
69
87
. arg ( "--out" )
70
88
. args ( [ & out_path, & shader_source_file] )
71
- . output ( )
72
- . unwrap ( ) ;
89
+ . output ( ) ?;
73
90
74
91
match output. status . code ( ) {
75
92
Some ( 0 ) => { }
76
93
code => {
77
94
let code = code. map_or_else ( || String :: from ( "unknown" ) , |c| c. to_string ( ) ) ;
78
95
79
- let msg = format ! (
80
- "failed to compile shader {shader_source_file:?}: exit status {code}: {}" ,
96
+ return Err ( format ! (
97
+ "failed to compile shader: exit status from `picasso` was {code}: {}" ,
81
98
String :: from_utf8_lossy( & output. stderr) ,
82
- ) ;
83
-
84
- return quote ! { compile_error!( #msg) } . into ( ) ;
99
+ )
100
+ . into ( ) ) ;
85
101
}
86
102
}
87
103
88
- let bytes = std:: fs:: read ( out_path) . unwrap ( ) ;
104
+ let bytes = std:: fs:: read ( out_path) ? ;
89
105
let source_file_path = shader_source_file. to_string_lossy ( ) ;
90
106
91
107
let result = quote ! {
@@ -111,5 +127,5 @@ pub fn include_shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream
111
127
}
112
128
} ;
113
129
114
- result. into ( )
130
+ Ok ( result. into ( ) )
115
131
}
0 commit comments