global settings preparation: separate compilation/loading stages of Eval

This commit is contained in:
Mark Harrah 2011-07-09 16:54:41 -04:00
parent cc554d4d64
commit b096d1b175
10 changed files with 37 additions and 28 deletions

View File

@ -16,12 +16,12 @@ import java.net.URLClassLoader
// TODO: provide a way to cleanup backing directory
final class EvalImports(val strings: Seq[(String,Int)], val srcName: String)
final class EvalResult(val tpe: String, val value: Any, val generated: Seq[File], val enclosingModule: String)
final class EvalResult(val tpe: String, val getValue: ClassLoader => Any, val generated: Seq[File], val enclosingModule: String)
final class EvalException(msg: String) extends RuntimeException(msg)
// not thread safe, since it reuses a Global instance
final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Settings => Reporter, parent: ClassLoader, backing: Option[File])
final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Settings => Reporter, backing: Option[File])
{
def this(mkReporter: Settings => Reporter, backing: Option[File]) = this(Nil, IO.classLocationFile[ScalaObject] :: Nil, mkReporter, getClass.getClassLoader, backing)
def this(mkReporter: Settings => Reporter, backing: Option[File]) = this(Nil, IO.classLocationFile[ScalaObject] :: Nil, mkReporter, backing)
def this() = this(s => new ConsoleReporter(s), None)
backing.foreach(IO.createDirectory)
@ -64,7 +64,7 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
val classFiles = getClassFiles(backing, moduleName)
new EvalResult(tpe, value, classFiles, moduleName)
}
def eval0(expression: String, imports: EvalImports, tpeName: Option[String], run: Run, unit: CompilationUnit, backing: Option[File], moduleName: String): (String, Any) =
def eval0(expression: String, imports: EvalImports, tpeName: Option[String], run: Run, unit: CompilationUnit, backing: Option[File], moduleName: String): (String, ClassLoader => Any) =
{
val dir = backing match { case None => new VirtualDirectory("<virtual>", None); case Some(dir) => new PlainFile(dir) }
settings.outputDirs setSingleOutput dir
@ -99,8 +99,8 @@ final class Eval(optionsNoncp: Seq[String], classpath: Seq[File], mkReporter: Se
(tpe, load(dir, moduleName))
}
def load(dir: AbstractFile, moduleName: String): Any = getValue(moduleName, new AbstractFileClassLoader(dir, parent))
def loadPlain(dir: File, moduleName: String): Any = getValue(moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent))
def load(dir: AbstractFile, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new AbstractFileClassLoader(dir, parent))
def loadPlain(dir: File, moduleName: String): ClassLoader => Any = parent => getValue[Any](moduleName, new URLClassLoader(Array(dir.toURI.toURL), parent))
val WrapValName = "$sbtdef"
//wrap tree in object objectName { def WrapValName = <tree> }

View File

@ -81,28 +81,31 @@ object RetrieveUnit
}
object EvaluateConfigurations
{
def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): Seq[Setting[_]] =
srcs.sortBy(_.getName) flatMap { src => evaluateConfiguration(eval, src, imports) }
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): Seq[Setting[_]] =
def apply(eval: Eval, srcs: Seq[File], imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
flatten(srcs.sortBy(_.getName) map { src => evaluateConfiguration(eval, src, imports) })
def evaluateConfiguration(eval: Eval, src: File, imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
evaluateConfiguration(eval, src.getPath, IO.readLines(src), imports, 0)
def evaluateConfiguration(eval: Eval, name: String, lines: Seq[String], imports: Seq[String], offset: Int): Seq[Setting[_]] =
def evaluateConfiguration(eval: Eval, name: String, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => Seq[Setting[_]] =
{
val (importExpressions, settingExpressions) = splitExpressions(lines)
addOffset(offset, settingExpressions) flatMap { case (settingExpression,line) =>
val settings = addOffset(offset, settingExpressions) map { case (settingExpression,line) =>
evaluateSetting(eval, name, (imports.map(s => (s, -1)) ++ addOffset(offset, importExpressions)), settingExpression, line)
}
flatten(settings)
}
def flatten(mksettings: Seq[ClassLoader => Seq[Setting[_]]]): ClassLoader => Seq[Setting[_]] =
loader => mksettings.flatMap(_ apply loader)
def addOffset(offset: Int, lines: Seq[(String,Int)]): Seq[(String,Int)] =
lines.map { case (s, i) => (s, i + offset) }
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, line: Int): Seq[Setting[_]] =
def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, line: Int): ClassLoader => Seq[Setting[_]] =
{
val result = try {
eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some("sbt.Project.SettingsDefinition"), line = line)
} catch {
case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage)
}
result.value.asInstanceOf[Project.SettingsDefinition].settings
loader => result.getValue(loader).asInstanceOf[Project.SettingsDefinition].settings
}
private[this] def isSpace = (c: Char) => Character isSpace c
private[this] def fstS(f: String => Boolean): ((String,Int)) => Boolean = { case (s,i) => f(s) }

