From 087bc569e05f28babbd443972b9f88b119136975 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Tue, 18 Jan 2011 18:24:11 -0500 Subject: [PATCH] multi-project model based on Settings and ProjectRef --- ivy/IvyInterface.scala | 1 + main/Build.scala | 492 ++++++++++++++++++++++ main/Command.scala | 45 +- main/KeyIndex.scala | 69 +++ main/LogManager.scala | 13 +- main/Main.scala | 133 +++--- main/Project.scala | 90 ++++ main/ProjectInfo.scala | 39 -- main/Scope.scala | 116 +++++ main/Structure.scala | 227 ++++++++++ main/Watched.scala | 14 +- main/{ => pending}/ClasspathProject.scala | 0 main/{ => pending}/DefaultProject.scala | 14 +- main/{ => pending}/MultiProject.scala | 0 main/{ => pending}/OutputTasks.scala | 0 main/{ => pending}/SingleProject.scala | 0 main/{ => pending}/TestProject.scala | 0 tasks/standard/Action.scala | 7 - tasks/standard/Cross.scala | 98 ----- tasks/standard/Streams.scala | 57 +-- tasks/standard/System.scala | 86 +--- tasks/standard/TaskExtra.scala | 93 +--- tasks/standard/TaskMap.scala | 20 - util/collection/Settings.scala | 79 +++- util/collection/TypeFunctions.scala | 8 + util/collection/Util.scala | 17 + 26 files changed, 1236 insertions(+), 482 deletions(-) create mode 100644 main/Build.scala create mode 100644 main/KeyIndex.scala create mode 100644 main/Project.scala delete mode 100644 main/ProjectInfo.scala create mode 100644 main/Scope.scala create mode 100644 main/Structure.scala rename main/{ => pending}/ClasspathProject.scala (100%) rename main/{ => pending}/DefaultProject.scala (96%) rename main/{ => pending}/MultiProject.scala (100%) rename main/{ => pending}/OutputTasks.scala (100%) rename main/{ => pending}/SingleProject.scala (100%) rename main/{ => pending}/TestProject.scala (100%) delete mode 100644 tasks/standard/Cross.scala delete mode 100644 tasks/standard/TaskMap.scala create mode 100644 util/collection/Util.scala diff --git a/ivy/IvyInterface.scala b/ivy/IvyInterface.scala index 52d02d876..415cc1b09 100644 --- a/ivy/IvyInterface.scala +++ b/ivy/IvyInterface.scala @@ -288,6 +288,7 @@ object Resolver object Configurations { def config(name: String) = new Configuration(name) + def default = defaultMavenConfigurations def defaultMavenConfigurations = Compile :: Runtime :: Test :: Provided :: System :: Optional :: Sources :: Javadoc :: Nil lazy val Default = config("default") diff --git a/main/Build.scala b/main/Build.scala new file mode 100644 index 000000000..9268ba0b1 --- /dev/null +++ b/main/Build.scala @@ -0,0 +1,492 @@ +/* sbt -- Simple Build Tool + * Copyright 2011 Mark Harrah + */ +package sbt + + import java.io.File + import java.net.URI + import compile.{Discovered,Discovery,Eval} + import classpath.ClasspathUtilities + import inc.Analysis + import scala.annotation.tailrec + import Compile.{Compilers,Inputs} + import Project.{ScopedKey, Setting} + import TypeFunctions.{Endo,Id} + import tools.nsc.reporters.ConsoleReporter + +// name is more like BuildDefinition, but that is too long +trait Build +{ + def projects: Seq[Project] +} +trait Plugin +{ + def settings: Seq[Project.Setting[_]] +} + +object Build +{ + def default(base: File): Build = new Build { def projects = defaultProject("default", base) :: Nil } + def defaultProject(id: String, base: File): Project = Project(id, base) +} +object RetrieveUnit +{ + def apply(tempDir: File, base: URI): 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 + "'") + } + } + 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 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) ! ; +} +object EvaluateConfigurations +{ + def apply(eval: Eval, srcs: Seq[File]): Seq[Setting[_]] = + srcs flatMap { src => evaluateConfiguration(eval, src) } + def evaluateConfiguration(eval: Eval, src: File): Seq[Setting[_]] = + evaluateConfiguration(eval, src.getPath, IO.readLines(src)) + def evaluateConfiguration(eval: Eval, name: String, lines: Seq[String]): Seq[Setting[_]] = + { + val (importExpressions, settingExpressions) = splitExpressions(name, lines) + for(settingExpression <- settingExpressions) yield evaluateSetting(eval, name, importExpressions, settingExpression) + } + + def evaluateSetting(eval: Eval, name: String, imports: Seq[String], expression: String): Setting[_] = + { + // TODO: Eval needs to be expanded to be able to: + // handle multiple expressions at once (for efficiency and better error handling) + // accept the source name for error display + // accept imports to use + eval.eval[Setting[_]](expression) + } + def splitExpressions(name: String, lines: Seq[String]): (Seq[String], Seq[String]) = + { + val blank = (_: String).trim.isEmpty + val importOrBlank = (t: String) => blank(t) || (t.trim startsWith "import ") + + val (imports, settings) = lines span importOrBlank + (imports dropWhile blank, groupedLines(settings, blank)) + } + def groupedLines(lines: Seq[String], delimiter: String => Boolean): Seq[String] = + { + @tailrec def group0(lines: Seq[String], delimiter: String => Boolean, accum: Seq[String]): Seq[String] = + { + val start = lines dropWhile delimiter + val (next, tail) = start.span (s => !delimiter(s)) + group0(tail, delimiter, next.mkString("\n") +: accum) + } + group0(lines, delimiter, Nil) + } +} +object EvaluateTask +{ + import Load.BuildStructure + import Project.display + import std.{TaskExtra,Transform} + import TaskExtra._ + + type Streams = std.Streams[ScopedKey[Task[_]]] + type TaskStreams = std.TaskStreams[ScopedKey[Task[_]]] + + val SystemProcessors = Runtime.getRuntime.availableProcessors + val PluginTaskKey = TaskKey[(Seq[File], Analysis)]("plugin-task") + val GlobalPath = "$global" + + val (state, dummyState) = dummy[State]("state") + val (streams, dummyStreams) = dummy[TaskStreams]("streams") + + def injectSettings = Seq( + state :== dummyState, + streams :== dummyStreams + ) + + def dummy[T](name: String): (TaskKey[T], Task[T]) = (TaskKey[T](name), dummyTask(name)) + def dummyTask[T](name: String): Task[T] = task( error("Dummy task '" + name + "' did not get converted to a full task.") ) named name + + def evalPluginDef(state: State, log: Logger)(pluginDef: BuildStructure): (Seq[File], Analysis) = + { + val evaluated = evaluateTask(pluginDef, ScopedKey(Scope.ThisScope, PluginTaskKey.key), state) + val result = evaluated getOrElse error("Plugin task does not exist for plugin definition at " + pluginDef.root) + processResult(result, log) + } + def evaluateTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[Result[T]] = + for( (task, toNode) <- getTask(structure, taskKey, state) ) yield + runTask(task, checkCycles, maxWorkers)(toNode) + + def getTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State): Option[(Task[T], Execute.NodeView[Task])] = + { + val x = transform(structure, "target" :: "log" :: Nil, dummyStreams, dummyState, state) + val thisScope = Scope(Select(Project.currentRef(state)), Global, Global, Global) + val resolvedScope = Scope.replaceThis(thisScope)( taskKey.scope ) + for( t <- structure.data.get(resolvedScope, taskKey.key)) yield + (t, x) + } + + def runTask[Task[_] <: AnyRef, T](root: Task[T], checkCycles: Boolean, maxWorkers: Int)(implicit taskToNode: Execute.NodeView[Task]): Result[T] = + { + val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers) + + val x = new Execute[Task](checkCycles)(taskToNode) + try { x.run(root)(service) } finally { shutdown() } + } + + def transform(structure: BuildStructure, logRelativePath: Seq[String], streamsDummy: Task[TaskStreams], stateDummy: Task[State], state: State) = + { + val streams = mkStreams(structure, logRelativePath) + val dummies = new Transform.Dummies(stateDummy :^: KNil, streamsDummy) + val inject = new Transform.Injected(state :+: HNil, streams) + Transform(dummies, inject)(structure.index.taskToKey) + } + + def mkStreams(structure: BuildStructure, logRelativePath: Seq[String]): Streams = + std.Streams( path(structure, logRelativePath), display, LogManager.construct(structure.data) ) + + def path(structure: BuildStructure, sep: Seq[String])(scoped: ScopedKey[_]): File = + { + val (base, sub) = projectPath(structure, scoped) + resolvePath(base, sep ++ sub ++ nonProjectPath(scoped) ) + } + def resolvePath(base: File, components: Seq[String]): File = + (base /: components)( (b,p) => new File(b,p) ) + + def pathComponent[T](axis: ScopeAxis[T], scoped: ScopedKey[_], label: String)(show: T => String): String = + axis match + { + case Global => GlobalPath + case This => error("Unresolved This reference for " + label + " in " + display(scoped)) + case Select(t) => show(t) + } + def nonProjectPath[T](scoped: ScopedKey[T]): Seq[String] = + { + val scope = scoped.scope + pathComponent(scope.config, scoped, "config")(_.name) :: + pathComponent(scope.task, scoped, "task")(_.label) :: + pathComponent(scope.extra, scoped, "extra")(_ => error("Unimplemented")) :: + Nil + } + def projectPath(structure: BuildStructure, scoped: ScopedKey[_]): (File, Seq[String]) = + scoped.scope.project match + { + case Global => (structure.units(structure.root).base, GlobalPath :: Nil) + case Select(ProjectRef(Some(uri), Some(id))) => (structure.units(uri).defined(id).base, Nil) + case Select(pr) => error("Unresolved project reference (" + pr + ") in " + display(scoped)) + case This => error("Unresolved project reference (This) in " + display(scoped)) + } + def processResult[T](result: Result[T], log: Logger): T = + result match + { + case Value(v) => v + case Inc(inc) => + log.error(Incomplete.show(inc, true)) + error("Task did not complete successfully") + } +} +object Index +{ + def taskToKeyMap(data: Settings[Scope]): Map[Task[_], ScopedKey[Task[_]]] = + { + // AttributeEntry + the checked type test 'value: Task[_]' ensures that the cast is correct. + // (scalac couldn't determine that 'key' is of type AttributeKey[Task[_]] on its own and a type match didn't still required the cast) + val pairs = for( scope <- data.scopes; AttributeEntry(key, value: Task[_]) <- data.data(scope).entries ) yield + (value, ScopedKey(scope, key.asInstanceOf[AttributeKey[Task[_]]])) // unclear why this cast is needed even with a type test in the above filter + pairs.toMap[Task[_], ScopedKey[Task[_]]] + } + def stringToKeyMap(settings: Settings[Scope]): Map[String, AttributeKey[_]] = + { + val multiMap = settings.data.values.flatMap(_.keys).toList.removeDuplicates.groupBy(_.label) + val duplicates = multiMap collect { case (k, x1 :: x2 :: _) => println(k + ": " + x1 + ", " + x2); k } + if(duplicates.isEmpty) + multiMap.mapValues(_.head) + else + error(duplicates.mkString("AttributeKey ID collisions detected for '", "', '", "'")) + } +} +object Load +{ + import BuildPaths._ + + def defaultLoad(state: State, log: Logger): BuildStructure = + { + val stagingDirectory = defaultStaging // TODO: properly configurable + val base = state.configuration.baseDirectory + val loader = getClass.getClassLoader + val provider = state.configuration.provider + val classpath = provider.mainClasspath ++ provider.scalaProvider.jars + val compilers = Compile.compilers(state.configuration, log) + val evalPluginDef = EvaluateTask.evalPluginDef(state, log) _ + val config = new LoadBuildConfiguration(stagingDirectory, classpath, loader, compilers, evalPluginDef, defaultDelegates, EvaluateTask.injectSettings, log) + apply(base, config) + } + def defaultDelegates: LoadedBuild => Scope => Seq[Scope] = (lb: LoadedBuild) => { + def resolveRef(project: ProjectRef) = Scope.resolveRef(lb.root, getRootProject(lb.units), project) + Scope.delegates( + project => projectInherit(lb, resolveRef(project)), + (project, config) => configInherit(lb, resolveRef(project), config), + (project, task) => Nil, + (project, extra) => Nil + ) + } + def configInherit(lb: LoadedBuild, ref: (URI, String), config: ConfigKey): Seq[ConfigKey] = + getConfiguration(lb.units, ref._1, ref._2, config).extendsConfigs.map(c => ConfigKey(c.name)) + + def projectInherit(lb: LoadedBuild, ref: (URI, String)): Seq[ProjectRef] = + getProject(lb.units, ref._1, ref._2).inherits + + // build, load, and evaluate all units. + // 1) Compile all plugin definitions + // 2) Evaluate plugin definitions to obtain and compile plugins and get the resulting classpath for the build definition + // 3) Instantiate Plugins on that classpath + // 4) Compile all build definitions using plugin classpath + // 5) Load build definitions. + // 6) Load all configurations using build definitions and plugins (their classpaths and loaded instances). + // 7) Combine settings from projects, plugins, and configurations + // 8) Evaluate settings + def apply(rootBase: File, config: LoadBuildConfiguration): BuildStructure = + { + val loaded = load(rootBase, config) + val projects = loaded.units + val settings = buildConfigurations(loaded, getRootProject(projects), config.injectSettings) + val data = Project.make(settings)(config.delegates(loaded)) + val index = structureIndex(data) + new BuildStructure(projects, loaded.root, settings, data, index) + } + + def structureIndex(settings: Settings[Scope]): StructureIndex = + new StructureIndex(Index.stringToKeyMap(settings), Index.taskToKeyMap(settings)) + + // Reevaluates settings after modifying them. Does not recompile or reload any build components. + def reapply(modifySettings: Endo[Seq[Setting[_]]], structure: BuildStructure, delegates: Scope => Seq[Scope]): BuildStructure = + { + val newSettings = modifySettings(structure.settings) + val newData = Project.make(newSettings)(delegates) + val newIndex = structureIndex(newData) + new BuildStructure(units = structure.units, root = structure.root, settings = newSettings, data = newData, index = newIndex) + } + + def isProjectThis(s: Setting[_]) = s.key.scope.project == This + def buildConfigurations(loaded: LoadedBuild, rootProject: URI => String, injectSettings: Seq[Setting[_]]): Seq[Setting[_]] = + loaded.units.toSeq flatMap { case (uri, build) => + val eval = mkEval(build.unit.definitions, build.unit.plugins, Nil) + val pluginSettings = build.unit.plugins.plugins + val (pluginThisProject, pluginGlobal) = pluginSettings partition isProjectThis + val projectSettings = build.defined flatMap { case (id, project) => + val srcs = configurationSources(project.base) + val settings = injectSettings ++ project.settings ++ pluginThisProject ++ configurations(srcs, eval) + + val ext = ProjectRef(uri, id) + val thisScope = Scope(Select(ext), Global, Global, Global) + + // map This to thisScope, Select(p) to mapRef(uri, rootProject, p) + Project.transform(Scope.resolveScope(thisScope, uri, rootProject), settings) + } + pluginGlobal ++ projectSettings + } + def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval = + { + val classpathString = Path.makeString(defs.target +: plugs.classpath) + val optionsCp = "-cp" +: classpathString +: options // TODO: probably need to properly set up options with CompilerArguments + new Eval(optionsCp, s => new ConsoleReporter(s), defs.loader) + } + def configurations(srcs: Seq[File], eval: Eval): Seq[Setting[_]] = + if(srcs.isEmpty) Nil else EvaluateConfigurations(eval, srcs) + + def load(file: File, config: LoadBuildConfiguration): LoadedBuild = load(file, uri => loadUnit(RetrieveUnit(config.stagingDirectory, uri), config) ) + def load(file: File, loader: URI => BuildUnit): LoadedBuild = loadURI(file.getAbsoluteFile.toURI, loader) + def loadURI(uri: URI, loader: URI => BuildUnit): LoadedBuild = + { + val (referenced, map) = loadAll(uri :: Nil, Map.empty, loader, Map.empty) + checkAll(referenced, map) + new LoadedBuild(uri, map) + } + def loaded(unit: BuildUnit): (LoadedBuildUnit, List[ProjectRef]) = + { + val baseURI = unit.base.toURI.normalize + def isRoot(p: Project) = p.base.toURI.normalize == baseURI + val defined = projects(unit) + val externals = referenced(defined).toList + val rootProjects = defined.filter(isRoot).map(_.id) + (new LoadedBuildUnit(unit, defined.map(d => (d.id, d)).toMap, rootProjects), externals) + } + + @tailrec def loadAll(bases: List[URI], references: Map[URI, List[ProjectRef]], externalLoader: URI => BuildUnit, builds: Map[URI, LoadedBuildUnit]): (Map[URI, List[ProjectRef]], Map[URI, LoadedBuildUnit]) = + bases match + { + case b :: bs => + if(builds contains b) + loadAll(bs, references, externalLoader, builds) + else + { + val (loadedBuild, refs) = loaded(externalLoader(b)) + loadAll(refs.flatMap(_.uri) reverse_::: bs, references.updated(b, refs), externalLoader, builds.updated(b, loadedBuild)) + } + case Nil => (references, builds) + } + def checkAll(referenced: Map[URI, List[ProjectRef]], builds: Map[URI, LoadedBuildUnit]) + { + val rootProject = getRootProject(builds) + for( (uri, refs) <- referenced; ref <- refs) + { + // mapRef guarantees each component is defined + val ProjectRef(Some(refURI), Some(refID)) = Scope.mapRef(uri, rootProject, ref) + val loadedUnit = builds(refURI) + if(! (loadedUnit.defined contains refID) ) + error("No project '" + refID + "' in '" + refURI + "'") + } + } + + def resolveBase(against: File): Project => Project = + { + val uri = against.getAbsoluteFile.toURI.normalize + p => p.copy(base = new File(uri.resolve(p.base.toURI).normalize)) + } + def projects(unit: BuildUnit): Seq[Project] = unit.definitions.builds.flatMap(_.projects map resolveBase(unit.base)) + def getRootProject(map: Map[URI, LoadedBuildUnit]): URI => String = + uri => getBuild(map, uri).rootProjects.headOption getOrElse emptyBuild(uri) + def getConfiguration(map: Map[URI, LoadedBuildUnit], uri: URI, id: String, conf: ConfigKey): Configuration = + getProject(map, uri, id).configurations.find(_.name == conf.name) getOrElse noConfiguration(uri, id, conf.name) + + def getProject(map: Map[URI, LoadedBuildUnit], uri: URI, id: String): Project = + getBuild(map, uri).defined.getOrElse(id, noProject(uri, id)) + def getBuild(map: Map[URI, LoadedBuildUnit], uri: URI): LoadedBuildUnit = + map.getOrElse(uri, noBuild(uri)) + + def emptyBuild(uri: URI) = error("No root project defined for build unit '" + uri + "'") + def noBuild(uri: URI) = error("Build unit '" + uri + "' not defined.") + def noProject(uri: URI, id: String) = error("No project '" + id + "' defined in '" + uri + "'.") + def noConfiguration(uri: URI, id: String, conf: String) = error("No configuration '" + conf + "' defined in project '" + id + "' in '" + uri +"'") + + def loadUnit(base: File, config: LoadBuildConfiguration): BuildUnit = + { + val defDir = selectProjectDir(base) + val pluginDir = pluginDirectory(defDir) + val plugs = plugins(pluginDir, config) + + val defs = definitionSources(defDir) + val loadedDefs = + if(defs.isEmpty) + new LoadedDefinitions(defDir, outputDirectory(defDir), plugs.loader, Build.default(base) :: Nil) + else + definitions(defDir, defs, plugs, config.compilers, config.log) + + new BuildUnit(base, loadedDefs, plugs) + } + + def plugins(dir: File, config: LoadBuildConfiguration): LoadedPlugins = if(dir.exists) buildPlugins(dir, config) else noPlugins(config) + def noPlugins(config: LoadBuildConfiguration): LoadedPlugins = new LoadedPlugins(config.classpath, config.loader, Nil) + def buildPlugins(dir: File, config: LoadBuildConfiguration): LoadedPlugins = + { + val pluginDef = apply(dir, config) + val (pluginClasspath, pluginAnalysis) = config.evalPluginDef(pluginDef) + val pluginLoader = ClasspathUtilities.toLoader(pluginClasspath, config.loader) + loadPlugins(pluginClasspath, pluginLoader, pluginAnalysis) + } + + def definitions(base: File, srcs: Seq[File], plugins: LoadedPlugins, compilers: Compilers, log: Logger): LoadedDefinitions = + { + val (inputs, defAnalysis) = build(plugins.classpath, srcs, outputDirectory(base), compilers, log) + val target = inputs.config.classesDirectory + val definitionLoader = ClasspathUtilities.toLoader(target :: Nil, plugins.loader) + val defs = loadDefinitions(definitionLoader, findDefinitions(defAnalysis)) + new LoadedDefinitions(base, target, definitionLoader, defs) + } + + def loadDefinitions(loader: ClassLoader, defs: Seq[String]): Seq[Build] = + defs map { definition => loadDefinition(loader, definition) } + def loadDefinition(loader: ClassLoader, definition: String): Build = + ModuleUtilities.getObject(definition, loader).asInstanceOf[Build] + + def build(classpath: Seq[File], sources: Seq[File], buildDir: File, compilers: Compilers, log: Logger): (Inputs, Analysis) = + { + val target = crossPath(new File(buildDir, "target"), compilers.scalac.scalaInstance) + val inputs = Compile.inputs(classpath, sources, target, Nil, Nil, Nil, Compile.DefaultMaxErrors)(compilers, log) + val analysis = Compile(inputs, log) + (inputs, analysis) + } + + def loadPlugins(classpath: Seq[File], loader: ClassLoader, analysis: Analysis): LoadedPlugins = + new LoadedPlugins(classpath, loader, if(classpath.isEmpty) Nil else loadPlugins(loader, findPlugins(analysis) ) ) + + def loadPlugins(loader: ClassLoader, pluginNames: Seq[String]): Seq[Setting[_]] = + pluginNames.flatMap(pluginName => loadPlugin(pluginName, loader)) + + def loadPlugin(pluginName: String, loader: ClassLoader): Seq[Setting[_]] = + ModuleUtilities.getObject(pluginName, loader).asInstanceOf[Plugin].settings + + def findPlugins(analysis: Analysis): Seq[String] = discover(analysis, "sbt.Plugin") + def findDefinitions(analysis: Analysis): Seq[String] = discover(analysis, "sbt.Build") + def discover(analysis: Analysis, subclasses: String*): Seq[String] = + { + val discovery = new Discovery(subclasses.toSet, Set.empty) + discovery(Test.allDefs(analysis)).collect { case (definition, Discovered(_,_,_,true)) => definition.name } + } + + def initialSession(structure: BuildStructure): SessionSettings = + new SessionSettings(structure.root, rootProjectMap(structure.units), structure.settings, Map.empty, Map.empty) + + def rootProjectMap(units: Map[URI, LoadedBuildUnit]): Map[URI, String] = + { + val getRoot = getRootProject(units) + units.keys.map(uri => (uri, getRoot(uri))).toMap + } + + final class EvaluatedConfigurations(val eval: Eval, val settings: Seq[Setting[_]]) + final class LoadedDefinitions(val base: File, val target: File, val loader: ClassLoader, val builds: Seq[Build]) + final class LoadedPlugins(val classpath: Seq[File], val loader: ClassLoader, val plugins: Seq[Setting[_]]) + object LoadedPlugins { + def empty(loader: ClassLoader) = new LoadedPlugins(Nil, loader, Nil) + } + final class BuildUnit(val base: File, val definitions: LoadedDefinitions, val plugins: LoadedPlugins) + + final class LoadedBuild(val root: URI, val units: Map[URI, LoadedBuildUnit]) + final class LoadedBuildUnit(val unit: BuildUnit, val defined: Map[String, Project], val rootProjects: Seq[String]) + { + assert(!rootProjects.isEmpty, "No root projects defined for build unit '" + unit.base + "'") + def base = unit.base + def classpath = unit.definitions.target +: unit.plugins.classpath + def loader = unit.definitions.loader + } + + // these are unresolved references + def referenced(definitions: Seq[Project]): Seq[ProjectRef] = definitions flatMap referenced + def referenced(definition: Project): Seq[ProjectRef] = definition.inherits ++ definition.aggregate ++ definition.dependencies.map(_.project) + + + final class BuildStructure(val units: Map[URI, LoadedBuildUnit], val root: URI, val settings: Seq[Setting[_]], val data: Settings[Scope], val index: StructureIndex) + final class LoadBuildConfiguration(val stagingDirectory: File, val classpath: Seq[File], val loader: ClassLoader, val compilers: Compilers, val evalPluginDef: BuildStructure => (Seq[File], Analysis), val delegates: LoadedBuild => Scope => Seq[Scope], val injectSettings: Seq[Setting[_]], val 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[_]]]) +} +object BuildPaths +{ + import Path._ + import GlobFilter._ + + def defaultStaging = Path.userHome / ".ivy2" / "staging" + + def definitionSources(base: File): Seq[File] = (base * "*.scala").getFiles.toSeq + def configurationSources(base: File): Seq[File] = (base * "*.sbt").getFiles.toSeq + def pluginDirectory(definitionBase: Path) = definitionBase / "plugins" + + def outputDirectory(base: Path) = base / "target" + def projectStandard(base: Path) = base / "project" + def projectHidden(base: Path) = base / ".sbt" + def selectProjectDir(base: Path) = + { + val a = projectHidden(base) + val b = projectStandard(base) + if(a.exists) a else b + } + + def crossPath(base: File, instance: ScalaInstance): File = base / ("scala_" + instance.version) +} \ No newline at end of file diff --git a/main/Command.scala b/main/Command.scala index 2feba2801..b33465fba 100644 --- a/main/Command.scala +++ b/main/Command.scala @@ -6,7 +6,14 @@ package sbt import Execute.NodeView import java.io.File import Function.untupled + import parse.Parser +trait NewCommand // to replace Command +{ + type T + def parser: State => Option[Parser[T]] + def run: (T, State) => State +} trait Command { def help: State => Seq[Help] @@ -25,11 +32,9 @@ object Help object Command { val Logged = AttributeKey[Logger]("log") - val HistoryPath = AttributeKey[Option[File]]("history") + val HistoryPath = SettingKey[Option[File]]("history") val Analysis = AttributeKey[inc.Analysis]("analysis") - val Watch = AttributeKey[Watched]("continuous-watch") - val Navigate = AttributeKey[Navigation]("navigation") - val TaskedKey = AttributeKey[Tasked]("tasked") + val Watch = SettingKey[Watched]("continuous-watch") def direct(h: Help*)(r: (Input, State) => Option[State]): Command = new Command { def help = _ => h; def run = r } @@ -45,38 +50,6 @@ object Command def simple(name: String, help: Help*)(f: (Input, State) => State): Command = Command( help: _* ){ case (in, s) if name == in.name => f( in, s) } } -/* -final case class ProjectSpace( - projects: Map[String, Project], -/* sessionPrepend: Seq[Setting], - sessionAppend: Seq[Setting], - data: Settings,*/ - external: Map[String, Project] -// eval: Option[Eval] -) extends Identity*/ - - -trait Navigation -{ - type Project <: AnyRef - def self: Project - def name: String - def parent: Option[Navigation] - def select(s: State): State - def selected: Navigation - def initial: Navigation - def closure: Seq[Navigation] - def root: Navigation -} -trait Tasked -{ - type Task[T] <: AnyRef - def act(in: Input, state: State): Option[(Task[State], NodeView[Task])] - def help: Seq[Help] - - def maxThreads = Runtime.getRuntime.availableProcessors - def checkCycles = false -} final case class Input(line: String, cursor: Option[Int]) { lazy val (name, arguments) = line match { case Input.NameRegex(n, a) => (n, a); case _ => (line, "") } diff --git a/main/KeyIndex.scala b/main/KeyIndex.scala new file mode 100644 index 000000000..c09f66489 --- /dev/null +++ b/main/KeyIndex.scala @@ -0,0 +1,69 @@ +/* sbt -- Simple Build Tool + * Copyright 2011 Mark Harrah + */ +package sbt + + import java.net.URI + import Project.ScopedKey + +object KeyIndex +{ + def empty: ExtendableKeyIndex = new KeyIndex0(Map.empty) + def apply(known: Seq[ScopedKey[_]]): ExtendableKeyIndex = + (empty /: known) { _ add _ } + def combine(indices: Seq[KeyIndex]): KeyIndex = new KeyIndex { + def buildURIs = concat(_.buildURIs) + def projects(uri: URI) = concat(_.projects(uri)) + def configs(proj: ProjectRef) = concat(_.configs(proj)) + def keys(proj: ProjectRef, conf: Option[String]) = concat(_.keys(proj, conf)) + def concat[T](f: KeyIndex => Set[T]): Set[T] = + (Set.empty[T] /: indices)( (s,k) => s ++ f(k) ) + } +} + +trait KeyIndex +{ + def buildURIs: Set[URI] + def projects(uri: URI): Set[String] + def configs(proj: ProjectRef): Set[String] + def keys(proj: ProjectRef, conf: Option[String]): Set[String] +} +trait ExtendableKeyIndex extends KeyIndex +{ + def add(scoped: ScopedKey[_]): ExtendableKeyIndex +} +private final class KeyIndex0(val data: Map[URI, Map[String, Map[ Option[String], Set[String]] ]]) extends ExtendableKeyIndex +{ + def buildURIs: Set[URI] = data.keys.toSet + def projects(uri: URI): Set[String] = get(data, uri).keys.toSet + def configs(project: ProjectRef): Set[String] = confMap(project).keys.flatten.toSet + def keys(project: ProjectRef, conf: Option[String]): Set[String] = get(confMap(project), conf) + + def confMap(proj: ProjectRef): Map[Option[String], Set[String]] = + proj match + { + case ProjectRef(Some(uri), Some(id)) => get( get(data, uri), id) + case _ => Map.empty + } + + private[this] def get[A,B,C](m: Map[A,Map[B,C]], key: A): Map[B,C] = getOr(m, key, Map.empty) + private[this] def get[A,B](m: Map[A,Set[B]], key: A): Set[B] = getOr(m, key, Set.empty) + private[this] def getOr[A,B](m: Map[A,B], key: A, or: B): B = m.getOrElse(key, or) + + def add(scoped: ScopedKey[_]): ExtendableKeyIndex = + scoped.scope match + { + case Scope(Select(ProjectRef(Some(uri), Some(id))), config, _, _) => add(uri, id, config, scoped.key) + case _ => this + } + def add(uri: URI, id: String, config: ScopeAxis[ConfigKey], key: AttributeKey[_]): ExtendableKeyIndex = + add(uri, id, config match { case Select(c) => Some(c.name); case _ => None }, key) + def add(uri: URI, id: String, config: Option[String], key: AttributeKey[_]): ExtendableKeyIndex = + { + val projectMap = get(data, uri) + val configMap = get(projectMap, id) + val newSet = get(configMap, config) + key.label + val newProjectMap = projectMap.updated(id, configMap.updated(config, newSet)) + new KeyIndex0( data.updated(uri, newProjectMap) ) + } +} diff --git a/main/LogManager.scala b/main/LogManager.scala index 35bccc561..ee9ee6d25 100644 --- a/main/LogManager.scala +++ b/main/LogManager.scala @@ -6,15 +6,14 @@ package sbt import java.io.PrintWriter import LogManager._ import std.Transform + import Project.ScopedKey object LogManager { - def construct(context: Transform.Context[Project]) = (task: Task[_], to: PrintWriter) => + def construct(data: Settings[Scope]) = (task: ScopedKey[Task[_]], to: PrintWriter) => { - val owner = context owner task - val ownerName = owner flatMap ( context ownerName _ ) getOrElse "" - val taskPath = (context staticName task).toList ::: ownerName :: Nil - def level(key: AttributeKey[Level.Value], default: Level.Value): Level.Value = default//settings.get(key, taskPath) getOrElse default + val scope = Scope.fillTaskAxis(task.scope, task.key) + def level(key: AttributeKey[Level.Value], default: Level.Value): Level.Value = data.get(scope, key) getOrElse default val screenLevel = level(ScreenLogLevel, Level.Info) val backingLevel = level(PersistLogLevel, Level.Debug) @@ -30,6 +29,6 @@ object LogManager multi: Logger } - val ScreenLogLevel = AttributeKey[Level.Value]("screen log level") - val PersistLogLevel = AttributeKey[Level.Value]("persist log level") + val ScreenLogLevel = AttributeKey[Level.Value]("screen-log-level") + val PersistLogLevel = AttributeKey[Level.Value]("persist-log-level") } \ No newline at end of file diff --git a/main/Main.scala b/main/Main.scala index 8922d7a30..118be87d2 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -6,8 +6,9 @@ package sbt import Execute.NodeView import complete.HistoryCommands import HistoryCommands.{Start => HistoryPrefix} -import sbt.build.{AggressiveCompile, Auto, Build, BuildException, LoadCommand, Parse, ParseException, ProjectLoad, SourceLoad} -import Command.{Analysis,HistoryPath,Logged,Navigate,TaskedKey,Watch} +import Project.{SessionKey, StructureKey} +import sbt.build.{AggressiveCompile, Auto, BuildException, LoadCommand, Parse, ParseException, ProjectLoad, SourceLoad} +import Command.{Analysis,HistoryPath,Logged,Watch} import scala.annotation.tailrec import scala.collection.JavaConversions._ import Path._ @@ -64,7 +65,7 @@ import CommandSupport._ object Commands { def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover, - projects, project, setOnFailure, ifLast, multi, shell, alias, append, act) + projects, project, setOnFailure, ifLast, multi, shell, alias, append) def ignore = nothing(Set(FailureWall)) @@ -98,7 +99,7 @@ object Commands } def shell = Command.simple(Shell, ShellBrief, ShellDetailed) { (in, s) => - val historyPath = (s get HistoryPath) getOrElse Some((s.baseDir / ".history").asFile) + val historyPath = (s get HistoryPath.key) getOrElse Some((s.baseDir / ".history").asFile) val reader = new LazyJLineReader(historyPath) val line = reader.readLine("> ") line match { @@ -175,14 +176,14 @@ object Commands def continuous = Command( Help(continuousBriefHelp) ) { case (in, s) if in.line startsWith ContinuousExecutePrefix => - withAttribute(s, Watch, "Continuous execution not configured.") { w => + withAttribute(s, Watch.key, "Continuous execution not configured.") { w => Watched.executeContinuously(w, s, in) } } def history = Command( historyHelp: _* ) { case (in, s) if in.line startsWith "!" => val logError = (msg: String) => CommandSupport.logger(s).error(msg) - HistoryCommands(in.line.substring(HistoryPrefix.length).trim, (s get HistoryPath) getOrElse None, 500/*JLine.MaxHistorySize*/, logError) match + HistoryCommands(in.line.substring(HistoryPrefix.length).trim, (s get HistoryPath.key) getOrElse None, 500/*JLine.MaxHistorySize*/, logError) match { case Some(commands) => commands.foreach(println) //printing is more appropriate than logging @@ -191,68 +192,75 @@ object Commands } } - def indent(withStar: Boolean) = if(withStar) "\t*" else "\t" + def indent(withStar: Boolean) = if(withStar) "\t*" else "\t " def listProject(name: String, current: Boolean, log: Logger) = log.info( indent(current) + name ) + def act = error("TODO") def projects = Command.simple(ProjectsCommand, projectsBrief, projectsDetailed ) { (in,s) => val log = logger(s) - withNavigation(s) { nav => - nav.closure.foreach { p => listProject(p.name, nav.self eq p.self, log) } - s + val session = Project.session(s) + val structure = Project.structure(s) + val (curi, cid) = session.current + for( (uri, build) <- structure.units) + { + log.info("In " + uri) + for(id <- build.defined.keys) listProject(id, cid == id, log) } + s } def withAttribute[T](s: State, key: AttributeKey[T], ifMissing: String)(f: T => State): State = (s get key) match { case None => logger(s).error(ifMissing); s.fail case Some(nav) => f(nav) } - def withNavigation(s: State)(f: Navigation => State): State = withAttribute(s, Navigate, "No navigation configured.")(f) def project = Command.simple(ProjectCommand, projectBrief, projectDetailed ) { (in,s) => - withNavigation(s) { nav => - val to = in.arguments - if(to.isEmpty) + val to = in.arguments + val session = Project.session(s) + val structure = Project.structure(s) + val uri = session.currentBuild + def setProject(id: String) = updateCurrent(s.put(SessionKey, session.setCurrent(uri, id))) + if(to.isEmpty) + { + logger(s).info(session.currentProject(uri) + " (in build " + uri + ")") + s + } + else if(to == "/") + { + val id = Load.getRootProject(structure.units)(uri) + setProject(id) + } + else if(to.startsWith("^")) + { + val newBuild = (new java.net.URI(to substring 1)).normalize + if(structure.units contains newBuild) + updateCurrent(s.put(SessionKey, session.setCurrent(uri, session currentProject uri))) + else { - logger(s).info(nav.name) + logger(s).error("Invalid build unit '" + newBuild + "' (type 'projects' to list available builds).") s } - else if(to == "/") - nav.root.select(s) - else if(to.forall(_ == '.')) - if(to.length > 1) gotoParent(to.length - 1, nav, s) else s - else - nav.closure.find { _.name == to } match - { - case Some(np) => np.select(s) - case None => logger(s).error("Invalid project name '" + to + "' (type 'projects' to list available projects)."); s.fail - } + } +/* else if(to.forall(_ == '.')) + if(to.length > 1) gotoParent(to.length - 1, nav, s) else s */ // semantics currently undefined + else if( structure.units(uri).defined.contains(to) ) + setProject(to) + else + { + logger(s).error("Invalid project name '" + to + "' (type 'projects' to list available projects).") + s.fail } } - @tailrec def gotoParent(n: Int, nav: Navigation, s: State): State = - nav.parent match - { - case Some(pp) => if(n <= 1) pp.select(s) else gotoParent(n-1, pp, s) - case None => nav.select(s) - } def exit = Command( Help(exitBrief) ) { case (in, s) if TerminateActions contains in.line => runExitHooks(s).exit(true) } - def act = new Command { - def help = s => (s get TaskedKey).toSeq.flatMap { _.help } - def run = (in, s) => (s get TaskedKey) flatMap { p => - import p.{checkCycles, maxThreads} - for( (task, taskToNode) <- p.act(in, s)) yield - processResult(runTask(task, checkCycles, maxThreads)(taskToNode), s, s.fail) - } - } - def discover = Command.simple(Discover, DiscoverBrief, DiscoverDetailed) { (in, s) => withAttribute(s, Analysis, "No analysis to process.") { analysis => val command = Parse.discover(in.arguments) - val discovered = Build.discover(analysis, command) + val discovered = build.Build.discover(analysis, command) println(discovered.mkString("\n")) s } @@ -260,28 +268,43 @@ object Commands def compile = Command.simple(CompileName, CompileBrief, CompileDetailed ) { (in, s) => val command = Parse.compile(in.arguments)(s.baseDir) try { - val analysis = Build.compile(command, s.configuration) + val analysis = build.Build.compile(command, s.configuration) s.put(Analysis, analysis) } catch { case e: xsbti.CompileFailed => s.fail /* already logged */ } } def loadProject = Command.simple(LoadProject, LoadProjectBrief, LoadProjectDetailed) { (in, s) => - val base = s.configuration.baseDirectory - lazy val p: Project = MultiProject.load(s.configuration, logger(s), ProjectInfo.externals(exts))(base) - // lazy so that p can forward-reference it - lazy val exts: Map[File, Project] = MultiProject.loadExternals(p :: Nil, p.info.construct).updated(base, p) - exts// force - setProject(p, p, runExitHooks(s)) + val structure = Load.defaultLoad(s, logger(s)) + val session = Load.initialSession(structure) + val newAttrs = s.attributes.put(StructureKey, structure).put(SessionKey, session) + val newState = s.copy(attributes = newAttrs) + updateCurrent(runExitHooks(newState)) } - def setProject(p: Project, initial: Project, s: State): State = + + def updateCurrent(s: State): State = { - logger(s).info("Set current project to " + p.name) - val nav = new MultiNavigation(p, setProject _, p, initial) - val watched = new MultiWatched(p) - // put(Logged, p.log) - val newAttrs = s.attributes.put(Analysis, p.info.analysis).put(Navigate, nav).put(Watch, watched).put(HistoryPath, p.historyPath).put(TaskedKey, p) + val structure = Project.structure(s) + val (uri, id) = Project.current(s) + val ref = ProjectRef(uri, id) + val project = Load.getProject(structure.units, uri, id) + logger(s).info("Set current project to " + id + " (in build " + uri +")") + + val data = structure.data + val historyPath = HistoryPath(ref).get(data).flatMap(identity) + val newAttrs = s.attributes.put(Watch.key, makeWatched(data, ref, project)).put(HistoryPath.key, historyPath) s.copy(attributes = newAttrs) } + def makeWatched(data: Settings[Scope], ref: ProjectRef, project: Project): Watched = + { + def getWatch(ref: ProjectRef) = Watch(ref).get(data) + getWatch(ref) match + { + case Some(currentWatch) => + val subWatches = project.uses flatMap { p => getWatch(p) } + Watched.multi(currentWatch, subWatches) + case None => Watched.empty + } + } def handleException(e: Throwable, s: State, trace: Boolean = true): State = { val log = logger(s) @@ -317,7 +340,7 @@ object Commands try { val parsed = Parse(line)(configuration.baseDirectory) - Right( Build( translateEmpty(parsed, defaultSuper), configuration, allowMultiple) ) + Right( build.Build( translateEmpty(parsed, defaultSuper), configuration, allowMultiple) ) } catch { case e @ (_: ParseException | _: BuildException | _: xsbti.CompileFailed) => Left(e) } diff --git a/main/Project.scala b/main/Project.scala new file mode 100644 index 000000000..6b1877f30 --- /dev/null +++ b/main/Project.scala @@ -0,0 +1,90 @@ +/* sbt -- Simple Build Tool + * Copyright 2011 Mark Harrah + */ +package sbt + + import java.io.File + import java.net.URI + import Project._ + +final case class Project(id: String, base: File, aggregate: Seq[ProjectRef] = Nil, dependencies: Seq[Project.ClasspathDependency] = Nil, inherits: Seq[ProjectRef] = Nil, + settings: Seq[Project.Setting[_]] = Project.defaultSettings, configurations: Seq[Configuration] = Configurations.default) +{ + def uses = aggregate ++ dependencies.map(_.project) +} + +object Project extends Init[Scope] +{ + def defaultSettings: Seq[Setting[_]] = Nil + + final case class ClasspathDependency(project: ProjectRef, configuration: Option[String]) + final class Constructor(p: ProjectRef) { + def %(conf: String): ClasspathDependency = new ClasspathDependency(p, Some(conf)) + } + + def getOrError[T](state: State, key: AttributeKey[T], msg: String): T = state get key getOrElse error(msg) + def structure(state: State): Load.BuildStructure = getOrError(state, StructureKey, "No build loaded.") + def session(state: State): SessionSettings = getOrError(state, SessionKey, "Session not initialized.") + + def current(state: State): (URI, String) = + { + val s = session(state) + val uri = s.currentBuild + (uri, s.currentProject(uri)) + } + def currentRef(state: State): ProjectRef = + { + val (unit, it) = current(state) + ProjectRef(Some(unit), Some(it)) + } + def display(scoped: ScopedKey[_]): String = Scope.display(scoped.scope, scoped.key.label) + + def mapScope(f: Scope => Scope) = new (ScopedKey ~> ScopedKey) { def apply[T](key: ScopedKey[T]) = + ScopedKey( f(key.scope), key.key) + } + def transform(g: Scope => Scope, ss: Seq[Setting[_]]): Seq[Setting[_]] = { + val f = mapScope(g) + ss.map(_ mapKey f) + } + + val SessionKey = AttributeKey[SessionSettings]("session-settings") + val StructureKey = AttributeKey[Load.BuildStructure]("build-structure") +} + + import SessionSettings._ + +final class SessionSettings(val currentBuild: URI, val currentProject: Map[URI, String], val original: Seq[Setting[_]], val prepend: SessionMap, val append: SessionMap) { + assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.") + def setCurrent(build: URI, project: String): SessionSettings = new SessionSettings(build, currentProject.updated(build, project), original, prepend, append) + def current: (URI, String) = (currentBuild, currentProject(currentBuild)) +} +object SessionSettings { + type SessionSetting = (Setting[_], String) + type SessionMap = Map[(URI, String), Seq[SessionSetting]] +} + +trait ProjectConstructors +{ + implicit def configDependencyConstructor[T <% ProjectRef](p: T): Project.Constructor = new Project.Constructor(p) + implicit def classpathDependency[T <% ProjectRef](p: T): Project.ClasspathDependency = new Project.ClasspathDependency(p, None) +} + +final case class ProjectRef(uri: Option[URI], id: Option[String]) +object ProjectRef +{ + def apply(base: URI, id: String): ProjectRef = ProjectRef(Some(base), Some(id)) + /** Reference to the project with 'id' in the current build unit.*/ + def apply(id: String): ProjectRef = ProjectRef(None, Some(id)) + def apply(base: File, id: String): ProjectRef = ProjectRef(Some(base.toURI), Some(id)) + /** Reference to the root project at 'base'.*/ + def apply(base: URI): ProjectRef = ProjectRef(Some(base), None) + /** Reference to the root project at 'base'.*/ + def apply(base: File): ProjectRef = ProjectRef(Some(base.toURI), None) + /** Reference to the root project in the current build unit.*/ + def root = ProjectRef(None, None) + + implicit def stringToRef(s: String): ProjectRef = ProjectRef(s) + implicit def projectToRef(p: Project): ProjectRef = ProjectRef(p.id) + implicit def uriToRef(u: URI): ProjectRef = ProjectRef(u) + implicit def fileToRef(f: File): ProjectRef = ProjectRef(f) +} \ No newline at end of file diff --git a/main/ProjectInfo.scala b/main/ProjectInfo.scala deleted file mode 100644 index 933890408..000000000 --- a/main/ProjectInfo.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2008, 2010 Mark Harrah - */ -package sbt - -import java.io.File -import xsbti.{AppConfiguration, AppProvider, ScalaProvider} -import inc.Analysis - -/** Represents the minimal information necessary to construct a Project. -* -* `projectDirectory` is the base directory for the project (not the root project directory) -* `builderPath` is the base directory for the project (not the root project directory) -* `dependencies` are the Projects that this Project depends on. -* `parent` is the parent Project, or None if this is the root project. -* `buildScalaVersion` contains the explicitly requested Scala version to use for building (as when using `+` or `++`) or None if the normal version should be used. -*/ -final case class ProjectInfo(name: Option[String], projectDirectory: File, builderDir: File, dependencies: Seq[ProjectDependency], parent: Option[Project])( - val configuration: AppConfiguration, val analysis: Analysis, val compileInputs: Compile.Inputs, val construct: File => Project, external: ExternalProjects) -{ - def app = configuration.provider - /** The version of Scala running sbt.*/ - def definitionScalaVersion = app.scalaProvider.version - /** The launcher instance that booted sbt.*/ - def launcher = app.scalaProvider.launcher - def globalLock = launcher.globalLock - /* cannot be referenced in a Project's constructor */ - lazy val externals = external.value -} -final class ExternalProjects(externalMap: => Map[File, Project]) -{ - lazy val value = externalMap -} - -object ProjectInfo -{ - def externals(externalMap: => Map[File, Project]) = new ExternalProjects(externalMap) - val MetadataDirectoryName = "project" -} \ No newline at end of file diff --git a/main/Scope.scala b/main/Scope.scala new file mode 100644 index 000000000..9e3d69aa1 --- /dev/null +++ b/main/Scope.scala @@ -0,0 +1,116 @@ +/* sbt -- Simple Build Tool + * Copyright 2011 Mark Harrah + */ +package sbt + + import java.io.File + import java.net.URI + +final case class Scope(project: ScopeAxis[ProjectRef], config: ScopeAxis[ConfigKey], task: ScopeAxis[AttributeKey[_]], extra: ScopeAxis[AttributeMap]) +object Scope +{ + val ThisScope = Scope(This, This, This, This) + val GlobalScope = Scope(Global, Global, Global, Global) + + def resolveScope(thisScope: Scope, current: URI, rootProject: URI => String): Scope => Scope = + replaceThis(thisScope) compose resolveProject(current, rootProject) + + def replaceThis(thisScope: Scope): Scope => Scope = (scope: Scope) => + Scope(subThis(thisScope.project, scope.project), subThis(thisScope.config, scope.config), subThis(thisScope.task, scope.task), subThis(thisScope.extra, scope.extra)) + + def subThis[T](sub: ScopeAxis[T], into: ScopeAxis[T]): ScopeAxis[T] = + if(into == This) sub else into + + def fillTaskAxis(scope: Scope, key: AttributeKey[_]): Scope = + scope.task match + { + case _: Select[_] => scope + case _ => scope.copy(task = Select(key)) + } + + def resolveProject(uri: URI, rootProject: URI => String): Scope => Scope = + { + case Scope(Select(ref), a,b,c) => + Scope(Select(mapRef(uri, rootProject, ref)), a,b,c) + case x => x + } + + def mapRef(current: URI, rootProject: URI => String, ref: ProjectRef): ProjectRef = + { + val (uri, id) = resolveRef(current, rootProject, ref) + ProjectRef(Some(uri), Some(id)) + } + def resolveRef(current: URI, rootProject: URI => String, ref: ProjectRef): (URI, String) = + { + val uri = ref.uri getOrElse current + (uri, ref.id getOrElse rootProject(uri)) + } + + def display(config: ConfigKey): String = if(config.name == "compile") "" else config.name + "-" + def display(scope: Scope, sep: String): String = + { + import scope.{project, config, task, extra} + val projectPrefix = project match { case Select(p) => "(" + p.uri + ")" + p.id; case Global => "*"; case This => "." } + val configPrefix = config match { case Select(c) => display(c); case _ => "" } + val taskPostfix = task match { case Select(t) => " for " + t.label; case _ => "" } + val extraPostfix = extra match { case Select(es) => es.entries.map( _.toString ).toList; case _ => Nil } + val extras = taskPostfix :: extraPostfix + val postfix = if(extras.isEmpty) "" else extras.mkString("(", ", ", ")") + projectPrefix + "/" + configPrefix + sep + postfix + } + + def parseScopedKey(command: String): (Scope, String) = + { + val ScopedKeyRegex(_, projectID, _, config, key) = command + val pref = if(projectID eq null) This else Select(ProjectRef(None, Some(projectID))) + val conf = if(config eq null) This else Select(ConfigKey(config)) + (Scope(pref, conf, This, This), transformTaskName(key)) + } + val ScopedKeyRegex = """((\w+)\/)?((\w+)\:)?([\w\-]+)""".r + + def transformTaskName(s: String) = + { + val parts = s.split("-+") + (parts.take(1) ++ parts.drop(1).map(_.capitalize)).mkString + } + + // *Inherit functions should be immediate delegates and not include argument itself. Transitivity will be provided by this method + def delegates(projectInherit: ProjectRef => Seq[ProjectRef], + configInherit: (ProjectRef, ConfigKey) => Seq[ConfigKey], + taskInherit: (ProjectRef, AttributeKey[_]) => Seq[AttributeKey[_]], + extraInherit: (ProjectRef, AttributeMap) => Seq[AttributeMap])(scope: Scope): Seq[Scope] = + scope.project match + { + case Global | This => scope :: Nil + case Select(proj) => + val prod = + for { + c <- linearize(scope.config)(configInherit(proj, _)) + t <- linearize(scope.task)(taskInherit(proj,_)) + e <- linearize(scope.extra)(extraInherit(proj,_)) + } yield + Scope(Select(proj),c,t,e) + val projI = + linearize(scope.project)(projectInherit) map { p => scope.copy(project = p) } + + prod ++ projI + } + def linearize[T](axis: ScopeAxis[T])(inherit: T => Seq[T]): Seq[ScopeAxis[T]] = + axis match + { + case Select(x) => Dag.topologicalSort(x)(inherit).map(Select.apply) :+ Global + case Global | This => Global :: Nil + } +} + + +sealed trait ScopeAxis[+S] +object This extends ScopeAxis[Nothing] +object Global extends ScopeAxis[Nothing] +final case class Select[S](s: S) extends ScopeAxis[S] + +final case class ConfigKey(name: String) +object ConfigKey +{ + implicit def configurationToKey(c: Configuration): ConfigKey = ConfigKey(c.name) +} \ No newline at end of file diff --git a/main/Structure.scala b/main/Structure.scala new file mode 100644 index 000000000..08bbaff54 --- /dev/null +++ b/main/Structure.scala @@ -0,0 +1,227 @@ +/* sbt -- Simple Build Tool + * Copyright 2011 Mark Harrah + */ +package sbt + +/** An abstraction on top of Settings for build configuration and task definition. */ + + import Types._ + import std.TaskExtra._ + import Task._ + import Project.{ScopedKey, Setting} + import parse.Parser + import java.io.File + import java.net.URI + +sealed trait InputTask[T] { + type S + def parser: Parser[S] + def apply(s: S): Task[T] +} +object InputTask { + def apply[I,T](p: Parser[I], c: I => Task[T]): InputTask[T] { type S = I } = + new InputTask[T] { type S = I; def parser = p; def apply(s: I) = c(s) } +} + +sealed trait Scoped { def scope: Scope } +sealed trait ScopedSetting[T] extends Scoped { def key: AttributeKey[T] } +sealed trait ScopedTask[T] extends Scoped { def key: AttributeKey[Task[T]] } +sealed trait ScopedInput[T] extends Scoped { def key: AttributeKey[InputTask[T]] } + +sealed trait Key[T] extends Scoped { final def scope: Scope = Scope(This,This,This,This) } +final class SettingKey[T] private(val key: AttributeKey[T]) extends Key[T] with ScopedSetting[T] +final class TaskKey[T] private(val key: AttributeKey[Task[T]]) extends Key[T] with ScopedTask[T] +final class InputKey[T] private(val key: AttributeKey[InputTask[T]]) extends Key[InputTask[T]] with ScopedInput[T] + +object Scoped +{ + implicit def richSettingScoped[T](s: ScopedSetting[T]): RichSettingScoped[T] = new RichSettingScoped[T](s.scope, s.key) + implicit def richTaskScoped[T](s: ScopedTask[T]): RichTaskScoped[T] = new RichTaskScoped[T](s.scope, s.key) + implicit def richInputScoped[T](s: ScopedInput[T]): RichInputScoped[T] = new RichInputScoped[T](s.scope, s.key) + + implicit def settingScoping[T](s: SettingKey[T]): ScopingSetting[T] = new ScopingSetting[T](s.key) + implicit def taskScoping[T](s: TaskKey[T]): ScopingTask[T] = new ScopingTask[T](s.key) + implicit def inputScoping[T](s: InputKey[T]): ScopingInput[T] = new ScopingInput[T](s.key) + + sealed abstract class AbstractScoping[T] + { + type Result + def key: AttributeKey[T] + def apply(s: Scope): Result + + final def apply(p: ProjectRef): Result = apply(Select(p), This, This) + final def apply(t: TaskKey[_]): Result = apply(This, This, Select(t)) + final def apply(c: ConfigKey): Result = apply(This, Select(c), This) + final def apply(c: ConfigKey, t: TaskKey[_]): Result = apply(This, Select(c), Select(t)) + final def apply(p: ProjectRef, c: ConfigKey): Result = apply(Select(p), Select(c), This) + final def apply(p: ProjectRef, t: TaskKey[_]): Result = apply(Select(p), This, Select(t)) + final def apply(p: ProjectRef, c: ConfigKey, t: TaskKey[_]): Result = apply(Select(p), Select(c), Select(t)) + final def apply(p: ScopeAxis[ProjectRef], c: ScopeAxis[ConfigKey], t: ScopeAxis[TaskKey[_]]): Result = apply( Scope(p, c, convert(t), This) ) + private def convert(tk: ScopeAxis[TaskKey[_]]): ScopeAxis[AttributeKey[_]] = + tk match { + case Select(t) => Select(t.key) + case This => This + case Global => Global + } + } + final class ScopingSetting[T](val key: AttributeKey[T]) extends AbstractScoping[T] + { + type Result = ScopedSetting[T] + def apply(s: Scope): Result = new ScopedSetting[T] { val scope = s; val key = ScopingSetting.this.key } + } + final class ScopingInput[T](val key: AttributeKey[InputTask[T]]) extends AbstractScoping[InputTask[T]] + { + type Result = ScopedInput[T] + def apply(s: Scope): Result = new ScopedInput[T] { val scope = s; val key = ScopingInput.this.key } + } + final class ScopingTask[T](taskKey: AttributeKey[Task[T]]) + { + def apply(p: ProjectRef): RichTaskScoped[T] = apply(Select(p), This) + def apply(c: ConfigKey): RichTaskScoped[T] = apply(This, Select(c)) + def apply(p: ProjectRef, c: ConfigKey): RichTaskScoped[T] = apply(Select(p), Select(c)) + def apply(p: ScopeAxis[ProjectRef], c: ScopeAxis[ConfigKey]): ScopedTask[T] = apply(Scope(p, c, This, This)) + def apply(s: Scope): ScopedTask[T] = scopedTask(s, taskKey) + } + private[this] def scopedTask[T](s: Scope, k: AttributeKey[Task[T]]): ScopedTask[T] = new ScopedTask[T] { val scope = s; val key = k } + + sealed abstract class RichBaseScoped[S] + { + def scope: Scope + def key: AttributeKey[S] + final val scoped = ScopedKey(scope, key) + final val scopedList = scoped :^: KNil + + final def :==(value: S): Setting[S] = :=(value) + final def := (value: => S): Setting[S] = Project.value(scoped)(value) + final def :~ (f: S => S): Setting[S] = Project.update(scoped)(f) + final def :- [HL <: HList](app: Apply[S]): Setting[S] = app toSetting scoped + + def apply[T](f: S => T): Apply[T] = Apply.mk(scopedList)(hl => f(hl.head)) + + def get(settings: Settings[Scope]): Option[S] = settings.get(scope, key) + } + final class RichInputScoped[T](val scope: Scope, val key: AttributeKey[InputTask[T]]) extends RichBaseScoped[InputTask[T]] + final class RichSettingScoped[S](val scope: Scope, val key: AttributeKey[S]) extends RichBaseScoped[S] + { + def map[T](f: S => T): Apply[Task[T]] = flatMap(s => task(f(s)) ) + def flatMap[T](f: S => Task[T]): Apply[Task[T]] = Apply.mk(scopedList)(hl => f(hl.head)) + } + final class RichTaskScoped[S](scope: Scope, key: AttributeKey[Task[S]]) + { + type ScS = Setting[Task[S]] + def :==(value: S): ScS = :=(value) + def :==(value: Task[S]): ScS = Project.value(scoped)( value ) + def := (value: => S): ScS = :==(task(value)) + def :== (v: TaskKey[S]): ScS = Project.app(scoped, ScopedKey(scope, v.key) :^: KNil)(_.head) + def :~ (f: S => S): ScS = Project.update(scoped)( _ map f ) + + def :- [HL <: HList](app: App[S]): ScS = app toSetting scoped + + def get(settings: Settings[Scope]): Option[Task[S]] = settings.get(scope, key) + + type App[T] = Apply[Task[T]] + private[this] def scoped = ScopedKey(scope, key) + private[this] def mk[T](onTask: Task[S] => Task[T]): App[T] = Apply.mk(scoped :^: KNil)(hl => onTask(hl.head)) + + def flatMapR[T](f: Result[S] => Task[T]): App[T] = mk(_ flatMapR f) + def flatMap[T](f: S => Task[T]): App[T] = mk(_ flatMap f) + def map[T](f: S => T): App[T] = mk(_ map f) + def mapR[T](f: Result[S] => T): App[T] = mk(_ mapR f) + def flatFailure[T](f: Incomplete => Task[T]): App[T] = mk(_ flatFailure f) + def mapFailure[T](f: Incomplete => T): App[T] = mk(_ mapFailure f) + def andFinally(fin: => Unit): App[S] = mk(_ andFinally fin) + def doFinally(t: Task[Unit]): App[S] = mk(_ doFinally t) + + def || [T >: S](alt: Task[T]): App[T] = mk(_ || alt) + def && [T](alt: Task[T]): App[T] = mk(_ && alt) + + def dependsOn(tasks: ScopedTask[_]*): App[S] = + { + val in = KCons(scopedTask(scope, key), KList.fromList(tasks)) + Apply.tasks(in) { case KCons(h,t) => h dependsOn(t.toList :_*) } + } + } + + implicit def richSettingKeys[HL <: HList](in: KList[ScopedSetting, HL]): RichSettingKeys[HL] = new RichSettingKeys(in) + final class RichSettingKeys[HL <: HList](keys: KList[ScopedSetting, HL]) + { + type App[T] = Apply[Task[T]] + def map[T](f: HL => T): App[T] = Apply(keys)(settings => task(f(settings))) + def flatMap[T](f: HL => Task[T]): App[T] = Apply(keys)(f) + } + final class RichTaskKeys[In <: HList](keys: KList[ScopedTask, In]) + { + type App[T] = Apply[Task[T]] + def flatMap[T](f: In => Task[T]): App[T] = mk(_ flatMap f) + def flatMapR[T](f: Results[In] => Task[T]): App[T] = mk(_ flatMapR f) + def map[T](f: In => T): App[T] = mk(_ map f) + def mapR[T](f: Results[In] => T): App[T] = mk(_ mapR f) + def flatFailure[T](f: Seq[Incomplete] => Task[T]): App[T] = mk(_ flatFailure f) + def mapFailure[T](f: Seq[Incomplete] => T): App[T] = mk(_ mapFailure f) + private[this] def mk[T](onTasks: KList[Task, In] => Task[T]): App[T] = Apply.tasks(keys)(onTasks) + } + + implicit def t2ToApp2[A,B](t2: (ScopedSetting[A], ScopedSetting[B]) ): Apply2[A,B] = new Apply2(t2) + implicit def t3ToApp3[A,B,C](t3: (ScopedSetting[A], ScopedSetting[B], ScopedSetting[C]) ): Apply3[A,B,C] = new Apply3(t3) + + final class Apply[T] private(val toSetting: ScopedKey[T] => Setting[T]) + + object Apply + { + def mk[H <: HList, T](in: KList[ScopedKey, H])(f: H => T): Apply[T] = + new Apply[T](scoped => Project.app(scoped, in)(f) ) + + def apply[HL <: HList, T](in: KList[ScopedSetting, HL])(f: HL => T): Apply[T] = mk(in transform ssToSK)(f) + def tasks[HL <: HList, T](in: KList[ScopedTask, HL])(f: KList[Task, HL] => T): Apply[T] = + { + val kapp = new Project.KApp[HL, Task, T] + new Apply[T](scoped => kapp(scoped, in transform stToSK)(f) ) + } + + private val ssToSK = new (ScopedSetting ~> ScopedKey) { def apply[T](sk: ScopedSetting[T]) = new ScopedKey(sk.scope, sk.key) } + private val stToSK = new (ScopedTask ~> ScopedTaskKey) { def apply[T](st: ScopedTask[T]) = new ScopedKey(st.scope, st.key) } + private type ScopedTaskKey[T] = ScopedKey[Task[T]] + } + + final class Apply2[A,B](t2: (ScopedSetting[A], ScopedSetting[B])) { + def apply[T](f: (A,B) => T) = + Apply(t2._1 :^: t2._2 :^: KNil){ case a :+: b :+: HNil => f(a,b) } + } + final class Apply3[A,B,C](t3: (ScopedSetting[A], ScopedSetting[B], ScopedSetting[C])) { + def apply[T](f: (A,B,C) => T) = + Apply(t3._1 :^: t3._2 :^: t3._3 :^: KNil){ case a :+: b :+: c :+: HNil => f(a,b,c) } + } + + /*def unresolved(scope: Scope): Seq[String] = unresolvedProject(scope.project) ++ unresolvedThis(scope) + def unresolvedProject(ps: ScopeAxis[ProjectRef]): Seq[String] = ps match { + case Select(p) => ifEmpty(p.unit, "Unspecified build unit") ++ ifEmpty(p.id, "Unspecified project ID") + case _ => Nil + } + def ifEmpty(p: Option[URI], msg: String): Seq[String] = if(p.isEmpty) msg :: Nil else Nil + def unresolvedThis(scope: Scope): Seq[String] = + unresolvedThis(scope.project, "project") ++ + unresolvedThis(scope.config, "configuration") ++ + unresolvedThis(scope.task, "task") ++ + unresolvedThis(scope.extra, "extra") + + def unresolvedThis(axis: ScopeAxis[_], label: String): Seq[String] = + if(axis == This) ("Unresolved This for " + label + " axis.") :: Nil else Nil*/ + +// private[this] val hcHead = new ( ({type l[t] = t :+: _})#l ~> Id ) { def apply[T](hc: t :+: _): t = hc.head } +} +object TaskKey +{ + def apply[T](label: String): TaskKey[T] = + apply( AttributeKey[Task[T]](label) ) + + def apply[T](akey: AttributeKey[Task[T]]): TaskKey[T] = + new TaskKey[T](akey) +} +object SettingKey +{ + def apply[T](label: String): SettingKey[T] = + apply( AttributeKey[T](label) ) + + def apply[T](akey: AttributeKey[T]): SettingKey[T] = + new SettingKey[T](akey) +} \ No newline at end of file diff --git a/main/Watched.scala b/main/Watched.scala index bb8964c2e..95022b10c 100644 --- a/main/Watched.scala +++ b/main/Watched.scala @@ -16,11 +16,19 @@ trait Watched object Watched { + private[this] class AWatched extends Watched + + def multi(base: Watched, paths: Seq[Watched]): Watched = + new AWatched + { + override val watchPaths = (base.watchPaths /: paths)(_ +++ _.watchPaths) + override def terminateWatch(key: Int): Boolean = base.terminateWatch(key) + } + def empty: Watched = new AWatched + val PollDelaySeconds = 1 def isEnter(key: Int): Boolean = key == 10 || key == 13 -/* - def watched(p: Project): Seq[Watched] = MultiProject.topologicalSort(p).collect { case w: Watched => w } - def sourcePaths(p: Project): PathFinder = (Path.emptyPathFinder /: watched(p))(_ +++ _.watchPaths)*/ + def executeContinuously(watched: Watched, s: State, in: Input): State = { @tailrec def shouldTerminate: Boolean = (System.in.available > 0) && (watched.terminateWatch(System.in.read()) || shouldTerminate) diff --git a/main/ClasspathProject.scala b/main/pending/ClasspathProject.scala similarity index 100% rename from main/ClasspathProject.scala rename to main/pending/ClasspathProject.scala diff --git a/main/DefaultProject.scala b/main/pending/DefaultProject.scala similarity index 96% rename from main/DefaultProject.scala rename to main/pending/DefaultProject.scala index c4625ba21..ac20faf31 100644 --- a/main/DefaultProject.scala +++ b/main/pending/DefaultProject.scala @@ -151,12 +151,6 @@ abstract class BasicProject extends TestProject with MultiClasspathProject with // lazy val test-only, test-quick, test-failed, package-src, package-doc, jetty-{run,stop,restart}, prepare-webapp - lazy val set = input map { in => - val Seq(name, value) = in.splitArgs.take(2) - println(name + "=" + value) - java.lang.System.setProperty(name, value) - } - def sourceFilter: FileFilter = "*.java" | "*.scala" def compileTask(inputs: Task[Compile.Inputs]): Task[Analysis] = @@ -279,10 +273,4 @@ class TestTasks(val prefix: Option[String], val project: ClasspathProject with P Test(frameworkMap, loader, discovered, options, s.log) } lazy val test = (project.streams, executeTests) map { case s :+: results :+: HNil => Test.showResults(s.log, results) } -} -/*class PackageTasks -/* def zipTask(sources: PathFinder, outputDirectory: Path, zipName: => String): Task = - zipTask(sources, outputDirectory / zipName) - def zipTask(sources: PathFinder, zipPath: => Path): Task = - fileTask("zip", zipPath from sources) { FileUtilities.zip(sources.get, zipPath, false, log) }*/ -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/main/MultiProject.scala b/main/pending/MultiProject.scala similarity index 100% rename from main/MultiProject.scala rename to main/pending/MultiProject.scala diff --git a/main/OutputTasks.scala b/main/pending/OutputTasks.scala similarity index 100% rename from main/OutputTasks.scala rename to main/pending/OutputTasks.scala diff --git a/main/SingleProject.scala b/main/pending/SingleProject.scala similarity index 100% rename from main/SingleProject.scala rename to main/pending/SingleProject.scala diff --git a/main/TestProject.scala b/main/pending/TestProject.scala similarity index 100% rename from main/TestProject.scala rename to main/pending/TestProject.scala diff --git a/tasks/standard/Action.scala b/tasks/standard/Action.scala index b529095f1..cc58df7ad 100644 --- a/tasks/standard/Action.scala +++ b/tasks/standard/Action.scala @@ -15,12 +15,9 @@ final case class Mapped[T, In <: HList](in: Tasks[In], f: Results[In] => T) exte final case class FlatMapped[T, In <: HList](in: Tasks[In], f: Results[In] => Task[T]) extends Action[T] final case class DependsOn[T](in: Task[T], deps: Seq[Task[_]]) extends Action[T] final case class Join[T, U](in: Seq[Task[U]], f: Seq[Result[U]] => Either[Task[T], T]) extends Action[T] -final case class CrossAction[T](subs: Cross[Task[T]] ) extends Action[T] - object Task { - type Cross[T] = Seq[(AttributeMap, T)] type Tasks[HL <: HList] = KList[Task, HL] type Results[HL <: HList] = KList[Result, HL] } @@ -36,10 +33,8 @@ final case class Info[T](attributes: AttributeMap = AttributeMap.empty, original import Info._ def name = attributes.get(Name) def description = attributes.get(Description) - def implied = attributes.get(Implied).getOrElse(false) def setName(n: String) = set(Name, n) def setDescription(d: String) = set(Description, d) - def setImplied(i: Boolean) = set(Implied, i) def set[T](key: AttributeKey[T], value: T) = copy(attributes = this.attributes.put(key, value)) override def toString = @@ -52,6 +47,4 @@ object Info { val Name = AttributeKey[String]("name") val Description = AttributeKey[String]("description") - val Implied = AttributeKey[Boolean]("implied") - val Cross = AttributeKey[AttributeMap]("cross-configuration") } \ No newline at end of file diff --git a/tasks/standard/Cross.scala b/tasks/standard/Cross.scala deleted file mode 100644 index eea17a49f..000000000 --- a/tasks/standard/Cross.scala +++ /dev/null @@ -1,98 +0,0 @@ -// TODO: join, reduce -package sbt -package std - - import Types._ - import Task._ - -object Cross -{ - type AttributeSet = Set[AttributeKey[_]] - - def hasCross(s: Seq[Task[_]]): Boolean = s.exists { _.work.isInstanceOf[CrossAction[_]] } - - def extract[T](in: AttributeMap = AttributeMap.empty): Task[T] => Task[T] = (result: Task[T]) => - result.work match - { - case CrossAction(subs) => - subs.filter { x => compatible(in)(x._1) } match { - case Seq( (_,matchingTask) ) => matchingTask - case Seq() => if(subs.isEmpty) error("Cannot introduce cross configurations with flatMap") else error("No compatible cross configurations returned by " + result) - case _ => error("Multiple compatible cross configurations returned by " + result) - } - case _ => result - } - - def compatible(base: AttributeMap)(nested: AttributeMap): Boolean = - nested.entries forall { case AttributeEntry(key, v) => base get(key) filter (_ == v) isDefined } - - def expandUniform[S](uniform: Seq[Task[S]]): Cross[ Seq[Task[S]] ] = - // the cast is necessary to avoid duplicating the contents of expandExist - // and expandCrossed (expandExist cannot be defined in terms of expandUniform - // because there is no valid instatiation of S) - expandExist(uniform).asInstanceOf[ Cross[ Seq[Task[S]] ] ] - - def expandExist(dependsOn: Seq[Task[_]]): Cross[ Seq[Task[_]] ] = - { - val klist = KList.fromList(dependsOn) - map( expandCrossed( klist ) ) { _.toList } - } - - def keys(c: Cross[_]): AttributeSet = - (Set.empty[AttributeKey[_]] /: c ){ _ ++ _._1.keys } - - def uniform[S,T](in: Seq[Task[S]])(f: (AttributeMap, Seq[Task[S]]) => Task[T]): Task[T] = - crossTask( expandUniform(in), f) - - def exist[T](in: Seq[Task[_]])(f: (AttributeMap, Seq[Task[_]]) => Task[T]): Task[T] = - crossTask( expandExist(in), f) - - def apply[T, HL <: HList](in: Tasks[HL])(f: (AttributeMap, Tasks[HL]) => Task[T]): Task[T] = - crossTask( expandCrossed(in), f) - - def crossTask[S,T](subs: Cross[S], f: (AttributeMap, S) => Task[T]): Task[T] = - crossTask( subs map { case (m, t) => (m, f(m, t)) } ) - - def crossTask[T](subs: Cross[Task[T]]): Task[T] = - TaskExtra.actionToTask( CrossAction(subs) ) - - def expandCrossed[HL <: HList](in: Tasks[HL]): Cross[Tasks[HL]] = - in match { - case KCons(head, tail) => - val crossTail = expandCrossed(tail) - head.work match { - case CrossAction(subs) => combine(subs, crossTail)(KCons.apply) - case _ => crossTail map { case (m, k) => (m, KCons(head, k) ) } - } - case x => (AttributeMap.empty, x) :: Nil - } - - - def combine[A,B,C](a: Cross[A], b: Cross[B])(f: (A,B) => C): Cross[C] = - { - val keysA = keys(a) - val keysB = keys(b) - val common = keysA & keysB - if( keysA.size > keysB.size ) - merge(b,a, common)( (x,y) => f(y,x) ) - else - merge(a,b, common)(f) - } - private[this] def merge[A,B,C](a: Cross[A], b: Cross[B], common: AttributeSet)(f: (A,B) => C): Seq[(AttributeMap, C)] = - { - def related(mapA: AttributeMap): Cross[B] = - b filter { case (mapB, _) => - common forall ( c => mapA(c) == mapB(c) ) - } - def check(aRb: Cross[B]) = if(aRb.isEmpty) error("Cross mismatch") else aRb - - for { - (mapA, taskA) <- a - (mapB, taskB) <- check(related(mapA)) - } yield - ( mapA ++ mapB, f(taskA, taskB) ) - } - - def map[A,B](c: Cross[A])(f: A => B): Cross[B] = - for( (m, a) <- c) yield (m, f(a) ) -} \ No newline at end of file diff --git a/tasks/standard/Streams.scala b/tasks/standard/Streams.scala index 70d123492..1eca33c4c 100644 --- a/tasks/standard/Streams.scala +++ b/tasks/standard/Streams.scala @@ -10,20 +10,18 @@ import java.io.{Closeable, File, FileInputStream, FileOutputStream, InputStreamR import Path._ -sealed trait TaskStreams +// no longer specific to Tasks, so 'TaskStreams' should be renamed +sealed trait TaskStreams[Key] { def default = outID def outID = "out" def errorID = "err" - def readText(a: Task[_], sid: String = default, update: Boolean = true): Task[BufferedReader] - def readBinary(a: Task[_], sid: String = default, update: Boolean = true): Task[BufferedInputStream] + def readText(key: Key, sid: String = default): BufferedReader + def readBinary(a: Key, sid: String = default): BufferedInputStream - final def readText(a: Task[_], sid: Option[String], update: Boolean): Task[BufferedReader] = - readText(a, getID(sid), update) - - final def readBinary(a: Task[_], sid: Option[String], update: Boolean): Task[BufferedInputStream] = - readBinary(a, getID(sid), update) + final def readText(a: Key, sid: Option[String]): BufferedReader = readText(a, getID(sid)) + final def readBinary(a: Key, sid: Option[String]): BufferedInputStream = readBinary(a, getID(sid)) def text(sid: String = default): PrintWriter def binary(sid: String = default): BufferedOutputStream @@ -34,37 +32,31 @@ sealed trait TaskStreams private[this] def getID(s: Option[String]) = s getOrElse default } -private[sbt] sealed trait ManagedTaskStreams extends TaskStreams +private[sbt] sealed trait ManagedStreams[Key] extends TaskStreams[Key] { def open() def close() } -sealed trait Streams +sealed trait Streams[Key] { - def apply(a: Task[_], update: Boolean = true): ManagedTaskStreams + def apply(a: Key): ManagedStreams[Key] } object Streams { private[this] val closeQuietly = (c: Closeable) => try { c.close() } catch { case _: IOException => () } - def multi[Owner](bases: Owner => File, taskOwner: Task[_] => Option[Owner], mkLogger: (Task[_], PrintWriter) => Logger): Streams = - { - val taskDirectory = (t: Task[_]) => taskOwner(t) map bases getOrElse error("Cannot get streams for task '" + name(t) + "' with no owner.") - apply(taskDirectory, mkLogger) - } + def apply[Key](taskDirectory: Key => File, name: Key => String, mkLogger: (Key, PrintWriter) => Logger): Streams[Key] = new Streams[Key] { - def apply(taskDirectory: Task[_] => File, mkLogger: (Task[_], PrintWriter) => Logger): Streams = new Streams { streams => - - def apply(a: Task[_], update: Boolean): ManagedTaskStreams = new ManagedTaskStreams { + def apply(a: Key): ManagedStreams[Key] = new ManagedStreams[Key] { private[this] var opened: List[Closeable] = Nil private[this] var closed = false - def readText(a: Task[_], sid: String = default, update: Boolean = true): Task[BufferedReader] = - maybeUpdate(a, readText0(a, sid), update) + def readText(a: Key, sid: String = default): BufferedReader = + make(a, sid)(f => new BufferedReader(new InputStreamReader(new FileInputStream(f), IO.defaultCharset)) ) - def readBinary(a: Task[_], sid: String = default, update: Boolean = true): Task[BufferedInputStream] = - maybeUpdate(a, readBinary0(a, sid), update) + def readBinary(a: Key, sid: String = default): BufferedInputStream = + make(a, sid)(f => new BufferedInputStream(new FileInputStream(f))) def text(sid: String = default): PrintWriter = make(a, sid)(f => new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), IO.defaultCharset))) ) @@ -74,7 +66,7 @@ object Streams def log(sid: String): Logger = mkLogger(a, text(sid)) - def make[T <: Closeable](a: Task[_], sid: String)(f: File => T): T = synchronized { + def make[T <: Closeable](a: Key, sid: String)(f: File => T): T = synchronized { checkOpen() val file = taskDirectory(a) / sid IO.touch(file) @@ -83,20 +75,6 @@ object Streams t } - def readText0(a: Task[_], sid: String): BufferedReader = - make(a, sid)(f => new BufferedReader(new InputStreamReader(new FileInputStream(f), IO.defaultCharset)) ) - - def readBinary0(a: Task[_], sid: String): BufferedInputStream = - make(a, sid)(f => new BufferedInputStream(new FileInputStream(f))) - - def maybeUpdate[T](base: Task[_], result: => T, update: Boolean) = - { - def basic(a: Action[T]) = Task(Info(), a) - val main = Pure(result _) - val act = if(update) DependsOn(basic(main), base :: Nil) else main - basic(act) - } - def open() {} def close(): Unit = synchronized { @@ -111,7 +89,4 @@ object Streams } } } - - def name(a: Task[_]): String = a.info.name getOrElse anonName(a) - def anonName(a: Task[_]) = "anon-" + java.lang.Integer.toString(java.lang.System.identityHashCode(a), 36) } \ No newline at end of file diff --git a/tasks/standard/System.scala b/tasks/standard/System.scala index 9ea822a1b..bc2c6d8ce 100644 --- a/tasks/standard/System.scala +++ b/tasks/standard/System.scala @@ -15,17 +15,17 @@ object System implicit def to_~>| [K[_], V[_]](map: RMap[K,V]) : K ~>| V = new (K ~>| V) { def apply[T](k: K[T]): Option[V[T]] = map.get(k) } - def dummyMap[Input, State, Owner](dummyIn: Task[Input], dummyState: Task[State], dummyCtx: Task[Transform.Context[Owner]])( - in: Input, state: State, context: Transform.Context[Owner]): Task ~>| Task = + def dummyMap[HL <: HList](dummies: KList[Task, HL])(inject: HL): Task ~>| Task = { - // helps ensure that the same Task[Nothing] can't be passed for dummyIn and dummyState - assert((dummyIn ne dummyState) - && (dummyState ne dummyCtx), "Dummy tasks for Input, State, and Context must be distinct.") - val pmap = new DelegatingPMap[Task, Task](new collection.mutable.ListMap) - pmap(dummyIn) = fromDummyStrict(dummyIn, in) - pmap(dummyState) = fromDummyStrict(dummyState, state) - pmap(dummyCtx) = fromDummyStrict(dummyCtx, context) + def loop[HL <: HList](ds: KList[Task, HL], vs: HL): Unit = + (ds, vs) match { + case (KCons(dh, dt), vh :+: vt) => + pmap(dh) = fromDummyStrict(dh, vh) + loop(dt, vt) + case _ => () + } + loop(dummies, inject) pmap } @@ -35,33 +35,6 @@ object System def apply[T](in: Task[T]): Task[T] = map(in).getOrElse(in) } - def implied[Owner](owner: Task[_] => Option[Owner], subs: Owner => Iterable[Owner], static: (Owner, String) => Option[Task[_]]): Task ~> Task = - new (Task ~> Task) { - - def impliedDeps(t: Task[_]): Seq[Task[_]] = - for( n <- t.info.name.toList; o <- owner(t.original).toList; agg <- subs(o); implied <- static(agg, n) ) yield implied - - def withImplied[T](in: Task[T]): Task[T] = - { - val deps = impliedDeps(in) - import TaskExtra._ - if( deps.isEmpty ) in else Task(Info(), DependsOn(in.local, deps)) - } - - def apply[T](in: Task[T]): Task[T] = if(in.info.implied) withImplied(in) else in - } - - def name(staticName: Task[_] => Option[String]): Task ~> Task = - new (Task ~> Task) { - def apply[T](in: Task[T]): Task[T] = { - val finalName = in.info.name orElse staticName(in.original) - finalName match { - case None => in - case Some(finalName) => in.copy(info = in.info.setName(finalName) ) - } - } - } - /** Creates a natural transformation that replaces occurrences of 'a' with 'b'. * Only valid for M invariant in its type parameter. */ def replace[M[_] <: AnyRef, A](a: M[A], b: M[A]) = new (M ~> M) { @@ -79,13 +52,13 @@ object System } - def streamed(streams: Streams, dummy: Task[TaskStreams]): Task ~> Task = + def streamed[Key](streams: Streams[Key], dummy: Task[TaskStreams[Key]], key: Task[_] => Key): Task ~> Task = new (Task ~> Task) { def apply[T](t: Task[T]): Task[T] = if(usedInputs(t.work) contains dummy) substitute(t) else t def substitute[T](t: Task[T]): Task[T] = { - val inStreams = streams(t) + val inStreams = streams(key(t)) val streamsTask = fromDummy(dummy){ inStreams.open(); inStreams } val depMap = replace( dummy, streamsTask ) @@ -107,40 +80,14 @@ object System } object Transform { - final class Dummies[Input, State, Owner](val dummyIn: Task[Input], val dummyState: Task[State], val dummyStreams: Task[TaskStreams], val dummyContext: Task[Context[Owner]]) - final class Injected[Input, State](val in: Input, val state: State, val streams: Streams) - trait Context[Owner] - { - def rootOwner: Owner - def staticName: Task[_] => Option[String] - def owner: Task[_] => Option[Owner] - def ownerName: Owner => Option[String] - def aggregate: Owner => Iterable[Owner] - def static: (Owner, String) => Option[Task[_]] - def allTasks(owner: Owner): Iterable[Task[_]] - def ownerForName(name: String): Option[Owner] - } - def setOriginal(delegate: Task ~> Task): Task ~> Task = - new (Task ~> Task) { - def apply[T](in: Task[T]): Task[T] = - { - val transformed = delegate(in) - if( (transformed eq in) || transformed.info.original.isDefined) - transformed - else - transformed.copy(info = transformed.info.copy(original = in.info.original orElse Some(in))) - } - } + final class Dummies[HL <: HList, Key](val direct: KList[Task, HL], val streams: Task[TaskStreams[Key]]) + final class Injected[HL <: HList, Key](val direct: HL, val streams: Streams[Key]) - def apply[Input, State, Owner](dummies: Dummies[Input, State, Owner], injected: Injected[Input, State], context: Context[Owner]) = + def apply[HL <: HList, Key](dummies: Dummies[HL, Key], injected: Injected[HL, Key])(implicit getKey: Task[_] => Key) = { - import dummies._ - import injected._ - import context._ import System._ - import Convert._ - val inputs = dummyMap(dummyIn, dummyState, dummyContext)(in, state, context) - Convert.taskToNode ∙ setOriginal(streamed(streams, dummyStreams)) ∙ implied(owner, aggregate, static) ∙ setOriginal(name(staticName)) ∙ getOrId(inputs) + val inputs = dummyMap(dummies.direct)(injected.direct) + Convert.taskToNode ∙ streamed(injected.streams, dummies.streams, getKey) ∙ getOrId(inputs) } } object Convert @@ -152,7 +99,6 @@ object Convert case FlatMapped(in, f) => toNode(in)( left ∙ f ) case DependsOn(in, deps) => toNode(KList.fromList(deps))( _ => Left(in) ) case Join(in, f) => uniform(in)(f) - case CrossAction(subs) => error("Cannot run cross task: " + subs.mkString("\n\t","\n\t","\n")) } } diff --git a/tasks/standard/TaskExtra.scala b/tasks/standard/TaskExtra.scala index 10b0bf861..ab337102d 100644 --- a/tasks/standard/TaskExtra.scala +++ b/tasks/standard/TaskExtra.scala @@ -7,7 +7,6 @@ package std import Types._ import Task._ import java.io.{BufferedInputStream, BufferedReader, File, InputStream} - import Cross.{combine, crossTask, exist, expandExist, extract, hasCross, uniform} sealed trait MultiInTask[In <: HList] { @@ -33,17 +32,10 @@ sealed trait SingleInTask[S] def || [T >: S](alt: Task[T]): Task[T] def && [T](alt: Task[T]): Task[T] } -sealed trait CrossMerge[T] -{ - def merge: Task[Cross[T]] -} sealed trait TaskInfo[S] { - def named(s: String): Task[S] def describedAs(s: String): Task[S] - def implies: Task[S] - def implied(flag: Boolean): Task[S] - def local: Task[S] + def named(s: String): Task[S] } sealed trait ForkTask[S, CC[_]] { @@ -84,9 +76,6 @@ trait TaskExtra final def nop: Task[Unit] = const( () ) final def const[T](t: T): Task[T] = task(t) - final def cross[T](key: AttributeKey[T])(values: T*): Task[T] = - CrossAction( for(v <- values) yield ( AttributeMap.empty put (key, v), task(v) ) ) - 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) } @@ -106,87 +95,43 @@ trait TaskExtra } final implicit def joinAnyTasks(in: Seq[Task[_]]): JoinTask[Any, Seq] = joinTasks[Any](in map (x => x: Task[Any])) - final implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = - if(hasCross(in)) multJoin(in) else basicJoin(in) - - final def multJoin[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { - def join: Task[Seq[S]] = uniform(in)( (_, s) => basicJoin(s).join ) - def reduce(f: (S,S) => S): Task[S] = basicJoin(in) reduce f - } - final def basicJoin[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { + final implicit def joinTasks[S](in: Seq[Task[S]]): JoinTask[S, Seq] = new JoinTask[S, Seq] { def join: Task[Seq[S]] = new Join(in, (s: Seq[Result[S]]) => Right(TaskExtra.all(s)) ) def reduce(f: (S,S) => S): Task[S] = TaskExtra.reduce(in.toIndexedSeq, f) } - final implicit def crossMerge[T](in: Task[T]): CrossMerge[T] = new CrossMerge[T] { - def merge: Task[Cross[T]] = in.work match { - case CrossAction(subs) => - val (maps, tasks) = subs.unzip - tasks.join.map { maps zip _ } - case _ => in map( x => (AttributeMap.empty, x) :: Nil) - } - } - final implicit def multInputTask[In <: HList](tasks: Tasks[In]): MultiInTask[In] = - if(hasCross(tasks.toList)) multCross(tasks) else multBasic(tasks) - - final def multCross[In <: HList](tasks: Tasks[In]): MultiInTask[In] = new MultiBase[In] { - def flatMapR[T](f: Results[In] => Task[T]): Task[T] = Cross(tasks)( (m, ts) => new FlatMapped[T,In](ts, extract[T](m) compose f) ) - def mapR[T](f: Results[In] => T): Task[T] = Cross(tasks)( (_, ts) => multBasic(ts) mapR f) - } - - final def multBasic[In <: HList](tasks: Tasks[In]): MultiInTask[In] = new MultiBase[In] { - def flatMapR[T](f: Results[In] => Task[T]): Task[T] = new FlatMapped(tasks, extract() ∙ f) + final implicit def multInputTask[In <: HList](tasks: Tasks[In]): MultiInTask[In] = new MultiBase[In] { + def flatMapR[T](f: Results[In] => Task[T]): Task[T] = new FlatMapped(tasks, f) def mapR[T](f: Results[In] => T): Task[T] = new Mapped(tasks, f) } - final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = - in.work match { - case CrossAction(subs) => singleCross(in, subs) - case x => singleBasic(in) - } - - final def singleCross[S](in: Task[S], subs: Cross[Task[S]]): SingleInTask[S] = - new SingleBase[S] { - def impl[T](f: (AttributeMap, Task[S]) => Task[T]): Task[T] = CrossAction( subs map { case (m, t) => (m, f(m, t)) } ) - def flatMapR[T](f: Result[S] => Task[T]): Task[T] = impl( (m, t) => singleBasic(t) flatMapR( Cross.extract(m) ∙ f) ) - def mapR[T](f: Result[S] => T): Task[T] = impl( (m,t) => t mapR f) - def dependsOn(tasks: Task[_]*): Task[S] = crossTask( combine( subs, expandExist(tasks) ){ (t,deps) => new DependsOn(t, deps) } ) - } - - final def singleBasic[S](in: Task[S]): SingleInTask[S] = new SingleBase[S] { + final implicit def singleInputTask[S](in: Task[S]): SingleInTask[S] = new SingleBase[S] { type HL = S :+: HNil private val ml = in :^: KNil private def headM = (_: Results[HL]).combine.head - def flatMapR[T](f: Result[S] => Task[T]): Task[T] = new FlatMapped[T, HL](ml, Cross.extract() ∙ f ∙ headM) + def flatMapR[T](f: Result[S] => Task[T]): Task[T] = new FlatMapped[T, HL](ml, f ∙ headM) def mapR[T](f: Result[S] => T): Task[T] = new Mapped[T, HL](ml, f ∙ headM) - def dependsOn(tasks: Task[_]*): Task[S] = - if(hasCross(tasks)) - singleCross(in, (AttributeMap.empty, in) :: Nil).dependsOn(tasks :_*) - else - new DependsOn(in, tasks) + def dependsOn(tasks: Task[_]*): Task[S] = new DependsOn(in, tasks) } final implicit def toTaskInfo[S](in: Task[S]): TaskInfo[S] = new TaskInfo[S] { - def named(s: String): Task[S] = in.copy(info = in.info.setName(s)) def describedAs(s: String): Task[S] = in.copy(info = in.info.setDescription(s)) - def implied(flag: Boolean): Task[S] = in.copy(info = in.info.setImplied(flag)) - def implies: Task[S] = implied(true) - def local: Task[S] = implied(false) - + def named(s: String): Task[S] = in.copy(info = in.info.setName(s)) } - final implicit def pipeToProcess(t: Task[_])(implicit streams: Task[TaskStreams]): ProcessPipe = new ProcessPipe { + final implicit def pipeToProcess[Key](t: Task[_])(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): ProcessPipe = new ProcessPipe { def #| (p: ProcessBuilder): Task[Int] = pipe0(None, p) def pipe(sid: String)(p: ProcessBuilder): Task[Int] = pipe0(Some(sid), p) private def pipe0(sid: Option[String], p: ProcessBuilder): Task[Int] = - for(s <- streams; in <- s.readBinary(t, sid, true)) yield { + for(s <- streams) yield { + val in = s.readBinary(key(t), sid) val pio = TaskExtra.processIO(s).withInput( out => { BasicIO.transferFully(in, out); out.close() } ) (p run pio).exitValue } } - final implicit def binaryPipeTask(in: Task[_])(implicit streams: Task[TaskStreams]): BinaryPipe = new BinaryPipe { + final implicit def binaryPipeTask[Key](in: Task[_])(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): BinaryPipe = new BinaryPipe { def binary[T](f: BufferedInputStream => T): Task[T] = pipe0(None, f) def binary[T](sid: String)(f: BufferedInputStream => T): Task[T] = pipe0(Some(sid), f) @@ -194,25 +139,25 @@ trait TaskExtra def #>(sid: String, f: File): Task[Unit] = pipe0(Some(sid), toFile(f)) private def pipe0 [T](sid: Option[String], f: BufferedInputStream => T): Task[T] = - streams flatMap { s => s.readBinary(in, sid, true) map f } + streams map { s => f(s.readBinary(key(in), sid)) } private def toFile(f: File) = (in: InputStream) => IO.transfer(in, f) } - final implicit def textPipeTask(in: Task[_])(implicit streams: Task[TaskStreams]): TextPipe = new TextPipe { + final implicit def textPipeTask[Key](in: Task[_])(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): TextPipe = new TextPipe { def text[T](f: BufferedReader => T): Task[T] = pipe0(None, f) def text [T](sid: String)(f: BufferedReader => T): Task[T] = pipe0(Some(sid), f) private def pipe0 [T](sid: Option[String], f: BufferedReader => T): Task[T] = - streams flatMap { s => s.readText(in, sid, true) map f } + streams map { s => f(s.readText(key(in), sid)) } } - final implicit def linesTask(in: Task[_])(implicit streams: Task[TaskStreams]): TaskLines = new TaskLines { + final implicit def linesTask[Key](in: Task[_])(implicit streams: Task[TaskStreams[Key]], key: Task[_] => Key): TaskLines = new TaskLines { def lines: Task[List[String]] = lines0(None) def lines(sid: String): Task[List[String]] = lines0(Some(sid)) private def lines0 [T](sid: Option[String]): Task[List[String]] = - streams flatMap { s => s.readText(in, sid, true) map IO.readLines } + streams map { s => IO.readLines(s.readText(key(in), sid) ) } } - implicit def processToTask(p: ProcessBuilder)(implicit streams: Task[TaskStreams]): Task[Int] = streams map { s => + implicit def processToTask(p: ProcessBuilder)(implicit streams: Task[TaskStreams[_]]): Task[Int] = streams map { s => val pio = TaskExtra.processIO(s) (p run pio).exitValue } @@ -245,7 +190,7 @@ trait TaskExtra } object TaskExtra extends TaskExtra { - def processIO(s: TaskStreams): ProcessIO = + def processIO(s: TaskStreams[_]): ProcessIO = { def transfer(id: String) = (in: InputStream) => BasicIO.transferFully(in, s.binary(id)) new ProcessIO(BasicIO.ignoreOut, transfer(s.outID), transfer(s.errorID)) diff --git a/tasks/standard/TaskMap.scala b/tasks/standard/TaskMap.scala deleted file mode 100644 index a4177d5d9..000000000 --- a/tasks/standard/TaskMap.scala +++ /dev/null @@ -1,20 +0,0 @@ -/* sbt -- Simple Build Tool - * Copyright 2010 Mark Harrah - */ -package sbt - - import std._ - import TaskExtra._ - import collection.mutable - -object TaskMap -{ - /** Memoizes a function that produces a Task. */ - def apply[A,B](f: A => Task[B]): A => Task[B] = - { - val map = new mutable.HashMap[A,Task[B]] - a => map.synchronized { map.getOrElseUpdate(a, f(a)) } - } - /** Lifts `f` to produce a Task and memoizes the lifted function. */ - def make[A,B](f: A => B): A => Task[B] = apply( a => task(f(a)) ) -} \ No newline at end of file diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index c1ea10cb7..cd94f012a 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -1,3 +1,6 @@ +/* sbt -- Simple Build Tool + * Copyright 2011 Mark Harrah + */ package sbt import Types._ @@ -6,15 +9,17 @@ package sbt sealed trait Settings[Scope] { - def data: Scope => AttributeMap - def scopes: Seq[Scope] + def data: Map[Scope, AttributeMap] + def keys(scope: Scope): Set[AttributeKey[_]] + def scopes: Set[Scope] def get[T](scope: Scope, key: AttributeKey[T]): Option[T] def set[T](scope: Scope, key: AttributeKey[T], value: T): Settings[Scope] } private final class Settings0[Scope](val data: Map[Scope, AttributeMap], val delegates: Scope => Seq[Scope]) extends Settings[Scope] { - def scopes: Seq[Scope] = data.keys.toSeq + def scopes: Set[Scope] = data.keySet.toSet + def keys(scope: Scope) = data(scope).keys.toSet def get[T](scope: Scope, key: AttributeKey[T]): Option[T] = delegates(scope).toStream.flatMap(sc => scopeLocal(sc, key) ).headOption @@ -30,7 +35,8 @@ private final class Settings0[Scope](val data: Map[Scope, AttributeMap], val del } } // delegates should contain the input Scope as the first entry -final class Init[Scope](val delegates: Scope => Seq[Scope]) +// this trait is intended to be mixed into an object +trait Init[Scope] { final case class ScopedKey[T](scope: Scope, key: AttributeKey[T]) @@ -42,18 +48,28 @@ final class Init[Scope](val delegates: Scope => Seq[Scope]) def value[T](key: ScopedKey[T])(value: => T): Setting[T] = new Value(key, value _) def update[T](key: ScopedKey[T])(f: T => T): Setting[T] = app(key, key :^: KNil)(h => f(h.head)) def app[HL <: HList, T](key: ScopedKey[T], inputs: KList[ScopedKey, HL])(f: HL => T): Setting[T] = new Apply(key, f, inputs) + def uniform[S,T](key: ScopedKey[T], inputs: Seq[ScopedKey[S]])(f: Seq[S] => T): Setting[T] = new Uniform(key, f, inputs) + def kapp[HL <: HList, M[_], T](key: ScopedKey[T], inputs: KList[({type l[t] = ScopedKey[M[t]]})#l, HL])(f: KList[M, HL] => T): Setting[T] = new KApply[HL, M, T](key, f, inputs) - def empty: Settings[Scope] = new Settings0(Map.empty, delegates) - def asTransform(s: Settings[Scope]): ScopedKey ~> Id = new (ScopedKey ~> Id) { - def apply[T](k: ScopedKey[T]): T = s.get(k.scope, k.key).get + // the following is a temporary workaround for the "... cannot be instantiated from ..." bug, which renders 'kapp' above unusable outside this source file + class KApp[HL <: HList, M[_], T] { + type Composed[S] = ScopedKey[M[S]] + def apply(key: ScopedKey[T], inputs: KList[Composed, HL])(f: KList[M, HL] => T): Setting[T] = new KApply[HL, M, T](key, f, inputs) } - def make(init: Seq[Setting[_]]): Settings[Scope] = + def empty(implicit delegates: Scope => Seq[Scope]): Settings[Scope] = new Settings0(Map.empty, delegates) + def asTransform(s: Settings[Scope]): ScopedKey ~> Id = new (ScopedKey ~> Id) { + def apply[T](k: ScopedKey[T]): T = getValue(s, k) + } + def getValue[T](s: Settings[Scope], k: ScopedKey[T]) = s.get(k.scope, k.key).get + def asFunction[T](s: Settings[Scope]): ScopedKey[T] => T = k => getValue(s, k) + + def make(init: Seq[Setting[_]])(implicit delegates: Scope => Seq[Scope]): Settings[Scope] = { // group by Scope/Key, dropping dead initializations val sMap: ScopedMap = grouped(init) // delegate references to undefined values according to 'delegates' - val dMap: ScopedMap = delegate(sMap) + val dMap: ScopedMap = delegate(sMap)(delegates) // merge Seq[Setting[_]] into Compiled val cMap: CompiledMap = compile(dMap) // order the initializations. cyclic references are detected here. @@ -80,9 +96,9 @@ final class Init[Scope](val delegates: Scope => Seq[Scope]) def append[T](ss: Seq[Setting[T]], s: Setting[T]): Seq[Setting[T]] = if(s.definitive) s :: Nil else ss :+ s - def delegate(sMap: ScopedMap): ScopedMap = + def delegate(sMap: ScopedMap)(implicit delegates: Scope => Seq[Scope]): ScopedMap = { - val md = memoDelegates + val md = memoDelegates(delegates) def refMap(refKey: ScopedKey[_]) = new (ScopedKey ~> ScopedKey) { def apply[T](k: ScopedKey[T]) = mapReferenced(sMap, k, md(k.scope), refKey) } val f = new (SettingSeq ~> SettingSeq) { def apply[T](ks: Seq[Setting[T]]) = ks.map{ s => s mapReferenced refMap(s.key) } } sMap mapValues f @@ -102,21 +118,27 @@ final class Init[Scope](val delegates: Scope => Seq[Scope]) private[this] def defines(map: ScopedMap, key: ScopedKey[_], refKey: ScopedKey[_]): Boolean = (map get key) match { case Some(Seq(x, _*)) => (refKey != key) || x.definitive; case _ => false } - private[this] def applyInits(ordered: Seq[Compiled]): Settings[Scope] = + private[this] def applyInits(ordered: Seq[Compiled])(implicit delegates: Scope => Seq[Scope]): Settings[Scope] = (empty /: ordered){ (m, comp) => comp.eval(m) } - private[this] def memoDelegates: Scope => Seq[Scope] = + private[this] def memoDelegates(implicit delegates: Scope => Seq[Scope]): Scope => Seq[Scope] = { val dcache = new mutable.HashMap[Scope, Seq[Scope]] (scope: Scope) => dcache.getOrElseUpdate(scope, delegates(scope)) } - private[this] def applySetting[T](map: Settings[Scope], a: Setting[T]): Settings[Scope] = - a match + private[this] def applySetting[T](map: Settings[Scope], setting: Setting[T]): Settings[Scope] = + { + def execK[HL <: HList, M[_]](a: KApply[HL, M, T]) = + map.set(a.key.scope, a.key.key, a.f(a.inputs.transform[M]( nestCon[ScopedKey, Id, M](asTransform(map)) )) ) + setting match { case s: Value[T] => map.set(s.key.scope, s.key.key, s.value()) - case a: Apply[hl, T] => map.set(a.key.scope, a.key.key, a.f(a.inputs.down(asTransform(map)))) + case u: Uniform[s, T] => map.set(u.key.scope, u.key.key, u.f(u.inputs map asFunction(map)) ) + case a: Apply[hl, T] => map.set(a.key.scope, a.key.key, a.f(a.inputs down asTransform(map) ) ) + case ka: KApply[hl, m, T] => execK[hl, m](ka) // separate method needed to workaround bug where m is not recognized as higher-kinded in inline version } + } final class Uninitialized(key: ScopedKey[_]) extends Exception("Update on uninitialized setting " + key.key.label + " (in " + key.scope + ")") final class Compiled(val dependencies: Iterable[ScopedKey[_]], val eval: Settings[Scope] => Settings[Scope]) @@ -126,18 +148,37 @@ final class Init[Scope](val delegates: Scope => Seq[Scope]) def key: ScopedKey[T] def definitive: Boolean def dependsOn: Seq[ScopedKey[_]] - def mapReferenced(f: MapScoped): Setting[T] + def mapReferenced(g: MapScoped): Setting[T] + def mapKey(g: MapScoped): Setting[T] } private[this] final class Value[T](val key: ScopedKey[T], val value: () => T) extends Setting[T] { def definitive = true def dependsOn = Nil - def mapReferenced(f: MapScoped) = this + def mapReferenced(g: MapScoped) = this + def mapKey(g: MapScoped): Setting[T] = new Value(g(key), value) } private[this] final class Apply[HL <: HList, T](val key: ScopedKey[T], val f: HL => T, val inputs: KList[ScopedKey, HL]) extends Setting[T] { def definitive = !inputs.toList.contains(key) - def dependsOn = inputs.toList - key + def dependsOn = remove(inputs.toList, key) def mapReferenced(g: MapScoped) = new Apply(key, f, inputs transform g) + def mapKey(g: MapScoped): Setting[T] = new Apply(g(key), f, inputs) } + private[this] final class KApply[HL <: HList, M[_], T](val key: ScopedKey[T], val f: KList[M, HL] => T, val inputs: KList[({type l[t] = ScopedKey[M[t]]})#l, HL]) extends Setting[T] + { + def definitive = !inputs.toList.contains(key) + def dependsOn = remove(unnest(inputs.toList), key) + def mapReferenced(g: MapScoped) = new KApply[HL, M, T](key, f, inputs.transform[({type l[t] = ScopedKey[M[t]]})#l]( nestCon(g) ) ) + def mapKey(g: MapScoped): Setting[T] = new KApply[HL, M, T](g(key), f, inputs) + private[this] def unnest(l: List[ScopedKey[M[T]] forSome { type T }]): List[ScopedKey[_]] = l.asInstanceOf[List[ScopedKey[_]]] + } + private[this] final class Uniform[S, T](val key: ScopedKey[T], val f: Seq[S] => T, val inputs: Seq[ScopedKey[S]]) extends Setting[T] + { + def definitive = !inputs.contains(key) + def dependsOn = remove(inputs, key) + def mapReferenced(g: MapScoped) = new Uniform(key, f, inputs map g.fn[S]) + def mapKey(g: MapScoped): Setting[T] = new Uniform(g(key), f, inputs) + } + private def remove[T](s: Seq[T], v: T) = s filterNot (_ == v) } diff --git a/util/collection/TypeFunctions.scala b/util/collection/TypeFunctions.scala index ca173f9bc..93e44154a 100644 --- a/util/collection/TypeFunctions.scala +++ b/util/collection/TypeFunctions.scala @@ -8,16 +8,24 @@ trait TypeFunctions type Id[X] = X sealed trait Const[A] { type Apply[B] = A } sealed trait Compose[A[_], B[_]] { type Apply[T] = A[B[T]] } + sealed trait ∙[A[_], B[_]] { type l[T] = A[B[T]] } sealed trait P1of2[M[_,_], A] { type Apply[B] = M[A,B]; type Flip[B] = M[B, A] } final val left = new (Id ~> P1of2[Left, Nothing]#Flip) { def apply[T](t: T) = Left(t) } 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) } + 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: + /* new ( (M ∙ G)#l ~> (N ∙ G)#l ) { + def apply[T](mg: M[G[T]]): N[G[T]] = f(mg) + }*/ + implicit def toFn1[A,B](f: A => B): Fn1[A,B] = new Fn1[A,B] { def ∙[C](g: C => A) = f compose g } + type Endo[T] = T=>T type ~>|[A[_],B[_]] = A ~> Compose[Option, B]#Apply } object TypeFunctions extends TypeFunctions diff --git a/util/collection/Util.scala b/util/collection/Util.scala new file mode 100644 index 000000000..a3bf330aa --- /dev/null +++ b/util/collection/Util.scala @@ -0,0 +1,17 @@ +/* sbt -- Simple Build Tool + * Copyright 2011 Mark Harrah + */ +package sbt + +object Collections +{ + def separate[T,A,B](ps: Seq[T])(f: T => Either[A,B]): (Seq[A], Seq[B]) = + ((Nil: Seq[A], Nil: Seq[B]) /: ps)( (xs, y) => prependEither(xs, f(y)) ) + + def prependEither[A,B](acc: (Seq[A], Seq[B]), next: Either[A,B]): (Seq[A], Seq[B]) = + next match + { + case Left(l) => (l +: acc._1, acc._2) + case Right(r) => (acc._1, r +: acc._2) + } +} \ No newline at end of file