Odoc docs
0

Configure Feed

Select the types of activity you want to include in your feed.

odd / lib / sync.ml
5.5 kB 155 lines
1module SS = Set.Make (String) 2 3type outcome = { built : string list; failed : string list } 4 5let summary o = 6 Printf.sprintf "%d built, %d failed" (List.length o.built) 7 (List.length o.failed) 8 9let timestamp () = 10 let tm = Unix.localtime (Unix.gettimeofday ()) in 11 Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d" (tm.Unix.tm_year + 1900) 12 (tm.Unix.tm_mon + 1) tm.Unix.tm_mday tm.Unix.tm_hour tm.Unix.tm_min 13 tm.Unix.tm_sec 14 15let log sw msg = 16 let write () = 17 ignore (Bos.OS.Dir.create ~path:true (Switch.state_dir sw)); 18 let fd = 19 Unix.openfile 20 (Fpath.to_string (Switch.log_file sw)) 21 [ O_WRONLY; O_APPEND; O_CREAT ] 22 0o644 23 in 24 Fun.protect 25 ~finally:(fun () -> Unix.close fd) 26 (fun () -> 27 let line = Printf.sprintf "[%s] %s\n" (timestamp ()) msg in 28 ignore (Unix.write_substring fd line 0 (String.length line))) 29 in 30 try write () with _ -> () 31 32let with_lock sw f = 33 Result.bind (Bos.OS.Dir.create ~path:true (Switch.state_dir sw)) 34 @@ fun (_ : bool) -> 35 match 36 Unix.openfile 37 (Fpath.to_string (Switch.lock_file sw)) 38 [ O_WRONLY; O_CREAT ] 0o644 39 with 40 | exception Unix.Unix_error (e, _, _) -> Error (`Msg (Unix.error_message e)) 41 | fd -> 42 Fun.protect 43 ~finally:(fun () -> Unix.close fd) 44 (fun () -> 45 Unix.lockf fd Unix.F_LOCK 0; 46 f ()) 47 48(* Package names are [A-Za-z0-9_+-]; refuse to delete anything else so a 49 corrupt pending file can never escape the doc tree. *) 50let valid_pkg_name n = 51 n <> "" 52 && String.for_all 53 (fun c -> 54 match c with 55 | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '-' | '_' | '+' -> true 56 | _ -> false) 57 n 58 59let erase_doc sw name = 60 if valid_pkg_name name then 61 ignore (Bos.OS.Dir.delete ~recurse:true Fpath.(Switch.odoc_dir sw / name)) 62 63(* The driver locates the switch by running `opam switch show`; make sure 64 that resolves to ours even when sync is invoked with an explicit 65 --prefix. *) 66let driver_env sw = 67 let prefix = Switch.prefix sw in 68 let switch_id = 69 if Fpath.basename prefix = "_opam" then 70 Fpath.to_string (Fpath.parent prefix) 71 else Fpath.basename prefix 72 in 73 let env = 74 Bos.OS.Env.current () |> Result.value ~default:Astring.String.Map.empty 75 in 76 env 77 |> Astring.String.Map.add "OPAM_SWITCH_PREFIX" (Fpath.to_string prefix) 78 |> Astring.String.Map.add "OPAMSWITCH" switch_id 79 80let run_driver sw ~driver ~env name = 81 let cmd = Bos.Cmd.(driver % name % "--actions" % "all") in 82 log sw (Printf.sprintf "running: %s" (Bos.Cmd.to_string cmd)); 83 let result = 84 Bos.OS.Dir.with_current (Switch.work_dir sw) 85 (fun () -> 86 Bos.OS.Cmd.(run_out ~env ~err:err_run_out cmd |> out_string ~trim:true)) 87 () 88 in 89 match result with 90 | Error (`Msg m) | Ok (Error (`Msg m)) -> 91 log sw (Printf.sprintf "%s: failed to run driver: %s" name m); 92 Error () 93 | Ok (Ok (output, (_, status))) -> ( 94 if output <> "" then log sw output; 95 match status with 96 | `Exited 0 -> 97 log sw (Printf.sprintf "%s: ok" name); 98 Ok () 99 | `Exited n -> 100 log sw (Printf.sprintf "%s: driver exited with %d" name n); 101 Error () 102 | `Signaled n -> 103 log sw (Printf.sprintf "%s: driver killed by signal %d" name n); 104 Error ()) 105 106let sync ?(driver = Bos.Cmd.v "odd-driver") sw = 107 with_lock sw @@ fun () -> 108 let installed = SS.of_list (List.map fst (Switch.installed sw)) in 109 let marked = Pending.read sw in 110 let stale = List.filter (fun n -> SS.mem n installed) marked in 111 (* A marker for a package that is no longer installed can only be left over 112 from an install+remove in the same session (the remove hook's [rm -rf] 113 normally clears it). Tidy it away. *) 114 List.iter (fun n -> if not (SS.mem n installed) then erase_doc sw n) marked; 115 (* Always regenerate the landing page (skipping the write when unchanged), so 116 a session that only removed packages still drops them from the list. *) 117 let finish outcome = 118 Result.bind (Index_page.write sw) @@ fun () -> 119 if outcome.built <> [] || outcome.failed <> [] then log sw (summary outcome); 120 Ok outcome 121 in 122 match stale with 123 | [] -> finish { built = []; failed = [] } 124 | _ -> 125 let driver_exists = 126 match Bos.OS.Cmd.resolve driver with Ok _ -> true | Error _ -> false 127 in 128 if not driver_exists then ( 129 log sw 130 (Printf.sprintf "driver %s not found; leaving %d package(s) pending" 131 (Bos.Cmd.to_string driver) (List.length stale)); 132 (* Markers left intact, so the next session retries. *) 133 finish { built = []; failed = stale }) 134 else 135 Result.bind 136 (Result.map 137 (fun (_ : bool) -> ()) 138 (Bos.OS.Dir.create ~path:true (Switch.work_dir sw))) 139 @@ fun () -> 140 let env = driver_env sw in 141 let ordered = Deps.order ~warn:(log sw) sw stale in 142 let built, failed = 143 List.fold_left 144 (fun (built, failed) name -> 145 (* Erase first (removing the marker) for a clean build; on failure 146 re-mark so the package is retried next session. *) 147 erase_doc sw name; 148 match run_driver sw ~driver ~env name with 149 | Ok () -> (name :: built, failed) 150 | Error () -> 151 ignore (Pending.mark sw name); 152 (built, name :: failed)) 153 ([], []) ordered 154 in 155 finish { built = List.rev built; failed = List.rev failed }