char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
a29d09f
main
1use std:: collections::{ BTreeMap , BTreeSet }; 2use std:: io:: Write ; 3use std:: path::{ Path , PathBuf }; 4use std:: sync:: atomic::{ AtomicU64 , Ordering }; 5 6use glob:: Pattern ; 7 8use crate :: Reporter ; 9use crate :: glob::{ MATCH_OPTIONS , collect_paths, file_id, normalize_pattern}; 10use crate :: graph:: Task ; 11 12# [ derive ( Debug , Clone )] 13pub struct Cache { 14root : PathBuf , 15} 16 17impl Cache { 18pub fn new ( root : PathBuf ) ->Self { 19Cache { root} 20} 21 22pub fn entry ( & self , fingerprint : & str ) ->PathBuf { 23self . root . join ( format! ( "{fingerprint}.json" )) 24} 25 26fn manifest_for ( 27root : & Path , 28task : & Task , 29) -> std:: io:: Result <( BTreeMap < String , String >, Vec < String >)> { 30let ( matches, unmatched) =expand_outputs ( root, & task. output ) ?; 31let mut manifest =BTreeMap :: new (); 32for relativein matches{ 33manifest_path ( & root. join ( & relative), & relative, & mut manifest) ?; 34} 35Ok (( manifest, unmatched)) 36} 37pub ( crate ) fn manifest_matches ( 38& self , 39root : & Path , 40task : & Task , 41fingerprint : & str , 42) -> std:: io:: Result < bool > { 43let entry =self . entry ( fingerprint); 44let Ok ( bytes) = std:: fs:: read ( entry) else { 45return Ok ( false ); 46}; 47let stored: BTreeMap < String , String > = serde_json:: from_slice ( & bytes) 48. map_err ( |e| std:: io:: Error :: new ( std:: io:: ErrorKind :: InvalidData , e)) ?; 49Ok ( Self :: manifest_for ( root, task) ?. 0 == stored) 50} 51 52pub ( crate ) fn record_at ( 53& self , 54root : & Path , 55task : & Task , 56fingerprint : & str , 57reporter : & dyn Reporter , 58task_name : & str , 59) -> std:: io:: Result <()> { 60let ( manifest, unmatched) =Self :: manifest_for ( root, task) ?; 61for patternin unmatched{ 62 reporter. unmatched_output ( task_name, & pattern); 63} 64 std:: fs:: create_dir_all ( & self . root ) ?; 65let entry =self . entry ( fingerprint); 66let bytes = serde_json:: to_vec ( & manifest) 67. map_err ( |e| std:: io:: Error :: new ( std:: io:: ErrorKind :: InvalidData , e)) ?; 68static SEQUENCE : AtomicU64 =AtomicU64 :: new ( 0 ); 69for _in 0 ..16 { 70let staging =self . root . join ( format! ( 71".tmp-{}-{}-{}.json" , 72 entry. file_name (). unwrap (). to_string_lossy (), 73 std:: process:: id (), 74SEQUENCE . fetch_add ( 1 , Ordering :: SeqCst ) 75)); 76let mut file =match std:: fs:: OpenOptions :: new () 77. write ( true ) 78. create_new ( true ) 79. open ( & staging) 80{ 81Ok ( file) => file, 82Err ( error) if error. kind () == std:: io:: ErrorKind :: AlreadyExists =>continue , 83Err ( error) =>return Err ( error), 84}; 85if let Err ( error) = file. write_all ( & bytes) { 86drop ( file); 87let _ = std:: fs:: remove_file ( & staging); 88return Err ( error); 89} 90drop ( file); 91if entry. exists () { 92 std:: fs:: remove_file ( & entry) ?; 93} 94return std:: fs:: rename ( & staging, & entry); 95} 96Err ( std:: io:: Error :: new ( 97 std:: io:: ErrorKind :: AlreadyExists , 98"Task cache staging file names are exhausted" , 99)) 100} 101} 102 103/// The minimal matched paths (a matched directory covers its contents), 104/// and the patterns that matched nothing. 105fn expand_outputs ( 106base : & Path , 107patterns : & [ String ], 108) -> std:: io:: Result <( BTreeSet < String >, Vec < String >)> { 109let entries =collect_paths ( base, true ); 110let mut matched =BTreeSet :: new (); 111let mut unmatched =Vec :: new (); 112for patternin patterns{ 113let compiled =Pattern :: new ( & normalize_pattern ( pattern)) 114. map_err ( |error| std:: io:: Error :: new ( std:: io:: ErrorKind :: InvalidData , error)) ?; 115let mut any =false ; 116for relativein & entries{ 117if compiled. matches_with ( relative, MATCH_OPTIONS ) { 118 any =true ; 119 matched. insert ( relative. clone ()); 120} 121} 122if !any{ 123 unmatched. push ( pattern. clone ()); 124} 125} 126let minimal = matched 127. iter () 128. filter ( |relative|{ 129let parts: Vec < & str > = relative. split ( '/' ). collect (); 130 !( 1 ..parts. len ()). any ( |end| matched. contains ( & parts[ ..end]. join ( "/" ))) 131}) 132. cloned () 133. collect (); 134Ok (( minimal, unmatched)) 135} 136 137fn manifest_path ( 138path : & Path , 139relative : & str , 140manifest : & mut BTreeMap < String , String >, 141) -> std:: io:: Result <()> { 142let metadata = path. symlink_metadata () ?; 143if metadata. is_dir () && !metadata. file_type (). is_symlink () 144 || std:: fs:: metadata ( path). is_ok_and ( |target| target. is_dir ()) 145{ 146 manifest. insert ( relative. to_owned (), "dir" . into ()); 147let mut entries: Vec < _ > = std:: fs:: read_dir ( path) ?. collect ::< std:: io:: Result < _ >>() ?; 148 entries. sort_by_key ( |entry| entry. file_name ()); 149for entryin entries{ 150let name = entry. file_name (). to_string_lossy (). into_owned (); 151manifest_path ( & entry. path (), & format! ( "{relative}/{name}" ), manifest) ?; 152} 153return Ok (()); 154} 155 manifest. insert ( relative. to_owned (), file_id ( path) ?); 156Ok (()) 157}