View File

@ -860,7 +860,7 @@ object Classpaths
def unmanagedLibs(dep: ResolvedReference, conf: String, data: Settings[Scope]): Task[Classpath] =
getClasspath(unmanagedJars, dep, conf, data)
def getClasspath(key: TaskKey[Classpath], dep: ResolvedReference, conf: String, data: Settings[Scope]): Task[Classpath] =
( key in (dep, ConfigKey(conf)) ) get data getOrElse const(Nil)
( key in (dep, ConfigKey(conf)) ) get data getOrElse constant(Nil)
def defaultConfigurationTask(p: ResolvedReference, data: Settings[Scope]): Configuration =
flatten(defaultConfiguration in p get data) getOrElse Configurations.Default
def flatten[T](o: Option[Option[T]]): Option[T] = o flatMap idFun

View File

@ -17,6 +17,7 @@ package sbt
import tools.nsc.reporters.ConsoleReporter
import Build.{analyzed, data}
import Scope.{GlobalScope, ThisScope}
import Types.const
object Load
{
@ -37,7 +38,7 @@ object Load
val evalPluginDef = EvaluateTask.evalPluginDef(log) _
val delegates = defaultDelegates
val injectGlobal: Seq[Project.Setting[_]] = ((appConfiguration in GlobalScope) :== state.configuration) +: EvaluateTask.injectSettings
val inject = InjectSettings(injectGlobal, Nil)
val inject = InjectSettings(injectGlobal, Nil, const(Nil))
val definesClass = FileValueCache(Locate.definesClass _)
val rawConfig = new LoadBuildConfiguration(stagingDirectory, classpath, loader, compilers, evalPluginDef, definesClass.get, delegates, EvaluateTask.injectStreams, inject, None, log)
val config = loadGlobal(state, baseDirectory, defaultGlobalPlugins, rawConfig)
@ -91,7 +92,7 @@ object Load
val loaded = resolveProjects(load(rootBase, s, config))
val projects = loaded.units
lazy val rootEval = lazyEval(loaded.units(loaded.root).unit)
val settings = finalTransforms(config.injectSettings.global ++ buildConfigurations(loaded, getRootProject(projects), rootEval, config.injectSettings.project))
val settings = finalTransforms(buildConfigurations(loaded, getRootProject(projects), rootEval, config.injectSettings))
val delegates = config.delegates(loaded)
val data = Project.makeSettings(settings, delegates, config.scopeLocal)
val index = structureIndex(data)
@ -137,8 +138,9 @@ object Load
}
def isProjectThis(s: Setting[_]) = s.key.scope.project match { case This | Select(ThisProject) => true; case _ => false }
def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, rootEval: () => Eval, injectProjectSettings: Seq[Setting[_]]): Seq[Setting[_]] =
loaded.units.toSeq flatMap { case (uri, build) =>
def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, rootEval: () => Eval, injectSettings: InjectSettings): Seq[Setting[_]] =
injectSettings.global ++
loaded.units.toSeq.flatMap { case (uri, build) =>
val eval = if(uri == loaded.root) rootEval else lazyEval(build.unit)
val pluginSettings = build.unit.plugins.plugins
val (pluginThisProject, pluginNotThis) = pluginSettings partition isProjectThis
@ -146,10 +148,12 @@ object Load
val srcs = configurationSources(project.base)
val ref = ProjectRef(uri, id)
val defineConfig = for(c <- project.configurations) yield ( (configuration in (ref, ConfigKey(c.name))) :== c)
val loader = build.unit.definitions.loader
val injected = injectSettings.project ++ injectSettings.projectLoaded(loader)
val settings =
(thisProject :== project) +:
(thisProjectRef :== ref) +:
(defineConfig ++ project.settings ++ pluginThisProject ++ configurations(srcs, eval, build.imports) ++ injectProjectSettings )
(defineConfig ++ project.settings ++ pluginThisProject ++ configurations(srcs, eval, build.imports)(loader) ++ injected)
// map This to thisScope, Select(p) to mapRef(uri, rootProject, p)
transformSettings(projectScope(ref), uri, rootProject, settings)
@ -170,10 +174,10 @@ object Load
}
def mkEval(unit: BuildUnit): Eval = mkEval(unit.definitions, unit.plugins, Nil)
def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval =
new Eval(options, defs.target +: plugs.classpath, s => new ConsoleReporter(s), defs.loader, Some(evalOutputDirectory(defs.base)))
new Eval(options, defs.target +: plugs.classpath, s => new ConsoleReporter(s), Some(evalOutputDirectory(defs.base)))
def configurations(srcs: Seq[File], eval: () => Eval, imports: Seq[String]): Seq[Setting[_]] =
if(srcs.isEmpty) Nil else EvaluateConfigurations(eval(), srcs, imports)
def configurations(srcs: Seq[File], eval: () => Eval, imports: Seq[String]): ClassLoader => Seq[Setting[_]] =
if(srcs.isEmpty) const(Nil) else EvaluateConfigurations(eval(), srcs, imports)
def load(file: File, s: State, config: LoadBuildConfiguration): PartBuild =
{

View File

@ -264,7 +264,7 @@ object BuiltinCommands
val extracted = Project extract s
import extracted._
val result = session.currentEval().eval(arg, srcName = "<eval>", imports = autoImports(extracted))
log.info("ans: " + result.tpe + " = " + result.value)
log.info("ans: " + result.tpe + " = " + result.getValue(currentLoader))
s
}
def sessionCommand = Command.make(SessionCommand, sessionBrief, SessionSettings.Help)(SessionSettings.command)
@ -277,7 +277,7 @@ object BuiltinCommands
def set = Command.single(SetCommand, setBrief, setDetailed) { (s, arg) =>
val extracted = Project extract s
import extracted._
val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "<set>", imports(extracted), arg, 0)
val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "<set>", imports(extracted), arg, 0)(currentLoader)
val append = Load.transformSettings(Load.projectScope(currentRef), currentRef.build, rootProject, settings)
val newSession = session.appendSettings( append map (a => (a, arg)))
reapply(newSession, structure, s)

