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+ pub struct Transformation {
15+ compilers : HashMap < PathBuf , Vec < config:: Compiler > > ,
1616}
1717
1818impl From < & config:: Output > for Transformation {
1919 fn from ( config : & config:: Output ) -> Self {
2020 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 ,
21+ config:: Output :: Clang { compilers, .. } => compilers. as_slice ( ) . into ( ) ,
22+ config:: Output :: Semantic { .. } => Transformation :: new ( ) ,
23+ }
24+ }
25+ }
26+
27+ impl From < & [ config:: Compiler ] > for Transformation {
28+ fn from ( config : & [ config:: Compiler ] ) -> Self {
29+ let mut compilers = HashMap :: new ( ) ;
30+ for compiler in config {
31+ compilers
32+ . entry ( compiler. path . clone ( ) )
33+ . or_insert_with ( Vec :: new)
34+ . push ( compiler. clone ( ) ) ;
3035 }
36+ Transformation { compilers }
3137 }
3238}
3339
34- impl Transform for Transformation {
40+ impl semantic :: Transform for Transformation {
3541 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) ,
42+ if let Some ( configs) = self . compilers . get ( & input. compiler ) {
43+ Self :: apply_when_not_empty ( configs. as_slice ( ) , input)
44+ } else {
45+ Some ( input)
7046 }
7147 }
7248}
7349
7450impl 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 ,
51+ fn new ( ) -> Self {
52+ Transformation {
53+ compilers : HashMap :: new ( ) ,
54+ }
55+ }
56+
57+ /// Apply the transformation to the compiler call.
58+ ///
59+ /// Multiple configurations can be applied to the same compiler call.
60+ /// And depending on the instruction from the configuration, the compiler call
61+ /// can be ignored, modified, or left unchanged. The conditional ignore will
62+ /// check if the compiler call matches the flags defined in the configuration.
63+ fn apply_when_not_empty (
64+ configs : & [ config:: Compiler ] ,
65+ input : semantic:: CompilerCall ,
66+ ) -> Option < semantic:: CompilerCall > {
67+ let mut current_input = Some ( input) ;
68+
69+ for config in configs {
70+ current_input = match config {
71+ config:: Compiler {
72+ ignore : config:: IgnoreOrConsider :: Always ,
73+ ..
74+ } => None ,
75+ config:: Compiler {
76+ ignore : config:: IgnoreOrConsider :: Conditional ,
77+ arguments,
78+ ..
79+ } => current_input. filter ( |input| !Self :: match_condition ( arguments, & input. passes ) ) ,
80+ config:: Compiler {
81+ ignore : config:: IgnoreOrConsider :: Never ,
82+ arguments,
83+ ..
84+ } => current_input. map ( |input| semantic:: CompilerCall {
85+ compiler : input. compiler . clone ( ) ,
86+ working_dir : input. working_dir . clone ( ) ,
87+ passes : Transformation :: apply_argument_changes (
88+ arguments,
89+ input. passes . as_slice ( ) ,
90+ ) ,
91+ } ) ,
92+ } ;
93+
94+ if current_input. is_none ( ) {
95+ break ;
96+ }
8097 }
98+ current_input
8199 }
82100
83- fn filter ( arguments : & config:: Arguments , passes : & [ semantic:: CompilerPass ] ) -> bool {
101+ /// Check if the compiler call matches the condition defined in the configuration.
102+ ///
103+ /// Any compiler pass that matches the flags defined in the configuration will cause
104+ /// the whole compiler call to be ignored.
105+ fn match_condition ( arguments : & config:: Arguments , passes : & [ semantic:: CompilerPass ] ) -> bool {
84106 let match_flags = arguments. match_ . as_slice ( ) ;
85107 passes. iter ( ) . any ( |pass| match pass {
86108 semantic:: CompilerPass :: Compile { flags, .. } => {
@@ -90,7 +112,11 @@ impl Transformation {
90112 } )
91113 }
92114
93- fn execute (
115+ /// Apply the changes defined in the configuration to the compiler call.
116+ ///
117+ /// The changes can be to remove or add flags to the compiler call.
118+ /// Only the flags will be changed, but applies to all compiler passes.
119+ fn apply_argument_changes (
94120 arguments : & config:: Arguments ,
95121 passes : & [ semantic:: CompilerPass ] ,
96122 ) -> Vec < semantic:: CompilerPass > {
@@ -122,3 +148,124 @@ impl Transformation {
122148 new_passes
123149 }
124150}
151+
152+ #[ cfg( test) ]
153+ mod tests {
154+ use super :: * ;
155+ use crate :: config:: { Arguments , Compiler , IgnoreOrConsider } ;
156+ use crate :: semantic:: { CompilerCall , CompilerPass , Transform } ;
157+ use std:: path:: PathBuf ;
158+
159+ #[ test]
160+ fn test_apply_no_filter ( ) {
161+ let input = CompilerCall {
162+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
163+ passes : vec ! [ CompilerPass :: Compile {
164+ source: PathBuf :: from( "main.c" ) ,
165+ output: PathBuf :: from( "main.o" ) . into( ) ,
166+ flags: vec![ "-O2" . into( ) ] ,
167+ } ] ,
168+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
169+ } ;
170+
171+ let sut = Transformation :: from ( & config:: Output :: Semantic { } ) ;
172+ let result = sut. apply ( input) ;
173+
174+ let expected = CompilerCall {
175+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
176+ passes : vec ! [ CompilerPass :: Compile {
177+ source: PathBuf :: from( "main.c" ) ,
178+ output: PathBuf :: from( "main.o" ) . into( ) ,
179+ flags: vec![ "-O2" . into( ) ] ,
180+ } ] ,
181+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
182+ } ;
183+ assert_eq ! ( result, Some ( expected) ) ;
184+ }
185+
186+ #[ test]
187+ fn test_apply_filter_match ( ) {
188+ let input = CompilerCall {
189+ compiler : std:: path:: PathBuf :: from ( "cc" ) ,
190+ passes : vec ! [ CompilerPass :: Compile {
191+ source: PathBuf :: from( "main.c" ) ,
192+ output: PathBuf :: from( "main.o" ) . into( ) ,
193+ flags: vec![ "-O2" . into( ) ] ,
194+ } ] ,
195+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
196+ } ;
197+
198+ let sut: Transformation = vec ! [ Compiler {
199+ path: std:: path:: PathBuf :: from( "cc" ) ,
200+ ignore: IgnoreOrConsider :: Always ,
201+ arguments: Arguments :: default ( ) ,
202+ } ]
203+ . as_slice ( )
204+ . into ( ) ;
205+ let result = sut. apply ( input) ;
206+ assert ! ( result. is_none( ) ) ;
207+ }
208+
209+ #[ test]
210+ fn test_apply_conditional_match ( ) {
211+ let input = CompilerCall {
212+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
213+ passes : vec ! [ CompilerPass :: Compile {
214+ source: PathBuf :: from( "main.c" ) ,
215+ output: PathBuf :: from( "main.o" ) . into( ) ,
216+ flags: vec![ "-O2" . into( ) , "-Wall" . into( ) ] ,
217+ } ] ,
218+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
219+ } ;
220+
221+ let sut: Transformation = vec ! [ Compiler {
222+ path: std:: path:: PathBuf :: from( "gcc" ) ,
223+ ignore: IgnoreOrConsider :: Conditional ,
224+ arguments: Arguments {
225+ match_: vec![ "-O2" . into( ) ] ,
226+ ..Arguments :: default ( )
227+ } ,
228+ } ]
229+ . as_slice ( )
230+ . into ( ) ;
231+ let result = sut. apply ( input) ;
232+ assert ! ( result. is_none( ) ) ;
233+ }
234+
235+ #[ test]
236+ fn test_apply_ignore_never_modify_arguments ( ) {
237+ let input = CompilerCall {
238+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
239+ passes : vec ! [ CompilerPass :: Compile {
240+ source: PathBuf :: from( "main.c" ) ,
241+ output: PathBuf :: from( "main.o" ) . into( ) ,
242+ flags: vec![ "-O2" . into( ) ] ,
243+ } ] ,
244+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
245+ } ;
246+
247+ let sut: Transformation = vec ! [ Compiler {
248+ path: std:: path:: PathBuf :: from( "gcc" ) ,
249+ ignore: IgnoreOrConsider :: Never ,
250+ arguments: Arguments {
251+ add: vec![ "-Wall" . into( ) ] ,
252+ remove: vec![ "-O2" . into( ) ] ,
253+ ..Arguments :: default ( )
254+ } ,
255+ } ]
256+ . as_slice ( )
257+ . into ( ) ;
258+ let result = sut. apply ( input) ;
259+
260+ let expected = CompilerCall {
261+ compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
262+ passes : vec ! [ CompilerPass :: Compile {
263+ source: PathBuf :: from( "main.c" ) ,
264+ output: PathBuf :: from( "main.o" ) . into( ) ,
265+ flags: vec![ "-Wall" . into( ) ] ,
266+ } ] ,
267+ working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
268+ } ;
269+ assert_eq ! ( result, Some ( expected) ) ;
270+ }
271+ }
0 commit comments