| /-! | |
| # Find deprecations | |
| Reports deprecated modules, deprecated syntax usages, and deprecated options | |
| whose `since` date is on or before a cutoff (i.e. old enough to be removed). | |
| Usage: `lean --run script/find-deprecations.lean [--cutoff YYYY-MM-DD]` | |
| `--cutoff` selects the latest `since` date to include; entries with a `since` | |
| strictly greater than the cutoff are skipped. Defaults to 180 days ago. | |
| Must be run from the `src/` directory and requires Lean to have been built | |
| first, since the script loads the Lake workspace and imports built modules. | |
| -/ | |
| import Lake.CLI.Main | |
| import Lean | |
| open Lean | |
| def daysAgoISO (days : Nat) : IO String := do | |
| let gnu ← IO.Process.output { | |
| cmd := "date", args := #["-d", s!"{days} days ago", "+%Y-%m-%d"] | |
| } | |
| if gnu.exitCode == 0 then | |
| return gnu.stdout.trimAscii.toString | |
| let bsd ← IO.Process.output { | |
| cmd := "date", args := #["-v", s!"-{days}d", "+%Y-%m-%d"] | |
| } | |
| if bsd.exitCode == 0 then | |
| return bsd.stdout.trimAscii.toString | |
| throw <| .userError "could not compute default cutoff; pass --cutoff YYYY-MM-DD" | |
| /-- Get the root package of the Lake workspace we are running in. -/ | |
| def getWorkspaceRoot : IO Lake.Package := do | |
| let (elanInstall?, leanInstall?, lakeInstall?) ← Lake.findInstall? | |
| let config ← Lake.MonadError.runEIO <| Lake.mkLoadConfig { elanInstall?, leanInstall?, lakeInstall? } | |
| let some workspace ← Lake.loadWorkspace config |>.toBaseIO | |
| | throw <| IO.userError "failed to load Lake workspace" | |
| return workspace.root | |
| def getTargetModules : IO <| Array Name := do | |
| let workspaceRoot ← getWorkspaceRoot | |
| let res := workspaceRoot.defaultTargets.flatMap fun target => | |
| if let some lib := workspaceRoot.findLeanLib? target then | |
| lib.roots | |
| else if let some exe := workspaceRoot.findLeanExe? target then | |
| #[exe.config.root] | |
| else | |
| #[] | |
| return res | |
| def parseArgs (args : List String) : IO <| Option String := do | |
| match args with | |
| | [] => return none | |
| | "--cutoff" :: date :: [] => do | |
| pure date | |
| | _ => do throw <| .userError "unrecognized option" | |
| def main (args : List String) : IO UInt32 := do | |
| let cutoff ← parseArgs args | |
| let cutoff := cutoff.getD (← daysAgoISO 180) | |
| let defaultTargets ← getTargetModules | |
| initSearchPath <| ← findSysroot | |
| let decls : NameMap OptionDecl ← getOptionDecls | |
| let mut deprecatedOptions : Array (Name × String):= #[] | |
| for (_, decl) in decls do | |
| let some dep := decl.deprecation? | continue | |
| unless dep.since > cutoff do | |
| deprecatedOptions := deprecatedOptions.push (decl.declName, dep.since) | |
| for currentModule in defaultTargets do | |
| IO.println s!"Checking module: {currentModule}" | |
| let currentModuleImport : Import := {module := currentModule} | |
| unsafe enableInitializersExecution | |
| let env ← importModules #[currentModuleImport] {} (trustLevel := 1024) (loadExts := true) | |
| let modNames := env.allImportedModuleNames | |
| for i in [0 : modNames.size] do | |
| let modName := modNames[i]! | |
| let some entry := env.getDeprecatedModuleByIdx? (i : ModuleIdx) | continue | |
| let some since := entry.since? | continue | |
| unless since > cutoff do | |
| let path ← findLean (← getSrcSearchPath) modName | |
| IO.println s!"deprecated module: {modName}, {since}, {path}" | |
| let deprecatedSyntax := Lean.Elab.deprecatedSyntaxExt.getModuleEntries env (i : ModuleIdx) | |
| for entry in deprecatedSyntax do | |
| let some since := entry.since? | continue | |
| unless since > cutoff do | |
| let some modIdx := env.getModuleIdxFor? entry.kind | continue | |
| let some modName := env.allImportedModuleNames[modIdx.toNat]? | continue | |
| let path ← findLean (← getSrcSearchPath) modName | |
| let some ranges := declRangeExt.find? (level := .server) env entry.kind | |
| | IO.eprintln s!"warning: no decl range for {entry.kind}; skipping" | |
| continue | |
| IO.println s!"deprecated syntax: {entry.kind}, {since}, {path}, {ranges.range.pos}, {ranges.range.endPos}" | |
| let mut remainingDeprecatedOptions : Array (Name × String) := #[] | |
| for (name, date) in deprecatedOptions do | |
| let some modIdx := env.getModuleIdxFor? name | | |
| remainingDeprecatedOptions := remainingDeprecatedOptions.push (name, date) | |
| continue | |
| let some modName := env.allImportedModuleNames[modIdx.toNat]? | continue | |
| let path ← findLean (← getSrcSearchPath) modName | |
| let some ranges := declRangeExt.find? (level := .server) env name | |
| | IO.eprintln s!"warning: no decl range for {name}; skipping" | |
| continue | |
| IO.println s!"deprecated option: {name}, {date}, {path}, {ranges.range.pos}, {ranges.range.endPos}" | |
| deprecatedOptions := remainingDeprecatedOptions | |
| return 0 |