feat: add lake shake command (#11921)
This PR adds `lake shake` as a built-in Lake command, moving the shake
functionality from `script/Shake.lean` into the Lake CLI.
## Motivation
Per discussion with @Kha and @tydeu, having shake as a top-level Lake
command is preferable to `lake exe shake` because:
- Avoids the awkwardness of accessing core tools via `lake exe`
- Compiles shake into the Lake binary, avoiding lakefile issues
- No benefit to lazy compilation on user machines for this tool
## Changes
- Move shake logic from `script/Shake.lean` to
`src/lake/Lake/CLI/Shake.lean`
- Add `lake shake` command dispatch in `Lake/CLI/Main.lean`
- Add help text in `Lake/CLI/Help.lean`
- Remove the standalone shake executable from `script/lakefile.toml`
## Usage
```
lake shake [OPTIONS] [<MODULE>...]
```
See `lake shake --help` for full documentation.
🤖 Prepared with Claude Code
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mac Malone <mac@lean-fro.org>
Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
This commit is contained in:
parent
aac353c6b9
commit
99b26ce49e
16 changed files with 356 additions and 219 deletions
|
|
@ -3,9 +3,3 @@ name = "scripts"
|
||||||
[[lean_exe]]
|
[[lean_exe]]
|
||||||
name = "modulize"
|
name = "modulize"
|
||||||
root = "Modulize"
|
root = "Modulize"
|
||||||
|
|
||||||
[[lean_exe]]
|
|
||||||
name = "shake"
|
|
||||||
root = "Shake"
|
|
||||||
# needed by `Lake.loadWorkspace`
|
|
||||||
supportInterpreter = true
|
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,18 @@ Whether the build should show progress information.
|
||||||
public def BuildConfig.showProgress (cfg : BuildConfig) : Bool :=
|
public def BuildConfig.showProgress (cfg : BuildConfig) : Bool :=
|
||||||
(cfg.noBuild ∧ cfg.verbosity == .verbose) ∨ cfg.verbosity != .quiet
|
(cfg.noBuild ∧ cfg.verbosity == .verbose) ∨ cfg.verbosity != .quiet
|
||||||
|
|
||||||
|
/-- Mutable reference of registered build jobs. -/
|
||||||
|
@[expose] -- for codegen
|
||||||
|
public def JobQueue := IO.Ref (Array OpaqueJob)
|
||||||
|
|
||||||
|
/-- Returns a new empty job queue. -/
|
||||||
|
@[inline] public def mkJobQueue : BaseIO JobQueue :=
|
||||||
|
IO.mkRef #[]
|
||||||
|
|
||||||
/-- A Lake context with a build configuration and additional build data. -/
|
/-- A Lake context with a build configuration and additional build data. -/
|
||||||
public structure BuildContext extends BuildConfig, Context where
|
public structure BuildContext extends BuildConfig, Context where
|
||||||
leanTrace : BuildTrace
|
leanTrace : BuildTrace
|
||||||
registeredJobs : IO.Ref (Array OpaqueJob)
|
registeredJobs : JobQueue
|
||||||
|
|
||||||
/-- A transformer to equip a monad with a `BuildContext`. -/
|
/-- A transformer to equip a monad with a `BuildContext`. -/
|
||||||
public abbrev BuildT := ReaderT BuildContext
|
public abbrev BuildT := ReaderT BuildContext
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ open System
|
||||||
namespace Lake
|
namespace Lake
|
||||||
|
|
||||||
/-- Create a fresh build context from a workspace and a build configuration. -/
|
/-- Create a fresh build context from a workspace and a build configuration. -/
|
||||||
|
@[deprecated "Deprecated without replacement." (since := "2025-01-08")]
|
||||||
public def mkBuildContext (ws : Workspace) (config : BuildConfig) : BaseIO BuildContext := do
|
public def mkBuildContext (ws : Workspace) (config : BuildConfig) : BaseIO BuildContext := do
|
||||||
return {
|
return {
|
||||||
opaqueWs := ws,
|
opaqueWs := ws,
|
||||||
|
|
@ -37,7 +38,7 @@ private def Monitor.spinnerFrames :=
|
||||||
|
|
||||||
/-- Context of the Lake build monitor. -/
|
/-- Context of the Lake build monitor. -/
|
||||||
private structure MonitorContext where
|
private structure MonitorContext where
|
||||||
jobs : IO.Ref (Array OpaqueJob)
|
jobs : JobQueue
|
||||||
out : IO.FS.Stream
|
out : IO.FS.Stream
|
||||||
outLv : LogLevel
|
outLv : LogLevel
|
||||||
failLv : LogLevel
|
failLv : LogLevel
|
||||||
|
|
@ -49,6 +50,9 @@ private structure MonitorContext where
|
||||||
/-- How often to poll jobs (in milliseconds). -/
|
/-- How often to poll jobs (in milliseconds). -/
|
||||||
updateFrequency : Nat
|
updateFrequency : Nat
|
||||||
|
|
||||||
|
@[inline] def MonitorContext.logger (ctx : MonitorContext) : MonadLog IO :=
|
||||||
|
.stream ctx.out ctx.outLv ctx.useAnsi
|
||||||
|
|
||||||
/-- State of the Lake build monitor. -/
|
/-- State of the Lake build monitor. -/
|
||||||
private structure MonitorState where
|
private structure MonitorState where
|
||||||
jobNo : Nat := 0
|
jobNo : Nat := 0
|
||||||
|
|
@ -189,12 +193,51 @@ private def main (init : Array OpaqueJob) : MonitorM PUnit := do
|
||||||
|
|
||||||
end Monitor
|
end Monitor
|
||||||
|
|
||||||
|
/-- **For internal use only.** -/
|
||||||
public structure MonitorResult where
|
public structure MonitorResult where
|
||||||
didBuild : Bool
|
didBuild : Bool
|
||||||
failures : Array String
|
failures : Array String
|
||||||
numJobs : Nat
|
numJobs : Nat
|
||||||
|
|
||||||
|
@[inline] def MonitorResult.isOk (self : MonitorResult) : Bool :=
|
||||||
|
self.failures.isEmpty
|
||||||
|
|
||||||
|
def mkMonitorContext (cfg : BuildConfig) (jobs : JobQueue) : BaseIO MonitorContext := do
|
||||||
|
let out ← cfg.out.get
|
||||||
|
let useAnsi ← cfg.ansiMode.isEnabled out
|
||||||
|
let outLv := cfg.outLv
|
||||||
|
let failLv := cfg.failLv
|
||||||
|
let isVerbose := cfg.verbosity = .verbose
|
||||||
|
let showProgress := cfg.showProgress
|
||||||
|
let minAction := if isVerbose then .unknown else .fetch
|
||||||
|
let showOptional := isVerbose
|
||||||
|
let showTime := isVerbose || !useAnsi
|
||||||
|
let updateFrequency := 100
|
||||||
|
return {
|
||||||
|
jobs, out, failLv, outLv, minAction, showOptional
|
||||||
|
useAnsi, showProgress, showTime, updateFrequency
|
||||||
|
}
|
||||||
|
|
||||||
|
def monitorJobs'
|
||||||
|
(ctx : MonitorContext)
|
||||||
|
(initJobs : Array OpaqueJob)
|
||||||
|
(initFailures : Array String := #[])
|
||||||
|
(resetCtrl : String := "")
|
||||||
|
: BaseIO MonitorResult := do
|
||||||
|
let s := {
|
||||||
|
resetCtrl
|
||||||
|
lastUpdate := ← IO.monoMsNow
|
||||||
|
failures := initFailures
|
||||||
|
}
|
||||||
|
let (_,s) ← Monitor.main initJobs |>.run ctx s
|
||||||
|
return {
|
||||||
|
failures := s.failures
|
||||||
|
numJobs := s.totalJobs
|
||||||
|
didBuild := s.didBuild
|
||||||
|
}
|
||||||
|
|
||||||
/-- The job monitor function. An auxiliary definition for `runFetchM`. -/
|
/-- The job monitor function. An auxiliary definition for `runFetchM`. -/
|
||||||
|
@[inline, deprecated "Deprecated without replacement" (since := "2025-01-08")]
|
||||||
public def monitorJobs
|
public def monitorJobs
|
||||||
(initJobs : Array OpaqueJob)
|
(initJobs : Array OpaqueJob)
|
||||||
(jobs : IO.Ref (Array OpaqueJob))
|
(jobs : IO.Ref (Array OpaqueJob))
|
||||||
|
|
@ -210,99 +253,148 @@ public def monitorJobs
|
||||||
jobs, out, failLv, outLv, minAction, showOptional
|
jobs, out, failLv, outLv, minAction, showOptional
|
||||||
useAnsi, showProgress, showTime, updateFrequency
|
useAnsi, showProgress, showTime, updateFrequency
|
||||||
}
|
}
|
||||||
let s := {
|
monitorJobs' ctx initJobs initFailures resetCtrl
|
||||||
resetCtrl
|
|
||||||
lastUpdate := ← IO.monoMsNow
|
|
||||||
failures := initFailures
|
|
||||||
}
|
|
||||||
let (_,s) ← Monitor.main initJobs |>.run ctx s
|
|
||||||
return {
|
|
||||||
failures := s.failures
|
|
||||||
numJobs := s.totalJobs
|
|
||||||
didBuild := s.didBuild
|
|
||||||
}
|
|
||||||
|
|
||||||
/-- Exit code to return if `--no-build` is set and a build is required. -/
|
/-- Exit code to return if `--no-build` is set and a build is required. -/
|
||||||
public def noBuildCode : ExitCode := 3
|
public def noBuildCode : ExitCode := 3
|
||||||
|
|
||||||
|
def Workspace.saveOutputs
|
||||||
|
[logger : MonadLog IO] (ws : Workspace)
|
||||||
|
(out : IO.FS.Stream) (outputsFile : FilePath) (isVerbose : Bool)
|
||||||
|
: IO Unit := do
|
||||||
|
unless ws.isRootArtifactCacheEnabled do
|
||||||
|
logWarning s!"{ws.root.prettyName}: \
|
||||||
|
the artifact cache is not enabled for this package, so the artifacts described \
|
||||||
|
by the mappings produced by `-o` will not necessarily be available in the cache."
|
||||||
|
if let some ref := ws.root.outputsRef? then
|
||||||
|
match (← (← ref.get).writeFile outputsFile {}) with
|
||||||
|
| .ok _ log =>
|
||||||
|
if !log.isEmpty && isVerbose then
|
||||||
|
print! out "There were issues saving input-to-output mappings from the build:\n"
|
||||||
|
log.replay
|
||||||
|
| .error _ log =>
|
||||||
|
print! out "Failed to save input-to-output mappings from the build.\n"
|
||||||
|
if isVerbose then
|
||||||
|
log.replay
|
||||||
|
else
|
||||||
|
print! out "Workspace missing input-to-output mappings from build. (This is likely a bug in Lake.)\n"
|
||||||
|
|
||||||
|
def reportResult (cfg : BuildConfig) (out : IO.FS.Stream) (result : MonitorResult) : BaseIO Unit := do
|
||||||
|
if result.failures.isEmpty then
|
||||||
|
if cfg.showProgress && cfg.showSuccess then
|
||||||
|
let numJobs := result.numJobs
|
||||||
|
let jobs := if numJobs == 1 then "1 job" else s!"{numJobs} jobs"
|
||||||
|
if cfg.noBuild then
|
||||||
|
print! out s!"All targets up-to-date ({jobs}).\n"
|
||||||
|
else
|
||||||
|
print! out s!"Build completed successfully ({jobs}).\n"
|
||||||
|
else
|
||||||
|
print! out "Some required targets logged failures:\n"
|
||||||
|
result.failures.forM (print! out s!"- {·}\n")
|
||||||
|
flush out
|
||||||
|
|
||||||
|
structure BuildResult (α : Type u) extends MonitorResult where
|
||||||
|
out : Except String α
|
||||||
|
|
||||||
|
instance : CoeOut (BuildResult α) MonitorResult := ⟨BuildResult.toMonitorResult⟩
|
||||||
|
|
||||||
|
@[inline] def BuildResult.isOk (self : BuildResult α) : Bool :=
|
||||||
|
self.out.isOk
|
||||||
|
|
||||||
|
def monitorJob (ctx : MonitorContext) (job : Job α) : BaseIO (BuildResult α) := do
|
||||||
|
let result ← monitorJobs' ctx #[job]
|
||||||
|
if result.isOk then
|
||||||
|
if let some a ← job.wait? then
|
||||||
|
return {toMonitorResult := result, out := .ok a}
|
||||||
|
else
|
||||||
|
-- Computation job failed but was unreported in the monitor. This should be impossible.
|
||||||
|
return {toMonitorResult := result, out := .error <|
|
||||||
|
"uncaught top-level build failure (this is likely a bug in Lake)"}
|
||||||
|
else
|
||||||
|
return {toMonitorResult := result, out := .error "build failed"}
|
||||||
|
|
||||||
|
def monitorFetchM
|
||||||
|
(mctx : MonitorContext) (bctx : BuildContext) (build : FetchM α)
|
||||||
|
(caption := "job computation")
|
||||||
|
: BaseIO (BuildResult α) := do
|
||||||
|
let compute := Job.async build (caption := caption)
|
||||||
|
let job ← compute.run.run'.run bctx |>.run nilTrace
|
||||||
|
monitorJob mctx job
|
||||||
|
|
||||||
|
def Workspace.finalizeBuild
|
||||||
|
(ws : Workspace) (cfg : BuildConfig) (ctx : MonitorContext) (result : BuildResult α)
|
||||||
|
: IO α := do
|
||||||
|
reportResult cfg ctx.out result
|
||||||
|
if let some outputsFile := cfg.outputsFile? then
|
||||||
|
ws.saveOutputs (logger := ctx.logger) ctx.out outputsFile (cfg.verbosity matches .verbose)
|
||||||
|
if cfg.noBuild && result.didBuild then
|
||||||
|
IO.Process.exit noBuildCode.toUInt8
|
||||||
|
else
|
||||||
|
IO.ofExcept result.out
|
||||||
|
|
||||||
|
def mkBuildContext' (ws : Workspace) (cfg : BuildConfig) (jobs : JobQueue) : BuildContext where
|
||||||
|
opaqueWs := ws
|
||||||
|
toBuildConfig := cfg
|
||||||
|
registeredJobs := jobs
|
||||||
|
leanTrace := .ofHash (pureHash ws.lakeEnv.leanGithash)
|
||||||
|
s!"Lean {Lean.versionStringCore}, commit {ws.lakeEnv.leanGithash}"
|
||||||
|
|
||||||
/--
|
/--
|
||||||
Run a build function in the Workspace's context using the provided configuration.
|
Run a build function in the Workspace's context using the provided configuration.
|
||||||
Reports incremental build progress and build logs. In quiet mode, only reports
|
Reports incremental build progress and build logs. In quiet mode, only reports
|
||||||
failing build jobs (e.g., when using `-q` or non-verbose `--no-build`).
|
failing build jobs (e.g., when using `-q` or non-verbose `--no-build`).
|
||||||
-/
|
-/
|
||||||
public def Workspace.runFetchM
|
public def Workspace.runFetchM
|
||||||
(ws : Workspace) (build : FetchM α) (cfg : BuildConfig := {})
|
(ws : Workspace) (build : FetchM α) (cfg : BuildConfig := {}) (caption := "job computation")
|
||||||
: IO α := do
|
: IO α := do
|
||||||
-- Configure
|
let jobs ← mkJobQueue
|
||||||
let out ← cfg.out.get
|
let mctx ← mkMonitorContext cfg jobs
|
||||||
let useAnsi ← cfg.ansiMode.isEnabled out
|
let bctx := mkBuildContext' ws cfg jobs
|
||||||
let outLv := cfg.outLv
|
let result ← monitorFetchM mctx bctx build caption
|
||||||
let failLv := cfg.failLv
|
ws.finalizeBuild cfg mctx result
|
||||||
let isVerbose := cfg.verbosity = .verbose
|
|
||||||
let showProgress := cfg.showProgress
|
def monitorBuild
|
||||||
let showSuccess := cfg.showSuccess
|
(mctx : MonitorContext) (bctx : BuildContext) (build : FetchM (Job α))
|
||||||
let ctx ← mkBuildContext ws cfg
|
(caption := "job computation")
|
||||||
-- Job Computation
|
: BaseIO (BuildResult α) := do
|
||||||
let caption := "job computation"
|
let result ← monitorFetchM mctx bctx build caption
|
||||||
let compute := Job.async build (caption := caption)
|
match result.out with
|
||||||
let job ← compute.run.run'.run ctx |>.run nilTrace
|
| .ok job =>
|
||||||
-- Job Monitor
|
if let some a ← job.wait? then
|
||||||
let minAction := if isVerbose then .unknown else .fetch
|
return {result with out := .ok a}
|
||||||
let showOptional := isVerbose
|
|
||||||
let showTime := isVerbose || !useAnsi
|
|
||||||
let {failures, numJobs, didBuild} ← monitorJobs #[job] ctx.registeredJobs
|
|
||||||
out failLv outLv minAction showOptional useAnsi showProgress showTime
|
|
||||||
-- Save input-to-output mappings
|
|
||||||
if let some outputsFile := cfg.outputsFile? then
|
|
||||||
let logger := .stream out outLv useAnsi
|
|
||||||
unless ws.isRootArtifactCacheEnabled do
|
|
||||||
logger.logEntry <| .warning s!"{ws.root.prettyName}: \
|
|
||||||
the artifact cache is not enabled for this package, so the artifacts described \
|
|
||||||
by the mappings produced by `-o` will not necessarily be available in the cache."
|
|
||||||
if let some ref := ws.root.outputsRef? then
|
|
||||||
match (← (← ref.get).writeFile outputsFile {}) with
|
|
||||||
| .ok _ log =>
|
|
||||||
if !log.isEmpty && isVerbose then
|
|
||||||
print! out "There were issues saving input-to-output mappings from the build:\n"
|
|
||||||
log.replay (logger := logger)
|
|
||||||
| .error _ log =>
|
|
||||||
print! out "Failed to save input-to-output mappings from the build.\n"
|
|
||||||
if isVerbose then
|
|
||||||
log.replay (logger := logger)
|
|
||||||
else
|
else
|
||||||
print! out "Workspace missing input-to-output mappings from build. (This is likely a bug in Lake.)\n"
|
-- Job failed but was unreported in the monitor. It was likely not properly registered.
|
||||||
-- Report
|
return {result with out := .error <|
|
||||||
let isNoBuild := cfg.noBuild
|
"uncaught top-level build failure (this is likely a bug in the build script)"}
|
||||||
if failures.isEmpty then
|
| .error e =>
|
||||||
let some a ← job.wait?
|
return {result with out := .error e}
|
||||||
| print! out "Uncaught top-level build failure (this is likely a bug in Lake).\n"
|
|
||||||
error "build failed"
|
/--
|
||||||
if showProgress && showSuccess then
|
Returns whether a build is needed to validate `build`. Does not report on the attempted build.
|
||||||
let jobs := if numJobs == 1 then "1 job" else s!"{numJobs} jobs"
|
|
||||||
if isNoBuild then
|
This is equivalent to checking whether `lake build --no-build` exits with code 0.
|
||||||
print! out s!"All targets up-to-date ({jobs}).\n"
|
-/
|
||||||
else
|
@[inline] public def Workspace.checkNoBuild
|
||||||
print! out s!"Build completed successfully ({jobs}).\n"
|
(ws : Workspace) (build : FetchM (Job α))
|
||||||
return a
|
: BaseIO Bool := do
|
||||||
else
|
let jobs ← mkJobQueue
|
||||||
print! out "Some required targets logged failures:\n"
|
let cfg := {noBuild := true}
|
||||||
failures.forM (print! out s!"- {·}\n")
|
let mctx ← mkMonitorContext cfg jobs
|
||||||
flush out
|
let bctx := mkBuildContext' ws cfg jobs
|
||||||
if isNoBuild && didBuild then
|
let result ← monitorBuild mctx bctx build
|
||||||
IO.Process.exit noBuildCode.toUInt8
|
return result.isOk && !result.didBuild
|
||||||
else
|
|
||||||
error "build failed"
|
|
||||||
|
|
||||||
/-- Run a build function in the Workspace's context and await the result. -/
|
/-- Run a build function in the Workspace's context and await the result. -/
|
||||||
@[inline] public def Workspace.runBuild
|
@[inline] public def Workspace.runBuild
|
||||||
(ws : Workspace) (build : FetchM (Job α)) (cfg : BuildConfig := {})
|
(ws : Workspace) (build : FetchM (Job α)) (cfg : BuildConfig := {})
|
||||||
: IO α := do
|
: IO α := do
|
||||||
let job ← ws.runFetchM build cfg
|
let jobs ← mkJobQueue
|
||||||
let some a ← job.wait? | error "build failed"
|
let mctx ← mkMonitorContext cfg jobs
|
||||||
return a
|
let bctx := mkBuildContext' ws cfg jobs
|
||||||
|
let result ← monitorBuild mctx bctx build
|
||||||
|
ws.finalizeBuild cfg mctx result
|
||||||
|
|
||||||
/-- Produce a build job in the Lake monad's workspace and await the result. -/
|
/-- Produce a build job in the Lake monad's workspace and await the result. -/
|
||||||
@[inline] public def runBuild
|
@[inline] public def runBuild
|
||||||
(build : FetchM (Job α)) (cfg : BuildConfig := {})
|
(build : FetchM (Job α)) (cfg : BuildConfig := {})
|
||||||
: LakeT IO α := do
|
: LakeT IO α := do (← getWorkspace).runBuild build cfg
|
||||||
(← getWorkspace).runBuild build cfg
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ public import Lake.CLI.Help
|
||||||
public import Lake.CLI.Init
|
public import Lake.CLI.Init
|
||||||
public import Lake.CLI.Main
|
public import Lake.CLI.Main
|
||||||
public import Lake.CLI.Serve
|
public import Lake.CLI.Serve
|
||||||
|
public import Lake.CLI.Shake
|
||||||
public import Lake.CLI.Translate
|
public import Lake.CLI.Translate
|
||||||
public import Lake.CLI.Translate.Lean
|
public import Lake.CLI.Translate.Lean
|
||||||
public import Lake.CLI.Translate.Toml
|
public import Lake.CLI.Translate.Toml
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ COMMANDS:
|
||||||
lint lint the package using the configured lint driver
|
lint lint the package using the configured lint driver
|
||||||
check-lint check if there is a properly configured lint driver
|
check-lint check if there is a properly configured lint driver
|
||||||
clean remove build outputs
|
clean remove build outputs
|
||||||
|
shake minimize imports in source files
|
||||||
env <cmd> <args>... execute a command in Lake's environment
|
env <cmd> <args>... execute a command in Lake's environment
|
||||||
lean <file> elaborate a Lean file in Lake's context
|
lean <file> elaborate a Lean file in Lake's context
|
||||||
update update dependencies and save them to the manifest
|
update update dependencies and save them to the manifest
|
||||||
|
|
@ -310,6 +311,44 @@ USAGE:
|
||||||
If no package is specified, deletes the build directories of every package in
|
If no package is specified, deletes the build directories of every package in
|
||||||
the workspace. Otherwise, just deletes those of the specified packages."
|
the workspace. Otherwise, just deletes those of the specified packages."
|
||||||
|
|
||||||
|
def helpShake :=
|
||||||
|
"Minimize imports in Lean source files
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
lake shake [OPTIONS] [<MODULE>...]
|
||||||
|
|
||||||
|
Checks the current project for unused imports by analyzing generated `.olean`
|
||||||
|
files to deduce required imports and ensuring that every import contributes
|
||||||
|
some constant or other elaboration dependency.
|
||||||
|
|
||||||
|
ARGUMENTS:
|
||||||
|
<MODULE> A module path like `Mathlib`. All files transitively
|
||||||
|
reachable from the provided module(s) will be checked.
|
||||||
|
If not specified, uses the package's default targets.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--force Skip the `lake build --no-build` sanity check
|
||||||
|
--keep-implied Preserve imports implied by other imports
|
||||||
|
--keep-prefix Prefer parent module imports over specific submodules
|
||||||
|
--keep-public Preserve all `public` imports for API stability
|
||||||
|
--add-public Add new imports as `public` if they were in the
|
||||||
|
original public closure
|
||||||
|
--explain Show which constants require each import
|
||||||
|
--fix Apply suggested fixes directly to source files
|
||||||
|
--gh-style Output in GitHub problem matcher format
|
||||||
|
|
||||||
|
ANNOTATIONS:
|
||||||
|
Source files can contain special comments to control shake behavior:
|
||||||
|
|
||||||
|
* `module -- shake: keep-downstream`
|
||||||
|
Preserves this module in all downstream modules
|
||||||
|
|
||||||
|
* `module -- shake: keep-all`
|
||||||
|
Preserves all existing imports in this module
|
||||||
|
|
||||||
|
* `import X -- shake: keep`
|
||||||
|
Preserves this specific import"
|
||||||
|
|
||||||
def helpCacheCli :=
|
def helpCacheCli :=
|
||||||
"Manage the Lake cache
|
"Manage the Lake cache
|
||||||
|
|
||||||
|
|
@ -557,6 +596,7 @@ public def help : (cmd : String) → String
|
||||||
| "lint" => helpLint
|
| "lint" => helpLint
|
||||||
| "check-lint" => helpCheckLint
|
| "check-lint" => helpCheckLint
|
||||||
| "clean" => helpClean
|
| "clean" => helpClean
|
||||||
|
| "shake" => helpShake
|
||||||
| "script" => helpScriptCli
|
| "script" => helpScriptCli
|
||||||
| "scripts" => helpScriptList
|
| "scripts" => helpScriptList
|
||||||
| "run" => helpScriptRun
|
| "run" => helpScriptRun
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ public import Init.System.IO
|
||||||
public import Lake.Util.Exit
|
public import Lake.Util.Exit
|
||||||
public import Lake.Load.Config
|
public import Lake.Load.Config
|
||||||
public import Lake.CLI.Error
|
public import Lake.CLI.Error
|
||||||
|
public import Lake.CLI.Shake
|
||||||
import Lake.Version
|
import Lake.Version
|
||||||
import Lake.Build.Run
|
import Lake.Build.Run
|
||||||
import Lake.Build.Targets
|
import Lake.Build.Targets
|
||||||
|
|
@ -74,6 +75,7 @@ public structure LakeOptions where
|
||||||
toolchain? : Option String := none
|
toolchain? : Option String := none
|
||||||
rev? : Option String := none
|
rev? : Option String := none
|
||||||
maxRevs : Nat := 100
|
maxRevs : Nat := 100
|
||||||
|
shake : Shake.Args := {}
|
||||||
|
|
||||||
def LakeOptions.outLv (opts : LakeOptions) : LogLevel :=
|
def LakeOptions.outLv (opts : LakeOptions) : LogLevel :=
|
||||||
opts.outLv?.getD opts.verbosity.minLogLv
|
opts.outLv?.getD opts.verbosity.minLogLv
|
||||||
|
|
@ -299,6 +301,16 @@ def lakeLongOption : (opt : String) → CliM PUnit
|
||||||
| "--" => do
|
| "--" => do
|
||||||
let subArgs ← takeArgs
|
let subArgs ← takeArgs
|
||||||
modifyThe LakeOptions ({· with subArgs})
|
modifyThe LakeOptions ({· with subArgs})
|
||||||
|
-- Shake options
|
||||||
|
| "--keep-implied" => modifyThe LakeOptions ({· with shake.keepImplied := true})
|
||||||
|
| "--keep-prefix" => modifyThe LakeOptions ({· with shake.keepPrefix := true})
|
||||||
|
| "--keep-public" => modifyThe LakeOptions ({· with shake.keepPublic := true})
|
||||||
|
| "--add-public" => modifyThe LakeOptions ({· with shake.addPublic := true})
|
||||||
|
| "--force" => modifyThe LakeOptions ({· with shake.force := true})
|
||||||
|
| "--gh-style" => modifyThe LakeOptions ({· with shake.githubStyle := true})
|
||||||
|
| "--explain" => modifyThe LakeOptions ({· with shake.explain := true})
|
||||||
|
| "--trace" => modifyThe LakeOptions ({· with shake.trace := true})
|
||||||
|
| "--fix" => modifyThe LakeOptions ({· with shake.fix := true})
|
||||||
| opt => throw <| CliError.unknownLongOption opt
|
| opt => throw <| CliError.unknownLongOption opt
|
||||||
|
|
||||||
def lakeOption :=
|
def lakeOption :=
|
||||||
|
|
@ -358,7 +370,6 @@ def parseTemplateLangSpec (spec : String) : Except CliError (InitTemplate × Con
|
||||||
| [tmp] => return (← parseTemplateSpec tmp, default)
|
| [tmp] => return (← parseTemplateSpec tmp, default)
|
||||||
| _ => return default
|
| _ => return default
|
||||||
|
|
||||||
|
|
||||||
/-! ## Commands -/
|
/-! ## Commands -/
|
||||||
|
|
||||||
namespace lake
|
namespace lake
|
||||||
|
|
@ -756,6 +767,31 @@ protected def clean : CliM PUnit := do
|
||||||
| some pkg => pure pkg
|
| some pkg => pure pkg
|
||||||
pkgs.forM (·.clean)
|
pkgs.forM (·.clean)
|
||||||
|
|
||||||
|
/-- The `lake shake` command: minimize imports in Lean source files. -/
|
||||||
|
protected def shake : CliM PUnit := do
|
||||||
|
processOptions lakeOption
|
||||||
|
let opts ← getThe LakeOptions
|
||||||
|
let config ← mkLoadConfig opts
|
||||||
|
let ws ← loadWorkspace config
|
||||||
|
-- Get remaining arguments as module names
|
||||||
|
let mods := (← takeArgs).toArray.map (·.toName)
|
||||||
|
-- Get default target modules from workspace if no modules specified
|
||||||
|
let mods := if mods.isEmpty then ws.defaultTargetRoots else mods
|
||||||
|
if h : 0 < mods.size then
|
||||||
|
let args := {opts.shake with mods}
|
||||||
|
unless args.force do
|
||||||
|
let specs ← parseTargetSpecs ws []
|
||||||
|
let upToDate ← ws.checkNoBuild (buildSpecs specs)
|
||||||
|
unless upToDate do
|
||||||
|
error "there are out of date oleans; run `lake build` or fetch them from a cache first"
|
||||||
|
-- Run shake with workspace search paths
|
||||||
|
Lean.searchPathRef.set ws.augmentedLeanPath
|
||||||
|
let exitCode ← Shake.run args h ws.augmentedLeanSrcPath
|
||||||
|
if exitCode != 0 then
|
||||||
|
exit exitCode
|
||||||
|
else
|
||||||
|
error "no modules specified and there are no applicable default targets"
|
||||||
|
|
||||||
protected def script : CliM PUnit := do
|
protected def script : CliM PUnit := do
|
||||||
if let some cmd ← takeArg? then
|
if let some cmd ← takeArg? then
|
||||||
processLeadingOptions lakeOption -- between `lake script <cmd>` and args
|
processLeadingOptions lakeOption -- between `lake script <cmd>` and args
|
||||||
|
|
@ -910,6 +946,7 @@ def lakeCli : (cmd : String) → CliM PUnit
|
||||||
| "lint" => lake.lint
|
| "lint" => lake.lint
|
||||||
| "check-lint" => lake.checkLint
|
| "check-lint" => lake.checkLint
|
||||||
| "clean" => lake.clean
|
| "clean" => lake.clean
|
||||||
|
| "shake" => lake.shake
|
||||||
| "script" => lake.script
|
| "script" => lake.script
|
||||||
| "scripts" => lake.script.list
|
| "scripts" => lake.script.list
|
||||||
| "run" => lake.script.run
|
| "run" => lake.script.run
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ Authors: Mario Carneiro, Sebastian Ullrich
|
||||||
-/
|
-/
|
||||||
module
|
module
|
||||||
|
|
||||||
|
prelude
|
||||||
|
public import Init.Prelude
|
||||||
|
public import Init.System.IO
|
||||||
|
public import Lean.Util.Path
|
||||||
import Lean.Environment
|
import Lean.Environment
|
||||||
import Lean.ExtraModUses
|
import Lean.ExtraModUses
|
||||||
|
|
||||||
import Lake.CLI.Main
|
|
||||||
import Lean.Parser.Module
|
import Lean.Parser.Module
|
||||||
import Lake.Load.Workspace
|
|
||||||
|
|
||||||
/-! # Shake: A Lean import minimizer
|
/-! # Shake: A Lean import minimizer
|
||||||
|
|
||||||
|
|
@ -20,84 +21,12 @@ ensuring that every import is used to contribute some constant or other elaborat
|
||||||
recorded by `recordExtraModUse` and friends.
|
recorded by `recordExtraModUse` and friends.
|
||||||
-/
|
-/
|
||||||
|
|
||||||
/-- help string for the command line interface -/
|
|
||||||
def help : String := "Lean project tree shaking tool
|
|
||||||
Usage: lake exe shake [OPTIONS] <MODULE>..
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
<MODULE>
|
|
||||||
A module path like `Mathlib`. All files transitively reachable from the
|
|
||||||
provided module(s) will be checked.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--force
|
|
||||||
Skips the `lake build --no-build` sanity check
|
|
||||||
|
|
||||||
--keep-implied
|
|
||||||
Preserves existing imports that are implied by other imports and thus not technically needed
|
|
||||||
anymore
|
|
||||||
|
|
||||||
--keep-prefix
|
|
||||||
If an import `X` would be replaced in favor of a more specific import `X.Y...` it implies,
|
|
||||||
preserves the original import instead. More generally, prefers inserting `import X` even if it
|
|
||||||
was not part of the original imports as long as it was in the original transitive import closure
|
|
||||||
of the current module.
|
|
||||||
|
|
||||||
--keep-public
|
|
||||||
Preserves all `public` imports to avoid breaking changes for external downstream modules
|
|
||||||
|
|
||||||
--add-public
|
|
||||||
Adds new imports as `public` if they have been in the original public closure of that module.
|
|
||||||
In other words, public imports will not be removed from a module unless they are unused even
|
|
||||||
in the private scope, and those that are removed will be re-added as `public` in downstream
|
|
||||||
modules even if only needed in the private scope there. Unlike `--keep-public`, this may
|
|
||||||
introduce breaking changes but will still limit the number of inserted imports.
|
|
||||||
|
|
||||||
--explain
|
|
||||||
Gives constants explaining why each module is needed
|
|
||||||
|
|
||||||
--fix
|
|
||||||
Apply the suggested fixes directly. Make sure you have a clean checkout
|
|
||||||
before running this, so you can review the changes.
|
|
||||||
|
|
||||||
--gh-style
|
|
||||||
Outputs messages that can be parsed by `gh-problem-matcher-wrap`
|
|
||||||
|
|
||||||
Annotations:
|
|
||||||
The following annotations can be added to Lean files in order to configure the behavior of
|
|
||||||
`shake`. Only the substring `shake: ` directly followed by a directive is checked for, so multiple
|
|
||||||
directives can be mixed in one line such as `-- shake: keep-downstream, shake: keep-all`, and they
|
|
||||||
can be surrounded by arbitrary comments such as `-- shake: keep (metaprogram output dependency)`.
|
|
||||||
|
|
||||||
* `module -- shake: keep-downstream`:
|
|
||||||
Preserves this module in all (current) downstream modules, adding new imports of it if needed.
|
|
||||||
|
|
||||||
* `module -- shake: keep-all`:
|
|
||||||
Preserves all existing imports in this module as is. New imports now needed because of upstream
|
|
||||||
changes may still be added.
|
|
||||||
|
|
||||||
* `import X -- shake: keep`:
|
|
||||||
Preserves this specific import in the current module. The most common use case is to preserve a
|
|
||||||
public import that will be needed in downstream modules to make sense of the output of a
|
|
||||||
metaprogram defined in this module. For example, if a tactic is defined that may synthesize a
|
|
||||||
reference to a theorem when run, there is no way for `shake` to detect this by itself and the
|
|
||||||
module of that theorem should be publicly imported and annotated with `keep` in the tactic's
|
|
||||||
module.
|
|
||||||
```
|
|
||||||
public import X -- shake: keep (metaprogram output dependency)
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
elab \"my_tactic\" : tactic => do
|
|
||||||
... mkConst ``f -- `f`, defined in `X`, may appear in the output of this tactic
|
|
||||||
```
|
|
||||||
"
|
|
||||||
|
|
||||||
open Lean
|
open Lean
|
||||||
|
|
||||||
/-- The parsed CLI arguments. See `help` for more information -/
|
namespace Lake.Shake
|
||||||
structure Args where
|
|
||||||
help : Bool := false
|
/-- The parsed CLI arguments for shake. -/
|
||||||
|
public structure Args where
|
||||||
keepImplied : Bool := false
|
keepImplied : Bool := false
|
||||||
keepPrefix : Bool := false
|
keepPrefix : Bool := false
|
||||||
keepPublic : Bool := false
|
keepPublic : Bool := false
|
||||||
|
|
@ -640,7 +569,7 @@ def visitModule (pkg : Name) (srcSearchPath : SearchPath)
|
||||||
if toRemove.any fun imp => imp == decodeImport stx then
|
if toRemove.any fun imp => imp == decodeImport stx then
|
||||||
let pos := inputCtx.fileMap.toPosition stx.raw.getPos?.get!
|
let pos := inputCtx.fileMap.toPosition stx.raw.getPos?.get!
|
||||||
println! "{path}:{pos.line}:{pos.column+1}: warning: unused import \
|
println! "{path}:{pos.line}:{pos.column+1}: warning: unused import \
|
||||||
(use `lake exe shake --fix` to fix this, or `lake exe shake --update` to ignore)"
|
(use `lake shake --fix` to fix this, or `lake shake --update` to ignore)"
|
||||||
if !toAdd.isEmpty then
|
if !toAdd.isEmpty then
|
||||||
-- we put the insert message on the beginning of the last import line
|
-- we put the insert message on the beginning of the last import line
|
||||||
let pos := inputCtx.fileMap.toPosition endHeader.offset
|
let pos := inputCtx.fileMap.toPosition endHeader.offset
|
||||||
|
|
@ -685,76 +614,31 @@ def visitModule (pkg : Name) (srcSearchPath : SearchPath)
|
||||||
run j
|
run j
|
||||||
for i in toAdd do run i
|
for i in toAdd do run i
|
||||||
|
|
||||||
/-- Convert a list of module names to a bitset of module indexes -/
|
|
||||||
def toBitset (s : State) (ns : List Name) : Bitset :=
|
|
||||||
ns.foldl (init := ∅) fun c name =>
|
|
||||||
match s.env.getModuleIdxFor? name with
|
|
||||||
| some i => c ∪ {i}
|
|
||||||
| none => c
|
|
||||||
|
|
||||||
local instance : Ord Import where
|
local instance : Ord Import where
|
||||||
compare :=
|
compare :=
|
||||||
let _ := @lexOrd
|
let _ := @lexOrd
|
||||||
compareOn fun imp => (!imp.isExported, imp.module.toString)
|
compareOn fun imp => (!imp.isExported, imp.module.toString)
|
||||||
|
|
||||||
/-- The main entry point. See `help` for more information on arguments. -/
|
/--
|
||||||
public def main (args : List String) : IO UInt32 := do
|
Run the shake analysis with the given arguments.
|
||||||
initSearchPath (← findSysroot)
|
|
||||||
-- Parse the arguments
|
|
||||||
let rec parseArgs (args : Args) : List String → Args
|
|
||||||
| [] => args
|
|
||||||
| "--help" :: rest => parseArgs { args with help := true } rest
|
|
||||||
| "--keep-implied" :: rest => parseArgs { args with keepImplied := true } rest
|
|
||||||
| "--keep-prefix" :: rest => parseArgs { args with keepPrefix := true } rest
|
|
||||||
| "--keep-public" :: rest => parseArgs { args with keepPublic := true } rest
|
|
||||||
| "--add-public" :: rest => parseArgs { args with addPublic := true } rest
|
|
||||||
| "--force" :: rest => parseArgs { args with force := true } rest
|
|
||||||
| "--fix" :: rest => parseArgs { args with fix := true } rest
|
|
||||||
| "--explain" :: rest => parseArgs { args with explain := true } rest
|
|
||||||
| "--trace" :: rest => parseArgs { args with trace := true } rest
|
|
||||||
| "--gh-style" :: rest => parseArgs { args with githubStyle := true } rest
|
|
||||||
| "--" :: rest => { args with mods := args.mods ++ rest.map (·.toName) }
|
|
||||||
| other :: rest => parseArgs { args with mods := args.mods.push other.toName } rest
|
|
||||||
let args := parseArgs {} args
|
|
||||||
|
|
||||||
-- Bail if `--help` is passed
|
Assumes Lean's search path has already been properly configured.
|
||||||
if args.help then
|
-/
|
||||||
IO.println help
|
public def run (args : Args) (h : 0 < args.mods.size)
|
||||||
IO.Process.exit 0
|
(srcSearchPath : SearchPath := {}) : IO UInt32 := do
|
||||||
|
|
||||||
if !args.force then
|
|
||||||
if (← IO.Process.output { cmd := "lake", args := #["build", "--no-build"] }).exitCode != 0 then
|
|
||||||
IO.println "There are out of date oleans. Run `lake build` or `lake exe cache get` first"
|
|
||||||
IO.Process.exit 1
|
|
||||||
|
|
||||||
-- Determine default module(s) to run shake on
|
|
||||||
let defaultTargetModules : Array Name ← try
|
|
||||||
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"
|
|
||||||
let defaultTargetModules := workspace.root.defaultTargets.flatMap fun target =>
|
|
||||||
if let some lib := workspace.root.findLeanLib? target then
|
|
||||||
lib.roots
|
|
||||||
else if let some exe := workspace.root.findLeanExe? target then
|
|
||||||
#[exe.config.root]
|
|
||||||
else
|
|
||||||
#[]
|
|
||||||
pure defaultTargetModules
|
|
||||||
catch _ =>
|
|
||||||
pure #[]
|
|
||||||
|
|
||||||
let srcSearchPath ← getSrcSearchPath
|
|
||||||
-- the list of root modules
|
-- the list of root modules
|
||||||
let mods := if args.mods.isEmpty then defaultTargetModules else args.mods
|
let mods := args.mods
|
||||||
-- Only submodules of `pkg` will be edited or have info reported on them
|
-- Only submodules of `pkg` will be edited or have info reported on them
|
||||||
let pkg := mods[0]!.components.head!
|
let pkg := mods[0].getRoot
|
||||||
|
|
||||||
-- Load all the modules
|
-- Load all the modules
|
||||||
let imps := mods.map ({ module := · })
|
let imps := mods.map ({ module := · })
|
||||||
let (_, s) ← importModulesCore imps (isExported := true) |>.run
|
let (_, s) ← importModulesCore imps (isExported := true) |>.run
|
||||||
let s := s.markAllExported
|
let s := s.markAllExported
|
||||||
let mut env ← finalizeImport s (isModule := true) imps {} (leakEnv := false) (loadExts := false)
|
let mut env ← finalizeImport s (isModule := true) imps {} (leakEnv := false) (loadExts := false)
|
||||||
|
if env.header.moduleData.any (!·.isModule) then
|
||||||
|
throw <| .userError "`lake shake` only works with `module`s currently"
|
||||||
-- the one env ext we want to initialize
|
-- the one env ext we want to initialize
|
||||||
let is := indirectModUseExt.toEnvExtension.getState env
|
let is := indirectModUseExt.toEnvExtension.getState env
|
||||||
let newState ← indirectModUseExt.addImportedFn is.importedEntries { env := env, opts := {} }
|
let newState ← indirectModUseExt.addImportedFn is.importedEntries { env := env, opts := {} }
|
||||||
|
|
@ -52,6 +52,16 @@ public instance : Nonempty Workspace :=
|
||||||
|
|
||||||
public hydrate_opaque_type OpaqueWorkspace Workspace
|
public hydrate_opaque_type OpaqueWorkspace Workspace
|
||||||
|
|
||||||
|
/-- Returns the names of the root modules of the package's default targets. -/
|
||||||
|
public def Package.defaultTargetRoots (self : Package) : Array Lean.Name :=
|
||||||
|
self.defaultTargets.flatMap fun target =>
|
||||||
|
if let some lib := self.findLeanLib? target then
|
||||||
|
lib.roots
|
||||||
|
else if let some exe := self.findLeanExe? target then
|
||||||
|
#[exe.root.name]
|
||||||
|
else
|
||||||
|
#[]
|
||||||
|
|
||||||
namespace Workspace
|
namespace Workspace
|
||||||
|
|
||||||
/-- **For internal use.** Whether this workspace is Lean itself. -/
|
/-- **For internal use.** Whether this workspace is Lean itself. -/
|
||||||
|
|
@ -102,6 +112,10 @@ public def isRootArtifactCacheEnabled (ws : Workspace) : Bool :=
|
||||||
@[inline] public def serverOptions (self : Workspace) : LeanOptions :=
|
@[inline] public def serverOptions (self : Workspace) : LeanOptions :=
|
||||||
self.root.moreServerOptions
|
self.root.moreServerOptions
|
||||||
|
|
||||||
|
/-- Returns the names of the root modules of the workpace root's default targets. -/
|
||||||
|
@[inline] public def defaultTargetRoots (self : Workspace) : Array Lean.Name :=
|
||||||
|
self.root.defaultTargetRoots
|
||||||
|
|
||||||
/-- The workspace's Lake manifest. -/
|
/-- The workspace's Lake manifest. -/
|
||||||
@[inline] public def manifestFile (self : Workspace) : FilePath :=
|
@[inline] public def manifestFile (self : Workspace) : FilePath :=
|
||||||
self.root.manifestFile
|
self.root.manifestFile
|
||||||
|
|
|
||||||
8
tests/lake/tests/shake/.gitignore
vendored
Normal file
8
tests/lake/tests/shake/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
.lake/
|
||||||
|
lake-manifest.json
|
||||||
|
lake-packages/
|
||||||
|
produced.out
|
||||||
|
# Working files copied from input/
|
||||||
|
lakefile.toml
|
||||||
|
Main.lean
|
||||||
|
Lib/
|
||||||
4
tests/lake/tests/shake/clean.sh
Executable file
4
tests/lake/tests/shake/clean.sh
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
rm -rf .lake lake-manifest.json lake-packages
|
||||||
|
rm -f lakefile.toml Main.lean
|
||||||
|
rm -rf Lib
|
||||||
6
tests/lake/tests/shake/expected/Main.lean
Normal file
6
tests/lake/tests/shake/expected/Main.lean
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
module
|
||||||
|
|
||||||
|
import Lib.A
|
||||||
|
|
||||||
|
-- Does not use Lib.B, uses Lib.A privately only
|
||||||
|
def myValue : Nat := valueA
|
||||||
3
tests/lake/tests/shake/input/Lib/A.lean
Normal file
3
tests/lake/tests/shake/input/Lib/A.lean
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module
|
||||||
|
|
||||||
|
public def valueA : Nat := 42
|
||||||
3
tests/lake/tests/shake/input/Lib/B.lean
Normal file
3
tests/lake/tests/shake/input/Lib/B.lean
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module
|
||||||
|
|
||||||
|
public def valueB : Nat := 99
|
||||||
7
tests/lake/tests/shake/input/Main.lean
Normal file
7
tests/lake/tests/shake/input/Main.lean
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
module
|
||||||
|
|
||||||
|
public import Lib.A
|
||||||
|
public import Lib.B
|
||||||
|
|
||||||
|
-- Does not use Lib.B, uses Lib.A privately only
|
||||||
|
def myValue : Nat := valueA
|
||||||
10
tests/lake/tests/shake/input/lakefile.toml
Normal file
10
tests/lake/tests/shake/input/lakefile.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
name = "test"
|
||||||
|
version = "0.1.0"
|
||||||
|
defaultTargets = ["Main"]
|
||||||
|
|
||||||
|
[[lean_lib]]
|
||||||
|
name = "Lib"
|
||||||
|
globs = ["Lib.**"]
|
||||||
|
|
||||||
|
[[lean_lib]]
|
||||||
|
name = "Main"
|
||||||
26
tests/lake/tests/shake/test.sh
Executable file
26
tests/lake/tests/shake/test.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
source ../common.sh
|
||||||
|
|
||||||
|
./clean.sh
|
||||||
|
|
||||||
|
# Test the `lake shake` command
|
||||||
|
|
||||||
|
# Copy input project to working directory
|
||||||
|
cp -r input/* .
|
||||||
|
|
||||||
|
# Build the project first (shake needs .olean files)
|
||||||
|
test_run build
|
||||||
|
|
||||||
|
# Run shake to check for unused imports
|
||||||
|
# Shake exits with code 1 when issues are found, which is expected here
|
||||||
|
lake_out shake Main || true
|
||||||
|
match_pat 'remove.*Lib.B' produced.out
|
||||||
|
|
||||||
|
# Test --fix mode: apply the fixes and verify the result
|
||||||
|
./clean.sh
|
||||||
|
cp -r input/* .
|
||||||
|
test_run build
|
||||||
|
test_run shake --fix Main
|
||||||
|
|
||||||
|
# Verify Main.lean matches expected (Lib.B import removed)
|
||||||
|
check_diff expected/Main.lean Main.lean
|
||||||
Loading…
Add table
Reference in a new issue