mirror of https://github.com/sbt/sbt.git
Pluggable build resolvers
This commit is contained in:
parent
0ad682f2c1
commit
40c6ca3b3d
|
|
@ -179,8 +179,7 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
|
|||
|
||||
|
||||
val DefaultStartLine = 0
|
||||
private[this] def makeModuleName(hash: String): String = "$" + halve(hash)
|
||||
private[this] def halve(s: String): String = if(s.length > 2) s.substring(0, s.length / 2) else s
|
||||
private[this] def makeModuleName(hash: String): String = "$" + Hash.halve(hash)
|
||||
private[this] def noImports = new EvalImports(Nil, "")
|
||||
private[this] def mkUnit(srcName: String, firstLine: Int, s: String) = new CompilationUnit(new EvalSourceFile(srcName, firstLine, s))
|
||||
private[this] def checkError(label: String) = if(reporter.hasErrors) throw new EvalException(label)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ trait Build
|
|||
{
|
||||
def projects: Seq[Project]
|
||||
def settings: Seq[Setting[_]] = Defaults.buildCore
|
||||
def buildResolvers: Seq[BuildLoader.BuildResolver] = Nil
|
||||
}
|
||||
trait Plugin
|
||||
{
|
||||
|
|
@ -34,24 +35,28 @@ object Build
|
|||
}
|
||||
object RetrieveUnit
|
||||
{
|
||||
def apply(tempDir: File, base: URI): File =
|
||||
def apply(tempDir: File, base: URI): Option[() => File] =
|
||||
{
|
||||
lazy val tmp = temporary(tempDir, base)
|
||||
base.getScheme match
|
||||
{
|
||||
case "file" => val f = new File(base); if(f.isDirectory) f else error("Not a directory: '" + base + "'")
|
||||
case "git" => gitClone(base, tmp); tmp
|
||||
case "http" | "https" => downloadAndExtract(base, tmp); tmp
|
||||
case _ => error("Unknown scheme in '" + base + "'")
|
||||
case "git" => Some { () => gitClone(base, tmp); tmp }
|
||||
case "http" | "https" => Some { () => downloadAndExtract(base, tmp); tmp }
|
||||
case "file" =>
|
||||
val f = new File(base)
|
||||
if(f.isDirectory) Some(() => f) else None
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
def downloadAndExtract(base: URI, tempDir: File): Unit = if(!tempDir.exists) IO.unzipURL(base.toURL, tempDir)
|
||||
def temporary(tempDir: File, uri: URI): File = new File(tempDir, hash(uri))
|
||||
def temporary(tempDir: File, uri: URI): File = new File(tempDir, Hash.halve(hash(uri)))
|
||||
def hash(uri: URI): String = Hash.toHex(Hash(uri.toASCIIString))
|
||||
|
||||
import Process._
|
||||
def gitClone(base: URI, tempDir: File): Unit =
|
||||
if(!tempDir.exists) ("git" :: "clone" :: base.toASCIIString :: tempDir.getAbsolutePath :: Nil) ! ;
|
||||
if(!tempDir.exists) ("git" :: "clone" :: dropFragment(base).toASCIIString :: tempDir.getAbsolutePath :: branch(base)) ! ;
|
||||
def branch(base: URI): List[String] = base.getFragment match { case null => Nil; case b => "-b" :: b :: Nil }
|
||||
def dropFragment(base: URI): URI = if(base.getFragment eq null) base else new URI(base.getScheme, base.getSchemeSpecificPart, null)
|
||||
}
|
||||
object EvaluateConfigurations
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/* sbt -- Simple Build Tool
|
||||
* Copyright 2011 Mark Harrah
|
||||
*/
|
||||
package sbt
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import Load.{BuildUnit, LoadBuildConfiguration}
|
||||
import BuildLoader._
|
||||
|
||||
final class ResolveInfo(val build: URI, val staging: File)
|
||||
final class BuildLoader(val load: (URI, File) => BuildUnit, val builtIn: BuildResolver, val root: Option[BuildResolver], val nonRoots: List[(URI, BuildResolver)], val fail: URI => Nothing, val config: LoadBuildConfiguration)
|
||||
{
|
||||
import Alternatives._
|
||||
import config.{log, stagingDirectory => dir}
|
||||
|
||||
def apply(uri: URI): BuildUnit = load(uri, resolve(new ResolveInfo(uri, dir)))
|
||||
def resolve(info: ResolveInfo): File =
|
||||
(baseLoader(info), applyNonRoots(info)) match
|
||||
{
|
||||
case (None, Nil) => fail(info.build)
|
||||
case (None, xs @ (_, nr) :: ignored ) =>
|
||||
if(!ignored.isEmpty) warn("Using first of multiple matching non-root build resolver for " + info.build, log, xs)
|
||||
nr()
|
||||
case (Some(b), xs) =>
|
||||
if(!xs.isEmpty) warn("Ignoring shadowed non-root build resolver(s) for " + info.build, log, xs)
|
||||
b()
|
||||
}
|
||||
|
||||
def baseLoader: BuildResolver = root match { case Some(rl) => rl | builtIn; case None => builtIn }
|
||||
|
||||
def addNonRoot(uri: URI, loader: BuildResolver) = new BuildLoader(load, builtIn, root, (uri, loader) :: nonRoots, fail, config)
|
||||
def setRoot(resolver: BuildResolver) = new BuildLoader(load, builtIn, Some(resolver), nonRoots, fail, config)
|
||||
def applyNonRoots(info: ResolveInfo): List[(URI, () => File)] =
|
||||
nonRoots flatMap { case (definingURI, loader) => loader(info) map { unit => (definingURI, unit) } }
|
||||
|
||||
private[this] def warn(baseMessage: String, log: Logger, matching: Seq[(URI, () => File)])
|
||||
{
|
||||
log.warn(baseMessage)
|
||||
log.debug("Non-root build resolvers defined in:")
|
||||
log.debug(matching.map(_._1).mkString("\n\t"))
|
||||
}
|
||||
}
|
||||
object BuildLoader
|
||||
{
|
||||
/** in: Build URI and staging directory
|
||||
* out: None if unhandled or Some containing the retrieve function, which returns the directory retrieved to (can be the same as the staging directory) */
|
||||
type BuildResolver = ResolveInfo => Option[() => File]
|
||||
def apply(load: (URI, File) => BuildUnit, builtIn: BuildResolver, fail: URI => Nothing, config: LoadBuildConfiguration): BuildLoader =
|
||||
new BuildLoader(load, builtIn, None, Nil, fail, config)
|
||||
}
|
||||
|
|
@ -167,15 +167,30 @@ object Load
|
|||
if(srcs.isEmpty) Nil else EvaluateConfigurations(eval(), srcs, imports)
|
||||
|
||||
def load(file: File, s: State, config: LoadBuildConfiguration): PartBuild =
|
||||
load(file, uri => loadUnit(uri, RetrieveUnit(config.stagingDirectory, uri), s, config) )
|
||||
def load(file: File, loader: URI => BuildUnit): PartBuild = loadURI(IO.directoryURI(file), loader)
|
||||
def loadURI(uri: URI, loader: URI => BuildUnit): PartBuild =
|
||||
{
|
||||
val loader = (uri: URI, local: File) => loadUnit(uri, local, s, config)
|
||||
val fail = (uri: URI) => error("Invalid build URI: " + uri)
|
||||
val builtinLoader = BuildLoader(loader, info => RetrieveUnit(info.staging, info.build), fail, config)
|
||||
load(file, builtinLoader)
|
||||
}
|
||||
def load(file: File, loaders: BuildLoader): PartBuild = loadURI(IO.directoryURI(file), loaders)
|
||||
def loadURI(uri: URI, loaders: BuildLoader): PartBuild =
|
||||
{
|
||||
IO.assertAbsolute(uri)
|
||||
val (referenced, map) = loadAll(uri :: Nil, Map.empty, loader, Map.empty)
|
||||
val (referenced, map) = loadAll(uri :: Nil, Map.empty, loaders, Map.empty)
|
||||
checkAll(referenced, map)
|
||||
new PartBuild(uri, map)
|
||||
}
|
||||
def addResolvers(unit: BuildUnit, isRoot: Boolean, loaders: BuildLoader): BuildLoader =
|
||||
unit.definitions.builds.flatMap(_.buildResolvers) match
|
||||
{
|
||||
case Nil => loaders
|
||||
case x :: xs =>
|
||||
import Alternatives._
|
||||
val resolver = (x /: xs){ _ | _ }
|
||||
if(isRoot) loaders.setRoot(resolver) else loaders.addNonRoot(unit.uri, resolver)
|
||||
}
|
||||
|
||||
def loaded(unit: BuildUnit): (PartBuildUnit, List[ProjectReference]) =
|
||||
{
|
||||
val defined = projects(unit)
|
||||
|
|
@ -197,17 +212,18 @@ object Load
|
|||
Project.transform(resolve, unit.definitions.builds.flatMap(_.settings))
|
||||
}
|
||||
|
||||
@tailrec def loadAll(bases: List[URI], references: Map[URI, List[ProjectReference]], externalLoader: URI => BuildUnit, builds: Map[URI, PartBuildUnit]): (Map[URI, List[ProjectReference]], Map[URI, PartBuildUnit]) =
|
||||
@tailrec def loadAll(bases: List[URI], references: Map[URI, List[ProjectReference]], loaders: BuildLoader, builds: Map[URI, PartBuildUnit]): (Map[URI, List[ProjectReference]], Map[URI, PartBuildUnit]) =
|
||||
bases match
|
||||
{
|
||||
case b :: bs =>
|
||||
if(builds contains b)
|
||||
loadAll(bs, references, externalLoader, builds)
|
||||
loadAll(bs, references, loaders, builds)
|
||||
else
|
||||
{
|
||||
val (loadedBuild, refs) = loaded(externalLoader(b))
|
||||
val (loadedBuild, refs) = loaded(loaders(b))
|
||||
checkBuildBase(loadedBuild.unit.localBase)
|
||||
loadAll(refs.flatMap(Reference.uri) reverse_::: bs, references.updated(b, refs), externalLoader, builds.updated(b, loadedBuild))
|
||||
val newLoader = addResolvers(loadedBuild.unit, builds.isEmpty, loaders)
|
||||
loadAll(refs.flatMap(Reference.uri) reverse_::: bs, references.updated(b, refs), newLoader, builds.updated(b, loadedBuild))
|
||||
}
|
||||
case Nil => (references, builds)
|
||||
}
|
||||
|
|
@ -456,10 +472,4 @@ object Load
|
|||
final case class LoadBuildConfiguration(stagingDirectory: File, commonPluginClasspath: Seq[Attributed[File]], classpath: Seq[File], loader: ClassLoader, compilers: Compilers, evalPluginDef: (BuildStructure, State) => Seq[Attributed[File]], delegates: LoadedBuild => Scope => Seq[Scope], scopeLocal: ScopeLocal, injectSettings: Seq[Setting[_]], log: Logger)
|
||||
// information that is not original, but can be reconstructed from the rest of BuildStructure
|
||||
final class StructureIndex(val keyMap: Map[String, AttributeKey[_]], val taskToKey: Map[Task[_], ScopedKey[Task[_]]], val triggers: Triggers[Task], val keyIndex: KeyIndex)
|
||||
|
||||
private[this] def memo[A,B](f: A => B): A => B =
|
||||
{
|
||||
val dcache = new mutable.HashMap[A,B]
|
||||
(a: A) => dcache.getOrElseUpdate(a, f(a))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ object Script
|
|||
Command.command(Name) { state =>
|
||||
val scriptArg = state.remainingCommands.headOption getOrElse error("No script file specified")
|
||||
val script = new File(scriptArg).getAbsoluteFile
|
||||
val hash = halve(Hash.toHex(Hash(script.getAbsolutePath)))
|
||||
val hash = Hash.halve(Hash.toHex(Hash(script.getAbsolutePath)))
|
||||
val base = new File(CommandSupport.bootDirectory(state), hash)
|
||||
IO.createDirectory(base)
|
||||
|
||||
|
|
@ -36,7 +36,6 @@ object Script
|
|||
val newState = "run" :: state.copy(remainingCommands = state.remainingCommands.drop(1))
|
||||
Project.setProject(session, newStructure, newState)
|
||||
}
|
||||
def halve(s: String): String = if(s.length > 3) s.substring(0, s.length / 2) else s
|
||||
|
||||
final case class Block(offset: Int, lines: Seq[String])
|
||||
def blocks(file: File): Seq[Block] =
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ object Hash
|
|||
}
|
||||
array
|
||||
}
|
||||
def halve(s: String): String = if(s.length > 3) s.substring(0, s.length / 2) else s
|
||||
|
||||
/** Calculates the SHA-1 hash of the given String.*/
|
||||
def apply(s: String): Array[Byte] = apply(s.getBytes("UTF-8"))
|
||||
/** Calculates the SHA-1 hash of the given Array[Byte].*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue