88
99use crate :: config;
1010use crate :: semantic;
11- use crate :: semantic:: Transform ;
1211
13- pub enum Transformation {
14- None ,
15- Config ( Vec < config:: Compiler > ) ,
12+ pub struct Transformation {
13+ compilers : Vec < config:: Compiler > ,
1614}
1715
1816impl From < & config:: Output > for Transformation {
1917 fn from ( config : & config:: Output ) -> Self {
2018 match config {
21- config:: Output :: Clang { compilers, .. } => {
22- if compilers. is_empty ( ) {
23- Transformation :: None
24- } else {
25- let compilers = compilers. clone ( ) ;
26- Transformation :: Config ( compilers)
27- }
28- }
29- config:: Output :: Semantic { .. } => Transformation :: None ,
19+ config:: Output :: Clang { compilers, .. } => Transformation {
20+ compilers : compilers. clone ( ) ,
21+ } ,
22+ config:: Output :: Semantic { .. } => Transformation { compilers : vec ! [ ] } ,
3023 }
3124 }
3225}
3326
34- impl Transform for Transformation {
27+ impl semantic:: Transform for Transformation {
28+ /// Apply the transformation to the compiler call.
29+ ///
30+ /// Optimize for the case when the configuration is empty.
3531 fn apply ( & self , input : semantic:: CompilerCall ) -> Option < semantic:: CompilerCall > {
36- let semantic:: CompilerCall {
37- compiler,
38- passes,
39- working_dir,
40- } = & input;
41- match self . lookup ( compiler) {
42- Some ( config:: Compiler {
43- ignore : config:: IgnoreOrConsider :: Always ,
44- ..
45- } ) => None ,
46- Some ( config:: Compiler {
47- ignore : config:: IgnoreOrConsider :: Conditional ,
48- arguments,
49- ..
50- } ) => {
51- if Self :: filter ( arguments, passes) {
52- None
53- } else {
54- Some ( input)
55- }
56- }
57- Some ( config:: Compiler {
58- ignore : config:: IgnoreOrConsider :: Never ,
59- arguments,
60- ..
61- } ) => {
62- let new_passes = Transformation :: execute ( arguments, passes) ;
63- Some ( semantic:: CompilerCall {
64- compiler : compiler. clone ( ) ,
65- working_dir : working_dir. clone ( ) ,
66- passes : new_passes,
67- } )
32+ if self . compilers . is_empty ( ) {
33+ Some ( input)
34+ } else {
35+ // Find the configurations that match the compiler name.
36+ let compiler = & input. compiler . clone ( ) ;
37+ let configs: Vec < _ > = self
38+ . compilers
39+ . iter ( )
40+ . filter ( |config| config. path == * compiler)
41+ . collect ( ) ;
42+
43+ // Apply the transformation if there are any configurations.
44+ if configs. is_empty ( ) {
45+ Some ( input)
46+ } else {
47+ Self :: apply_when_not_empty ( configs. as_slice ( ) , input)
6848 }
69- None => Some ( input) ,
7049 }
7150 }
7251}
7352
7453impl Transformation {
75- // TODO: allow multiple matches for the same compiler
76- fn lookup ( & self , compiler : & std:: path:: Path ) -> Option < & config:: Compiler > {
77- match self {
78- Transformation :: Config ( compilers) => compilers. iter ( ) . find ( |c| c. path == compiler) ,
79- _ => None ,
54+ /// Apply the transformation to the compiler call.
55+ ///
56+ /// Multiple configurations can be applied to the same compiler call.
57+ /// And depending on the instruction from the configuration, the compiler call
58+ /// can be ignored, modified, or left unchanged. The conditional ignore will
59+ /// check if the compiler call matches the flags defined in the configuration.
60+ fn apply_when_not_empty (
61+ configs : & [ & config:: Compiler ] ,
62+ input : semantic:: CompilerCall ,
63+ ) -> Option < semantic:: CompilerCall > {
64+ let mut current_input = Some ( input) ;
65+
66+ for config in configs {
67+ current_input = match config {
68+ config:: Compiler {
69+ ignore : config:: IgnoreOrConsider :: Always ,
70+ ..
71+ } => None ,
72+ config:: Compiler {
73+ ignore : config:: IgnoreOrConsider :: Conditional ,
74+ arguments,
75+ ..
76+ } => current_input. filter ( |input| !Self :: match_condition ( arguments, & input. passes ) ) ,
77+ config:: Compiler {
78+ ignore : config:: IgnoreOrConsider :: Never ,
79+ arguments,
80+ ..
81+ } => current_input. map ( |input| semantic:: CompilerCall {
82+ compiler : input. compiler . clone ( ) ,
83+ working_dir : input. working_dir . clone ( ) ,
84+ passes : Transformation :: apply_argument_changes (
85+ arguments,
86+ input. passes . as_slice ( ) ,
87+ ) ,
88+ } ) ,
89+ } ;
90+
91+ if current_input. is_none ( ) {
92+ break ;
93+ }
8094 }
95+ current_input
8196 }
8297
83- fn filter ( arguments : & config:: Arguments , passes : & [ semantic:: CompilerPass ] ) -> bool {
98+ /// Check if the compiler call matches the condition defined in the configuration.
99+ ///
100+ /// Any compiler pass that matches the flags defined in the configuration will cause
101+ /// the whole compiler call to be ignored.
102+ fn match_condition ( arguments : & config:: Arguments , passes : & [ semantic:: CompilerPass ] ) -> bool {
84103 let match_flags = arguments. match_ . as_slice ( ) ;
85104 passes. iter ( ) . any ( |pass| match pass {
86105 semantic:: CompilerPass :: Compile { flags, .. } => {
@@ -90,7 +109,11 @@ impl Transformation {
90109 } )
91110 }
92111
93- fn execute (
112+ /// Apply the changes defined in the configuration to the compiler call.
113+ ///
114+ /// The changes can be to remove or add flags to the compiler call.
115+ /// Only the flags will be changed, but applies to all compiler passes.
116+ fn apply_argument_changes (
94117 arguments : & config:: Arguments ,
95118 passes : & [ semantic:: CompilerPass ] ,
96119 ) -> Vec < semantic:: CompilerPass > {
@@ -122,3 +145,124 @@ impl Transformation {
122145 new_passes
123146 }
124147}
148+
149+ #[ cfg( test) ]
150+ mod tests {
151+ use super :: * ;
152+ use crate :: config:: { Arguments , Compiler , IgnoreOrConsider } ;
153+ use crate :: semantic:: { CompilerCall , CompilerPass , Transform } ;
154+ use std:: path:: PathBuf ;
155+
156+ #[ test]
157+ fn test_apply_no_filter ( ) {
158+ let input = CompilerCall {
159+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
160+ passes : vec ! [ CompilerPass :: Compile {
161+ source: PathBuf :: from( "main.c" ) ,
162+ output: PathBuf :: from( "main.o" ) . into( ) ,
163+ flags: vec![ "-O2" . into( ) ] ,
164+ } ] ,
165+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
166+ } ;
167+
168+ let sut = Transformation { compilers : vec ! [ ] } ;
169+ let result = sut. apply ( input) ;
170+
171+ let expected = CompilerCall {
172+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
173+ passes : vec ! [ CompilerPass :: Compile {
174+ source: PathBuf :: from( "main.c" ) ,
175+ output: PathBuf :: from( "main.o" ) . into( ) ,
176+ flags: vec![ "-O2" . into( ) ] ,
177+ } ] ,
178+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
179+ } ;
180+ assert_eq ! ( result, Some ( expected) ) ;
181+ }
182+
183+ #[ test]
184+ fn test_apply_filter_match ( ) {
185+ let input = CompilerCall {
186+ compiler : std:: path:: PathBuf :: from ( "cc" ) ,
187+ passes : vec ! [ CompilerPass :: Compile {
188+ source: PathBuf :: from( "main.c" ) ,
189+ output: PathBuf :: from( "main.o" ) . into( ) ,
190+ flags: vec![ "-O2" . into( ) ] ,
191+ } ] ,
192+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
193+ } ;
194+
195+ let sut = Transformation {
196+ compilers : vec ! [ Compiler {
197+ path: std:: path:: PathBuf :: from( "cc" ) ,
198+ ignore: IgnoreOrConsider :: Always ,
199+ arguments: Arguments :: default ( ) ,
200+ } ] ,
201+ } ;
202+ let result = sut. apply ( input) ;
203+ assert ! ( result. is_none( ) ) ;
204+ }
205+
206+ #[ test]
207+ fn test_apply_conditional_match ( ) {
208+ let input = CompilerCall {
209+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
210+ passes : vec ! [ CompilerPass :: Compile {
211+ source: PathBuf :: from( "main.c" ) ,
212+ output: PathBuf :: from( "main.o" ) . into( ) ,
213+ flags: vec![ "-O2" . into( ) , "-Wall" . into( ) ] ,
214+ } ] ,
215+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
216+ } ;
217+
218+ let sut = Transformation {
219+ compilers : vec ! [ Compiler {
220+ path: std:: path:: PathBuf :: from( "gcc" ) ,
221+ ignore: IgnoreOrConsider :: Conditional ,
222+ arguments: Arguments {
223+ match_: vec![ "-O2" . into( ) ] ,
224+ ..Arguments :: default ( )
225+ } ,
226+ } ] ,
227+ } ;
228+ let result = sut. apply ( input) ;
229+ assert ! ( result. is_none( ) ) ;
230+ }
231+
232+ #[ test]
233+ fn test_apply_ignore_never_modify_arguments ( ) {
234+ let input = CompilerCall {
235+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
236+ passes : vec ! [ CompilerPass :: Compile {
237+ source: PathBuf :: from( "main.c" ) ,
238+ output: PathBuf :: from( "main.o" ) . into( ) ,
239+ flags: vec![ "-O2" . into( ) ] ,
240+ } ] ,
241+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
242+ } ;
243+
244+ let sut = Transformation {
245+ compilers : vec ! [ Compiler {
246+ path: std:: path:: PathBuf :: from( "gcc" ) ,
247+ ignore: IgnoreOrConsider :: Never ,
248+ arguments: Arguments {
249+ add: vec![ "-Wall" . into( ) ] ,
250+ remove: vec![ "-O2" . into( ) ] ,
251+ ..Arguments :: default ( )
252+ } ,
253+ } ] ,
254+ } ;
255+ let result = sut. apply ( input) ;
256+
257+ let expected = CompilerCall {
258+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
259+ passes : vec ! [ CompilerPass :: Compile {
260+ source: PathBuf :: from( "main.c" ) ,
261+ output: PathBuf :: from( "main.o" ) . into( ) ,
262+ flags: vec![ "-Wall" . into( ) ] ,
263+ } ] ,
264+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
265+ } ;
266+ assert_eq ! ( result, Some ( expected) ) ;
267+ }
268+ }
0 commit comments