@@ -17,6 +17,7 @@ pub trait Transformation: Send {
1717
1818#[ derive( Debug , Error ) ]
1919pub enum Error {
20+ // FIXME: Should we report the path that failed?
2021 #[ error( "Path canonicalize failed: {0}" ) ]
2122 PathCanonicalize ( #[ from] io:: Error ) ,
2223 #[ error( "Path {0} can't be relative to {1}" ) ]
@@ -96,7 +97,7 @@ mod formatter {
9697 use std:: env;
9798 use std:: path:: { Path , PathBuf } ;
9899
99- #[ derive( Default ) ]
100+ #[ derive( Default , Debug ) ]
100101 pub enum PathFormatter {
101102 DoFormat ( config:: PathFormat , PathBuf ) ,
102103 #[ default]
@@ -511,16 +512,16 @@ mod formatter {
511512mod filter_by_compiler {
512513 use super :: * ;
513514 use std:: collections:: HashMap ;
514- use std:: path:: PathBuf ;
515+ use std:: path;
515516
516517 /// Transformation contains rearranged information from the configuration.
517518 ///
518519 /// The configuration is a list of instructions on how to transform the compiler call.
519520 /// The transformation groups the instructions by the compiler path, so it can be
520521 /// applied to the compiler call when it matches the path.
521- #[ derive( Default ) ]
522+ #[ derive( Default , Debug ) ]
522523 pub struct FilterByCompiler {
523- compilers : HashMap < PathBuf , Vec < config:: Compiler > > ,
524+ compilers : HashMap < path :: PathBuf , Vec < config:: Compiler > > ,
524525 }
525526
526527 impl Transformation for FilterByCompiler {
@@ -982,3 +983,264 @@ mod filter_by_compiler {
982983 }
983984 }
984985}
986+
987+ mod filter_by_source_dir {
988+ use super :: * ;
989+ use crate :: config;
990+ use std:: path;
991+
992+ /// FilterBySourceDir is a transformation that filters the compiler calls
993+ /// based on the source directory. If the compilation has multiple source
994+ /// files, it will ignore the whole compilation if any of the source files
995+ /// matches the filter.
996+ #[ derive( Default , Debug ) ]
997+ pub struct FilterBySourceDir {
998+ filters : Vec < config:: DirectoryFilter > ,
999+ }
1000+
1001+ impl Transformation for FilterBySourceDir {
1002+ // FIXME: This is currently ignore the whole compiler call if any of the
1003+ // pass matches the filter. This should be changed to ignore only the
1004+ // pass that matches the filter.
1005+ fn apply ( & self , input : semantic:: CompilerCall ) -> Result < semantic:: CompilerCall , Error > {
1006+ // Check if the compiler call matches the source directory filter
1007+ for filter in & self . filters {
1008+ // Check the source for each pass
1009+ let matching = input. passes . iter ( ) . any ( |pass| {
1010+ if let semantic:: CompilerPass :: Compile { source, .. } = pass {
1011+ // Check if the source is in the filter directory
1012+ return source. starts_with ( & filter. path ) ;
1013+ }
1014+ false
1015+ } ) ;
1016+ // If the source matches the filter, we should ignore or include the call
1017+ if matching {
1018+ return if filter. ignore == config:: Ignore :: Always {
1019+ // Ignore the compiler call if the source matches the filter
1020+ Err ( Error :: FilteredOut )
1021+ } else {
1022+ // Include the compiler call if the source matches the filter
1023+ Ok ( input)
1024+ } ;
1025+ }
1026+ }
1027+ // When no matching filter is found, we should not ignore the call
1028+ Ok ( input)
1029+ }
1030+ }
1031+
1032+ #[ derive( Debug , Error ) ]
1033+ pub enum ConfigurationError {
1034+ #[ error( "Duplicate directory: {0}" ) ]
1035+ DuplicateItem ( path:: PathBuf ) ,
1036+ #[ error( "Same directory to include and exclude: {0}" ) ]
1037+ DuplicateInstruction ( path:: PathBuf ) ,
1038+ // FIXME: Should we report the path that failed?
1039+ #[ error( "Canonicalization failed: {0}" ) ]
1040+ Canonicalization ( #[ from] io:: Error ) ,
1041+ }
1042+
1043+ impl TryFrom < & config:: SourceFilter > for FilterBySourceDir {
1044+ type Error = ConfigurationError ;
1045+
1046+ // FIXME: Should we check if the allowed directory and the ignored directory are
1047+ // parents of each other? It make sens if the allowed directory is first,
1048+ // and the ignored directory is second and parent of the first. But if the
1049+ // order is reversed, the allowed will never be used.
1050+ fn try_from ( value : & config:: SourceFilter ) -> Result < Self , Self :: Error > {
1051+ // Convert the source filter to a list of directory filters
1052+ let filters: Vec < config:: DirectoryFilter > = value. try_into ( ) ?;
1053+ let mut verified: Vec < config:: DirectoryFilter > = vec ! [ ] ;
1054+
1055+ // Check the semantics of the filters
1056+ for filter in filters. iter ( ) {
1057+ // Check if the same path is already in the list
1058+ if let Some ( duplicate) = verified. iter ( ) . find ( |f| f. path == filter. path ) {
1059+ // Classify the error based on the ignore flag match
1060+ let path = filter. path . clone ( ) ;
1061+ return if duplicate. ignore == filter. ignore {
1062+ Err ( ConfigurationError :: DuplicateItem ( path) )
1063+ } else {
1064+ Err ( ConfigurationError :: DuplicateInstruction ( path) )
1065+ } ;
1066+ }
1067+ verified. push ( filter. clone ( ) ) ;
1068+ }
1069+
1070+ Ok ( Self { filters } )
1071+ }
1072+ }
1073+
1074+ /// Convert the source filter to a list of directory filters.
1075+ ///
1076+ /// The conversion is done by canonicalizing the paths when the filesystem
1077+ /// is accessible. Otherwise, the filter paths left as is.
1078+ impl TryFrom < & config:: SourceFilter > for Vec < config:: DirectoryFilter > {
1079+ type Error = io:: Error ;
1080+
1081+ fn try_from ( value : & config:: SourceFilter ) -> Result < Self , Self :: Error > {
1082+ let filters = value
1083+ . paths
1084+ . iter ( )
1085+ . flat_map ( |filter| {
1086+ if value. only_existing_files {
1087+ filter. path . canonicalize ( ) . map ( |p| config:: DirectoryFilter {
1088+ path : p,
1089+ ignore : filter. ignore . clone ( ) ,
1090+ } )
1091+ } else {
1092+ Ok ( filter. clone ( ) )
1093+ }
1094+ } )
1095+ . collect ( ) ;
1096+ Ok ( filters)
1097+ }
1098+ }
1099+
1100+ #[ cfg( test) ]
1101+ mod tests {
1102+ use super :: super :: Error ;
1103+ use super :: { ConfigurationError , FilterBySourceDir } ;
1104+ use crate :: config:: { DirectoryFilter , Ignore , SourceFilter } ;
1105+ use crate :: semantic:: transformation:: Transformation ;
1106+ use crate :: semantic:: { CompilerCall , CompilerPass } ;
1107+ use std:: path:: PathBuf ;
1108+
1109+ #[ test]
1110+ fn test_filter_by_source_dir_try_from_without_filesystem ( ) {
1111+ let config = SourceFilter {
1112+ only_existing_files : false ,
1113+ paths : vec ! [
1114+ DirectoryFilter {
1115+ path: PathBuf :: from( "/project/src" ) ,
1116+ ignore: Ignore :: Never ,
1117+ } ,
1118+ DirectoryFilter {
1119+ path: PathBuf :: from( "/project/tests" ) ,
1120+ ignore: Ignore :: Always ,
1121+ } ,
1122+ ] ,
1123+ } ;
1124+
1125+ let result: Result < FilterBySourceDir , ConfigurationError > = ( & config) . try_into ( ) ;
1126+ assert ! ( result. is_ok( ) ) ;
1127+
1128+ let filter_by_source_dir = result. unwrap ( ) ;
1129+ assert_eq ! ( filter_by_source_dir. filters. len( ) , 2 ) ;
1130+ assert_eq ! (
1131+ filter_by_source_dir. filters[ 0 ] . path,
1132+ PathBuf :: from( "/project/src" )
1133+ ) ;
1134+ assert_eq ! ( filter_by_source_dir. filters[ 0 ] . ignore, Ignore :: Never ) ;
1135+ assert_eq ! (
1136+ filter_by_source_dir. filters[ 1 ] . path,
1137+ PathBuf :: from( "/project/tests" )
1138+ ) ;
1139+ assert_eq ! ( filter_by_source_dir. filters[ 1 ] . ignore, Ignore :: Always ) ;
1140+ }
1141+
1142+ #[ test]
1143+ fn test_filter_by_source_dir_duplicate_instruction ( ) {
1144+ let config = SourceFilter {
1145+ only_existing_files : false ,
1146+ paths : vec ! [
1147+ DirectoryFilter {
1148+ path: PathBuf :: from( "/project/src" ) ,
1149+ ignore: Ignore :: Always ,
1150+ } ,
1151+ DirectoryFilter {
1152+ path: PathBuf :: from( "/project/test" ) ,
1153+ ignore: Ignore :: Always ,
1154+ } ,
1155+ DirectoryFilter {
1156+ path: PathBuf :: from( "/project/src" ) ,
1157+ ignore: Ignore :: Never ,
1158+ } ,
1159+ ] ,
1160+ } ;
1161+
1162+ let result: Result < FilterBySourceDir , ConfigurationError > = ( & config) . try_into ( ) ;
1163+ assert ! ( result. is_err( ) ) ;
1164+ assert ! ( matches!(
1165+ result. unwrap_err( ) ,
1166+ ConfigurationError :: DuplicateInstruction ( path) if path == PathBuf :: from( "/project/src" )
1167+ ) ) ;
1168+ }
1169+
1170+ #[ test]
1171+ fn test_filter_by_source_dir_duplicate_entry ( ) {
1172+ let config = SourceFilter {
1173+ only_existing_files : false ,
1174+ paths : vec ! [
1175+ DirectoryFilter {
1176+ path: PathBuf :: from( "/project/src" ) ,
1177+ ignore: Ignore :: Always ,
1178+ } ,
1179+ DirectoryFilter {
1180+ path: PathBuf :: from( "/project/test" ) ,
1181+ ignore: Ignore :: Never ,
1182+ } ,
1183+ DirectoryFilter {
1184+ path: PathBuf :: from( "/project/src" ) ,
1185+ ignore: Ignore :: Always ,
1186+ } ,
1187+ ] ,
1188+ } ;
1189+
1190+ let result: Result < FilterBySourceDir , ConfigurationError > = ( & config) . try_into ( ) ;
1191+ assert ! ( result. is_err( ) ) ;
1192+ assert ! ( matches!(
1193+ result. unwrap_err( ) ,
1194+ ConfigurationError :: DuplicateItem ( path) if path == PathBuf :: from( "/project/src" )
1195+ ) ) ;
1196+ }
1197+
1198+ #[ test]
1199+ fn test_filter_by_source_dir_apply_filtered_out ( ) {
1200+ let filter = FilterBySourceDir {
1201+ filters : vec ! [ DirectoryFilter {
1202+ path: PathBuf :: from( "/project/src" ) ,
1203+ ignore: Ignore :: Always ,
1204+ } ] ,
1205+ } ;
1206+
1207+ let result = filter. apply ( COMPILER_CALL . clone ( ) ) ;
1208+ assert ! ( result. is_err( ) ) ;
1209+ assert ! ( matches!( result. unwrap_err( ) , Error :: FilteredOut ) ) ;
1210+ }
1211+
1212+ #[ test]
1213+ fn test_filter_by_source_dir_apply_not_filtered_out_include ( ) {
1214+ let filter = FilterBySourceDir {
1215+ filters : vec ! [ DirectoryFilter {
1216+ path: PathBuf :: from( "/project/src" ) ,
1217+ ignore: Ignore :: Never ,
1218+ } ] ,
1219+ } ;
1220+
1221+ let result = filter. apply ( COMPILER_CALL . clone ( ) ) ;
1222+ assert ! ( result. is_ok( ) ) ;
1223+ assert_eq ! ( result. unwrap( ) , COMPILER_CALL . clone( ) ) ;
1224+ }
1225+
1226+ #[ test]
1227+ fn test_filter_by_source_dir_apply_no_instructions ( ) {
1228+ let filter = FilterBySourceDir { filters : vec ! [ ] } ;
1229+
1230+ let result = filter. apply ( COMPILER_CALL . clone ( ) ) ;
1231+ assert ! ( result. is_ok( ) ) ;
1232+ assert_eq ! ( result. unwrap( ) , COMPILER_CALL . clone( ) ) ;
1233+ }
1234+
1235+ static COMPILER_CALL : std:: sync:: LazyLock < CompilerCall > =
1236+ std:: sync:: LazyLock :: new ( || CompilerCall {
1237+ compiler : PathBuf :: from ( "gcc" ) ,
1238+ working_dir : PathBuf :: from ( "/project" ) ,
1239+ passes : vec ! [ CompilerPass :: Compile {
1240+ source: PathBuf :: from( "/project/src/main.c" ) ,
1241+ output: None ,
1242+ flags: vec![ ] ,
1243+ } ] ,
1244+ } ) ;
1245+ }
1246+ }
0 commit comments