/- Copyright (c) 2021 Mac Malone. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Mac Malone -/ import Lake.Build.Index import Lake.CLI.Error namespace Lake /-! ## Build Target Specifiers -/ structure BuildSpec where info : BuildInfo getJob : BuildData info.key → Job Unit /-- Get the `Job` associated with some `BuildJob` `BuildData`. -/ @[inline] def BuildData.toJob [FamilyDef BuildData k (BuildJob α)] (data : BuildData k) : Job Unit := discard <| ofFamily data @[inline] def mkBuildSpec (info : BuildInfo) [FamilyDef BuildData info.key (BuildJob α)] : BuildSpec := {info, getJob := BuildData.toJob} @[inline] def mkConfigBuildSpec (facetType : String) (info : BuildInfo) (config : FacetConfig Fam ι facet) (h : BuildData info.key = Fam facet) : Except CliError BuildSpec := do let some getJob := config.getJob? | throw <| CliError.nonCliFacet facetType facet return {info, getJob := h ▸ getJob} def BuildSpec.build (self : BuildSpec) : RecBuildM (Job Unit) := self.getJob <$> buildIndexTop' self.info def buildSpecs (specs : Array BuildSpec) : BuildM PUnit := do let jobs ← RecBuildM.run do specs.mapM (·.build) jobs.forM (discard <| ·.await) /-! ## Parsing CLI Build Target Specifiers -/ def parsePackageSpec (ws : Workspace) (spec : String) : Except CliError Package := if spec.isEmpty then return ws.root else match ws.findPackage? spec.toName with | some pkg => return pkg | none => throw <| CliError.unknownPackage spec open Module in def resolveModuleTarget (ws : Workspace) (mod : Module) (facet : Name) : Except CliError BuildSpec := if facet.isAnonymous then return mkBuildSpec <| mod.facet leanBinFacet else if let some config := ws.findModuleFacetConfig? facet then do mkConfigBuildSpec "module" (mod.facet facet) config rfl else throw <| CliError.unknownFacet "module" facet def resolveLibTarget (ws : Workspace) (lib : LeanLib) (facet : Name) : Except CliError BuildSpec := if facet.isAnonymous then return mkBuildSpec lib.lean else if let some config := ws.findLibraryFacetConfig? facet then do mkConfigBuildSpec "library" (lib.facet facet) config rfl else throw <| CliError.unknownFacet "library" facet def resolveExeTarget (exe : LeanExe) (facet : Name) : Except CliError BuildSpec := if facet.isAnonymous || facet == `exe then return mkBuildSpec exe.exe else throw <| CliError.unknownFacet "executable" facet def resolveExternLibTarget (lib : ExternLib) (facet : Name) : Except CliError BuildSpec := if facet.isAnonymous || facet = `static then return mkBuildSpec lib.static else if facet = `shared then return mkBuildSpec lib.shared else throw <| CliError.unknownFacet "external library" facet def resolveCustomTarget (pkg : Package) (name facet : Name) (config : TargetConfig pkg.name name) : Except CliError BuildSpec := if !facet.isAnonymous then throw <| CliError.invalidFacet name facet else do let info := pkg.target name have h : BuildData info.key = CustomData (pkg.name, name) := rfl return {info, getJob := fun data => discard (h ▸ config.getJob data).toJob} def resolveTargetInPackage (ws : Workspace) (pkg : Package) (target facet : Name) : Except CliError BuildSpec := if let some config := pkg.findTargetConfig? target then resolveCustomTarget pkg target facet config else if let some exe := pkg.findLeanExe? target then resolveExeTarget exe facet else if let some lib := pkg.findExternLib? target then resolveExternLibTarget lib facet else if let some lib := pkg.findLeanLib? target then resolveLibTarget ws lib facet else if let some mod := pkg.findModule? target then resolveModuleTarget ws mod facet else throw <| CliError.missingTarget pkg.name (target.toString false) def resolveDefaultPackageTarget (ws : Workspace) (pkg : Package) : Except CliError (Array BuildSpec) := pkg.defaultTargets.mapM (resolveTargetInPackage ws pkg · .anonymous) def resolvePackageTarget (ws : Workspace) (pkg : Package) (facet : Name) : Except CliError (Array BuildSpec) := if facet.isAnonymous then resolveDefaultPackageTarget ws pkg else if let some config := ws.findPackageFacetConfig? facet then do Array.singleton <$> mkConfigBuildSpec "package" (pkg.facet facet) config rfl else throw <| CliError.unknownFacet "package" facet def resolveTargetInWorkspace (ws : Workspace) (target : Name) (facet : Name) : Except CliError (Array BuildSpec) := if let some ⟨pkg, config⟩ := ws.findTargetConfig? target then Array.singleton <$> resolveCustomTarget pkg target facet config else if let some exe := ws.findLeanExe? target then Array.singleton <$> resolveExeTarget exe facet else if let some lib := ws.findExternLib? target then Array.singleton <$> resolveExternLibTarget lib facet else if let some lib := ws.findLeanLib? target then Array.singleton <$> resolveLibTarget ws lib facet else if let some pkg := ws.findPackage? target then resolvePackageTarget ws pkg facet else if let some mod := ws.findModule? target then Array.singleton <$> resolveModuleTarget ws mod facet else throw <| CliError.unknownTarget target def resolveTargetBaseSpec (ws : Workspace) (spec : String) (facet : Name) : Except CliError (Array BuildSpec) := do match spec.splitOn "/" with | [spec] => if spec.isEmpty then resolvePackageTarget ws ws.root facet else if spec.startsWith "@" then let pkg ← parsePackageSpec ws <| spec.drop 1 resolvePackageTarget ws pkg facet else if spec.startsWith "+" then let mod := spec.drop 1 |>.toName if let some mod := ws.findModule? mod then Array.singleton <$> resolveModuleTarget ws mod facet else throw <| CliError.unknownModule mod else resolveTargetInWorkspace ws spec.toName facet | [pkgSpec, targetSpec] => let pkgSpec := if pkgSpec.startsWith "@" then pkgSpec.drop 1 else pkgSpec let pkg ← parsePackageSpec ws pkgSpec if targetSpec.isEmpty then resolvePackageTarget ws pkg facet else if targetSpec.startsWith "+" then let mod := targetSpec.drop 1 |>.toName if let some mod := pkg.findModule? mod then Array.singleton <$> resolveModuleTarget ws mod facet else throw <| CliError.unknownModule mod else Array.singleton <$> resolveTargetInPackage ws pkg targetSpec facet | _ => throw <| CliError.invalidTargetSpec spec '/' def parseTargetSpec (ws : Workspace) (spec : String) : Except CliError (Array BuildSpec) := do match spec.splitOn ":" with | [spec] => resolveTargetBaseSpec ws spec .anonymous | [rootSpec, facet] => resolveTargetBaseSpec ws rootSpec facet.toName | _ => throw <| CliError.invalidTargetSpec spec ':' def parseTargetSpecs (ws : Workspace) (specs : List String) : Except CliError (Array BuildSpec) := do let mut results := #[] for spec in specs do results := results ++ (← parseTargetSpec ws spec) if results.isEmpty then results ← resolveDefaultPackageTarget ws ws.root return results