View File

@ -65,6 +65,7 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings,
def rootProject = structure.rootProject
lazy val currentUnit = structure units currentRef.build
lazy val currentProject = currentUnit defined currentRef.project
lazy val currentLoader: ClassLoader = currentUnit.loader
def get[T](key: ScopedTask[T]): Task[T] = get(key.task)
def get[T](key: ScopedSetting[T]): T =
{

View File

@ -25,7 +25,7 @@ object Script
import extracted._
val embeddedSettings = blocks(script).flatMap { block =>
evaluate(eval(), script.getPath, block.lines, currentUnit.imports, block.offset+1)
evaluate(eval(), script.getPath, block.lines, currentUnit.imports, block.offset+1)(currentLoader)
}
val scriptAsSource = sources in Compile := script :: Nil
val asScript = scalacOptions ++= Seq("-Xscript", script.getName.stripSuffix(".scala"))

View File

@ -175,7 +175,7 @@ object Scoped
def :==(value: S): ScS = :=(value)
def ::=(value: Task[S]): ScS = Project.setting(scoped, Project.value( value ))
def := (value: => S): ScS = ::=(mktask(value))
def :== (v: ScopedSetting[S]): ScS = <<=( v(const))
def :== (v: ScopedSetting[S]): ScS = <<=( v(constant))
def ~= (f: S => S): ScS = Project.update(scoped)( _ map f )
def <<= (app: App[S]): ScS = Project.setting(scoped, app)

View File

@ -74,8 +74,8 @@ sealed trait ProcessPipe
trait TaskExtra
{
final def nop: Task[Unit] = const( () )
final def const[T](t: T): Task[T] = task(t)
final def nop: Task[Unit] = constant( () )
final def constant[T](t: T): Task[T] = task(t)
final implicit def t2ToMulti[A,B](t: (Task[A],Task[B])) = multInputTask(t._1 :^: t._2 :^: KNil)
final implicit def f2ToHfun[A,B,R](f: (A,B) => R): (A :+: B :+: HNil => R) = { case a :+: b :+: HNil => f(a,b) }

View File

@ -15,6 +15,7 @@ trait TypeFunctions
final val right = new (Id ~> P1of2[Right, Nothing]#Apply) { def apply[T](t: T) = Right(t) }
final val some = new (Id ~> Some) { def apply[T](t: T) = Some(t) }
final def idFun[T] = (t: T) => t
final def const[A,B](b: B): A=> B = _ => b
def nestCon[M[_], N[_], G[_]](f: M ~> N): (M G)#l ~> (N G)#l =
f.asInstanceOf[(M G)#l ~> (N G)#l] // implemented with a cast to avoid extra object+method call. castless version: