diff --git a/compile/Eval.scala b/compile/Eval.scala index 0c24db64d..e829824d1 100644 --- a/compile/Eval.scala +++ b/compile/Eval.scala @@ -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) diff --git a/main/Build.scala b/main/Build.scala index 93c25f3a8..5046337af 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -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 { diff --git a/main/BuildLoader.scala b/main/BuildLoader.scala new file mode 100644 index 000000000..3256bab49 --- /dev/null +++ b/main/BuildLoader.scala @@ -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) +} \ No newline at end of file diff --git a/main/Load.scala b/main/Load.scala index 2937ef349..3b298a61e 100644 --- a/main/Load.scala +++ b/main/Load.scala @@ -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)) - } } diff --git a/main/Script.scala b/main/Script.scala index 4aabdec9b..9c6423f3f 100644 --- a/main/Script.scala +++ b/main/Script.scala @@ -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] = diff --git a/util/io/Hash.scala b/util/io/Hash.scala index 18f9fa29f..e627e16fa 100644 --- a/util/io/Hash.scala +++ b/util/io/Hash.scala @@ -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].*/