/- Copyright (c) 2024 Joseph Tooby-Smith. All rights reserved. Released under Apache 2.0 license. Authors: Joseph Tooby-Smith -/ import Batteries.Lean.HashSet import Lean /-! # Check file imports This file checks that the imports in `PhysLean.lean` are sorted and complete. It can be run from the terminal using `lake exe check_file_imports`. ## Note on adaptation The functions `addModulesIn`, `expectedPhysLeanImports`, and `checkMissingImports` are adapted from `batteries.scripts.check_imports.lean` authored by Joe Hendrix. -/ open Lean System Meta /-- Recursively finds files in directory. -/ partial def addModulesIn (recurse : Bool) (prev : Array Name) (root : Name := .anonymous) (path : FilePath) : IO (Array Name) := do let mut r := prev for entry in ← path.readDir do if ← entry.path.isDir then if recurse then r ← addModulesIn recurse r (root.mkStr entry.fileName) entry.path else let .some mod := FilePath.fileStem entry.fileName | continue r := r.push (root.mkStr mod) pure r /-- Compute imports expected by `PhysLean.lean` by looking at file structure. -/ def expectedPhysLeanImports : IO (Array Name) := do let mut needed := #[] for top in ← FilePath.readDir "PhysLean" do let nm := `PhysLean let rootname := FilePath.withExtension top.fileName "" let root := nm.mkStr rootname.toString if ← top.path.isDir then needed ← addModulesIn (recurse := true) needed (root := root) top.path else needed := needed.push root pure needed def listDif : (a: List String) → (b : List String) → (List String × List String) | [], [] => ([], []) | a :: as, [] => (a :: as, []) | [], b :: bs => ([], b :: bs) | a :: as, b :: bs => if a = b then listDif as bs else (a :: (listDif as bs).1, b :: (listDif as bs).2) /-- Checks whether an array `imports` is sorted after `Init` is removed. -/ def arrayImportSorted (imports : Array Import) : IO Bool := do let X := (imports.map (fun x => x.module.toString)).filter (fun x => x != "Init") let mut warned := false if ! X = X.qsort (· < ·) then IO.print s!"Import file is not sorted. \n" let ldif := listDif X.toList (X.qsort (· < ·)).toList let lzip := List.zip ldif.1 ldif.2 let lstring := String.intercalate "\n" (lzip.map (fun x => s!"{x.1} > {x.2}")) println! lstring warned := true pure warned /-- Checks every file in `reqImports` is imported into `modData` return true if this is NOT the case. -/ def checkMissingImports (modData : ModuleData) (reqImports : Array Name) : IO Bool := do let names : Std.HashSet Name := Std.HashSet.ofArray (modData.imports.map (·.module)) let mut warned := false let nameArray := reqImports.filterMap ( fun req => if !names.contains req then some req else none) if nameArray.size ≠ 0 then let nameArraySort := nameArray.qsort (·.toString < ·.toString) IO.print s!"Files are not imported add the following to the `PhysLean` file: \n" for name in nameArraySort do IO.print s!"import {name}\n" warned := true pure warned def main (_ : List String) : IO UInt32 := do initSearchPath (← findSysroot) let mods : Name := `PhysLean let imp : Import := {module := mods} let mFile ← findOLean imp.module unless (← mFile.pathExists) do throw <| IO.userError s!"object file '{mFile}' of module {imp.module} does not exist" let (hepLeanMod, _) ← readModuleData mFile let ePhysLeanImports ← expectedPhysLeanImports let sortedWarned ← arrayImportSorted hepLeanMod.imports let warned ← checkMissingImports hepLeanMod ePhysLeanImports if (warned ∨ sortedWarned) then throw <| IO.userError s!"The PhysLean module is not sorted, or has missing imports." IO.println s!"Import check complete." pure 0