66//! It can also alter the compiler flags of the compiler calls. The actions
77//! are defined in the configuration this module is given.
88
9- use crate :: config;
10- use crate :: semantic;
11- use crate :: semantic:: Transform ;
9+ use crate :: { config, semantic} ;
1210
13- pub enum Transformation {
14- None ,
15- Config ( Vec < config:: Compiler > ) ,
11+ use std:: collections:: HashMap ;
12+ use std:: path:: PathBuf ;
13+
14+ /// Transformation contains rearranged information from the configuration.
15+ ///
16+ /// The configuration is a list of instruction on how to transform the compiler call.
17+ /// The transformation group the instructions by the compiler path, so it can be
18+ /// applied to the compiler call when it matches the path.
19+ #[ derive( Debug , PartialEq ) ]
20+ pub struct Transformation {
21+ compilers : HashMap < PathBuf , Vec < config:: Compiler > > ,
1622}
1723
1824impl From < & config:: Output > for Transformation {
1925 fn from ( config : & config:: Output ) -> Self {
2026 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 ,
27+ config:: Output :: Clang { compilers, .. } => compilers. as_slice ( ) . into ( ) ,
28+ config:: Output :: Semantic { .. } => Transformation :: new ( ) ,
29+ }
30+ }
31+ }
32+
33+ impl From < & [ config:: Compiler ] > for Transformation {
34+ fn from ( config : & [ config:: Compiler ] ) -> Self {
35+ let mut compilers = HashMap :: new ( ) ;
36+ for compiler in config {
37+ compilers
38+ . entry ( compiler. path . clone ( ) )
39+ . or_insert_with ( Vec :: new)
40+ . push ( compiler. clone ( ) ) ;
3041 }
42+ Transformation { compilers }
3143 }
3244}
3345
34- impl Transform for Transformation {
46+ impl semantic :: Transform for Transformation {
3547 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- } )
68- }
69- None => Some ( input) ,
48+ if let Some ( configs) = self . compilers . get ( & input. compiler ) {
49+ Self :: apply_when_not_empty ( configs. as_slice ( ) , input)
50+ } else {
51+ Some ( input)
7052 }
7153 }
7254}
7355
7456impl 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 ,
57+ fn new ( ) -> Self {
58+ Transformation {
59+ compilers : HashMap :: new ( ) ,
60+ }
61+ }
62+
63+ /// Apply the transformation to the compiler call.
64+ ///
65+ /// Multiple configurations can be applied to the same compiler call.
66+ /// And depending on the instruction from the configuration, the compiler call
67+ /// can be ignored, modified, or left unchanged. The conditional ignore will
68+ /// check if the compiler call matches the flags defined in the configuration.
69+ fn apply_when_not_empty (
70+ configs : & [ config:: Compiler ] ,
71+ input : semantic:: CompilerCall ,
72+ ) -> Option < semantic:: CompilerCall > {
73+ let mut current_input = Some ( input) ;
74+
75+ for config in configs {
76+ current_input = match config {
77+ config:: Compiler {
78+ ignore : config:: IgnoreOrConsider :: Always ,
79+ ..
80+ } => None ,
81+ config:: Compiler {
82+ ignore : config:: IgnoreOrConsider :: Conditional ,
83+ arguments,
84+ ..
85+ } => current_input. filter ( |input| !Self :: match_condition ( arguments, & input. passes ) ) ,
86+ config:: Compiler {
87+ ignore : config:: IgnoreOrConsider :: Never ,
88+ arguments,
89+ ..
90+ } => current_input. map ( |input| semantic:: CompilerCall {
91+ compiler : input. compiler . clone ( ) ,
92+ working_dir : input. working_dir . clone ( ) ,
93+ passes : Transformation :: apply_argument_changes (
94+ arguments,
95+ input. passes . as_slice ( ) ,
96+ ) ,
97+ } ) ,
98+ } ;
99+
100+ if current_input. is_none ( ) {
101+ break ;
102+ }
80103 }
104+ current_input
81105 }
82106
83- fn filter ( arguments : & config:: Arguments , passes : & [ semantic:: CompilerPass ] ) -> bool {
107+ /// Check if the compiler call matches the condition defined in the configuration.
108+ ///
109+ /// Any compiler pass that matches the flags defined in the configuration will cause
110+ /// the whole compiler call to be ignored.
111+ fn match_condition ( arguments : & config:: Arguments , passes : & [ semantic:: CompilerPass ] ) -> bool {
84112 let match_flags = arguments. match_ . as_slice ( ) ;
85113 passes. iter ( ) . any ( |pass| match pass {
86114 semantic:: CompilerPass :: Compile { flags, .. } => {
@@ -90,7 +118,11 @@ impl Transformation {
90118 } )
91119 }
92120
93- fn execute (
121+ /// Apply the changes defined in the configuration to the compiler call.
122+ ///
123+ /// The changes can be to remove or add flags to the compiler call.
124+ /// Only the flags will be changed, but applies to all compiler passes.
125+ fn apply_argument_changes (
94126 arguments : & config:: Arguments ,
95127 passes : & [ semantic:: CompilerPass ] ,
96128 ) -> Vec < semantic:: CompilerPass > {
@@ -122,3 +154,124 @@ impl Transformation {
122154 new_passes
123155 }
124156}
157+
158+ #[ cfg( test) ]
159+ mod tests {
160+ use super :: * ;
161+ use crate :: config:: { Arguments , Compiler , IgnoreOrConsider } ;
162+ use crate :: semantic:: { CompilerCall , CompilerPass , Transform } ;
163+ use std:: path:: PathBuf ;
164+
165+ #[ test]
166+ fn test_apply_no_filter ( ) {
167+ let input = CompilerCall {
168+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
169+ passes : vec ! [ CompilerPass :: Compile {
170+ source: PathBuf :: from( "main.c" ) ,
171+ output: PathBuf :: from( "main.o" ) . into( ) ,
172+ flags: vec![ "-O2" . into( ) ] ,
173+ } ] ,
174+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
175+ } ;
176+
177+ let sut = Transformation :: from ( & config:: Output :: Semantic { } ) ;
178+ let result = sut. apply ( input) ;
179+
180+ let expected = CompilerCall {
181+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
182+ passes : vec ! [ CompilerPass :: Compile {
183+ source: PathBuf :: from( "main.c" ) ,
184+ output: PathBuf :: from( "main.o" ) . into( ) ,
185+ flags: vec![ "-O2" . into( ) ] ,
186+ } ] ,
187+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
188+ } ;
189+ assert_eq ! ( result, Some ( expected) ) ;
190+ }
191+
192+ #[ test]
193+ fn test_apply_filter_match ( ) {
194+ let input = CompilerCall {
195+ compiler : std:: path:: PathBuf :: from ( "cc" ) ,
196+ passes : vec ! [ CompilerPass :: Compile {
197+ source: PathBuf :: from( "main.c" ) ,
198+ output: PathBuf :: from( "main.o" ) . into( ) ,
199+ flags: vec![ "-O2" . into( ) ] ,
200+ } ] ,
201+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
202+ } ;
203+
204+ let sut: Transformation = vec ! [ Compiler {
205+ path: std:: path:: PathBuf :: from( "cc" ) ,
206+ ignore: IgnoreOrConsider :: Always ,
207+ arguments: Arguments :: default ( ) ,
208+ } ]
209+ . as_slice ( )
210+ . into ( ) ;
211+ let result = sut. apply ( input) ;
212+ assert ! ( result. is_none( ) ) ;
213+ }
214+
215+ #[ test]
216+ fn test_apply_conditional_match ( ) {
217+ let input = CompilerCall {
218+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
219+ passes : vec ! [ CompilerPass :: Compile {
220+ source: PathBuf :: from( "main.c" ) ,
221+ output: PathBuf :: from( "main.o" ) . into( ) ,
222+ flags: vec![ "-O2" . into( ) , "-Wall" . into( ) ] ,
223+ } ] ,
224+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
225+ } ;
226+
227+ let sut: Transformation = vec ! [ Compiler {
228+ path: std:: path:: PathBuf :: from( "gcc" ) ,
229+ ignore: IgnoreOrConsider :: Conditional ,
230+ arguments: Arguments {
231+ match_: vec![ "-O2" . into( ) ] ,
232+ ..Arguments :: default ( )
233+ } ,
234+ } ]
235+ . as_slice ( )
236+ . into ( ) ;
237+ let result = sut. apply ( input) ;
238+ assert ! ( result. is_none( ) ) ;
239+ }
240+
241+ #[ test]
242+ fn test_apply_ignore_never_modify_arguments ( ) {
243+ let input = CompilerCall {
244+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
245+ passes : vec ! [ CompilerPass :: Compile {
246+ source: PathBuf :: from( "main.c" ) ,
247+ output: PathBuf :: from( "main.o" ) . into( ) ,
248+ flags: vec![ "-O2" . into( ) ] ,
249+ } ] ,
250+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
251+ } ;
252+
253+ let sut: Transformation = vec ! [ Compiler {
254+ path: std:: path:: PathBuf :: from( "gcc" ) ,
255+ ignore: IgnoreOrConsider :: Never ,
256+ arguments: Arguments {
257+ add: vec![ "-Wall" . into( ) ] ,
258+ remove: vec![ "-O2" . into( ) ] ,
259+ ..Arguments :: default ( )
260+ } ,
261+ } ]
262+ . as_slice ( )
263+ . into ( ) ;
264+ let result = sut. apply ( input) ;
265+
266+ let expected = CompilerCall {
267+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
268+ passes : vec ! [ CompilerPass :: Compile {
269+ source: PathBuf :: from( "main.c" ) ,
270+ output: PathBuf :: from( "main.o" ) . into( ) ,
271+ flags: vec![ "-Wall" . into( ) ] ,
272+ } ] ,
273+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
274+ } ;
275+ assert_eq ! ( result, Some ( expected) ) ;
276+ }
277+ }
0 commit comments