This commit is contained in:
Mark Harrah 2011-03-19 14:06:11 -04:00
parent 50f1bd73d6
commit 3821239b43
27 changed files with 0 additions and 4727 deletions

View File

@ -1,4 +0,0 @@
<plugin>
<name>sbt-analyze</name>
<classname>sbt.Analyzer</classname>
</plugin>

View File

@ -1,571 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt
import scala.xml.{Node, NodeSeq}
import StringUtilities.{appendable,nonEmpty}
import BasicManagedProject._
/** A project that provides a classpath. */
trait ClasspathProject extends Project
{
/** The local classpath for this project.*/
def projectClasspath(config: Configuration): PathFinder
/** Returns the classpath of this project and the classpaths of all dependencies for the
* given configuration. Specifically, this concatentates projectClasspath(config) for all
* projects of type ClasspathProject in topologicalSort. */
def fullClasspath(config: Configuration): PathFinder =
Path.lazyPathFinder
{
val set = new wrap.MutableSetWrapper(new java.util.LinkedHashSet[Path])
for(project <- topologicalSort)
{
project match
{
case sp: ClasspathProject => set ++= sp.projectClasspath(config).get
case _ => ()
}
}
set.toList
}
/* Filter used to select dependencies for the classpath from managed and unmanaged directories.
* By default, it explicitly filters (x)sbt-launch(er)-<version>.jar, since it contains minified versions of various classes.*/
def classpathFilter: FileFilter = "*.jar" - "*sbt-launch*.jar"
}
trait BasicDependencyProject extends BasicManagedProject with UnmanagedClasspathProject
{
/** This returns the classpath for only this project for the given configuration.*/
def projectClasspath(config: Configuration) = fullUnmanagedClasspath(config) +++ managedClasspath(config)
}
/** A project that provides a directory in which jars can be manually managed.*/
trait UnmanagedClasspathProject extends ClasspathProject
{
/** The location of the manually managed (unmanaged) dependency directory.*/
def dependencyPath: Path
/** The classpath containing all jars in the unmanaged directory. */
def unmanagedClasspath: PathFinder =
{
val base = descendents(dependencyPath, classpathFilter)
if(scratch)
base +++ (info.projectPath * classpathFilter)
else
base
}
/** The classpath containing all unmanaged classpath elements for the given configuration. This typically includes
* at least 'unmanagedClasspath'.*/
def fullUnmanagedClasspath(config: Configuration): PathFinder
}
trait IvyTasks extends Project
{
def ivyTask(action: => Unit) =
task
{
try { action; None }
catch {
case e: ResolveException =>
log.error(e.toString)
Some(e.toString)
case e: Exception =>
log.trace(e)
log.error(e.toString)
Some(e.toString)
}
}
def updateTask(module: => IvySbt#Module, configuration: => UpdateConfiguration) =
ivyTask { IvyActions.update(module, configuration) }
def publishTask(module: => IvySbt#Module, publishConfiguration: => PublishConfiguration) =
ivyTask
{
val publishConfig = publishConfiguration
import publishConfig._
val deliveredIvy = if(publishIvy) Some(deliveredPattern) else None
IvyActions.publish(module, resolverName, srcArtifactPatterns, deliveredIvy, configurations)
}
def deliverTask(module: => IvySbt#Module, deliverConfiguration: => PublishConfiguration, logging: => UpdateLogging.Value) =
ivyTask
{
val deliverConfig = deliverConfiguration
import deliverConfig._
IvyActions.deliver(module, status, deliveredPattern, extraDependencies, configurations, logging)
}
@deprecated def makePomTask(module: => IvySbt#Module, output: => Path, extraDependencies: => Iterable[ModuleID], pomExtra: => NodeSeq, configurations: => Option[Iterable[Configuration]]): Task =
makePomTask(module, MakePomConfiguration(extraDependencies, configurations, pomExtra), output)
def makePomTask(module: => IvySbt#Module, configuration: => MakePomConfiguration, output: => Path): Task =
ivyTask { IvyActions.makePom(module, configuration, output asFile) }
def installTask(module: IvySbt#Module, from: Resolver, to: Resolver) =
ivyTask { IvyActions.install(module, from.name, to.name) }
def cleanCacheTask(ivySbt: => IvySbt) =
ivyTask { IvyActions.cleanCache(ivySbt) }
def cleanLibTask(managedDependencyPath: Path) =
task { FileUtilities.clean(managedDependencyPath.get, log) }
}
/** A project that provides automatic dependency management.*/
trait ManagedProject extends ClasspathProject with IvyTasks
{
/** This is the public ID of the project (used for publishing, for example) */
def moduleID: String = normalizedName
/** This is the full public ID of the project (used for publishing, for example) */
def projectID: ModuleID = ModuleID(organization, moduleID, version.toString).artifacts(artifacts.toSeq : _*).cross(true)
/** This is the default name for artifacts (such as jars) without any version string.*/
def artifactID = moduleID
/** This is the default name for artifacts (such as jars) including the version string.*/
def artifactBaseName = artifactID + "-" + version.toString
def artifacts: Iterable[Artifact]
def managedDependencyPath: Path
/** The managed classpath for the given configuration. This can be overridden to add jars from other configurations
* so that the Ivy 'extends' mechanism is not required. That way, the jars are only copied to one configuration.*/
def managedClasspath(config: Configuration): PathFinder = configurationClasspath(config)
/** All dependencies in the given configuration. */
final def configurationClasspath(config: Configuration): PathFinder = descendents(configurationPath(config), classpathFilter)
/** The base path to which dependencies in configuration 'config' are downloaded.*/
def configurationPath(config: Configuration): Path = managedDependencyPath / config.toString
/** Creates a new configuration with the given name.*/
def config(name: String) = new Configuration(name)
}
/** This class groups required configuration for the deliver and publish tasks. */
trait PublishConfiguration extends NotNull
{
/** The name of the resolver to which publishing should be done.*/
def resolverName: String
/** The Ivy pattern used to determine the delivered Ivy file location. An example is
* (outputPath / "[artifact]-[revision].[ext]").relativePath. */
def deliveredPattern: String
/** Ivy patterns used to find artifacts for publishing. An example pattern is
* (outputPath / "[artifact]-[revision].[ext]").relativePath */
def srcArtifactPatterns: Iterable[String]
/** Additional dependencies to include for delivering/publishing only. These are typically dependencies on
* subprojects. */
def extraDependencies: Iterable[ModuleID]
/** The status to use when delivering or publishing. This might be "release" or "integration" or another valid Ivy status. */
def status: String
/** The configurations to include in the publish/deliver action: specify none for all configurations. */
def configurations: Option[Iterable[Configuration]]
/** True if the Ivy file should be published. */
def publishIvy: Boolean
}
object ManagedStyle extends Enumeration
{
val Maven, Ivy, Auto = Value
}
import ManagedStyle.{Auto, Ivy, Maven, Value => ManagedType}
trait BasicManagedProject extends ManagedProject with ReflectiveManagedProject with BasicDependencyPaths
{
def ivyUpdateConfiguration = new UpdateConfiguration(managedDependencyPath.asFile, outputPattern, true/*sync*/, ivyUpdateLogging)
def ivyUpdateLogging = UpdateLogging.DownloadOnly
def ivyLocalOnly: Boolean = offline.value
def ivyRepositories: Seq[Resolver] =
{
val repos = repositories.toSeq
if(repos.isEmpty) Nil else Resolver.withDefaultResolvers(repos)
}
def otherRepositories: Seq[Resolver] = defaultPublishRepository.toList
def ivyValidate = true
def ivyScala: Option[IvyScala] = Some(new IvyScala(buildScalaVersion, checkScalaInConfigurations, checkExplicitScalaDependencies, filterScalaJars))
def ivyCacheDirectory: Option[Path] = None
def ivyPaths: IvyPaths = new IvyPaths(info.projectPath.asFile, ivyCacheDirectory.map(_.asFile))
def inlineIvyConfiguration = new InlineIvyConfiguration(ivyPaths, ivyRepositories.toSeq, otherRepositories, moduleConfigurations.toSeq, ivyLocalOnly, Some(info.launcher.globalLock), log)
def ivyConfiguration: IvyConfiguration =
{
val in = inlineIvyConfiguration
def adapt(c: IvyConfiguration): IvyConfiguration = c.withBase(in.baseDirectory)
def parentIvyConfiguration(default: IvyConfiguration)(p: Project) = p match { case b: BasicManagedProject => adapt(b.ivyConfiguration); case _ => default }
if(in.resolvers.isEmpty)
{
if(in.moduleConfigurations.isEmpty && in.otherResolvers.isEmpty)
{
IvyConfiguration(in.paths, in.lock, in.localOnly, in.log) match
{
case e: ExternalIvyConfiguration => e
case i => info.parent map(parentIvyConfiguration(i)) getOrElse(i)
}
}
else
in.changeResolvers(Resolver.withDefaultResolvers(Nil))
}
else
in
}
def moduleSettings: ModuleSettings = defaultModuleSettings
def byIvyFile(path: Path): IvyFileConfiguration = new IvyFileConfiguration(path.asFile, ivyScala, ivyValidate)
def byPom(path: Path): PomConfiguration = new PomConfiguration(path.asFile, ivyScala, ivyValidate)
/** The settings that represent inline declarations. The default settings combines the information
* from 'ivyXML', 'projectID', 'repositories', ivyConfigurations, defaultConfiguration,
* ivyScala, and 'libraryDependencies' and does not typically need to be be overridden. */
def inlineSettings = new InlineConfiguration(projectID, withCompat, ivyXML, ivyConfigurations, defaultConfiguration, ivyScala, ivyValidate)
/** Library dependencies with extra dependencies for compatibility*/
private def withCompat =
{
val deps = libraryDependencies
deps ++ compatExtra(deps)
}
/** Determines extra libraries needed for compatibility. Currently, this is the compatibility test framework. */
private def compatExtra(deps: Set[ModuleID]) =
if(isScala27 && deps.exists(requiresCompat)) { log.debug("Using compatibility implementation of test interface."); compatTestFramework } else Nil
/** True if the given dependency requires the compatibility test framework. */
private def requiresCompat(m: ModuleID) =
{
def nameMatches(name: String, id: String) = name == id || name.startsWith(id + "_2.7.")
(nameMatches(m.name, "scalacheck") && Set("1.5", "1.6").contains(m.revision)) ||
(nameMatches(m.name, "specs") && Set("1.6.0", "1.6.1").contains(m.revision)) ||
(nameMatches(m.name, "scalatest") && m.revision == "1.0")
}
/** Extra dependencies to add if a dependency on an older test framework (one released before the uniform test interface) is declared.
* This is the compatibility test framework by default.*/
def compatTestFramework = Set("org.scala-tools.sbt" %% "test-compat" % "0.4.1" % "test")
def defaultModuleSettings: ModuleSettings =
{
val in = inlineSettings
if(in.configurations.isEmpty)
{
if(in.dependencies.isEmpty && in.ivyXML.isEmpty && (in.module.explicitArtifacts.size <= 1) && in.configurations.isEmpty)
externalSettings
else if(useDefaultConfigurations)
in withConfigurations ( Configurations.defaultMavenConfigurations )
else
in
}
else
in
}
def externalSettings = ModuleSettings(ivyScala, ivyValidate, projectID)(info.projectPath.asFile, log)
def ivySbt: IvySbt = new IvySbt(ivyConfiguration)
def ivyModule: IvySbt#Module = newIvyModule(moduleSettings)
def newIvyModule(moduleSettings: ModuleSettings): IvySbt#Module =
{
val i = ivySbt
new i.Module(moduleSettings)
}
/** The pattern for Ivy to use when retrieving dependencies into the local project. Classpath management
* depends on the first directory being [conf] and the extension being [ext].*/
def outputPattern = "[conf]/[artifact](-[revision])(-[classifier]).[ext]"
/** Override this to specify the publications, configurations, and/or dependencies sections of an Ivy file.
* See http://code.google.com/p/simple-build-tool/wiki/LibraryManagement for details.*/
def ivyXML: NodeSeq = NodeSeq.Empty
def pomExtra: NodeSeq = NodeSeq.Empty
override def ivyConfigurations: Iterable[Configuration] =
{
val reflective = super.ivyConfigurations
val extra = extraDefaultConfigurations
if(useDefaultConfigurations)
{
if(reflective.isEmpty && extra.isEmpty)
Nil
else
Configurations.removeDuplicates(Configurations.defaultMavenConfigurations ++ reflective ++ extra)
}
else
reflective ++ extra
}
def extraDefaultConfigurations: List[Configuration] = Nil
def useIntegrationTestConfiguration = false
def defaultConfiguration: Option[Configuration] = Some(Configurations.DefaultConfiguration(useDefaultConfigurations))
def useMavenConfigurations = true // TODO: deprecate after going through a minor version series to verify that this works ok
def useDefaultConfigurations = useMavenConfigurations
def managedStyle: ManagedType =
info.parent match
{
case Some(m: BasicManagedProject) => m.managedStyle
case _ => Auto
}
protected implicit final val defaultPatterns: Patterns =
{
managedStyle match
{
case Maven => Resolver.mavenStylePatterns
case Ivy => Resolver.ivyStylePatterns
case Auto => Resolver.defaultPatterns
}
}
def updateModuleSettings = moduleSettings
def updateIvyModule = newIvyModule(updateModuleSettings)
def deliverModuleSettings = moduleSettings.noScala
def deliverIvyModule = newIvyModule(deliverModuleSettings)
def publishModuleSettings = deliverModuleSettings
def publishIvyModule = newIvyModule(publishModuleSettings)
/** True if the default implicit extensions should be used when determining classpaths. The default value is true. */
def defaultConfigurationExtensions = true
/** If true, verify that explicit dependencies on Scala libraries use the same version as scala.version. */
def checkExplicitScalaDependencies = true
/** If true, filter dependencies on scala-library and scala-compiler. This is true by default to avoid conflicts with
* the jars provided by sbt. You can set this to false to download these jars. Overriding checkScalaInConfigurations might
* be more appropriate, however.*/
def filterScalaJars = true
/** The configurations to check/filter.*/
def checkScalaInConfigurations: Iterable[Configuration] = ivyConfigurations
def defaultPublishRepository: Option[Resolver] =
{
reflectiveRepositories.get(PublishToName) orElse
info.parent.flatMap
{
case managed: BasicManagedProject => managed.defaultPublishRepository
case _ => None
}
}
/** Includes the Compile configuration on the Runtime classpath, and Compile and Runtime on the Test classpath.
* Including Compile and Runtime can be disabled by setting defaultConfigurationExtensions to false.*/
override def managedClasspath(config: Configuration) =
{
import Configurations.{Compile, Default, Runtime, Test}
val baseClasspath = configurationClasspath(config)
config match
{
case Compile => baseClasspath +++ managedClasspath(Default)
case Runtime if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Compile)
case Test if defaultConfigurationExtensions => baseClasspath +++ managedClasspath(Runtime)
case _ => baseClasspath
}
}
protected def updateAction = updateTask(updateIvyModule, ivyUpdateConfiguration) describedAs UpdateDescription
protected def cleanLibAction = cleanLibTask(managedDependencyPath) describedAs CleanLibDescription
protected def cleanCacheAction = cleanCacheTask(ivySbt) describedAs CleanCacheDescription
protected def deliverProjectDependencies: Iterable[ModuleID] =
{
val interDependencies = new scala.collection.mutable.ListBuffer[ModuleID]
dependencies.foreach(dep => dep match { case mp: ManagedProject => interDependencies += mp.projectID; case _ => () })
if(filterScalaJars)
interDependencies ++= deliverScalaDependencies
interDependencies.readOnly
}
def pomIncludeRepository(repo: MavenRepository): Boolean = !repo.root.startsWith("file:")
def pomPostProcess(pom: Node): Node = pom
def makePomConfiguration = new MakePomConfiguration(deliverProjectDependencies, None, pomExtra, pomPostProcess, pomIncludeRepository)
protected def deliverScalaDependencies: Iterable[ModuleID] = Nil
protected def makePomAction = makePomTask(deliverIvyModule, makePomConfiguration, pomPath)
protected def deliverLocalAction = deliverTask(deliverIvyModule, publishLocalConfiguration, ivyUpdateLogging)
protected def publishLocalAction =
{
val dependencies = deliverLocal :: publishPomDepends
publishTask(publishIvyModule, publishLocalConfiguration) dependsOn(dependencies : _*)
}
protected def publishLocalConfiguration = new DefaultPublishConfiguration("local", "release", true)
protected def deliverAction = deliverTask(deliverIvyModule, publishConfiguration, ivyUpdateLogging)
protected def publishAction =
{
val dependencies = deliver :: publishPomDepends
publishTask(publishIvyModule, publishConfiguration) dependsOn(dependencies : _*)
}
private def publishPomDepends = if(managedStyle == Maven) makePom :: Nil else Nil
protected def publishConfiguration =
{
val repository = defaultPublishRepository.getOrElse(error("Repository to publish to not specified."))
val publishIvy = managedStyle != Maven
new DefaultPublishConfiguration(repository, "release", publishIvy)
}
protected class DefaultPublishConfiguration(val resolverName: String, val status: String, val publishIvy: Boolean) extends PublishConfiguration
{
def this(resolver: Resolver, status: String, publishIvy: Boolean) = this(resolver.name, status, publishIvy)
def this(resolverName: String, status: String) = this(resolverName, status, true)
def this(resolver: Resolver, status: String) = this(resolver.name, status)
protected def deliveredPathPattern = outputPath / "[artifact]-[revision](-[classifier]).[ext]"
def deliveredPattern = deliveredPathPattern.relativePath
def srcArtifactPatterns: Iterable[String] =
{
val pathPatterns =
(outputPath / "[artifact]-[revision]-[type](-[classifier]).[ext]") ::
(outputPath / "[artifact]-[revision](-[classifier]).[ext]") ::
Nil
pathPatterns.map(_.relativePath)
}
def extraDependencies: Iterable[ModuleID] = deliverProjectDependencies
/** The configurations to include in the publish/deliver action: specify none for all public configurations. */
def configurations: Option[Iterable[Configuration]] = None
}
def packageToPublishActions: Seq[ManagedTask] = Nil
private[this] def depMap[T](f: BasicManagedProject => T) =
topologicalSort.dropRight(1).flatMap { case m: BasicManagedProject => f(m) :: Nil; case _ => Nil }
lazy val update = updateAction
lazy val makePom = makePomAction dependsOn(packageToPublishActions : _*)
lazy val cleanLib = cleanLibAction
lazy val cleanCache = cleanCacheAction
// deliver must run after its dependencies' `publish` so that the artifacts produced by the dependencies can be resolved
// (deliver requires a resolve first)
lazy val deliverLocal: Task = deliverLocalAction dependsOn((depMap(_.publishLocal) ++ packageToPublishActions) : _*)
lazy val publishLocal: Task = publishLocalAction
lazy val deliver: Task = deliverAction dependsOn((depMap(_.publish) ++ packageToPublishActions) : _*)
lazy val publish: Task = publishAction
}
object BasicManagedProject
{
val UpdateDescription =
"Resolves and retrieves automatically managed dependencies."
val CleanLibDescription =
"Deletes the managed library directory."
val CleanCacheDescription =
"Deletes the cache of artifacts downloaded for automatically managed dependencies."
val PublishToName = "publish-to"
val RetrieveFromName = "retrieve-from"
}
class DefaultInstallProject(val info: ProjectInfo) extends InstallProject with MavenStyleScalaPaths with BasicDependencyProject
{
def fullUnmanagedClasspath(config: Configuration) = unmanagedClasspath
def dependencies = info.dependencies
}
trait InstallProject extends BasicManagedProject
{
def installModuleSettings: ModuleSettings = moduleSettings.noScala
def installIvyModule: IvySbt#Module = newIvyModule(installModuleSettings)
lazy val install = installTask(installIvyModule, fromResolver, toResolver)
def toResolver = reflectiveRepositories.get(PublishToName).getOrElse(error("No repository to publish to was specified"))
def fromResolver = reflectiveRepositories.get(RetrieveFromName).getOrElse(error("No repository to retrieve from was specified"))
}
trait BasicDependencyPaths extends ManagedProject
{
import BasicDependencyPaths._
def dependencyDirectoryName = DefaultDependencyDirectoryName
def managedDirectoryName = DefaultManagedDirectoryName
def pomName = artifactBaseName + PomExtension
def dependencyPath = path(dependencyDirectoryName)
def managedDependencyPath = crossPath(managedDependencyRootPath)
def managedDependencyRootPath: Path = managedDirectoryName
def pomPath = outputPath / pomName
}
object BasicDependencyPaths
{
val DefaultManagedDirectoryName = "lib_managed"
val DefaultManagedSourceDirectoryName = "src_managed"
val DefaultDependencyDirectoryName = "lib"
val PomExtension = ".pom"
}
import scala.collection.{Map, mutable}
/** A Project that determines its tasks by reflectively finding all vals with a type
* that conforms to Task.*/
trait ReflectiveTasks extends Project
{
def tasks: Map[String, ManagedTask] = reflectiveTaskMappings
def reflectiveTaskMappings : Map[String, Task] = Reflective.reflectiveMappings[Task](this)
}
/** A Project that determines its method tasks by reflectively finding all vals with a type
* that conforms to MethodTask.*/
trait ReflectiveMethods extends Project
{
def methods: Map[String, MethodTask] = reflectiveMethodMappings
def reflectiveMethodMappings : Map[String, MethodTask] = Reflective.reflectiveMappings[MethodTask](this)
}
/** A Project that determines its dependencies on other projects by reflectively
* finding all vals with a type that conforms to Project.*/
trait ReflectiveModules extends Project
{
override def subProjects: Map[String, Project] = reflectiveModuleMappings
def reflectiveModuleMappings : Map[String, Project] = Reflective.reflectiveMappings[Project](this)
}
/** A Project that determines its dependencies on other projects by reflectively
* finding all vals with a type that conforms to Project and determines its tasks
* by reflectively finding all vals with a type that conforms to Task.*/
trait ReflectiveProject extends ReflectiveModules with ReflectiveTasks with ReflectiveMethods
/** This Project subclass is used to contain other projects as dependencies.*/
class ParentProject(val info: ProjectInfo) extends BasicDependencyProject with Cleanable
{
def dependencies: Iterable[Project] = info.dependencies ++ subProjects.values.toList
/** The directories to which a project writes are listed here and is used
* to check a project and its dependencies for collisions.*/
override def outputDirectories = managedDependencyPath :: outputPath :: Nil
def fullUnmanagedClasspath(config: Configuration) = unmanagedClasspath
}
object Reflective
{
def reflectiveMappings[T](obj: AnyRef)(implicit m: scala.reflect.Manifest[T]): Map[String, T] =
{
val mappings = new mutable.HashMap[String, T]
for ((name, value) <- ReflectUtilities.allVals[T](obj))
mappings(ReflectUtilities.transformCamelCase(name, '-')) = value
mappings
}
}
/** A Project that determines its library dependencies by reflectively finding all vals with a type
* that conforms to ModuleID.*/
trait ReflectiveLibraryDependencies extends ManagedProject
{
def excludeIDs: Iterable[ModuleID] = projectID :: Nil
/** Defines the library dependencies of this project. By default, this finds vals of type ModuleID defined on the project.
* This can be overridden to directly provide dependencies */
def libraryDependencies: Set[ModuleID] = reflectiveLibraryDependencies
def reflectiveLibraryDependencies : Set[ModuleID] = Set[ModuleID](Reflective.reflectiveMappings[ModuleID](this).values.toList: _*) -- excludeIDs
}
trait ReflectiveConfigurations extends Project
{
def ivyConfigurations: Iterable[Configuration] = reflectiveIvyConfigurations
def reflectiveIvyConfigurations: Set[Configuration] = Configurations.removeDuplicates(Reflective.reflectiveMappings[Configuration](this).values.toList)
}
trait ReflectiveArtifacts extends ManagedProject
{
def managedStyle: ManagedType
def artifacts: Set[Artifact] =
{
val reflective = reflectiveArtifacts
managedStyle match
{
case Maven => reflective ++ List(Artifact(artifactID, "pom", "pom"))
case Ivy => reflective
case Auto => reflective
}
}
def reflectiveArtifacts: Set[Artifact] = Set(Reflective.reflectiveMappings[Artifact](this).values.toList: _*)
}
/** A Project that determines its library dependencies by reflectively finding all vals with a type
* that conforms to ModuleID.*/
trait ReflectiveRepositories extends Project
{
def repositories: Set[Resolver] =
{
val reflective = Set[Resolver]() ++ reflectiveRepositories.toList.flatMap { case (PublishToName, _) => Nil; case (_, value) => List(value) }
info.parent match
{
case Some(p: ReflectiveRepositories) => p.repositories ++ reflective
case None => reflective
}
}
def reflectiveRepositories: Map[String, Resolver] = Reflective.reflectiveMappings[Resolver](this)
def moduleConfigurations: Set[ModuleConfiguration] =
{
val reflective = Set[ModuleConfiguration](reflectiveModuleConfigurations.values.toList: _*)
info.parent match
{
case Some(p: ReflectiveRepositories) => p.moduleConfigurations ++ reflective
case None => reflective
}
}
def reflectiveModuleConfigurations: Map[String, ModuleConfiguration] = Reflective.reflectiveMappings[ModuleConfiguration](this)
}
trait ReflectiveManagedProject extends ReflectiveProject with ReflectiveArtifacts with ReflectiveRepositories with ReflectiveLibraryDependencies with ReflectiveConfigurations

View File

@ -1,262 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah, David MacIver
*/
package sbt
import BasicProjectPaths._
import scala.collection.{immutable, Map}
import immutable.Map.{empty => emptyMap}
sealed abstract class InternalProject extends Project
{
override def defaultLoggingLevel = Level.Warn
override final def historyPath = None
override def tasks: Map[String, ManagedTask] = emptyMap
override final protected def disableCrossPaths = false
override final def shouldCheckOutputDirectories = false
}
sealed abstract class BasicBuilderProject extends InternalProject
{
def sourceFilter = "*.scala" | "*.java"
def jarFilter: NameFilter = "*.jar"
def compilePath = outputPath / DefaultMainCompileDirectoryName
def mainResourcesPath = path(DefaultResourcesDirectoryName)
def dependencyPath = path(DefaultDependencyDirectoryName)
def libraries = descendents(dependencyPath, jarFilter)
override final def dependencies = Nil
protected final def logInfo(messages: String*): Unit = atInfo { messages.foreach(message => log.info(message)) }
protected final def atInfo(action: => Unit)
{
val oldLevel = log.getLevel
log.setLevel(Level.Info)
action
log.setLevel(oldLevel)
}
def projectClasspath = compilePath +++ libraries +++ sbtJars
def sbtJars = info.sbtClasspath
abstract class BuilderCompileConfiguration extends AbstractCompileConfiguration
{
def projectPath = info.projectPath
def log = BasicBuilderProject.this.log
def options = CompileOptions.Deprecation :: CompileOptions.Unchecked :: Nil
def javaOptions = Nil
def maxErrors = ScalaProject.DefaultMaximumCompileErrors
def compileOrder = CompileOrder.Mixed
}
def definitionCompileConfiguration =
new BuilderCompileConfiguration
{
def label = "builder"
def sourceRoots = info.projectPath +++ path(DefaultSourceDirectoryName)
def sources = (info.projectPath * sourceFilter) +++ path(DefaultSourceDirectoryName).descendentsExcept(sourceFilter, defaultExcludes)
def outputDirectory = compilePath
def classpath = projectClasspath
def analysisPath = outputPath / DefaultMainAnalysisDirectoryName
}
def tpe: String
import xsbt.ScalaInstance
lazy val definitionCompileConditional = new BuilderCompileConditional(definitionCompileConfiguration, buildCompiler, tpe)
final class BuilderCompileConditional(config: BuilderCompileConfiguration, compiler: xsbt.AnalyzingCompiler, tpe: String) extends AbstractCompileConditional(config, compiler)
{
type AnalysisType = BuilderCompileAnalysis
override protected def constructAnalysis(analysisPath: Path, projectPath: Path, log: Logger) =
new BuilderCompileAnalysis(analysisPath, projectPath, log)
override protected def execute(cAnalysis: ConditionalAnalysis): Option[String] =
{
if(cAnalysis.dirtySources.isEmpty)
None
else
{
definitionChanged()
logInfo(
"Recompiling " + tpe + "...",
"\t" + cAnalysis.toString)
super.execute(cAnalysis)
}
}
protected def analysisCallback: AnalysisCallback =
new BasicAnalysisCallback(info.projectPath, analysis)
{
def superclassNames = List(Project.ProjectClassName)
def annotationNames = Nil
def foundApplication(sourcePath: Path, className: String) {}
def foundAnnotated(sourcePath: Path, subclassName: String, annotationName: String, isModule: Boolean) {}
def foundSubclass(sourcePath: Path, subclassName: String, superclassName: String, isModule: Boolean)
{
if(superclassName == Project.ProjectClassName && !isModule)
{
log.debug("Found " + tpe + " " + subclassName)
analysis.addProjectDefinition(sourcePath, subclassName)
}
}
}
}
protected def definitionChanged() {}
lazy val compile = compileTask
def compileTask = task { definitionCompileConditional.run }
def projectDefinition: Either[String, Option[String]] =
{
definitionCompileConditional.analysis.allProjects.toList match
{
case Nil =>
log.debug("No " + tpe + "s detected using default project.")
Right(None)
case singleDefinition :: Nil => Right(Some(singleDefinition))
case multipleDefinitions =>Left(multipleDefinitions.mkString("Multiple " + tpe + "s detected: \n\t","\n\t","\n"))
}
}
override final def methods = emptyMap
}
/** The project definition used to build project definitions. */
final class BuilderProject(val info: ProjectInfo, val pluginPath: Path, rawLogger: Logger) extends BasicBuilderProject with ReflectiveTasks
{
override def name = "Project Definition Builder"
lazy val pluginProject =
{
if(pluginPath.exists)
Some(new PluginBuilderProject(ProjectInfo(pluginPath.asFile, Nil, None)(rawLogger, info.app, info.buildScalaVersion)))
else
None
}
override def projectClasspath = super.projectClasspath +++
pluginProject.map(_.pluginClasspath).getOrElse(Path.emptyPathFinder)
def tpe = "project definition"
override def compileTask = super.compileTask dependsOn(pluginProject.map(_.sync).toList : _*)
override def tasks = immutable.Map() ++ super[ReflectiveTasks].tasks ++ pluginProject.toList.flatMap { _.tasks.map { case (k,v) => (k + "-plugins", v) } }
final class PluginBuilderProject(val info: ProjectInfo) extends BasicBuilderProject with ReflectiveTasks
{
override def name = "Plugin Builder"
// don't include Java sources because BND includes Java sources in its jar (#85)
def pluginSourceFilter: NameFilter = "*.scala"
lazy val pluginUptodate = propertyOptional[Boolean](false)
def tpe = "plugin definition"
def managedSourcePath = path(BasicDependencyPaths.DefaultManagedSourceDirectoryName)
def managedDependencyPath = crossPath(BasicDependencyPaths.DefaultManagedDirectoryName)
override protected def definitionChanged() { setUptodate(false) }
override def tasks: Map[String, ManagedTask] = super[ReflectiveTasks].tasks
def setUptodate(flag: Boolean)
{
pluginUptodate() = flag
saveEnvironment()
}
private def pluginTask(f: => Option[String]) = task { if(!pluginUptodate.value) f else None }
lazy val sync = pluginTask(doSync()) dependsOn(extract)
lazy val extract = pluginTask(extractSources()) dependsOn(autoUpdate)
lazy val autoUpdate = pluginTask(loadAndUpdate(false)) dependsOn(compile)
// manual update. force uptodate = false
lazy val update = task { manualUpdate() } dependsOn(compile) describedAs("Manual plugin update. Used when autoUpdate is disabled.")
def manualUpdate() =
{
setUptodate(false)
val result = loadAndUpdate(true)
logInfo("'reload' required to rebuild plugins.")
result
}
def doSync() = pluginCompileConditional.run orElse { setUptodate(true); None }
def extractSources() =
{
FileUtilities.clean(managedSourcePath, log) orElse
Control.lazyFold(plugins.get.toList) { jar =>
Control.thread(FileUtilities.unzip(jar, extractTo(jar), pluginSourceFilter, log)) { extracted =>
if(!extracted.isEmpty)
logInfo("\tExtracted source plugin " + jar + " ...")
None
}
}
}
def loadAndUpdate(forceUpdate: Boolean) =
{
Control.thread(projectDefinition) {
case Some(definition) =>
val pluginInfo = ProjectInfo(info.projectPath.asFile, Nil, None)(rawLogger, info.app, info.buildScalaVersion)
val pluginBuilder = Project.constructProject(pluginInfo, Project.getProjectClass[PluginDefinition](definition, projectClasspath, getClass.getClassLoader))
if(forceUpdate || pluginBuilder.autoUpdate)
{
logInfo("\nUpdating plugins...")
pluginBuilder.projectName() = "Plugin Definition"
pluginBuilder.projectVersion() = OpaqueVersion("1.0")
val result = pluginBuilder.update.run
if(result.isEmpty)
{
atInfo {
log.success("Plugins updated successfully.")
log.info("")
}
}
result
}
else
{
log.warn("Plugin definition recompiled, but autoUpdate is disabled.\n\tUsing already retrieved plugins...")
None
}
case None => None
}
}
def extractTo(jar: Path) =
{
val name = jar.asFile.getName
managedSourcePath / name.substring(0, name.length - ".jar".length)
}
def plugins = descendents(managedDependencyPath, jarFilter)
def pluginClasspath: PathFinder = plugins +++ pluginCompileConfiguration.outputDirectory
lazy val pluginCompileConditional = new BuilderCompileConditional(pluginCompileConfiguration, buildCompiler, "plugin")
lazy val pluginCompileConfiguration =
new BuilderCompileConfiguration
{
def label = "plugin builder"
def sourceRoots = managedSourcePath
def sources = descendents(sourceRoots, sourceFilter)
def outputDirectory = outputPath / "plugin-classes"
def classpath: PathFinder = pluginClasspath +++ sbtJars
def analysisPath = outputPath / "plugin-analysis"
}
}
}
class PluginDefinition(val info: ProjectInfo) extends InternalProject with BasicManagedProject
{
override def defaultLoggingLevel = Level.Info
override final def outputPattern = "[artifact](-[revision]).[ext]"
override final val tasks = immutable.Map("update" -> update)
override def projectClasspath(config: Configuration) = Path.emptyPathFinder
override def dependencies = info.dependencies
def autoUpdate = true
}
class PluginProject(info: ProjectInfo) extends DefaultProject(info)
{
/* Since plugins are distributed as source, there is no need to append _<scala.version> */
override def moduleID = normalizedName
/* Fix the version used to build to the version currently running sbt. */
override def buildScalaVersion = defScalaVersion.value
/* Add sbt to the classpath */
override def unmanagedClasspath = super.unmanagedClasspath +++ info.sbtClasspath
/* Package the plugin as source. */
override def packageAction = packageSrc dependsOn(test)
override def packageSrcJar = jarPath
/* Some setup to make publishing quicker to configure. */
override def useMavenConfigurations = true
override def managedStyle = ManagedStyle.Maven
}
class ProcessorProject(info: ProjectInfo) extends DefaultProject(info)
{
/* Fix the version used to build to the version currently running sbt. */
override def buildScalaVersion = defScalaVersion.value
/* Add sbt to the classpath */
override def unmanagedClasspath = super.unmanagedClasspath +++ info.sbtClasspath
/* Some setup to make publishing quicker to configure. */
override def useMavenConfigurations = true
override def managedStyle = ManagedStyle.Maven
}

View File

@ -1,73 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package sbt
/** The trap methods execute the provided code in a try block and handle a thrown exception.*/
object Control
{
def trap[T](errorMessagePrefix: => String, log: Logger)(execute: => Either[String, T]): Either[String, T] =
try { execute }
catch { case e => log.trace(e); Left(errorMessagePrefix + e.toString) }
def trapAndFinally[T](errorMessagePrefix: => String, log: Logger)(execute: => Either[String, T])(doFinally: => Unit): Either[String, T] =
try { execute }
catch { case e => log.trace(e); Left(errorMessagePrefix + e.toString) }
finally { trapAndLog(log)(doFinally) }
def trapUnit(errorMessagePrefix: => String, log: Logger)(execute: => Option[String]): Option[String] =
try { execute }
catch { case e => log.trace(e); Some(errorMessagePrefix + e.toString) }
def trapUnitAndFinally(errorMessagePrefix: => String, log: Logger)(execute: => Option[String])(doFinally: => Unit): Option[String] =
try { execute }
catch { case e => log.trace(e); Some(errorMessagePrefix + e.toString) }
finally { trapAndLog(log)(doFinally) }
def trap(execute: => Unit)
{
try { execute }
catch { case e: Exception => () }
}
def trapAndLog(log: Logger)(execute: => Unit)
{
try { execute }
catch { case e => log.trace(e); log.error(e.toString) }
}
def convertException[T](t: => T): Either[Exception, T] =
{
try { Right(t) }
catch { case e: Exception => Left(e) }
}
def convertErrorMessage[T](log: Logger)(t: => T): Either[String, T] =
{
try { Right(t) }
catch { case e: Exception => log.trace(e); Left(e.toString) }
}
def getOrError[T](result: Either[String, T]): T = result.fold(error, x=>x)
final def lazyFold[T](list: List[T])(f: T => Option[String]): Option[String] =
list match
{
case Nil => None
case head :: tail =>
f(head) match
{
case None => lazyFold(tail)(f)
case x => x
}
}
final def lazyFold[T, S](list: List[T], value: S)(f: (S,T) => Either[String, S]): Either[String, S] =
list match
{
case Nil => Right(value)
case head :: tail =>
f(value, head) match
{
case Right(newValue) => lazyFold(tail, newValue)(f)
case x => x
}
}
def thread[T](e: Either[String, T])(f: T => Option[String]): Option[String] =
e.right.flatMap( t => f(t).toLeft(()) ).left.toOption
}

View File

@ -1,109 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt
import scala.collection.{mutable, Map, Set}
sealed trait ProductsSources extends NotNull
{
def products: Iterable[Path]
def sources: Iterable[Path]
}
sealed trait ProductsWrapper extends NotNull
{
def from(sources: => Iterable[Path]): ProductsSources = from(Path.lazyPathFinder(sources))
def from(sources: PathFinder): ProductsSources
}
/** Provides methods to define tasks with basic conditional execution based on the sources
* and products of the task. */
trait FileTasks extends Project
{
implicit def wrapProduct(product: => Path): ProductsWrapper = FileTasks.wrapProduct(product)
implicit def wrapProducts(productsList: => Iterable[Path]): ProductsWrapper = FileTasks.wrapProducts(productsList)
/** Runs 'action' if the given products are out of date with respect to the given sources. */
def fileTask(label: String, files: ProductsSources)(action: => Option[String]): Task =
task { FileTasks.runOption(label, files, log)(action) }
/** Runs 'action' if any of the given products do not exist. */
def fileTask(label: String, products: => Iterable[Path])(action: => Option[String]): Task =
task { FileTasks.existenceCheck[Option[String]](label, products, log)(action)(None) }
/** Creates a new task that performs 'action' only when the given products are out of date with respect to the given sources.. */
def fileTask(files: ProductsSources)(action: => Option[String]): Task = fileTask("", files)(action)
/** Creates a new task that performs 'action' only when at least one of the given products does not exist.. */
def fileTask(products: => Iterable[Path])(action: => Option[String]): Task = fileTask("", products)(action)
}
object FileTasks
{
implicit def wrapProduct(product: => Path): ProductsWrapper = wrapProducts(product :: Nil)
implicit def wrapProducts(productsList: => Iterable[Path]): ProductsWrapper =
new ProductsWrapper
{
def from(sourceFinder: PathFinder) =
new ProductsSources
{
def products = productsList
def sources = sourceFinder.get
}
}
/** Runs 'ifOutofdate' if the given products are out of date with respect to the given sources.*/
def runOption(label: String, files: ProductsSources, log: Logger)(ifOutofdate: => Option[String]): Option[String] =
{
val result = apply[Option[String]](label, files, log)(ifOutofdate)(None)
if(result.isDefined)
FileUtilities.clean(files.products, true, log)
result
}
/** Returns 'ifOutofdate' if the given products are out of date with respect to the given sources. Otherwise, returns ifUptodate. */
def apply[T](label: String, files: ProductsSources, log: Logger)(ifOutofdate: => T)(ifUptodate: => T): T =
{
val products = files.products
require(!products.isEmpty, "No products were specified; products must be known in advance.")
existenceCheck[T](label, products, log)(ifOutofdate)
{
val sources = files.sources
if(sources.isEmpty)
{
log.debug("Running " + label + " task because no sources exist.")
ifOutofdate
}
else
{
val oldestProductModifiedTime = mapLastModified(products).reduceLeft(_ min _)
val newestSourceModifiedTime = mapLastModified(sources).reduceLeft(_ max _)
if(oldestProductModifiedTime < newestSourceModifiedTime)
{
if(log.atLevel(Level.Debug))
{
log.debug("Running " + label + " task because the following sources are newer than at least one product: ")
logDebugIndented(sources.filter(_.lastModified > oldestProductModifiedTime), log)
log.debug(" The following products are older than at least one source: ")
logDebugIndented(products.filter(_.lastModified < newestSourceModifiedTime), log)
}
ifOutofdate
}
else
ifUptodate
}
}
}
/** Checks that all 'products' exist. If they do, 'ifAllExists' is returned, otherwise 'products' is returned.*/
private def existenceCheck[T](label: String, products: Iterable[Path], log: Logger)(action: => T)(ifAllExist: => T) =
{
val nonexisting = products.filter(!_.exists)
if(nonexisting.isEmpty)
ifAllExist
else
{
if(log.atLevel(Level.Debug))
{
log.debug("Running " + label + " task because at least one product does not exist:")
logDebugIndented(nonexisting, log)
}
action
}
}
private def logDebugIndented[T](it: Iterable[T], log: Logger) { it.foreach(x => log.debug("\t" + x)) }
private def mapLastModified(paths: Iterable[Path]): Iterable[Long] = paths.map(_.lastModified)
}

View File

@ -1,933 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Mark Harrah, Nathan Hamblen, Justin Caballero
*/
package sbt
import java.io.{Closeable, File, FileInputStream, FileOutputStream, InputStream, OutputStream}
import java.io.{ByteArrayOutputStream, InputStreamReader, OutputStreamWriter}
import java.io.{BufferedReader, BufferedWriter, FileReader, FileWriter, Reader, Writer}
import java.util.zip.{GZIPInputStream, GZIPOutputStream}
import java.net.{URL, URISyntaxException}
import java.nio.charset.{Charset, CharsetDecoder, CharsetEncoder}
import java.nio.channels.FileChannel
import java.util.jar.{Attributes, JarEntry, JarFile, JarInputStream, JarOutputStream, Manifest}
import java.util.zip.{GZIPOutputStream, ZipEntry, ZipFile, ZipInputStream, ZipOutputStream}
import OpenResource._
final class Preserved private[sbt](toRestore: scala.collection.Map[File, Path], temp: File) extends NotNull
{
def restore(log: Logger) =
{
try
{
Control.lazyFold(toRestore.toList) { case (src, dest) =>
FileUtilities.copyFile(src, dest.asFile, log)
}
}
finally { FileUtilities.clean(Path.fromFile(temp) :: Nil, true, log) }
}
}
/** A collection of file related methods. */
object FileUtilities
{
import wrap.Wrappers.readOnly
/** The size of the byte or char buffer used in various methods.*/
private val BufferSize = 8192
val Newline = System.getProperty("line.separator")
/** A pattern used to split a String by path separator characters.*/
private val PathSeparatorPattern = java.util.regex.Pattern.compile(File.pathSeparator)
/** Splits a String around path separator characters. */
private[sbt] def pathSplit(s: String) = PathSeparatorPattern.split(s)
def preserve(paths: Iterable[Path], log: Logger): Either[String, Preserved] =
{
for(tmp <- createTemporaryDirectory(log).right) yield
{
val pathMap = new scala.collection.mutable.HashMap[File, Path]
val destinationDirectory = Path.fromFile(tmp)
for(source <- paths)
{
val toPath = Path.fromString(destinationDirectory, source.relativePath)
copyFile(source, toPath, log)
pathMap(toPath.asFile) = source
}
new Preserved(readOnly(pathMap), tmp)
}
}
/** Gzips the file 'in' and writes it to 'out'. 'in' cannot be the same file as 'out'. */
def gzip(in: Path, out: Path, log: Logger): Option[String] =
{
require(in != out, "Input file cannot be the same as the output file.")
readStream(in.asFile, log) { inputStream =>
writeStream(out.asFile, log) { outputStream =>
gzip(inputStream, outputStream, log)
}
}
}
/** Gzips the InputStream 'in' and writes it to 'output'. Neither stream is closed.*/
def gzip(input: InputStream, output: OutputStream, log: Logger): Option[String] =
gzipOutputStream.ioOption(output, "gzipping", log) { gzStream => transfer(input, gzStream, log) }
def gunzip(input: InputStream, output: OutputStream, log: Logger): Option[String] =
gzipInputStream.ioOption(input, "gunzipping", log) { gzStream => transfer(gzStream, output, log) }
/** Gunzips the file 'in' and writes it to 'out'. 'in' cannot be the same file as 'out'. */
def gunzip(in: Path, out: Path, log: Logger): Option[String] =
{
require(in != out, "Input file cannot be the same as the output file.")
readStream(in.asFile, log) { inputStream =>
writeStream(out.asFile, log) { outputStream =>
gunzip(inputStream, outputStream, log)
}
}
}
/** Creates a jar file.
* @param sources The files to include in the jar file. The path used for the jar is
* relative to the base directory for the source. That is, the path in the jar for source
* <code>(basePath ###) / x / y</code> is <code>x / y</code>.
* @param outputJar The file to write the jar to.
* @param manifest The manifest for the jar.
* @param recursive If true, any directories in <code>sources</code> are recursively processed. Otherwise,
* they are not
* @param log The Logger to use. */
def jar(sources: Iterable[Path], outputJar: Path, manifest: Manifest, recursive: Boolean, log: Logger) =
archive(sources, outputJar, Some(manifest), recursive, log)
/** Creates a zip file.
* @param sources The files to include in the jar file. The path used for the jar is
* relative to the base directory for the source. That is, the path in the jar for source
* <code>(basePath ###) / x / y</code> is <code>x / y</code>.
* @param outputZip The file to write the zip to.
* @param recursive If true, any directories in <code>sources</code> are recursively processed. Otherwise,
* they are not
* @param log The Logger to use. */
def zip(sources: Iterable[Path], outputZip: Path, recursive: Boolean, log: Logger) =
archive(sources, outputZip, None, recursive, log)
private def archive(sources: Iterable[Path], outputPath: Path, manifest: Option[Manifest], recursive: Boolean, log: Logger) =
{
log.info("Packaging " + outputPath + " ...")
val outputFile = outputPath.asFile
if(outputFile.isDirectory)
Some("Specified output file " + outputFile + " is a directory.")
else
{
val outputDir = outputFile.getParentFile
val result = createDirectory(outputDir, log) orElse
withZipOutput(outputFile, manifest, log)
{ output =>
val createEntry: (String => ZipEntry) = if(manifest.isDefined) new JarEntry(_) else new ZipEntry(_)
writeZip(sources, output, recursive, log)(createEntry)
}
if(result.isEmpty)
log.info("Packaging complete.")
result
}
}
private def writeZip(sources: Iterable[Path], output: ZipOutputStream, recursive: Boolean, log: Logger)(createEntry: String => ZipEntry) =
{
def add(source: Path)
{
val sourceFile = source.asFile
if(sourceFile.isDirectory)
{
if(recursive)
wrapNull(sourceFile.listFiles).foreach(file => add(source / file.getName))
}
else if(sourceFile.exists)
{
val relativePath = source.relativePathString("/")
log.debug("\tAdding " + source + " as " + relativePath + " ...")
val nextEntry = createEntry(relativePath)
nextEntry.setTime(sourceFile.lastModified)
output.putNextEntry(nextEntry)
transferAndClose(new FileInputStream(sourceFile), output, log)
output.closeEntry()
}
else
log.warn("\tSource " + source + " does not exist.")
}
sources.foreach(add)
None
}
private def withZipOutput(file: File, manifest: Option[Manifest], log: Logger)(f: ZipOutputStream => Option[String]): Option[String] =
{
writeStream(file, log)
{
fileOut =>
{
val (zipOut, ext) =
manifest match
{
case Some(mf) =>
{
import Attributes.Name.MANIFEST_VERSION
val main = mf.getMainAttributes
if(!main.containsKey(MANIFEST_VERSION))
main.put(MANIFEST_VERSION, "1.0")
(new JarOutputStream(fileOut, mf), "jar")
}
case None => (new ZipOutputStream(fileOut), "zip")
}
Control.trapUnitAndFinally("Error writing " + ext + ": ", log)
{ f(zipOut) } { zipOut.close }
}
}
}
import scala.collection.Set
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.*/
def unzip(from: Path, toDirectory: Path, log: Logger): Either[String, Set[Path]] =
unzip(from, toDirectory, AllPassFilter, log)
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.*/
def unzip(from: File, toDirectory: Path, log: Logger): Either[String, Set[Path]] =
unzip(from, toDirectory, AllPassFilter, log)
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.*/
def unzip(from: InputStream, toDirectory: Path, log: Logger): Either[String, Set[Path]] =
unzip(from, toDirectory, AllPassFilter, log)
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.*/
def unzip(from: URL, toDirectory: Path, log: Logger): Either[String, Set[Path]] =
unzip(from, toDirectory, AllPassFilter, log)
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.
* Only the entries that match the given filter are extracted. */
def unzip(from: Path, toDirectory: Path, filter: NameFilter, log: Logger): Either[String, Set[Path]] =
unzip(from.asFile, toDirectory, filter, log)
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.
* Only the entries that match the given filter are extracted. */
def unzip(from: File, toDirectory: Path, filter: NameFilter, log: Logger): Either[String, Set[Path]] =
readStreamValue(from, log)(in => unzip(in, toDirectory, filter, log))
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.
* Only the entries that match the given filter are extracted. */
def unzip(from: URL, toDirectory: Path, filter: NameFilter, log: Logger): Either[String, Set[Path]] =
readStreamValue(from, log) { stream => unzip(stream, toDirectory, filter, log) }
/** Unzips the contents of the zip file <code>from</code> to the <code>toDirectory</code> directory.
* Only the entries that match the given filter are extracted. */
def unzip(from: InputStream, toDirectory: Path, filter: NameFilter, log: Logger): Either[String, Set[Path]] =
{
createDirectory(toDirectory, log) match
{
case Some(err) => Left(err)
case None => zipInputStream.io(from, "unzipping", log) { zipInput => extract(zipInput, toDirectory, filter, log) }
}
}
private def extract(from: ZipInputStream, toDirectory: Path, filter: NameFilter, log: Logger) =
{
val set = new scala.collection.mutable.HashSet[Path]
// don't touch dirs as we unzip because we don't know order of zip entires (any child will
// update the dir's time)
val dirTimes = new scala.collection.mutable.HashMap[Path, Long]
def next(): Option[String] =
{
val entry = from.getNextEntry
if(entry == null)
None
else
{
val name = entry.getName
val entryErr =
if(filter.accept(name))
{
val target = Path.fromString(toDirectory, name)
log.debug("Extracting zip entry '" + name + "' to '" + target + "'")
if(entry.isDirectory)
{
dirTimes += target -> entry.getTime
createDirectory(target, log)
}
else
writeStream(target.asFile, log) { out => FileUtilities.transfer(from, out, log) } orElse
{
set += target
touchExisting(target.asFile, entry.getTime, log)
None
}
}
else
{
log.debug("Ignoring zip entry '" + name + "'")
None
}
from.closeEntry()
entryErr match { case None => next(); case x => x }
}
}
val result = next()
for ((dir, time) <- dirTimes) touchExisting(dir.asFile, time, log)
result.toLeft(readOnly(set))
}
/** Copies all bytes from the given input stream to the given output stream.
* Neither stream is closed.*/
def transfer(in: InputStream, out: OutputStream, log: Logger): Option[String] =
transferImpl(in, out, false, log)
/** Copies all bytes from the given input stream to the given output stream. The
* input stream is closed after the method completes.*/
def transferAndClose(in: InputStream, out: OutputStream, log: Logger): Option[String] =
transferImpl(in, out, true, log)
private def transferImpl(in: InputStream, out: OutputStream, close: Boolean, log: Logger): Option[String] =
{
Control.trapUnitAndFinally("Error during transfer: ", log)
{
val buffer = new Array[Byte](BufferSize)
def read: None.type =
{
val byteCount = in.read(buffer)
if(byteCount >= 0)
{
out.write(buffer, 0, byteCount)
read
}
else
None
}
read
}
{ if(close) in.close }
}
/** Creates a file at the given location.*/
def touch(path: Path, log: Logger): Option[String] = touch(path.asFile, log)
/** Creates a file at the given location.*/
def touch(file: File, log: Logger): Option[String] =
{
Control.trapUnit("Could not create file " + file + ": ", log)
{
if(file.exists)
touchExisting(file, System.currentTimeMillis, log)
else
createDirectory(file.getParentFile, log) orElse { file.createNewFile(); None }
}
}
/** Sets the last mod time on the given {@code file}, which must already exist */
private def touchExisting(file: File, time: Long, log: Logger): Option[String] =
{
def updateFailBase = "Could not update last modified for file " + file
Control.trapUnit(updateFailBase + ": ", log)
{ if(file.setLastModified(time)) None else Some(updateFailBase) }
}
/** Creates a directory at the given location.*/
def createDirectory(dir: Path, log: Logger): Option[String] = createDirectory(dir.asFile, log)
/** Creates a directory at the given location.*/
def createDirectory(dir: File, log: Logger): Option[String] =
{
Control.trapUnit("Could not create directory " + dir + ": ", log)
{
if(dir.exists)
{
if(dir.isDirectory)
None
else
Some(dir + " exists and is not a directory.")
}
else
{
dir.mkdirs()
log.debug("Created directory " + dir)
None
}
}
}
/** Creates directories at the given locations.*/
def createDirectories(d: Iterable[Path], log: Logger): Option[String] = createDirectories(Path.getFiles(d).toList, log)
/** Creates directories at the given locations.*/
def createDirectories(d: List[File], log: Logger): Option[String] =
d match
{
case Nil => None
case head :: tail => createDirectory(head, log) orElse createDirectories(tail, log)
}
/** The maximum number of times a unique temporary filename is attempted to be created.*/
private val MaximumTries = 10
/** Creates a temporary directory and returns it.*/
def createTemporaryDirectory(log: Logger): Either[String, File] =
{
def create(tries: Int): Either[String, File] =
{
if(tries > MaximumTries)
Left("Could not create temporary directory.")
else
{
val randomName = "sbt_" + java.lang.Integer.toHexString(random.nextInt)
val f = new File(temporaryDirectory, randomName)
if(createDirectory(f, log).isEmpty)
Right(f)
else
create(tries + 1)
}
}
create(0)
}
def withTemporaryDirectory(log: Logger)(action: File => Option[String]): Option[String] =
doInTemporaryDirectory(log: Logger)(file => action(file).toLeft(())).left.toOption
/** Creates a temporary directory and provides its location to the given function. The directory
* is deleted after the function returns.*/
def doInTemporaryDirectory[T](log: Logger)(action: File => Either[String, T]): Either[String, T] =
{
def doInDirectory(dir: File): Either[String, T] =
{
Control.trapAndFinally("", log)
{ action(dir) }
{ delete(dir, true, log) }
}
createTemporaryDirectory(log).right.flatMap(doInDirectory)
}
def withTemporaryFile[T](log: Logger, prefix: String, postfix: String)(action: File => Either[String, T]): Either[String, T] =
{
Control.trap("Error creating temporary file: ", log)
{
val file = File.createTempFile(prefix, postfix)
Control.trapAndFinally("", log)
{ action(file) }
{ file.delete() }
}
}
/** Copies the files declared in <code>sources</code> to the <code>destinationDirectory</code>
* directory. The source directory hierarchy is flattened so that all copies are immediate
* children of <code>destinationDirectory</code>. Directories are not recursively entered.*/
def copyFlat(sources: Iterable[Path], destinationDirectory: Path, log: Logger) =
{
val targetSet = new scala.collection.mutable.HashSet[Path]
copyImpl(sources, destinationDirectory, log)
{
source =>
{
val from = source.asFile
val toPath = destinationDirectory / from.getName
targetSet += toPath
val to = toPath.asFile
if(!to.exists || from.lastModified > to.lastModified && !from.isDirectory)
{
log.debug("Copying " + source + " to " + toPath)
copyFile(from, to, log)
}
else
None
}
}.toLeft(readOnly(targetSet))
}
private def copyImpl(sources: Iterable[Path], destinationDirectory: Path, log: Logger)
(doCopy: Path => Option[String]): Option[String] =
{
val target = destinationDirectory.asFile
val creationError =
if(target.isDirectory)
None
else
createDirectory(target, log)
def copy(sources: List[Path]): Option[String] =
{
sources match
{
case src :: remaining =>
{
doCopy(src) match
{
case None => copy(remaining)
case error => error
}
}
case Nil => None
}
}
creationError orElse ( Control.trapUnit("", log) { copy(sources.toList) } )
}
/** Retrieves the content of the given URL and writes it to the given File. */
def download(url: URL, to: File, log: Logger) =
{
readStream(url, log) { inputStream =>
writeStream(to, log) { outputStream =>
transfer(inputStream, outputStream, log)
}
}
}
/**
* Equivalent to {@code copy(sources, destinationDirectory, false, log)}.
*/
def copy(sources: Iterable[Path], destinationDirectory: Path, log: Logger): Either[String, Set[Path]] =
copy(sources, destinationDirectory, false, log)
/**
* Equivalent to {@code copy(sources, destinationDirectory, overwrite, false, log)}.
*/
def copy(sources: Iterable[Path], destinationDirectory: Path, overwrite: Boolean, log: Logger): Either[String, Set[Path]] =
copy(sources, destinationDirectory, overwrite, false, log)
/** Copies the files declared in <code>sources</code> to the <code>destinationDirectory</code>
* directory. Directories are not recursively entered. The destination hierarchy matches the
* source paths relative to any base directories. For example:
*
* A source <code>(basePath ###) / x / y</code> is copied to <code>destinationDirectory / x / y</code>.
*
* @param overwrite if true, existing destination files are always overwritten
* @param preserveLastModified if true, the last modified time of copied files will be set equal to
* their corresponding source files.
*/
def copy(sources: Iterable[Path], destinationDirectory: Path,
overwrite: Boolean, preserveLastModified: Boolean, log: Logger): Either[String, Set[Path]] =
{
val targetSet = new scala.collection.mutable.HashSet[Path]
copyImpl(sources, destinationDirectory, log)
{
source =>
{
val from = source.asFile
val toPath = Path.fromString(destinationDirectory, source.relativePath)
targetSet += toPath
val to = toPath.asFile
if(!to.exists || overwrite || from.lastModified > to.lastModified)
{
val result =
if(from.isDirectory)
createDirectory(to, log)
else
{
log.debug("Copying " + source + " to " + toPath)
copyFile(from, to, log)
}
if (result.isEmpty && preserveLastModified)
touchExisting(to, from.lastModified, log)
else
result
}
else
None
}
}.toLeft(readOnly(targetSet))
}
/** Copies the files declared in <code>sources</code> to the <code>targetDirectory</code>
* directory. The source directory hierarchy is flattened so that all copies are immediate
* children of <code>targetDirectory</code>. Directories are not recursively entered.*/
def copyFilesFlat(sources: Iterable[File], targetDirectory: Path, log: Logger) =
{
require(targetDirectory.asFile.isDirectory, "Target '" + targetDirectory + "' is not a directory.")
val byName = new scala.collection.mutable.HashMap[String, File]
for(source <- sources) byName.put(source.getName, source)
val uniquelyNamedSources = byName.values
val targetSet = new scala.collection.mutable.HashSet[Path]
def copy(source: File): Option[String] =
{
if(source.isDirectory)
copyAll(source.listFiles.toList)
else if(source.exists)
{
val targetPath = targetDirectory / source.getName
targetSet += targetPath
if(!targetPath.exists || source.lastModified > targetPath.lastModified)
{
log.debug("Copying " + source + " to " + targetPath)
copyFile(source, targetPath.asFile, log)
}
else
None
}
else
None
}
def copyAll(sources: List[File]): Option[String] =
sources match
{
case head :: tail =>
copy(head) match
{
case None => copyAll(tail)
case x => x
}
case Nil => None
}
Control.trap("Error copying files: ", log) { copyAll(uniquelyNamedSources.toList).toLeft(readOnly(targetSet)) }
}
/** Copies <code>sourceFile</code> to <code>targetFile</code>. If <code>targetFile</code>
* exists, it is overwritten. Note that unlike higher level copies in FileUtilities, this
* method always performs the copy, even if sourceFile is older than targetFile.*/
def copyFile(sourceFile: Path, targetFile: Path, log: Logger): Option[String] =
copyFile(sourceFile.asFile, targetFile.asFile, log)
/** Copies <code>sourceFile</code> to <code>targetFile</code>. If <code>targetFile</code>
* exists, it is overwritten. Note that unlike higher level copies in FileUtilities, this
* method always performs the copy, even if sourceFile is older than targetFile.*/
def copyFile(sourceFile: File, targetFile: File, log: Logger): Option[String] =
{
require(sourceFile.exists, "Source file '" + sourceFile.getAbsolutePath + "' does not exist.")
require(!sourceFile.isDirectory, "Source file '" + sourceFile.getAbsolutePath + "' is a directory.")
readChannel(sourceFile, log)(
in => writeChannel(targetFile, log) {
out => {
val copied = out.transferFrom(in, 0, in.size)
if(copied == in.size)
None
else
Some("Could not copy '" + sourceFile + "' to '" + targetFile + "' (" + copied + "/" + in.size + " bytes copied)")
}
}
)
}
/** Synchronizes the contents of the <code>sourceDirectory</code> directory to the
* <code>targetDirectory</code> directory.*/
def sync(sourceDirectory: Path, targetDirectory: Path, log: Logger): Option[String] =
syncPaths((sourceDirectory ###) ** AllPassFilter, targetDirectory, log)
def syncPaths(sources: PathFinder, targetDirectory: Path, log: Logger): Option[String] =
{
copy(sources.get, targetDirectory, log).right.flatMap
{ copiedTo => prune(targetDirectory, copiedTo, log).toLeft(()) }.left.toOption
}
def prune(directory: Path, keepOnly: Iterable[Path], log: Logger): Option[String] =
{
val existing = ((directory ###) ** AllPassFilter).get
val toRemove = scala.collection.mutable.HashSet(existing.toSeq: _*)
toRemove --= keepOnly
if(log.atLevel(Level.Debug))
toRemove.foreach(r => log.debug("Pruning " + r))
clean(toRemove, true, log)
}
/** Copies the contents of the <code>source</code> directory to the <code>target</code> directory .*/
def copyDirectory(source: Path, target: Path, log: Logger): Option[String] =
copyDirectory(source.asFile, target.asFile, log)
/** Copies the contents of the <code>source</code> directory to the <code>target</code> directory .*/
def copyDirectory(source: File, target: File, log: Logger): Option[String] =
{
require(source.isDirectory, "Source '" + source.getAbsolutePath + "' is not a directory.")
require(!target.exists, "Target '" + target.getAbsolutePath + "' already exists.")
def copyDirectory(sourceDir: File, targetDir: File): Option[String] =
createDirectory(targetDir, log) orElse copyContents(sourceDir, targetDir)
def copyContents(sourceDir: File, targetDir: File): Option[String] =
sourceDir.listFiles.foldLeft(None: Option[String])
{
(result, file) =>
result orElse
{
val targetFile = new File(targetDir, file.getName)
if(file.isDirectory)
copyDirectory(file, targetFile)
else
copyFile(file, targetFile, log)
}
}
copyDirectory(source, target)
}
/** Deletes the given file recursively.*/
def clean(file: Path, log: Logger): Option[String] = clean(file :: Nil, log)
/** Deletes the given files recursively.*/
def clean(files: Iterable[Path], log: Logger): Option[String] = clean(files, false, log)
/** Deletes the given files recursively. <code>quiet</code> determines the logging level.
* If it is true, each file in <code>files</code> is logged at the <code>info</code> level.
* If it is false, the <code>debug</code> level is used.*/
def clean(files: Iterable[Path], quiet: Boolean, log: Logger): Option[String] =
deleteFiles(Path.getFiles(files), quiet, log)
private def deleteFiles(files: Iterable[File], quiet: Boolean, log: Logger): Option[String] =
((None: Option[String]) /: files)( (result, file) => result orElse delete(file, quiet, log))
private def delete(file: File, quiet: Boolean, log: Logger): Option[String] =
{
def logMessage(message: => String)
{
log.log(if(quiet) Level.Debug else Level.Info, message)
}
Control.trapUnit("Error deleting file " + file + ": ", log)
{
if(file.isDirectory)
{
logMessage("Deleting directory " + file)
deleteFiles(wrapNull(file.listFiles), true, log)
file.delete
}
else if(file.exists)
{
logMessage("Deleting file " + file)
file.delete
}
None
}
}
/** Appends the given <code>String content</code> to the provided <code>file</code> using the default encoding.
* A new file is created if it does not exist.*/
def append(file: File, content: String, log: Logger): Option[String] = append(file, content, Charset.defaultCharset, log)
/** Appends the given <code>String content</code> to the provided <code>file</code> using the given encoding.
* A new file is created if it does not exist.*/
def append(file: File, content: String, charset: Charset, log: Logger): Option[String] =
write(file, content, charset, true, log)
/** Writes the given <code>String content</code> to the provided <code>file</code> using the default encoding.
* If the file exists, it is overwritten.*/
def write(file: File, content: String, log: Logger): Option[String] = write(file, content, Charset.defaultCharset, log)
/** Writes the given <code>String content</code> to the provided <code>file</code> using the given encoding.
* If the file already exists, it is overwritten.*/
def write(file: File, content: String, charset: Charset, log: Logger): Option[String] =
write(file, content, charset, false, log)
private def write(file: File, content: String, charset: Charset, append: Boolean, log: Logger): Option[String] =
{
if(charset.newEncoder.canEncode(content))
write(file, charset, append, log) { w => w.write(content); None }
else
Some("String cannot be encoded by charset " + charset.name)
}
/** Opens a <code>Writer</code> on the given file using the default encoding,
* passes it to the provided function, and closes the <code>Writer</code>.*/
def write(file: File, log: Logger)(f: Writer => Option[String]): Option[String] =
write(file, Charset.defaultCharset, log)(f)
/** Opens a <code>Writer</code> on the given file using the given encoding,
* passes it to the provided function, and closes the <code>Writer</code>.*/
def write(file: File, charset: Charset, log: Logger)(f: Writer => Option[String]): Option[String] =
write(file, charset, false, log)(f)
private def write(file: File, charset: Charset, append: Boolean, log: Logger)(f: Writer => Option[String]): Option[String] =
fileWriter(charset, append).ioOption(file, Writing, log)(f)
/** Opens a <code>Reader</code> on the given file using the default encoding,
* passes it to the provided function, and closes the <code>Reader</code>.*/
def read(file: File, log: Logger)(f: Reader => Option[String]): Option[String] =
read(file, Charset.defaultCharset, log)(f)
/** Opens a <code>Reader</code> on the given file using the default encoding,
* passes it to the provided function, and closes the <code>Reader</code>.*/
def read(file: File, charset: Charset, log: Logger)(f: Reader => Option[String]): Option[String] =
fileReader(charset).ioOption(file, Reading, log)(f)
/** Opens a <code>Reader</code> on the given file using the default encoding,
* passes it to the provided function, and closes the <code>Reader</code>.*/
def readValue[R](file: File, log: Logger)(f: Reader => Either[String, R]): Either[String, R] =
readValue(file, Charset.defaultCharset, log)(f)
/** Opens a <code>Reader</code> on the given file using the given encoding,
* passes it to the provided function, and closes the <code>Reader</code>.*/
def readValue[R](file: File, charset: Charset, log: Logger)(f: Reader => Either[String, R]): Either[String, R] =
fileReader(charset).io(file, Reading, log)(f)
/** Reads the contents of the given file into a <code>String</code> using the default encoding.
* The resulting <code>String</code> is wrapped in <code>Right</code>.*/
def readString(file: File, log: Logger): Either[String, String] = readString(file, Charset.defaultCharset, log)
/** Reads the contents of the given file into a <code>String</code> using the given encoding.
* The resulting <code>String</code> is wrapped in <code>Right</code>.*/
def readString(file: File, charset: Charset, log: Logger): Either[String, String] = readValue(file, charset, log)(readString)
def readString(in: InputStream, log: Logger): Either[String, String] = readString(in, Charset.defaultCharset, log)
def readString(in: InputStream, charset: Charset, log: Logger): Either[String, String] =
streamReader.io((in, charset), Reading, log)(readString)
def readString(in: Reader, log: Logger): Either[String, String] =
Control.trapAndFinally("Error reading bytes from reader: ", log)
{ readString(in) }
{ in.close() }
private def readString(in: Reader): Either[String, String] =
{
val builder = new StringBuilder
val buffer = new Array[Char](BufferSize)
def readNext()
{
val read = in.read(buffer, 0, buffer.length)
if(read >= 0)
{
builder.append(buffer, 0, read)
readNext()
}
else
None
}
readNext()
Right(builder.toString)
}
/** Appends the given bytes to the given file. */
def append(file: File, bytes: Array[Byte], log: Logger): Option[String] =
writeBytes(file, bytes, true, log)
/** Writes the given bytes to the given file. If the file already exists, it is overwritten.*/
def write(file: File, bytes: Array[Byte], log: Logger): Option[String] =
writeBytes(file, bytes, false, log)
private def writeBytes(file: File, bytes: Array[Byte], append: Boolean, log: Logger): Option[String] =
writeStream(file, append, log) { out => out.write(bytes); None }
/** Reads the entire file into a byte array. */
def readBytes(file: File, log: Logger): Either[String, Array[Byte]] = readStreamValue(file, log)(readBytes)
def readBytes(in: InputStream, log: Logger): Either[String, Array[Byte]] =
Control.trapAndFinally("Error reading bytes from input stream: ", log)
{ readBytes(in) }
{ in.close() }
private def readBytes(in: InputStream): Either[String, Array[Byte]] =
{
val out = new ByteArrayOutputStream
val buffer = new Array[Byte](BufferSize)
def readNext()
{
val read = in.read(buffer)
if(read >= 0)
{
out.write(buffer, 0, read)
readNext()
}
}
readNext()
Right(out.toByteArray)
}
/** Opens an <code>OutputStream</code> on the given file with append=true and passes the stream
* to the provided function. The stream is closed before this function returns.*/
def appendStream(file: File, log: Logger)(f: OutputStream => Option[String]): Option[String] =
fileOutputStream(true).ioOption(file, Appending, log)(f)
/** Opens an <code>OutputStream</code> on the given file and passes the stream
* to the provided function. The stream is closed before this function returns.*/
def writeStream(file: File, log: Logger)(f: OutputStream => Option[String]): Option[String] =
fileOutputStream(false).ioOption(file, Writing, log)(f)
private def writeStream(file: File, append: Boolean, log: Logger)(f: OutputStream => Option[String]): Option[String] =
if(append) appendStream(file, log)(f) else writeStream(file, log)(f)
/** Opens an <code>InputStream</code> on the given file and passes the stream
* to the provided function. The stream is closed before this function returns.*/
def readStream(file: File, log: Logger)(f: InputStream => Option[String]): Option[String] =
fileInputStream.ioOption(file, Reading, log)(f)
/** Opens an <code>InputStream</code> on the given file and passes the stream
* to the provided function. The stream is closed before this function returns.*/
def readStreamValue[R](file: File, log: Logger)(f: InputStream => Either[String, R]): Either[String, R] =
fileInputStream.io(file, Reading, log)(f)
/** Opens an <code>InputStream</code> on the given <code>URL</code> and passes the stream
* to the provided function. The stream is closed before this function returns.*/
def readStream(url: URL, log: Logger)(f: InputStream => Option[String]): Option[String] =
urlInputStream.ioOption(url, Reading, log)(f)
/** Opens an <code>InputStream</code> on the given <code>URL</code> and passes the stream
* to the provided function. The stream is closed before this function returns.*/
def readStreamValue[R](url: URL, log: Logger)(f: InputStream => Either[String, R]): Either[String, R] =
urlInputStream.io(url, Reading, log)(f)
/** Opens a <code>FileChannel</code> on the given file for writing and passes the channel
* to the given function. The channel is closed before this function returns.*/
def writeChannel(file: File, log: Logger)(f: FileChannel => Option[String]): Option[String] =
fileOutputChannel.ioOption(file, Writing, log)(f)
/** Opens a <code>FileChannel</code> on the given file for reading and passes the channel
* to the given function. The channel is closed before this function returns.*/
def readChannel(file: File, log: Logger)(f: FileChannel => Option[String]): Option[String] =
fileInputChannel.ioOption(file, Reading, log)(f)
/** Opens a <code>FileChannel</code> on the given file for reading and passes the channel
* to the given function. The channel is closed before this function returns.*/
def readChannelValue[R](file: File, log: Logger)(f: FileChannel => Either[String, R]): Either[String, R] =
fileInputChannel.io(file, Reading, log)(f)
private[sbt] (a: Array[File]): Array[File] =
if(a == null)
new Array[File](0)
else
a
/** Writes the given string to the writer followed by a newline.*/
private[sbt] def writeLine(writer: Writer, line: String)
{
writer.write(line)
writer.write(Newline)
}
def toFile(url: URL) =
try { new File(url.toURI) }
catch { case _: URISyntaxException => new File(url.getPath) }
/** The directory in which temporary files are placed.*/
val temporaryDirectory = new File(System.getProperty("java.io.tmpdir"))
def classLocation(cl: Class[_]): URL =
{
val codeSource = cl.getProtectionDomain.getCodeSource
if(codeSource == null) error("No class location for " + cl)
else codeSource.getLocation
}
def classLocationFile(cl: Class[_]): File = toFile(classLocation(cl))
def classLocation[T](implicit mf: scala.reflect.Manifest[T]): URL = classLocation(mf.erasure)
def classLocationFile[T](implicit mf: scala.reflect.Manifest[T]): File = classLocationFile(mf.erasure)
lazy val scalaLibraryJar: File = classLocationFile[scala.ScalaObject]
lazy val scalaCompilerJar: File = classLocationFile[scala.tools.nsc.Settings]
def scalaJars: Iterable[File] = List(scalaLibraryJar, scalaCompilerJar)
/** The producer of randomness for unique name generation.*/
private val random = new java.util.Random
private val Reading = "reading"
private val Writing = "writing"
private val Appending = "appending"
}
private abstract class OpenResource[Source, T] extends NotNull
{
import OpenResource.{unwrapEither, wrapEither}
protected def open(src: Source, log: Logger): Either[String, T]
def ioOption(src: Source, op: String, log: Logger)(f: T => Option[String]) =
unwrapEither( io(src, op, log)(wrapEither(f)) )
def io[R](src: Source, op: String, log: Logger)(f: T => Either[String,R]): Either[String, R] =
open(src, log).right flatMap
{
resource => Control.trapAndFinally("Error " + op + " "+ src + ": ", log)
{ f(resource) }
{ close(resource) }
}
protected def close(out: T): Unit
}
private trait CloseableOpenResource[Source, T <: Closeable] extends OpenResource[Source, T]
{
protected def close(out: T): Unit = out.close()
}
import scala.reflect.{Manifest => SManifest}
private abstract class WrapOpenResource[Source, T <: Closeable](implicit srcMf: SManifest[Source], targetMf: SManifest[T]) extends CloseableOpenResource[Source, T]
{
private def label[S](m: SManifest[S]) = m.erasure.getSimpleName
protected def open(source: Source): T
protected final def open(source: Source, log: Logger): Either[String, T] =
Control.trap("Error wrapping " + label(srcMf) + " in " + label(targetMf) + ": ", log) { Right(open(source)) }
}
private abstract class OpenFile[T] extends OpenResource[File, T]
{
protected def open(file: File): T
protected final def open(file: File, log: Logger): Either[String, T] =
{
val parent = file.getParentFile
if(parent != null)
FileUtilities.createDirectory(parent, log)
Control.trap("Error opening " + file + ": ", log) { Right(open(file)) }
}
}
private abstract class CloseableOpenFile[T <: Closeable] extends OpenFile[T] with CloseableOpenResource[File, T]
private object OpenResource
{
private def wrapEither[R](f: R => Option[String]): (R => Either[String, Unit]) = (r: R) => f(r).toLeft(())
private def unwrapEither(e: Either[String, Unit]): Option[String] = e.left.toOption
def fileOutputStream(append: Boolean) =
new CloseableOpenFile[FileOutputStream] { protected def open(file: File) = new FileOutputStream(file, append) }
def fileInputStream = new CloseableOpenFile[FileInputStream]
{ protected def open(file: File) = new FileInputStream(file) }
def urlInputStream = new CloseableOpenResource[URL, InputStream]
{ protected def open(url: URL, log: Logger) = Control.trap("Error opening " + url + ": ", log) { Right(url.openStream) } }
def fileOutputChannel = new CloseableOpenFile[FileChannel]
{ protected def open(f: File) = (new FileOutputStream(f)).getChannel }
def fileInputChannel = new CloseableOpenFile[FileChannel]
{ protected def open(f: File) = (new FileInputStream(f)).getChannel }
def fileWriter(charset: Charset, append: Boolean) = new CloseableOpenFile[Writer]
{ protected def open(f: File) = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f, append), charset)) }
def fileReader(charset: Charset) = new CloseableOpenFile[Reader]
{ protected def open(f: File) = new BufferedReader(new InputStreamReader(new FileInputStream(f), charset)) }
def jarFile(verify: Boolean) = new OpenFile[JarFile]
{ protected def open(f: File) = new JarFile(f, verify)
override protected def close(j: JarFile) = j.close() }
def zipFile = new OpenFile[ZipFile]
{ protected def open(f: File) = new ZipFile(f)
override protected def close(z: ZipFile) = z.close() }
def streamReader = new WrapOpenResource[(InputStream, Charset), Reader]
{ protected def open(streamCharset: (InputStream, Charset)) = new InputStreamReader(streamCharset._1, streamCharset._2) }
def gzipInputStream = new WrapOpenResource[InputStream, GZIPInputStream]
{ protected def open(in: InputStream) = new GZIPInputStream(in) }
def zipInputStream = new WrapOpenResource[InputStream, ZipInputStream]
{ protected def open(in: InputStream) = new ZipInputStream(in) }
def gzipOutputStream = new WrapOpenResource[OutputStream, GZIPOutputStream]
{ protected def open(out: OutputStream) = new GZIPOutputStream(out)
override protected def close(out: GZIPOutputStream) = out.finish() }
def jarOutputStream = new WrapOpenResource[OutputStream, JarOutputStream]
{ protected def open(out: OutputStream) = new JarOutputStream(out) }
def jarInputStream = new WrapOpenResource[InputStream, JarInputStream]
{ protected def open(in: InputStream) = new JarInputStream(in) }
def zipEntry(zip: ZipFile) = new CloseableOpenResource[ZipEntry, InputStream] {
protected def open(entry: ZipEntry, log: Logger) =
Control.trap("Error opening " + entry.getName + " in " + zip + ": ", log) { Right(zip.getInputStream(entry)) }
}
}

View File

@ -1,94 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Steven Blundy, Mark Harrah, Josh Cough
*/
package sbt
import ScalaProject.{optionsAsString, javaOptionsAsString}
trait IntegrationTesting extends NotNull
{
/** Override to provide pre-test setup. */
protected def pretests: Option[String] = None
/** Override to provide post-test cleanup. */
protected def posttests: Option[String] = None
}
trait ScalaIntegrationTesting extends IntegrationTesting
{ self: ScalaProject =>
protected def integrationTestTask(frameworks: Seq[TestFramework], classpath: PathFinder, analysis: CompileAnalysis, options: => Seq[TestOption]) =
testTask(frameworks, classpath, analysis, options)
}
trait BasicScalaIntegrationTesting extends BasicIntegrationTesting with MavenStyleIntegrationTestPaths
{ self: BasicScalaProject => }
/** A fully featured integration testing that may be mixed in with any subclass of <code>BasicScalaProject</code>.
* Pre-suite setup and post-suite cleanup are provide by overriding <code>pretests</code> and <code>posttests</code> respectively.*/
trait BasicIntegrationTesting extends ScalaIntegrationTesting with IntegrationTestPaths with BasicDependencyProject
{
self: BasicScalaProject =>
import BasicScalaIntegrationTesting._
lazy val integrationTestCompile = integrationTestCompileAction
lazy val integrationTest = integrationTestAction
val integrationTestCompileConditional = new CompileConditional(integrationTestCompileConfiguration, buildCompiler)
protected def integrationTestAction = integrationTestTask(integrationTestFrameworks, integrationTestClasspath, integrationTestCompileConditional.analysis, integrationTestOptions) dependsOn integrationTestCompile describedAs IntegrationTestCompileDescription
protected def integrationTestCompileAction = integrationTestCompileTask() dependsOn compile describedAs IntegrationTestDescription
protected def integrationTestCompileTask() = task{ integrationTestCompileConditional.run }
def integrationTestOptions: Seq[TestOption] =
TestSetup(() => pretests) ::
TestCleanup(() => posttests) ::
testOptions.toList
def integrationTestCompileOptions = testCompileOptions
def javaIntegrationTestCompileOptions: Seq[JavaCompileOption] = testJavaCompileOptions
def integrationTestConfiguration = if(useIntegrationTestConfiguration) Configurations.IntegrationTest else Configurations.Test
def integrationTestClasspath = fullClasspath(integrationTestConfiguration) +++ optionalClasspath
def integrationTestLabel = "integration-test"
def integrationTestCompileConfiguration = new IntegrationTestCompileConfig
protected def integrationTestDependencies = new LibraryDependencies(this, integrationTestCompileConditional)
def integrationTestFrameworks = testFrameworks
override def useIntegrationTestConfiguration = false
abstract override def extraDefaultConfigurations =
{
val superConfigurations = super.extraDefaultConfigurations
if(useIntegrationTestConfiguration)
integrationTestConfiguration :: superConfigurations
else
superConfigurations
}
abstract override def fullUnmanagedClasspath(config: Configuration) =
{
val superClasspath = super.fullUnmanagedClasspath(config)
if(config == integrationTestConfiguration)
integrationTestCompilePath +++ integrationTestResourcesPath +++ superClasspath
else
superClasspath
}
class IntegrationTestCompileConfig extends BaseCompileConfig
{
def label = integrationTestLabel
def sourceRoots = integrationTestScalaSourceRoots
def sources = integrationTestSources
def outputDirectory = integrationTestCompilePath
def classpath = integrationTestClasspath
def analysisPath = integrationTestAnalysisPath
def baseCompileOptions = integrationTestCompileOptions
def javaOptions = javaOptionsAsString(javaCompileOptions)
def fingerprints = getFingerprints(integrationTestFrameworks)
}
}
object BasicScalaIntegrationTesting
{
val IntegrationTestCompileDescription = "Compiles integration test sources."
val IntegrationTestDescription = "Runs all integration tests detected during compilation."
}

View File

@ -1,774 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009, 2010 Steven Blundy, Mark Harrah, David MacIver, Mikko Peltonen
*/
package sbt
import java.io.File
import scala.collection.immutable.TreeSet
import complete.HistoryCommands
import HistoryCommands.{Start => HistoryPrefix}
/** This class is the entry point for sbt. If it is given any arguments, it interprets them
* as actions, executes the corresponding actions, and exits. If there were no arguments provided,
* sbt enters interactive mode.*/
object Main
{
val NormalExitCode = 0
val SetupErrorExitCode = 1
val SetupDeclinedExitCode = 2
val LoadErrorExitCode = 3
val UsageErrorExitCode = 4
val BuildErrorExitCode = 5
val ProgramErrorExitCode = 6
val MaxInt = java.lang.Integer.MAX_VALUE
}
import Main._
class xMain extends xsbti.AppMain
{
final def run(configuration: xsbti.AppConfiguration): xsbti.MainResult =
{
def run0(remainingArguments: List[String], buildScalaVersion: Option[String]): xsbti.MainResult =
{
// done this way because in Scala 2.7.7, tail recursion in catch blocks is not optimized
val result = try { Right(run(configuration, remainingArguments, buildScalaVersion)) } catch { case re: ReloadException => Left(re) }
result match
{
case Left(re) => run0(re.remainingArguments, re.buildScalaVersion)
case Right(r) => r
}
}
run0(configuration.arguments.map(_.trim).toList, None)
}
final def run(configuration: xsbti.AppConfiguration, remainingArguments: List[String], buildScalaVersion: Option[String]): xsbti.MainResult =
{
val startTime = System.currentTimeMillis
Project.loadProject(configuration.provider, buildScalaVersion) match
{
case err: LoadSetupError =>
println("\n" + err.message)
ExitHooks.runExitHooks(Project.bootLogger)
Exit(SetupErrorExitCode)
case LoadSetupDeclined =>
ExitHooks.runExitHooks(Project.bootLogger)
Exit(SetupDeclinedExitCode)
case err: LoadError =>
{
val log = Project.bootLogger
println(err.message)
ExitHooks.runExitHooks(log)
// Because this is an error that can probably be corrected, prompt user to try again.
val line =
try { SimpleReader.readLine("\n Hit enter to retry or 'exit' to quit: ") }
catch
{
case e =>
log.trace(e)
log.error(e.toString)
None
}
line match
{
case Some(l) => if(!isTerminateAction(l)) run(configuration, remainingArguments, buildScalaVersion) else Exit(NormalExitCode)
case None => Exit(LoadErrorExitCode)
}
}
case success: LoadSuccess =>
{
import success.project
try
{
// in interactive mode, fill all undefined properties
if(configuration.arguments.length > 0 || fillUndefinedProjectProperties(project.projectClosure.toList.reverse))
startProject(project, configuration, remainingArguments, startTime)
else
Exit(NormalExitCode)
}
finally { ExitHooks.runExitHooks(project.log) }
}
}
}
/** If no arguments are provided, drop to interactive prompt.
* If the user wants to run commands before dropping to the interactive prompt,
* make dropping to the interactive prompt the action to perform on failure */
private def initialize(args: List[String]): List[String] =
args.lastOption match
{
case None => InteractiveCommand :: Nil
case Some(InteractiveCommand) => (FailureHandlerPrefix + InteractiveCommand) :: args
case Some(ExitCommand | QuitCommand) => args
case _ => args ::: ExitCommand :: Nil
}
private def startProject(project: Project, configuration: xsbti.AppConfiguration, remainingArguments: List[String], startTime: Long): xsbti.MainResult =
{
project.log.info("Building project " + project.name + " " + project.version.toString + " against Scala " + project.buildScalaVersion)
project.log.info(" using " + project.getClass.getName + " with sbt " + ComponentManager.version + " and Scala " + project.defScalaVersion.value)
processArguments(project, initialize(remainingArguments), configuration, startTime) match
{
case e: xsbti.Exit =>
printTime(project, startTime, "session")
if(e.code == NormalExitCode)
project.log.success("Build completed successfully.")
else
project.log.error("Error during build.")
e
case r => r
}
}
/** This is the top-level command processing method. */
private def processArguments(baseProject: Project, arguments: List[String], configuration: xsbti.AppConfiguration, startTime: Long): xsbti.MainResult =
{
type OnFailure = Option[String]
def ExitOnFailure = None
lazy val interactiveContinue = Some( InteractiveCommand )
def remoteContinue(port: Int) = Some( FileCommandsPrefix + "-" + port )
lazy val PHandler = new processor.Handler(baseProject)
// replace in 2.8
trait Trampoline
class Done(val r: xsbti.MainResult) extends Trampoline
class Continue(project: Project, arguments: List[String], failAction: OnFailure) extends Trampoline {
def apply() = process(project, arguments, failAction)
}
def continue(project: Project, arguments: List[String], failAction: OnFailure) = new Continue(project, arguments, failAction)
def result(r: xsbti.MainResult) = new Done(r)
def run(t: Trampoline): xsbti.MainResult = t match { case d: Done => d.r; case c: Continue => run(c()) }
def process(project: Project, arguments: List[String], failAction: OnFailure): Trampoline =
{
project.log.debug("commands " + failAction.map("(on failure: " + _ + "): ").mkString + arguments.mkString(", "))
def rememberCurrent(newArgs: List[String]) = rememberProject(rememberFail(newArgs))
def rememberProject(newArgs: List[String]) =
if(baseProject.name != project.name && !internal(project)) (ProjectAction + " " + project.name) :: newArgs else newArgs
def rememberFail(newArgs: List[String]) = failAction.map(f => (FailureHandlerPrefix + f)).toList ::: newArgs
def tryOrFail(action: => Trampoline) = try { action } catch { case e: Exception => logCommandError(project.log, e); failed(BuildErrorExitCode) }
def reload(args: List[String]) =
{
val newID = new ApplicationID(configuration.provider.id, baseProject.sbtVersion.value)
result( new Reboot(project.defScalaVersion.value, rememberCurrent(args), newID, configuration.baseDirectory) )
}
def failed(code: Int) =
failAction match
{
case Some(c) => continue(project, c :: Nil, ExitOnFailure)
case None => result( Exit(code) )
}
arguments match
{
case "" :: tail => continue(project, tail, failAction)
case ResetCommand :: tail => JLine.resetTerminal(); continue(project, tail, failAction)
case x :: tail if x.startsWith(";") => continue(project, x.split("""\s*;\s*""").toList ::: tail, failAction)
case (ExitCommand | QuitCommand) :: _ => result( Exit(NormalExitCode) )
case RebootCommand :: tail => reload( tail )
case InteractiveCommand :: _ => continue(project, prompt(baseProject, project) :: arguments, interactiveContinue)
case BuilderCommand :: tail =>
Project.getProjectBuilder(project.info, project.log) match
{
case Some(b) => project.log.info("Set current project to builder of " + project.name); continue(b, tail, failAction)
case None => project.log.error("No project/build directory for " + project.name + ".\n Not switching to builder project."); failed(BuildErrorExitCode)
}
case SpecificBuild(version, action) :: tail =>
if(Some(version) != baseProject.info.buildScalaVersion)
{
if(checkVersion(baseProject, version))
throw new ReloadException(rememberCurrent(action :: tail), Some(version))
else
failed(UsageErrorExitCode)
}
else
continue(project, action :: tail, failAction)
case CrossBuild(action) :: tail =>
if(checkAction(project, action))
{
CrossBuild(project, action) match
{
case Some(actions) => continue(project, actions ::: tail, failAction)
case None => failed(UsageErrorExitCode)
}
}
else
failed(UsageErrorExitCode)
case SetProject(name) :: tail =>
SetProject(baseProject, name, project) match
{
case Some(newProject) => continue(newProject, tail, failAction)
case None => failed(BuildErrorExitCode)
}
case action :: tail if action.startsWith(HistoryPrefix) =>
HistoryCommands(action.substring(HistoryPrefix.length).trim, baseProject.historyPath, JLine.MaxHistorySize, project.log) match
{
case Some(commands) =>
commands.foreach(println) //better to print it than to log it
continue(project, commands ::: tail, failAction)
case None => failed(UsageErrorExitCode)
}
case action :: tail if action.startsWith(FileCommandsPrefix) =>
getSource(action.substring(FileCommandsPrefix.length).trim, baseProject.info.projectDirectory) match
{
case Left(portAndSuccess) =>
val port = Math.abs(portAndSuccess)
val previousSuccess = portAndSuccess >= 0
readMessage(port, previousSuccess) match
{
case Some(message) => continue(project, message :: (FileCommandsPrefix + port) :: Nil, remoteContinue(port))
case None =>
project.log.error("Connection closed.")
failed(BuildErrorExitCode)
}
case Right(file) =>
readLines(project, file) match
{
case Some(lines) => continue(project, lines ::: tail , failAction)
case None => failed(UsageErrorExitCode)
}
}
case action :: tail if action.startsWith(FailureHandlerPrefix) =>
val errorAction = action.substring(FailureHandlerPrefix.length).trim
continue(project, tail, if(errorAction.isEmpty) None else Some(errorAction) )
case action :: tail if action.startsWith(ProcessorPrefix) =>
val processorCommand = action.substring(ProcessorPrefix.length).trim
val runner = processor.CommandRunner(PHandler.manager, PHandler.defParser, ProcessorPrefix, project.log)
tryOrFail {
runner(processorCommand)
continue(project, tail, failAction)
}
case PHandler(parsed) :: tail =>
tryOrFail {
parsed.processor(parsed.label, project, failAction, parsed.arguments) match
{
case s: processor.Success => continue(s.project, s.insertArguments ::: tail, s.onFailure)
case e: processor.Exit => result( Exit(e.code) )
case r: processor.Reload => reload( r.insertArguments ::: tail )
}
}
case action :: tail =>
val success = processAction(baseProject, project, action, failAction == interactiveContinue)
if(success) continue(project, tail, failAction)
else failed(BuildErrorExitCode)
case Nil =>
project.log.error("Invalid internal sbt state: no arguments")
result( Exit(ProgramErrorExitCode) )
}
}
run(process(baseProject, arguments, ExitOnFailure))
}
private def internal(p: Project) = p.isInstanceOf[InternalProject]
private def isInteractive(failureActions: Option[List[String]]) = failureActions == Some(InteractiveCommand :: Nil)
private def getSource(action: String, baseDirectory: File) =
{
try { Left(action.toInt) }
catch { case _: NumberFormatException => Right(new File(baseDirectory, action)) }
}
private def readMessage(port: Int, previousSuccess: Boolean): Option[String] =
{
// split into two connections because this first connection ends the previous communication
xsbt.IPC.client(port) { _.send(previousSuccess.toString) }
// and this second connection starts the next communication
xsbt.IPC.client(port) { ipc =>
val message = ipc.receive
if(message eq null) None else Some(message)
}
}
object SetProject
{
def unapply(s: String) =
if(s.startsWith(ProjectAction + " "))
Some(s.substring(ProjectAction.length + 1))
else
None
def apply(baseProject: Project, projectName: String, currentProject: Project) =
{
val found = baseProject.projectClosure.find(_.name == projectName)
found match
{
case Some(newProject) => printProject("Set current project to ", newProject)
case None => currentProject.log.error("Invalid project name '" + projectName + "' (type 'projects' to list available projects).")
}
found
}
}
object SpecificBuild
{
import java.util.regex.Pattern.{compile,quote}
val pattern = compile(quote(SpecificBuildPrefix) + """\s*(\S+)\s*(.*)""")
def unapply(s: String) =
{
val m = pattern.matcher(s)
if(m.matches)
Some(m.group(1).trim, m.group(2).trim)
else
None
}
}
def checkVersion(p: Project, version: String) =
{
try { p.getScalaInstance(version); true }
catch { case e: xsbti.RetrieveException => p.log.error(e.getMessage); false }
}
object CrossBuild
{
def unapply(s: String) = if(s.startsWith(CrossBuildPrefix) && !s.startsWith(SpecificBuildPrefix)) Some(s.substring(1)) else None
def apply(project: Project, action: String): Option[List[String]] =
{
val againstScalaVersions = project.crossScalaVersions
if(againstScalaVersions.isEmpty)
{
Console.println("Project does not declare any Scala versions to cross-build against, building against current version...")
Some(action :: Nil)
}
else
{
if( !againstScalaVersions.forall(v => checkVersion(project, v)) )
None
else
{
val actions =
againstScalaVersions.toList.map(SpecificBuildPrefix + _ + " " + action) ::: // build against all versions
(SpecificBuildPrefix + project.buildScalaVersion) :: // reset to the version before the cross-build
Nil
Some(actions)
}
}
}
}
private def readLines(project: Project, file: File): Option[List[String]] =
{
try { Some(xsbt.FileUtilities.readLines(file)) }
catch { case e: Exception =>
project.log.trace(e)
project.log.error("Error reading commands from file " + file.getAbsolutePath + ": " + e.toString)
None
}
}
private def prompt(baseProject: Project, project: Project): String =
{
// the times for evaluating the lazy vals here are a few hundred ms out of a 2s startup
lazy val projectNames = baseProject.projectClosure.map(_.name)
val prefixes = ContinuousExecutePrefix :: CrossBuildPrefix :: Nil
lazy val scalaVersions = baseProject.crossScalaVersions ++ Seq(baseProject.defScalaVersion.value)
lazy val methods = project.methods
lazy val methodCompletions = new ExtraCompletions { def names = methods.keys.toList; def completions(name: String) = methods(name).completions }
lazy val completors = new Completors(ProjectAction, projectNames, basicCommands, List(GetAction, SetAction), SpecificBuildPrefix, scalaVersions, prefixes, project.taskNames, project.propertyNames, methodCompletions)
val reader = new LazyJLineReader(baseProject.historyPath, MainCompletor(completors), baseProject.log)
reader.readLine("> ").getOrElse(ExitCommand)
}
/** The name of the command that loads a console with access to the current project through the variable 'project'.*/
val ProjectConsoleAction = "console-project"
/** The name of the command that shows the current project and logging level of that project.*/
val ShowCurrent = "current"
/** The name of the command that shows all available actions.*/
val ShowActions = "actions"
/** The name of the command that sets the currently active project.*/
val ProjectAction = "project"
/** The name of the command that shows all available projects.*/
val ShowProjectsAction = "projects"
val ExitCommand = "exit"
val QuitCommand = "quit"
/** The name of the command that resets JLine. This is necessary when resuming from suspension.*/
val ResetCommand = "reset"
/** The name of the command that switches to the builder project.*/
val BuilderCommand = "builder"
/** The name of the command that loads the interactive shell.*/
val InteractiveCommand = "shell"
/** The list of lowercase command names that may be used to terminate the program.*/
val TerminateActions: Iterable[String] = ExitCommand :: QuitCommand :: Nil
/** The name of the command that sets the value of the property given as its argument.*/
val SetAction = "set"
/** The name of the command that gets the value of the property given as its argument.*/
val GetAction = "get"
/** The name of the command that displays the help message. */
val HelpAction = "help"
/** The command for reloading sbt.*/
val RebootCommand = "reload"
/** The name of the command that toggles logging stacktraces. */
val TraceCommand = "trace"
/** The name of the command that compiles all sources continuously when they are modified. */
val ContinuousCompileCommand = "cc"
/** The prefix used to identify a request to execute the remaining input on source changes.*/
val ContinuousExecutePrefix = "~"
/** The prefix used to identify a request to execute the remaining input across multiple Scala versions.*/
val CrossBuildPrefix = "+"
/** The prefix used to identify a request to execute the remaining input after the next space against the
* Scala version between this prefix and the space (i.e. '++version action' means execute 'action' using
* Scala version 'version'. */
val SpecificBuildPrefix = "++"
/** The prefix used to identify a file or local port to read commands from. */
val FileCommandsPrefix = "<"
/** The prefix used to identify the action to run after an error*/
val FailureHandlerPrefix = "-"
/** The prefix used to identify commands for managing processors.*/
val ProcessorPrefix = "*"
/** The number of seconds between polling by the continuous compile command.*/
val ContinuousCompilePollDelaySeconds = 1
/** The list of logging levels.*/
private def logLevels: Iterable[String] = TreeSet.empty[String] ++ Level.values.map(_.toString)
/** The list of all interactive commands other than logging level.*/
private def basicCommands: Iterable[String] = TreeSet(ShowProjectsAction, ShowActions, ShowCurrent, HelpAction,
RebootCommand, TraceCommand, ContinuousCompileCommand, ProjectConsoleAction, BuilderCommand) ++
logLevels.toList ++ TerminateActions ++
HistoryCommands.plainCommands
private def processAction(baseProject: Project, currentProject: Project, action: String, isInteractive: Boolean): Boolean =
action match
{
case HelpAction => displayHelp(isInteractive); true
case ShowProjectsAction => baseProject.projectClosure.foreach(listProject); true
case ProjectConsoleAction =>
showResult(Run.projectConsole(currentProject), currentProject.log)
case _ =>
if(action.startsWith(SetAction + " "))
setProperty(currentProject, action.substring(SetAction.length + 1))
else if(action.startsWith(GetAction + " "))
getProperty(currentProject, action.substring(GetAction.length + 1))
else if(action.startsWith(TraceCommand + " "))
setTrace(currentProject, action.substring(TraceCommand.length + 1))
else
handleCommand(currentProject, action)
}
private def printCmd(name:String, desc:String) = Console.println(" " + name + " : " + desc)
val BatchHelpHeader = "You may execute any project action or method or one of the commands described below."
val InteractiveHelpHeader = "You may execute any project action or one of the commands described below. Only one action " +
"may be executed at a time in interactive mode and is entered by name, as it would be at the command line." +
" Also, tab completion is available."
private def displayHelp(isInteractive: Boolean)
{
Console.println(if(isInteractive) InteractiveHelpHeader else BatchHelpHeader)
Console.println("Available Commands:")
printCmd("<action name>", "Executes the project specified action.")
printCmd("<method name> <parameter>*", "Executes the project specified method.")
printCmd("<processor label> <arguments>", "Runs the specified processor.")
printCmd(ContinuousExecutePrefix + " <command>", "Executes the project specified action or method whenever source files change.")
printCmd(FileCommandsPrefix + " file", "Executes the commands in the given file. Each command should be on its own line. Empty lines and lines beginning with '#' are ignored")
printCmd(CrossBuildPrefix + " <command>", "Executes the project specified action or method for all versions of Scala defined in crossScalaVersions.")
printCmd(SpecificBuildPrefix + "<version> <command>", "Changes the version of Scala building the project and executes the provided command. <command> is optional.")
printCmd(ProcessorPrefix, "Prefix for commands for managing processors. Run '" + ProcessorPrefix + "help' for details.")
printCmd(HistoryPrefix, "Prefix for history commands. Run '" + HistoryPrefix+ "' for history command help.")
printCmd(ShowActions, "Shows all available actions.")
printCmd(RebootCommand, "Reloads sbt, picking up modifications to sbt.version or scala.version and recompiling modified project definitions.")
printCmd(HelpAction, "Displays this help message.")
printCmd(ShowCurrent, "Shows the current project, Scala version, and logging level.")
printCmd(Level.values.mkString(", "), "Set logging for the current project to the specified level.")
printCmd(TraceCommand + " " + validTraceArguments, "Configures stack trace logging. " + traceExplanation)
printCmd(ProjectAction + " <project name>", "Sets the currently active project.")
printCmd(ShowProjectsAction, "Shows all available projects.")
printCmd(TerminateActions.elements.mkString(", "), "Terminates the build.")
printCmd(SetAction + " <property> <value>", "Sets the value of the property given as its argument.")
printCmd(GetAction + " <property>", "Gets the value of the property given as its argument.")
printCmd(ProjectConsoleAction, "Enters the Scala interpreter with the current project definition bound to the variable 'current' and all members imported.")
printCmd(BuilderCommand, "Set the current project to be the project definition builder.")
if(!isInteractive)
printCmd(InteractiveCommand, "Enters the sbt interactive shell")
}
private def listProject(p: Project) = printProject("\t", p)
private def printProject(prefix: String, p: Project): Unit =
Console.println(prefix + p.name + " " + p.version)
/** Handles the given command string provided at the command line. Returns false if there was an error*/
private def handleCommand(project: Project, command: String): Boolean =
{
command match
{
case GetAction => getArgumentError(project.log)
case SetAction => setArgumentError(project.log)
case ProjectAction => setProjectError(project.log)
case TraceCommand => setTraceError(project.log); true
case ShowCurrent =>
printProject("Current project is ", project)
Console.println("Current Scala version is " + project.buildScalaVersion)
Console.println("Current log level is " + project.log.getLevel)
printTraceEnabled(project)
true
case ShowActions => showActions(project); true
case Level(level) => setLevel(project, level); true
case ContinuousCompileCommand => compileContinuously(project)
case action if action.startsWith(ContinuousExecutePrefix) => executeContinuously(project, action.substring(ContinuousExecutePrefix.length).trim)
case action => handleAction(project, action)
}
}
private def showActions(project: Project): Unit = Console.println(project.taskAndMethodList)
// returns true if it succeeded
private def handleAction(project: Project, action: String): Boolean =
{
def show(result: Option[String]): Boolean = showResult(result, project.log)
val startTime = System.currentTimeMillis
val result = withAction(project, action)( (name, params) => show(project.call(name, params)))( name => show(project.act(name)))
printTime(project, startTime, "")
result
}
// returns true if it succeeded
private def showResult(result: Option[String], log: Logger): Boolean =
{
result match
{
case Some(errorMessage) => log.error(errorMessage); false
case None => log.success("Successful."); true
}
}
// true if the action exists
private def checkAction(project: Project, actionString: String): Boolean =
withAction(project, actionString)( (n,p) => true)( n => true)
private def withAction(project: Project, actionString: String)(ifMethod: (String, Array[String]) => Boolean)(ifAction: String => Boolean): Boolean =
{
def didNotExist(taskType: String, name: String) =
{
project.log.error("No " + taskType + " named '" + name + "' exists.")
project.log.info("Execute 'help' for a list of commands or 'actions' for a list of available project actions and methods.")
false
}
impl.CommandParser.parse(actionString) match
{
case Left(errMsg) => project.log.error(errMsg); false
case Right((name, parameters)) =>
if(project.methods.contains(name))
ifMethod(name, parameters.toArray)
else if(!parameters.isEmpty)
didNotExist("method", name)
else if(project.deepTasks.contains(name))
ifAction(name)
else
didNotExist("action", name)
}
}
/** Toggles whether stack traces are enabled.*/
private def setTrace(project: Project, value: String): Boolean =
{
try
{
val newValue = if(value == "on") MaxInt else if(value == "off") -1 else if(value == "nosbt") 0 else value.toInt
project.projectClosure.foreach(_.log.setTrace(newValue))
printTraceEnabled(project)
true
}
catch { case _: NumberFormatException => setTraceError(project.log) }
}
private def printTraceEnabled(project: Project)
{
def traceLevel(level: Int) = if(level == 0) " (no sbt stack elements)" else if(level == MaxInt) "" else " (maximum " + level + " stack elements per exception)"
Console.println("Stack traces are " + (if(project.log.traceEnabled) "enabled" + traceLevel(project.log.getTrace) else "disabled"))
}
/** Sets the logging level on the given project.*/
private def setLevel(project: Project, level: Level.Value)
{
project.projectClosure.foreach(_.log.setLevel(level))
Console.println("Set log level to " + project.log.getLevel)
}
/** Prints the elapsed time to the given project's log using the given
* initial time and the label 's'.*/
private def printTime(project: Project, startTime: Long, s: String)
{
val endTime = System.currentTimeMillis()
project.log.info("")
val ss = if(s.isEmpty) "" else s + " "
project.log.info("Total " + ss + "time: " + (endTime - startTime + 500) / 1000 + " s, completed " + nowString)
}
private def nowString =
{
import java.text.DateFormat
val format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
format.format(new java.util.Date)
}
/** Provides a partial message describing why the given property is undefined. */
private def undefinedMessage(property: Project#UserProperty[_]): String =
{
property.resolve match
{
case vu: UndefinedValue => " is not defined."
case e: ResolutionException => " has invalid value: " + e.toString
case _ => ""
}
}
/** Prompts the user for the value of undefined properties. 'first' is true if this is the first time
* that the current property has been prompted.*/
private def fillUndefinedProperties(project: Project, properties: List[(String, Project#Property[_])], first: Boolean): Boolean =
{
properties match
{
case (name, variable) :: tail =>
{
val shouldAdvanceOrQuit =
variable match
{
case property: Project#UserProperty[_] =>
if(first)
project.log.error(" Property '" + name + "' " + undefinedMessage(property))
for(newValue <- SimpleReader.readLine(" Enter new value for " + name + " : ")) yield
{
try
{
property.setStringValue(newValue)
true
}
catch
{
case e =>
project.log.error("Invalid value: " + e.getMessage)
false
}
}
case _ => Some(true)
}
shouldAdvanceOrQuit match
{
case Some(shouldAdvance) => fillUndefinedProperties(project, if(shouldAdvance) tail else properties, shouldAdvance)
case None => false
}
}
case Nil => true
}
}
/** Iterates over the undefined properties in the given projects, prompting the user for the value of each undefined
* property.*/
private def fillUndefinedProjectProperties(projects: List[Project]): Boolean =
{
projects match
{
case project :: remaining =>
val uninitialized = project.uninitializedProperties.toList
if(uninitialized.isEmpty)
fillUndefinedProjectProperties(remaining)
else
{
project.log.error("Project in " + project.info.projectDirectory.getAbsolutePath + " has undefined properties.")
val result = fillUndefinedProperties(project, uninitialized, true) && fillUndefinedProjectProperties(remaining)
project.saveEnvironment()
result
}
case Nil => true
}
}
/** Prints the value of the property with the given name in the given project. */
private def getProperty(project: Project, propertyName: String): Boolean =
{
if(propertyName.isEmpty)
{
project.log.error("No property name specified.")
false
}
else
{
project.getPropertyNamed(propertyName) match
{
case Some(property) =>
property.resolve match
{
case u: UndefinedValue => project.log.error("Value of property '" + propertyName + "' is undefined."); false
case ResolutionException(m, e) => project.log.error(m); false
case DefinedValue(value, isInherited, isDefault) => Console.println(value.toString); true
}
case None =>
val value = System.getProperty(propertyName)
if(value == null)
project.log.error("No property named '" + propertyName + "' is defined.")
else
Console.println(value)
value != null
}
}
}
/** Separates the space separated property name/value pair and stores the value in the user-defined property
* with the given name in the given project. If no such property exists, the value is stored in a system
* property. */
private def setProperty(project: Project, propertyNameAndValue: String): Boolean =
{
val m = """(\S+)(\s+\S.*)?""".r.pattern.matcher(propertyNameAndValue)
if(m.matches())
{
val name = m.group(1)
val newValue =
{
val v = m.group(2)
if(v == null) "" else v.trim
}
def notePending(changed: String): Unit = Console.println(" Build will use " + changed + newValue + " after running 'reload' command or restarting sbt.")
project.getPropertyNamed(name) match
{
case Some(property) =>
{
try
{
property.setStringValue(newValue)
property match
{
case project.defScalaVersion | project.buildScalaVersions => notePending("Scala ")
case project.sbtVersion => notePending("sbt ")
case _ => Console.println(" Set property '" + name + "' = '" + newValue + "'")
}
true
}
catch { case e =>
project.log.error("Error setting property '" + name + "' in " + project.environmentLabel + ": " + e.toString)
false
}
finally { project.saveEnvironment().foreach(msg => project.log.error("Error saving environment: " + msg)) }
}
case None =>
{
System.setProperty(name, newValue)
project.log.info(" Set system property '" + name + "' = '" + newValue + "'")
true
}
}
}
else
setArgumentError(project.log)
}
private def compileContinuously(project: Project) = executeContinuously(project, "test-compile")
private def executeContinuously(project: Project, action: String) =
{
def shouldTerminate: Boolean = (System.in.available > 0) && (project.terminateWatch(System.in.read()) || shouldTerminate)
val actionValid = checkAction(project, action)
if(actionValid)
{
var count = 0
val sourcesFinder: PathFinder = (Path.emptyPathFinder /: project.topologicalSort)(_ +++ _.watchPaths)
SourceModificationWatch.watchUntil(sourcesFinder, ContinuousCompilePollDelaySeconds)(shouldTerminate)
{
count += 1
handleAction(project, action)
Console.println(count + ". Waiting for source changes... (press enter to interrupt)")
}
while (System.in.available() > 0) System.in.read()
}
actionValid
}
def validTraceArguments = "'on', 'nosbt', 'off', or <integer>"
def traceExplanation = "'nosbt' prints stack traces up to the first sbt frame. An integer gives the number of frames to show per exception."
private def isTerminateAction(s: String) = TerminateActions.elements.contains(s.toLowerCase)
private def setTraceError(log: Logger) = logError(log)("Invalid arguments for 'trace': expected " + validTraceArguments + ".")
private def setArgumentError(log: Logger) = logError(log)("Invalid arguments for 'set': expected property name and new value.")
private def getArgumentError(log: Logger) = logError(log)("Invalid arguments for 'get': expected property name.")
private def setProjectError(log: Logger) = logError(log)("Invalid arguments for 'project': expected project name.")
private def logError(log: Logger)(s: String) = { log.error(s); false }
private def logCommandError(log: Logger, e: Throwable) =
e match
{
case pe: processor.ProcessorException =>
if(pe.getCause ne null) log.trace(pe.getCause)
log.error(e.getMessage)
case e =>
log.trace(e)
log.error(e.toString)
}
}

View File

@ -1,537 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah, David MacIver
*/
package sbt
import xsbti.{AppProvider, ScalaProvider}
import xsbt.{AnalyzingCompiler, ScalaInstance}
import java.io.File
import java.net.URLClassLoader
import scala.collection._
import FileUtilities._
import Project._
trait Project extends TaskManager with Dag[Project] with BasicEnvironment
{
/** The logger for this project definition. */
final val log: Logger = logImpl
protected def logImpl: Logger =
{
val lg = new BufferedLogger(new FilterLogger(info.logger))
lg.setLevel(defaultLoggingLevel)
lg
}
protected def defaultLoggingLevel = Level.Info
trait ActionOption extends NotNull
/** Basic project information. */
def info: ProjectInfo
/** The project name. */
def name: String = projectName.value
/** The project version. */
def version: Version = projectVersion.value
/** The project organization. */
def organization: String = projectOrganization.value
/** True if the project should cater to a quick throwaway project setup.*/
def scratch = projectScratch.value
final type ManagerType = Project
final type ManagedTask = Project#Task
/** The tasks declared on this project. */
def tasks: Map[String, ManagedTask]
/** The task methods declared on this project */
def methods: Map[String, MethodTask]
/** The names of all available tasks that may be called through `act`. These include
* the names of the Tasks in `tasks` and those of all dependencies.*/
def taskNames: Iterable[String] = deepTasks.keys.toList
/** The names of all available method tasks that may be called through `call`. These
* only include the names of the MethodTasks in `methods` and not those of dependencies.*/
def methodNames: Iterable[String] = methods.keys.toList
/** A description of all available method tasks in this project, but not of dependencies. */
def methodList: String = descriptionList(methods)
/** A description of all available tasks in this project and all dependencies. If there
* are different tasks with the same name, only one will be included. */
def taskList: String = descriptionList(deepTasks)
final def taskName(task: Task) = tasks.find( _._2 eq task ).map(_._1)
/** A description of all available tasks in this project and all dependencies and all
* available method tasks in this project, but not of dependencies. If there
* are different tasks or methods with the same name, only one will be included. */
def taskAndMethodList: String = descriptionList(tasksAndMethods)
/** The actions and methods declared on this project. */
final def tasksAndMethods: Map[String, Described] =
immutable.TreeMap.empty[String, Described] ++ methods ++ tasks
private def descriptionList(described: Map[String, Described]): String =
{
val buffer = new StringBuilder
for((name, d) <- described)
buffer.append("\t" + name + d.description.map(x => ": " + x).getOrElse("") + "\n")
buffer.toString
}
/** Combines the method task maps of this project and all dependencies.*/
private[sbt] def deepMethods: Map[String, Project#MethodTask] = deep(_.methods)
/** Combines the task maps of this project and all dependencies.*/
private[sbt] def deepTasks: Map[String, Project#Task] = deep(_.tasks)
private def deep[T](p: Project => Map[String, T]): Map[String, T] =
{
var tasks: immutable.SortedMap[String,T] = new immutable.TreeMap[String, T]
for(dependentProject <- topologicalSort)
tasks ++= p(dependentProject).elements
tasks
}
/** A map of names to projects for all subprojects of this project. These are typically explicitly
* specified for the project and are different from those specified in the project constructor. The
* main use within sbt is in ParentProject.*/
def subProjects: Map[String, Project] = immutable.Map.empty
def projectClosure: List[Project] = Dag.topologicalSort(this)(p => p.dependencies ++ p.subProjects.values.toList)
def call(name: String, parameters: Array[String]): Option[String] =
{
methods.get(name) match
{
case Some(method) =>run(method(parameters), name)
case None => Some("Method '" + name + "' does not exist.")
}
}
private def run(task: Project#Task, taskName: String): Option[String] =
impl.RunTask(task, taskName, parallelExecution) match
{
case Nil => None
case x => Some(Set(x: _*).mkString("\n"))
}
/** Executes the task with the given name. This involves executing the task for all
* project dependencies (transitive) and then for this project. Not every dependency
* must define a task with the given name. If this project and all dependencies
* do not define a task with the given name, an error is generated indicating this.*/
def act(name: String): Option[String] =
{
val ordered = topologicalSort
val definedTasks = ordered.flatMap(_.tasks.get(name).toList)
def virtualTask(name: String): Task = new Task(None, definedTasks.filter(!_.interactive), false, None)
if(definedTasks.isEmpty)
Some("Action '" + name + "' does not exist.")
else
{
tasks.get(name) match
{
case None =>
val virtual = virtualTask(name)
if(virtual.dependencies.size == definedTasks.size)
run(virtual, name)
else
{
Some("Cannot run interactive action '" + name +
"' defined on multiple subprojects (change to the desired project with 'project <name>').")
}
case Some(task) => run(task, name)
}
}
}
/** Logs the list of projects at the debug level.*/
private def showBuildOrder(order: Iterable[Project])
{
log.debug("Project build order:")
order.foreach(x => log.debug(" " + x.name) )
log.debug("")
}
/** Converts a String to a path relative to the project directory of this project. */
implicit def path(component: String): Path = info.projectPath / component
/** Converts a String to a simple name filter. * has the special meaning: zero or more of any character */
implicit def filter(simplePattern: String): NameFilter = GlobFilter(simplePattern)
/** Loads the project at the given path and declares the project to have the given
* dependencies. This method will configure the project according to the
* project/ directory in the directory denoted by path.*/
def project(path: Path, deps: Project*): Project = getProject(Project.loadProject(path, deps, Some(this), info.logger, info.app, info.buildScalaVersion), path)
/** Loads the project at the given path using the given name and inheriting this project's version.
* The builder class is the default builder class, sbt.DefaultProject. The loaded project is declared
* to have the given dependencies. Any project/build/ directory for the project is ignored.*/
def project(path: Path, name: String, deps: Project*): Project = project(path, name, Project.DefaultBuilderClass, deps: _*)
/** Loads the project at the given path using the given name and inheriting it's version from this project.
* The Project implementation used is given by builderClass. The dependencies are declared to be
* deps. Any project/build/ directory for the project is ignored.*/
def project[P <: Project](path: Path, name: String, builderClass: Class[P], deps: Project*): P =
{
require(builderClass != this.getClass, "Cannot recursively construct projects of same type: " + builderClass.getName)
project(path, name, info => Project.constructProject(info, builderClass), deps: _*)
}
/** Loads the project at the given path using the given name and inheriting it's version from this project.
* The construct function is used to obtain the Project instance. Any project/build/ directory for the project
* is ignored. The project is declared to have the dependencies given by deps.*/
def project[P <: Project](path: Path, name: String, construct: ProjectInfo => P, deps: Project*): P =
initialize(construct(ProjectInfo(path.asFile, deps, Some(this))(info.logger, info.app, info.buildScalaVersion)), Some(new SetupInfo(name, None, None, false)), log)
/** Initializes the project directories when a user has requested that sbt create a new project.*/
def initializeDirectories() {}
/** True if projects should be run in parallel, false if they should run sequentially.
* This only has an effect for multi-projects. If this project has a parent, this value is
* inherited from that parent project.*/
def parallelExecution: Boolean =
info.parent match
{
case Some(parent) => parent.parallelExecution
case None => false
}
/** True if a project and its dependencies should be checked to ensure that their
* output directories are not the same, false if they should not be checked. */
def shouldCheckOutputDirectories = true
/** The list of directories to which this project writes. This is used to verify that multiple
* projects have not been defined with the same output directories. */
def outputDirectories: Iterable[Path] = outputPath :: Nil
def rootProject = Project.rootProject(this)
/** The path to the file that provides persistence for properties.*/
final def envBackingPath = info.builderPath / Project.DefaultEnvBackingName
/** The path to the file that provides persistence for history. */
def historyPath: Option[Path] = Some(outputRootPath / ".history")
def outputPath = crossPath(outputRootPath)
def outputRootPath: Path = outputDirectoryName
def outputDirectoryName = DefaultOutputDirectoryName
private def getProject(result: LoadResult, path: Path): Project =
result match
{
case LoadSetupDeclined => Predef.error("No project exists at path " + path)
case lse: LoadSetupError => Predef.error("Error setting up new project at path " + path + " : " + lse.message)
case err: LoadError => Predef.error("Error loading project at path " + path + " : " + err.message)
case success: LoadSuccess => success.project
}
/** The property for the project's version. */
final val projectVersion = property[Version]
/** The property for the project's name. */
final val projectName = propertyLocalF[String](NonEmptyStringFormat)
/** The property for the project's organization. Defaults to the parent project's organization or the project name if there is no parent. */
final val projectOrganization = propertyOptional[String](normalizedName, true)
/** The property that defines the version of Scala to use with the project definition. This can be different
* from the version of Scala used to build the project (current version used is buildScalaVersion, available are in buildScalaVersions).
* This property is only read by `sbt` on startup and reload.*/
final val defScalaVersion = propertyOptional[String](info.definitionScalaVersion)
/** The property to specify the sbt revision to use.
* Note that this can by a dynamic revision (see Ivy documentation for details on dynamic revisions).
*Therefore, use `sbt.ComponentManager.version` and `timestamp` for actual version information. */
final val sbtVersion = property[String]
final val projectInitialize = propertyOptional[Boolean](false)
final val projectScratch = propertyOptional[Boolean](false, true)
final val offline = propertyOptional[Boolean](false)
/** The property that defines the versions of Scala to build this project against as a comma separated string. This can be
* different from the version of Scala used to build and run the project definition (defined by defScalaVersion).
* This property is only read by `sbt` on startup and reload. The definitive source for the version of Scala currently
* being used is buildScalaVersion.*/
final val buildScalaVersions = propertyOptional[String](defScalaVersion.value, true)
/** The definitive source for the version of Scala being requested to *build* the project.
* For the full version information, see buildScalaInstance.actualVersion.*/
def buildScalaVersion = info.buildScalaVersion.getOrElse(crossScalaVersions.first)
private[sbt] def isScala27 = buildScalaInstance.actualVersion.startsWith("2.7.")
def componentManager = new ComponentManager(info.launcher.globalLock, info.app.components, log)
def buildScalaInstance = buildScalaInstance0
final def buildLibraryJar = Path.fromFile(buildScalaInstance.libraryJar)
final def buildCompilerJar = Path.fromFile(buildScalaInstance.compilerJar)
final def buildScalaJars = Path.finder { buildScalaInstance.jars }
final def buildScalaInstance0: ScalaInstance =
{
val scalaVersion = buildScalaVersion
try { getScalaInstance(scalaVersion) }
catch { case e: xsbti.RetrieveException if info.buildScalaVersion.isEmpty => // only catch the exception if this is the default Scala version
log.error(e.getMessage)
SimpleReader.readLine("\nProvide a new Scala version or press enter to exit: ") match
{
case Some(v) if v.length > 0=>
buildScalaVersions() = replace(scalaVersion, v)
saveEnvironment()
buildScalaInstance0
case _ => throw e
}
}
}
private def replace(originalV: String, newV: String) = buildScalaVersions.value.replaceAll("""\b\Q""" + originalV + """\E\b""", newV)
def getScalaInstance(version: String) =
localScalaInstances.find(_.version == version) getOrElse
xsbt.ScalaInstance(version, info.launcher)
lazy val localScalaInstances: Seq[ScalaInstance] = localScala ++ info.parent.toList.flatMap(_.localScalaInstances)
def localScala: Seq[ScalaInstance] = Nil
lazy val buildCompiler = new AnalyzingCompiler(buildScalaInstance, componentManager, log)
/** Get a `ScalaInstance` for the Scala version with base directory `home`. The library and compiler jars are
* assumed to be at `new File(home, "lib/scala-library.jar")` and `new File(home, "lib/scala-compiler.jar")`.
* The label for this instance is determined by the version String in the `compiler.properties` file in `scala-compiler.jar`.*/
def defineScala(home: File): ScalaInstance = ScalaInstance(home, info.launcher)
/** Get a `ScalaInstance` for the Scala version with base directory `home`. The library and compiler jars are
* assumed to be at `new File(home, "lib/scala-library.jar")` and `new File(home, "lib/scala-compiler.jar")`.
* `version` is used as the label for this instance.*/
def defineScala(version: String, home: File): ScalaInstance = ScalaInstance(version, home, info.launcher)
/** If this project is cross-building, returns `base` with an additional path component containing the scala version
* currently used to build the project. Otherwise, this returns `base`.
* By default, cross-building is enabled when a project is loaded by the loader and crossScalaVersions is not empty.*/
def crossPath(base: Path) = if(disableCrossPaths) base else base / crossString
/** If modifying paths for cross-building is enabled, this returns ScalaVersion.currentString.
* Otherwise, this returns the empty string. */
def crossScalaVersionString: String = if(disableCrossPaths) "" else buildScalaVersion
private def crossString = "scala_" + buildScalaVersion
/** True if crossPath should be the identity function.*/
protected def disableCrossPaths = crossScalaVersions.isEmpty
/** By default, this is the build.scala.versions property split around whitespace. This can be overridden directly if preferred.*/
def crossScalaVersions: Seq[String] =
info.parent match
{
case Some(p) => p.crossScalaVersions
case None => buildScalaVersions.value.split("""\s+""").toList.reverse.removeDuplicates.reverse
}
/** A `PathFinder` that determines the files watched when an action is run with a preceeding ~ when this is the current
* project. This project does not need to include the watched paths for projects that this project depends on.*/
def watchPaths: PathFinder = Path.emptyPathFinder
def terminateWatch(key: Int): Boolean = key == 10 || key == 13
protected final override def parentEnvironment = info.parent
// .* included because svn doesn't mark .svn hidden
def defaultExcludes: FileFilter = (".*" - ".") || HiddenFileFilter
/** Short for parent.descendentsExcept(include, defaultExcludes)*/
def descendents(parent: PathFinder, include: FileFilter) = parent.descendentsExcept(include, defaultExcludes)
override def toString = "Project " + projectName.get.getOrElse("at " + environmentLabel)
def normalizedName = StringUtilities.normalize(name)
}
private[sbt] sealed trait LoadResult extends NotNull
private[sbt] final class LoadSuccess(val project: Project) extends LoadResult
private[sbt] final class LoadError(val message: String) extends LoadResult
private[sbt] final object LoadSetupDeclined extends LoadResult
private[sbt] final class LoadSetupError(val message: String) extends LoadResult
object Project
{
val BootDirectoryName = "boot"
val DefaultOutputDirectoryName = "target"
val DefaultEnvBackingName = "build.properties"
val DefaultBuilderClassName = "sbt.DefaultProject"
val DefaultBuilderClass = Class.forName(DefaultBuilderClassName).asSubclass(classOf[Project])
/** The name of the directory for project definitions.*/
val BuilderProjectDirectoryName = "build"
/** The name of the directory for plugin definitions.*/
val PluginProjectDirectoryName = "plugins"
/** The name of the class that all projects must inherit from.*/
val ProjectClassName = classOf[Project].getName
/** The logger that should be used before the root project definition is loaded.*/
private[sbt] def bootLogger =
{
val log = new ConsoleLogger
log.setLevel(Level.Debug)
log
}
private[sbt] def booted = java.lang.Boolean.getBoolean("sbt.boot")
private[sbt] def loadProject(app: AppProvider): LoadResult = loadProject(app, None)
/** Loads the project in the current working directory. */
private[sbt] def loadProject(app: AppProvider, buildScalaVersion: Option[String]): LoadResult = loadProject(bootLogger, app, buildScalaVersion)
/** Loads the project in the current working directory.*/
private[sbt] def loadProject(log: Logger, app: AppProvider, buildScalaVersion: Option[String]): LoadResult =
checkOutputDirectories(loadProject(new File("."), Nil, None, log, app, buildScalaVersion))
/** Loads the project in the directory given by 'path' and with the given dependencies.*/
private[sbt] def loadProject(path: Path, deps: Iterable[Project], parent: Option[Project], log: Logger, app: AppProvider, buildScalaVersion: Option[String]): LoadResult =
loadProject(path.asFile, deps, parent, log, app, buildScalaVersion)
/** Loads the project in the directory given by 'projectDirectory' and with the given dependencies.*/
private[sbt] def loadProject(projectDirectory: File, deps: Iterable[Project], parent: Option[Project], log: Logger, app: AppProvider, buildScalaVersion: Option[String]): LoadResult =
{
val info = ProjectInfo(projectDirectory, deps, parent)(log, app, buildScalaVersion)
ProjectInfo.setup(info, log) match
{
case err: SetupError => new LoadSetupError(err.message)
case SetupDeclined => LoadSetupDeclined
case AlreadySetup => loadProject(info, None, log)
case setup: SetupInfo => loadProject(info, Some(setup), log)
}
}
private def loadProject(info: ProjectInfo, setupInfo: Option[SetupInfo], log: Logger): LoadResult =
{
try
{
val result =
for(builderClass <- getProjectDefinition(info, log).right) yield
initialize(constructProject(info, builderClass), setupInfo, log)
result.fold(new LoadError(_), new LoadSuccess(_))
}
catch
{
case ite: java.lang.reflect.InvocationTargetException =>
{
val cause =
if(ite.getCause == null) ite
else ite.getCause
errorLoadingProject(cause, log)
}
case nme: NoSuchMethodException => new LoadError("Constructor with one argument of type sbt.ProjectInfo required for project definition.")
case e: Exception => errorLoadingProject(e, log)
}
}
private def errorLoadingProject(e: Throwable, log: Logger) =
e match
{
case _: xsbti.RetrieveException => LoadSetupDeclined
case _ =>
log.trace(e)
new LoadError("Error loading project: " + e.toString)
}
/** Loads the project for the given `info` and represented by an instance of 'builderClass'.*/
private[sbt] def constructProject[P <: Project](info: ProjectInfo, builderClass: Class[P]): P =
builderClass.getConstructor(classOf[ProjectInfo]).newInstance(info)
/** Checks the project's dependencies, initializes its environment, and possibly its directories.*/
private def initialize[P <: Project](p: P, setupInfo: Option[SetupInfo], log: Logger): P =
{
def save() = p.saveEnvironment() foreach { errorMsg => log.error(errorMsg) }
setupInfo match
{
case Some(setup) =>
{
p.projectName() = setup.name
for(v <- setup.version)
p.projectVersion() = v
for(org <- setup.organization)
p.projectOrganization() = org
if(!setup.initializeDirectories)
p.setEnvironmentModified(false)
save()
if(setup.initializeDirectories)
p.initializeDirectories()
}
case None =>
if(p.projectInitialize.value)
{
p.initializeDirectories()
p.projectInitialize() = false
save()
}
}
val useName = p.projectName.get.getOrElse("at " + p.info.projectDirectory.getAbsolutePath)
checkDependencies(useName, p.info.dependencies, log)
p.buildScalaInstance // done so that build Scala version is initialized on project startup
p
}
/** Compiles the project definition classes and returns the project definition class name
* and the class loader that should be used to load the definition. */
private def getProjectDefinition(info: ProjectInfo, buildLog: Logger): Either[String, Class[P] forSome { type P <: Project }] =
getProjectBuilder(info, buildLog) match
{
case Some(builder) => buildProjectDefinition(builder)
case None => Right(DefaultBuilderClass)
}
private def buildProjectDefinition(builderProject: BuilderProject): Either[String, Class[P] forSome { type P <: Project }] =
builderProject.compile.run.toLeft(()).right.flatMap { ignore =>
builderProject.projectDefinition.right.map {
case Some(definition) => getProjectClass[Project](definition, builderProject.projectClasspath, getClass.getClassLoader)
case None => DefaultBuilderClass
}
}
private[sbt] def getProjectClasspath(project: Project): PathFinder =
getProjectBuilder(project.info, project.log) match
{
case Some(builder) => builder.projectClasspath
case _ if project.getClass == DefaultBuilderClass => project.info.sbtClasspath
case _ =>
project.info.parent match
{
case Some(p) => getProjectClasspath(p)
case None => project.info.sbtClasspath
}
}
private[sbt] def getProjectBuilder(info: ProjectInfo, buildLog: Logger): Option[BuilderProject] =
{
if(info.builderProjectPath.asFile.isDirectory)
{
val builderInfo = ProjectInfo(info.builderProjectPath.asFile, Nil, None)(buildLog, info.app, Some(info.definitionScalaVersion))
val builderProject = new BuilderProject(builderInfo, info.pluginsPath, buildLog)
Some(builderProject)
}
else
None
}
/** Verifies that the given list of project dependencies contains no nulls. The
* String argument should be the project name with the dependencies.*/
private def checkDependencies(forProject: String, deps: Iterable[Project], log: Logger)
{
for(nullDep <- deps.find(_ == null))
{
log.error("Project " + forProject + " had a null dependency. This is probably an initialization problem and might be due to a circular dependency.")
throw new RuntimeException("Null dependency in project " + forProject)
}
}
/** Verifies that output directories of the given project and all of its dependencies are
* all different. No verification is done if the project overrides
* 'shouldCheckOutputDirectories' to be false. The 'Project.outputDirectories' method is
* used to determine a project's output directories. */
private def checkOutputDirectories(result: LoadResult): LoadResult =
result match
{
case success: LoadSuccess =>
if(success.project.shouldCheckOutputDirectories)
checkOutputDirectoriesImpl(success.project)
else
success
case x => x
}
/** Verifies that output directories of the given project and all of its dependencies are
* all different. The 'Project.outputDirectories' method is used to determine a project's
* output directories. */
private def checkOutputDirectoriesImpl(project: Project): LoadResult =
{
val projects = project.projectClosure
import scala.collection.mutable.{HashMap, HashSet, Set}
val outputDirectories = new HashMap[Path, Set[Project]]
for(p <- projects; path <- p.outputDirectories)
outputDirectories.getOrElseUpdate(path, new HashSet[Project]) += p
val shared = outputDirectories.filter(_._2.size > 1)
if(shared.isEmpty)
new LoadSuccess(project)
else
{
val sharedString =
{
val s =
for((path, projectsSharingPath) <- shared) yield
projectsSharingPath.map(_.name).mkString(", ") + " share " + path
s.mkString("\n\t")
}
new LoadError("The same directory is used for output for multiple projects:\n\t" + sharedString +
"\n (If this is intentional, use 'override def shouldCheckOutputDirectories = false' in your project definition.)")
}
}
import scala.reflect.Manifest
private[sbt] def getProjectClass[P <: Project](name: String, classpath: PathFinder, additional: ClassLoader)(implicit mf: Manifest[P]): Class[P] =
{
val loader =ClasspathUtilities.toLoader(classpath, additional)
val builderClass = Class.forName(name, false, loader)
val projectClass = mf.erasure
require(projectClass.isAssignableFrom(builderClass), "Builder class '" + builderClass + "' does not extend " + projectClass.getName + ".")
builderClass.asSubclass(projectClass).asInstanceOf[Class[P]]
}
/** Writes the project name and a separator to the project's log at the info level.*/
def showProjectHeader(project: Project)
{
val projectHeader = "Project " + project.name
project.log.info("")
project.log.info(projectHeader)
project.log.info("=" * projectHeader.length)
}
def rootProject(p: Project): Project =
p.info.parent match
{
case Some(parent) => rootProject(parent)
case None => p
}
}

View File

@ -1,79 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package sbt
import scala.tools.nsc.{GenericRunnerCommand, Interpreter, InterpreterLoop, ObjectRunner, Settings}
import scala.tools.nsc.interpreter.InteractiveReader
import scala.tools.nsc.reporters.Reporter
import scala.tools.nsc.util.ClassPath
/** This module is an interface to starting the scala interpreter or runner.*/
object ProjectConsole
{
/** Create a settings object and execute the provided function if the settings are created ok.*/
private def createSettings(log: Logger)(f: Settings => Option[String]) =
{
val command = new GenericRunnerCommand(Nil, message => log.error(message))
if(command.ok)
f(command.settings)
else
Some(command.usageMsg)
}
/** Starts a Scala interpreter session with 'project' bound to the value 'current' in the console
* and the following two lines executed:
* import sbt._
* import current._
*/
def apply(project: Project): Option[String] =
{
import project.log
createSettings(log) { interpreterSettings =>
createSettings(log) { compilerSettings =>
log.info("Starting scala interpreter with project definition " + project.name + " ...")
log.info("")
Control.trapUnit("Error during session: ", log)
{
JLine.withJLine {
val loop = new ProjectInterpreterLoop(compilerSettings, project)
executeTrapExit(loop.main(interpreterSettings), log)
}
}
}}
}
/** A custom InterpreterLoop with the purpose of creating an interpreter with Project 'project' bound to the value 'current',
* and the following three lines interpreted:
* import sbt._
* import Process._
* import current._.
* To do this,
* 1) The compiler uses a different settings instance: 'compilerSettings', which will have its classpath set to include
* the Scala compiler and library jars and the classpath used to compile the project.
* 2) The parent class loader for the interpreter is the loader that loaded the project, so that the project can be bound to a variable
* in the interpreter.
*/
private class ProjectInterpreterLoop(compilerSettings: Settings, project: Project) extends InterpreterLoop
{
override def createInterpreter()
{
val projectLoader = project.getClass.getClassLoader
val classpath = Project.getProjectClasspath(project)
val fullClasspath = classpath.get ++ Path.fromFiles(project.info.app.scalaProvider.jars)
compilerSettings.classpath.value = Path.makeString(fullClasspath)
project.log.debug(" console-project classpath:\n\t" + fullClasspath.mkString("\n\t"))
in = InteractiveReader.createDefault()
interpreter = new Interpreter(settings)
{
override protected def parentClassLoader = projectLoader
override protected def newCompiler(settings: Settings, reporter: Reporter) = super.newCompiler(compilerSettings, reporter)
}
interpreter.setContextClassLoader()
interpreter.bind("current", project.getClass.getName, project)
interpreter.interpret("import sbt._")
interpreter.interpret("import Process._")
interpreter.interpret("import current._")
}
}
}

View File

@ -1,140 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008 Mark Harrah
*/
package sbt
import java.io.File
import xsbti.{AppProvider, ScalaProvider}
import FileUtilities._
/** Represents the minimal information necessary to construct a Project.
*
* `projectDirectory` 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.
* `log` is the Logger to use as a base for the default project Logger.
* `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(projectDirectory: File, dependencies: Iterable[Project], parent: Option[Project])
(log: Logger, val app: AppProvider, val buildScalaVersion: Option[String]) extends NotNull
{
/** The version of Scala running sbt.*/
def definitionScalaVersion = app.scalaProvider.version
/** The launcher instance that booted sbt.*/
def launcher = app.scalaProvider.launcher
val logger = log
/** The base path for the project, preserving information to the root project directory.*/
val projectPath: Path =
{
val toRoot = parent.flatMap(p => Path.relativize(p.info.projectPath, projectDirectory))
new ProjectDirectory(projectDirectory, toRoot)
}
/** The path to build information. The current location is `project/`.
* Note: The directory used to be `metadata/`, hence the name of the constant in the implementation.
* Note 2: Although it is called builderPath, it is not the path to the builder definition, which is `builderProjectPath`*/
val builderPath = projectPath / ProjectInfo.MetadataDirectoryName
/** The boot directory contains the jars needed for building the project, including Scala, sbt, processors and dependencies of these.*/
def bootPath = builderPath / Project.BootDirectoryName
/** The path to the build definition project.
* Currently, this is 'project/build'. */
def builderProjectPath = builderPath / Project.BuilderProjectDirectoryName
def builderProjectOutputPath = builderProjectPath / Project.DefaultOutputDirectoryName
/** The path to the plugin definition project. This declares the plugins to use for the build definition.*/
def pluginsPath = builderPath / Project.PluginProjectDirectoryName
def pluginsOutputPath = pluginsPath / Project.DefaultOutputDirectoryName
/** The path to which the source code for plugins are extracted.*/
def pluginsManagedSourcePath = pluginsPath / BasicDependencyPaths.DefaultManagedSourceDirectoryName
/** The path to which plugins are retrieved.*/
def pluginsManagedDependencyPath = pluginsPath / BasicDependencyPaths.DefaultManagedDirectoryName
/** The classpath containing all jars comprising sbt, except for the launcher.*/
def sbtClasspath = Path.finder(app.mainClasspath)
}
private[sbt] sealed trait SetupResult extends NotNull
private[sbt] final object SetupDeclined extends SetupResult
private[sbt] final class SetupError(val message: String) extends SetupResult
private[sbt] final object AlreadySetup extends SetupResult
private[sbt] final class SetupInfo(val name: String, val version: Option[Version], val organization: Option[String], val initializeDirectories: Boolean) extends SetupResult
object ProjectInfo
{
val MetadataDirectoryName = "project"
private val DefaultOrganization = "empty"
def setup(info: ProjectInfo, log: Logger): SetupResult =
{
val builderDirectory = info.builderPath.asFile
if(builderDirectory.exists)
{
if(builderDirectory.isDirectory)
AlreadySetup
else
new SetupError("'" + builderDirectory.getAbsolutePath + "' is not a directory.")
}
else
setupProject(info.projectDirectory, log)
}
private def setupProject(projectDirectory: File, log: Logger): SetupResult =
{
if(confirmPrompt("No project found. Create new project?", false))
{
val name = trim(SimpleReader.readLine("Project Name: "))
if(name.isEmpty)
new SetupError("Project not created: no name specified.")
else
{
val organization =
{
val org = trim(SimpleReader.readLine("Organization [" + DefaultOrganization + "]: "))
if(org.isEmpty)
DefaultOrganization
else
org
}
readVersion(projectDirectory, log) match
{
case None => new SetupError("Project not created: no version specified.")
case Some(version) =>
if(verifyCreateProject(name, version, organization))
new SetupInfo(name, Some(version), Some(organization), true)
else
SetupDeclined
}
}
}
else
SetupDeclined
}
private def verifyCreateProject(name: String, version: Version, organization: String): Boolean =
confirmPrompt("Create new project " + name + " " + version + " with organization " + organization +" ?", true)
private def confirmPrompt(question: String, defaultYes: Boolean) =
{
val choices = if(defaultYes) " (Y/n) " else " (y/N) "
val answer = trim(SimpleReader.readLine(question + choices))
val yes = "y" :: "yes" :: (if(defaultYes) List("") else Nil)
yes.contains(answer.toLowerCase)
}
private def readVersion(projectDirectory: File, log: Logger): Option[Version] =
{
val version = trim(SimpleReader.readLine("Version: "))
if(version.isEmpty)
None
else
{
Version.fromString(version) match
{
case Left(errorMessage) =>
{
log.error("Invalid version: " + errorMessage)
readVersion(projectDirectory, log)
}
case Right(v) => Some(v)
}
}
}
private def trim(s: Option[String]) = s.getOrElse("")
}

View File

@ -1,302 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package sbt
trait PackagePaths extends NotNull
{
def jarPath: Path
def packageTestJar: Path
def packageDocsJar: Path
def packageSrcJar: Path
def packageTestSrcJar: Path
def packageProjectZip: Path
}
/** These are the paths required by BasicScalaProject.*/
trait ScalaPaths extends PackagePaths
{
/** A PathFinder that selects all main sources.*/
def mainSources: PathFinder
/** A PathFinder that selects all test sources.*/
def testSources: PathFinder
def mainSourceRoots: PathFinder
def testSourceRoots: PathFinder
/** A PathFinder that selects all main resources.*/
def mainResources: PathFinder
/** A PathFinder that selects all test resources. */
def testResources: PathFinder
def mainCompilePath: Path
def testCompilePath: Path
def mainAnalysisPath: Path
def testAnalysisPath: Path
def mainDocPath: Path
def testDocPath: Path
def graphSourcesPath: Path
def graphPackagesPath: Path
def mainResourcesOutputPath: Path
def testResourcesOutputPath: Path
/** A PathFinder that selects all the classes compiled from the main sources.*/
def mainClasses: PathFinder
/** A PathFinder that selects all the classes compiled from the test sources.*/
def testClasses: PathFinder
/** Declares all paths to be packaged by the package action.*/
def packagePaths: PathFinder
/** Declares all paths to be packaged by the package-test action.*/
def packageTestPaths: PathFinder
/** Declares all sources to be packaged by the package-src action.*/
def packageSourcePaths: PathFinder
/** Declares all sources to be packaged by the package-test-src action.*/
def packageTestSourcePaths: PathFinder
/** Declares all paths to be packaged by the package-project action.*/
def packageProjectPaths: PathFinder
/** These are the directories that are created when a user makes a new project from sbt.*/
protected def directoriesToCreate: List[Path]
/** The directories to which a project writes are listed here and is used
* to check a project and its dependencies for collisions.*/
def outputDirectories: Iterable[Path]
def artifactBaseName: String
}
trait BasicScalaPaths extends Project with ScalaPaths
{
def mainResourcesPath: PathFinder
def testResourcesPath: PathFinder
def managedDependencyPath: Path
def managedDependencyRootPath: Path
def dependencyPath: Path
protected def sources(base: PathFinder) = descendents(base, sourceExtensions)
protected def sourceExtensions = "*.scala" | "*.java"
def mainSources =
{
val normal = sources(mainSourceRoots)
if(scratch)
normal +++ (info.projectPath * sourceExtensions)
else
normal
}
def testSources = sources(testSourceRoots)
def mainResources = descendents(mainResourcesPath ###, "*")
def testResources = descendents(testResourcesPath ###, "*")
def mainClasses = (mainCompilePath ###) ** "*.class"
def testClasses = (testCompilePath ###) ** "*.class"
def packagePaths = mainClasses +++ mainResources
def packageTestPaths = testClasses +++ testResources
def packageSourcePaths = mainSources +++ mainResources
def packageTestSourcePaths = testSources +++ testResources
def packageProjectPaths = descendents( (info.projectPath ###), "*") --- (packageProjectExcludes ** "*")
protected def packageProjectExcludes: PathFinder =
outputRootPath +++ managedDependencyRootPath +++
info.bootPath +++ info.builderProjectOutputPath +++
info.pluginsOutputPath +++ info.pluginsManagedSourcePath +++ info.pluginsManagedDependencyPath
override def outputDirectories = outputPath :: managedDependencyPath :: Nil
}
trait MavenStyleScalaPaths extends BasicScalaPaths with BasicPackagePaths
{
import BasicProjectPaths._
def outputPath: Path
def sourceDirectoryName = DefaultSourceDirectoryName
def mainDirectoryName = DefaultMainDirectoryName
def scalaDirectoryName = DefaultScalaDirectoryName
def javaDirectoryName = DefaultJavaDirectoryName
def resourcesDirectoryName = DefaultResourcesDirectoryName
def testDirectoryName = DefaultTestDirectoryName
def mainCompileDirectoryName = DefaultMainCompileDirectoryName
def testCompileDirectoryName = DefaultTestCompileDirectoryName
def docDirectoryName = DefaultDocDirectoryName
def apiDirectoryName = DefaultAPIDirectoryName
def graphDirectoryName = DefaultGraphDirectoryName
def mainAnalysisDirectoryName = DefaultMainAnalysisDirectoryName
def testAnalysisDirectoryName = DefaultTestAnalysisDirectoryName
def mainResourcesOutputDirectoryName = DefautMainResourcesOutputDirectoryName
def testResourcesOutputDirectoryName = DefautTestResourcesOutputDirectoryName
def sourcePath = path(sourceDirectoryName)
def mainSourcePath = sourcePath / mainDirectoryName
def mainScalaSourcePath = mainSourcePath / scalaDirectoryName
def mainJavaSourcePath = mainSourcePath / javaDirectoryName
def mainResourcesPath = mainSourcePath / resourcesDirectoryName
def mainDocPath = docPath / mainDirectoryName / apiDirectoryName
def mainCompilePath = outputPath / mainCompileDirectoryName
def mainResourcesOutputPath = outputPath / mainResourcesOutputDirectoryName
def mainAnalysisPath = outputPath / mainAnalysisDirectoryName
def testSourcePath = sourcePath / testDirectoryName
def testJavaSourcePath = testSourcePath / javaDirectoryName
def testScalaSourcePath = testSourcePath / scalaDirectoryName
def testResourcesPath = testSourcePath / resourcesDirectoryName
def testDocPath = docPath / testDirectoryName / apiDirectoryName
def testCompilePath = outputPath / testCompileDirectoryName
def testResourcesOutputPath = outputPath / testResourcesOutputDirectoryName
def testAnalysisPath = outputPath / testAnalysisDirectoryName
def docPath = outputPath / docDirectoryName
def graphPath = outputPath / graphDirectoryName
def graphPackagesPath = graphPath / "packages"
def graphSourcesPath = graphPath / "sources"
/** These are the directories that are created when a user makes a new project from sbt.*/
protected def directoriesToCreate: List[Path] =
dependencyPath ::
mainScalaSourcePath ::
mainResourcesPath ::
testScalaSourcePath ::
testResourcesPath ::
Nil
def mainSourceRoots = (mainJavaSourcePath###) +++ (mainScalaSourcePath##)
def testSourceRoots = (testJavaSourcePath###) +++ (testScalaSourcePath##)
}
trait BasicPackagePaths extends ScalaPaths with PackagePaths
{
def outputPath: Path
def defaultJarBaseName: String = artifactBaseName
def defaultJarName = defaultJarBaseName + ".jar"
def jarPath = outputPath / defaultJarName
def packageTestJar = defaultJarPath("-test.jar")
def packageDocsJar = defaultJarPath("-docs.jar")
def packageSrcJar= defaultJarPath("-src.jar")
def packageTestSrcJar = defaultJarPath("-test-src.jar")
def packageProjectZip = defaultJarPath("-project.zip")
def defaultJarPath(extension: String) = outputPath / (artifactBaseName + extension)
}
object BasicProjectPaths
{
val DefaultSourceDirectoryName = "src"
val DefaultMainCompileDirectoryName = "classes"
val DefaultTestCompileDirectoryName = "test-classes"
val DefaultDocDirectoryName = "doc"
val DefaultAPIDirectoryName = "api"
val DefaultGraphDirectoryName = "graph"
val DefaultMainAnalysisDirectoryName = "analysis"
val DefaultTestAnalysisDirectoryName = "test-analysis"
val DefautMainResourcesOutputDirectoryName = "resources"
val DefautTestResourcesOutputDirectoryName = "test-resources"
val DefaultMainDirectoryName = "main"
val DefaultScalaDirectoryName = "scala"
val DefaultJavaDirectoryName = "java"
val DefaultResourcesDirectoryName = "resources"
val DefaultTestDirectoryName = "test"
}
trait WebScalaPaths extends ScalaPaths
{
def temporaryWarPath: Path
def webappResources: PathFinder
def jettyContextPath: String
def warPath: Path
}
trait MavenStyleWebScalaPaths extends WebScalaPaths with MavenStyleScalaPaths
{
import WebProjectPaths._
def temporaryWarPath = outputPath / webappDirectoryName
def webappPath = mainSourcePath / webappDirectoryName
def webappDirectoryName = DefaultWebappDirectoryName
def jettyContextPath = DefaultJettyContextPath
def defaultWarName = defaultJarBaseName + ".war"
def warPath = outputPath / defaultWarName
/** Additional files to include in the web application. */
protected def extraWebappFiles: PathFinder = Path.emptyPathFinder
def webappResources = descendents(webappPath ###, "*") +++ extraWebappFiles
}
object WebProjectPaths
{
val DefaultWebappDirectoryName = "webapp"
val DefaultJettyContextPath = "/"
}
/** Defines default paths for a webstart project. It directly extends WebstartOptions to make
* it easy to implement and override webstart options in the common case of one webstartTask per
* project.*/
trait WebstartPaths extends ScalaPaths
{
import WebstartPaths._
def outputPath: Path
def jnlpPath: Path
def webstartOutputDirectory = outputPath / webstartDirectoryName
def jnlpFile = webstartOutputDirectory / jnlpFileName
def webstartLibDirectory = webstartOutputDirectory / webstartLibName
def webstartZip: Option[Path] = Some(outputPath / webstartZipName)
def jnlpResourcesPath = jnlpPath / BasicProjectPaths.DefaultResourcesDirectoryName
def webstartLibName = DefaultWebstartLibName
def webstartDirectoryName = DefaultWebstartDirectoryName
def webstartZipName: String
def jnlpFileName: String
}
object WebstartPaths
{
val DefaultWebstartDirectoryName = "webstart"
val DefaultJnlpName = "jnlp"
val DefaultWebstartLibName = "lib"
}
trait MavenStyleWebstartPaths extends WebstartPaths with MavenStyleScalaPaths
{
import WebstartPaths._
def jnlpPath = mainSourcePath / DefaultJnlpName
def webstartMainJar = jarPath
def jnlpFileName = DefaultJnlpFileName
def webstartZipName = artifactBaseName + ".zip"
def DefaultJnlpFileName = artifactBaseName + ".jnlp"
}
trait IntegrationTestPaths extends NotNull
{
def integrationTestSources: PathFinder
def integrationTestScalaSourceRoots: PathFinder
def integrationTestResourcesPath: Path
def integrationTestCompilePath: Path
def integrationTestAnalysisPath: Path
}
trait BasicIntegrationTestPaths extends IntegrationTestPaths
{
def integrationTestScalaSourcePath: Path
def integrationTestScalaSourceRoots: PathFinder = integrationTestScalaSourcePath
def integrationTestSources = sources(integrationTestScalaSourceRoots)
protected def sources(base: PathFinder): PathFinder
}
trait MavenStyleIntegrationTestPaths extends BasicIntegrationTestPaths with MavenStyleScalaPaths
{
import IntegrationTestPaths._
def integrationTestDirectoryName = DefaultIntegrationTestDirectoryName
def integrationTestCompileDirectoryName = DefaultIntegrationTestCompileDirectoryName
def integrationTestAnalysisDirectoryName = DefaultIntegrationTestAnalysisDirectoryName
def integrationTestSourcePath = sourcePath / integrationTestDirectoryName
def integrationTestScalaSourcePath = integrationTestSourcePath / scalaDirectoryName
def integrationTestResourcesPath = integrationTestSourcePath / resourcesDirectoryName
def integrationTestCompilePath = outputPath / integrationTestCompileDirectoryName
def integrationTestAnalysisPath = outputPath / integrationTestAnalysisDirectoryName
}
object IntegrationTestPaths
{
val DefaultIntegrationTestDirectoryName = "it"
val DefaultIntegrationTestCompileDirectoryName = "it-classes"
val DefaultIntegrationTestAnalysisDirectoryName = "it-analysis"
}

View File

@ -1,104 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008 David MacIver, Mark Harrah
*/
package sbt
import TaskManager._
trait Described extends NotNull
{
def description: Option[String]
}
trait TaskManager{
type ManagerType >: this.type <: TaskManager
type ManagedTask >: Task <: TaskManager#Task with Dag[ManagedTask]
/** Creates a task that executes the given action when invoked.*/
def task(action : => Option[String]) = new Task(None, Nil, false, action)
/** An interactive task is one that is not executed across all dependent projects when
* it is called directly. The dependencies of the task are still invoked across all dependent
* projects, however. */
def interactiveTask(action: => Option[String]) = new Task(None, Nil, true, action)
/** Creates a method task that executes the given action when invoked. */
def task(action: Array[String] => ManagedTask) = new MethodTask(None, action, Nil)
def taskName(t: Task): Option[String]
final def taskNameString(task: Task): String = taskName(task).getOrElse(UnnamedName)
/** A method task is an action that has parameters. Note that it is not a Task, though,
* because it requires arguments to perform its work. It therefore cannot be a dependency of
* a Task..*/
final class MethodTask(val description: Option[String], action: Array[String] => ManagedTask, getCompletions: => Seq[String]) extends Described
{
/** Creates a new method task, identical to this method task, except with thE[String]e given description.*/
def describedAs(description : String) = new MethodTask(Some(description), action, getCompletions)
/** Invokes this method task with the given arguments.*/
def apply(arguments: Array[String]) = action(arguments)
def manager: ManagerType = TaskManager.this
def completeWith(add: => Seq[String]) = new MethodTask(description, action, add)
def completions = getCompletions
}
sealed class Task(val explicitName: Option[String], val description : Option[String], val dependencies : List[ManagedTask],
val interactive: Boolean, action : => Option[String]) extends Dag[ManagedTask] with Described
{
def this(description : Option[String], dependencies : List[ManagedTask], interactive: Boolean, action : => Option[String]) =
this(None, description, dependencies, interactive, action)
checkTaskDependencies(dependencies)
def manager: ManagerType = TaskManager.this
def name = explicitName.getOrElse(taskNameString(this))
private[sbt] def implicitName = taskName(this)
def named(name: String) = construct(Some(name), description,dependencies, interactive, action)
override def toString = "Task " + name
/** Creates a new task, identical to this task, except with the additional dependencies specified.*/
def dependsOn(tasks : ManagedTask*) = setDependencies(tasks.toList ::: dependencies)
private[sbt] def setDependencies(dependencyList: List[ManagedTask]) =
{
checkTaskDependencies(dependencyList)
construct(explicitName, description, dependencyList, interactive, action)
}
/** Creates a new task, identical to this task, except with the given description.*/
def describedAs(description : String) = construct(explicitName, Some(description), dependencies, interactive, action);
private[sbt] def invoke = action;
final def setInteractive = construct(explicitName, description, dependencies, true, action)
final def run = runSequentially(topologicalSort)
final def runDependenciesOnly = runSequentially(topologicalSort.dropRight(1))
private def runSequentially(tasks: List[ManagedTask]) = Control.lazyFold(tasks)(_.invoke)
def &&(that : Task) =
construct(explicitName, None, dependencies ::: that.dependencies, interactive || that.interactive, this.invoke.orElse(that.invoke))
protected def construct(explicitName: Option[String], description: Option[String], dependencies: List[ManagedTask], interactive: Boolean,
action : => Option[String]): Task = new Task(explicitName, description, dependencies, interactive, action)
}
final class CompoundTask private (explicitName: Option[String], description : Option[String], dependencies : List[ManagedTask], interactive: Boolean,
action : => Option[String], createWork: => SubWork[Project#Task]) extends Task(description, dependencies, interactive, action)
with CompoundWork[Project#Task]
{
def this(createWork: => SubWork[Project#Task]) = this(None, None, Nil, false, None, createWork)
override protected def construct(explicitName: Option[String], description: Option[String], dependencies: List[ManagedTask],
interactive: Boolean, action : => Option[String]) = new CompoundTask(explicitName, description, dependencies, interactive, action, createWork)
def work = createWork
}
def dynamic(createTask: => Project#Task) = new CompoundTask(SubWork[Project#Task](checkDynamic(createTask)))
/** Verifies that the given dynamically created task does not depend on any statically defined tasks.
* Returns the task if it is valid.*/
private def checkDynamic(task: Project#Task) =
{
for(t <- task.topologicalSort if !(t eq task); staticName <- t.implicitName)
error("Dynamic task " + task.name + " depends on static task " + staticName)
task
}
private def checkTaskDependencies(dependencyList: List[ManagedTask])
{
val nullDependencyIndex = dependencyList.findIndexOf(_ == null)
require(nullDependencyIndex < 0, "Dependency (at index " + nullDependencyIndex + ") is null. This may be an initialization issue or a circular dependency.")
val interactiveDependencyIndex = dependencyList.findIndexOf(_.interactive)
require(interactiveDependencyIndex < 0, "Dependency (at index " + interactiveDependencyIndex + ") is interactive. Interactive tasks cannot be dependencies.")
}
}
object TaskManager
{
val UnnamedName = "<anonymous>"
}

View File

@ -1,61 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.impl
import scala.util.parsing.combinator.Parsers
import scala.util.parsing.input.CharSequenceReader
import scala.util.parsing.input.CharArrayReader.EofCh
/** Parses a command of the form:
* identifier argument*
* where argument may be quoted to include spaces and
* quotes and backslashes should be escaped.
*/
object Arguments
{
def apply(commandString: String): Either[String, (String, List[String])] =
CommandParser.parse(commandString)
}
/* Most of the complexity is for error handling.*/
private[sbt] object CommandParser extends Parsers
{
type Elem = Char
def parse(commandString: String): Either[String, (String, List[String])] =
{
command(new CharSequenceReader(commandString.trim, 0)) match
{
case Success(id ~ args, next) => Right((id, args))
case err: NoSuccess =>
{
val pos = err.next.pos
Left("Could not parse command: (" + pos.line + "," + pos.column + "): " + err.msg)
}
}
}
def command = phrase(identifier ~! (argument*))
def identifier = unquoted | err("Expected identifier")
def argument = ( (whitespaceChar+) ~> (unquoted | quoted) )
def unquoted: Parser[String] = ((unquotedChar ~! (unquotedMainChar*)) ^^ { case a ~ tail => (a :: tail).mkString("") })
def quoted: Parser[String] = quote ~> quotedChars <~ (quote | err("Missing closing quote character"))
def quotedChars: Parser[String] = (escape | nonescapeChar)*
def escape: Parser[Char] = backslash ~> (escapeChar | err("Illegal escape"))
def escapeChar: Parser[Char] = quote | backslash
def nonescapeChar: Parser[Char] = elem("", ch => !isEscapeChar(ch) && ch != EofCh)
def unquotedChar: Parser[Char] = elem("", ch => !isEscapeChar(ch) && !Character.isWhitespace(ch) && ch != EofCh)
def unquotedMainChar: Parser[Char] = unquotedChar | (errorIfEscape ~> failure(""))
private def errorIfEscape = (not(quote) | err("Unexpected quote character")) ~>
(not(backslash) | err("Escape sequences can only occur in a quoted argument"))
private def isEscapeChar(ch: Char) = ch == '\\' || ch == '"'
def quote: Parser[Char] = '"'
def backslash: Parser[Char] = '\\'
def whitespaceChar: Parser[Char] = elem("whitespace", ch => Character.isWhitespace(ch))
private implicit def toString(p: Parser[List[Char]]): Parser[String] = p ^^ {_ mkString "" }
}

View File

@ -1,28 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
/** Parses and executes a command (connects a parser to a runner). */
class CommandRunner(parser: CommandParsing, execute: Executing)
{
def apply(processorCommand: String): Unit =
parser.parseCommand(processorCommand) match
{
case Left(err) => throw new ProcessorException(err)
case Right(command) => execute(command)
}
}
object CommandRunner
{
/** Convenience method for constructing a CommandRunner with the minimal information required.*/
def apply(manager: Manager, defParser: DefinitionParser, prefix: String, log: Logger): CommandRunner =
{
val parser = new CommandParser(defaultErrorMessage(prefix), defParser)
val info = new InfoImpl(manager, prefix, parser, System.out.println)
val execute = new Execute(manager, info, log)
new CommandRunner(parser, execute)
}
def defaultErrorMessage(prefix: String) =
"Invalid processor command. Run " + prefix + "help to see valid commands."
}

View File

@ -1,24 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
/** Executes a parsed command. */
class Execute(manager: Manager, info: Info, log: Logger) extends Executing
{
def apply(command: Command): Unit =
command match
{
case dr: DefineRepository =>
manager.defineRepository(dr.repo)
log.info("Defined new processor repository '" + dr.repo + "'")
case dp: DefineProcessor =>
manager.defineProcessor(dp.pdef)
log.info("Defined new processor '" + dp.pdef + "'")
case rd: RemoveDefinition =>
val removed = manager.removeDefinition(rd.label)
log.info("Removed '" + removed + "'")
case Help => info.help()
case Show => info.show()
}
}

View File

@ -1,35 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
class Handler(baseProject: Project) extends NotNull
{
def unapply(line: String): Option[ParsedProcessor] =
line.split("""\s+""", 2) match
{
case Array(label @ GetProcessor(processor), args @ _*) => Some( new ParsedProcessor(label, processor, args.mkString) )
case _ => None
}
private object GetProcessor
{
def unapply(name: String): Option[Processor] =
manager.processorDefinition(name).flatMap(manager.processor)
}
def lock = baseProject.info.launcher.globalLock
lazy val scalaVersion = baseProject.defScalaVersion.value
lazy val base = baseProject.info.bootPath / ("scala-" + scalaVersion) / "sbt-processors"
lazy val persistBase = Path.userHome / ".ivy2" / "sbt"
def retrieveLockFile = base / lockName
def persistLockFile = persistBase / lockName
def lockName = "processors.lock"
def definitionsFile = persistBase / "processors.properties"
def files = new ManagerFiles(base.asFile, retrieveLockFile.asFile, definitionsFile.asFile)
lazy val defParser = new DefinitionParser
lazy val manager = new ManagerImpl(files, scalaVersion, new Persist(lock, persistLockFile.asFile, defParser), baseProject.offline.value, baseProject.log)
}
class ParsedProcessor(val label: String, val processor: Processor, val arguments: String) extends NotNull

View File

@ -1,26 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
class InfoImpl(manager: Manager, prefix: String, parser: CommandParser, print: String => Unit) extends Info
{
def show()
{
print("Processors:\n\t" + manager.processors.values.mkString("\n\t"))
print("\nProcessor repositories:\n\t" + manager.repositories.values.mkString("\n\t"))
}
def help()
{
import parser.{ShowCommand, HelpCommand, ProcessorCommand, RemoveCommand, RepositoryCommand}
val usage =
(HelpCommand -> "Display this help message") ::
(ShowCommand -> "Display defined processors and repositories") ::
(ProcessorCommand -> "Define 'label' to be the processor with the given ID") ::
(RepositoryCommand -> "Add a repository for searching for processors") ::
(RemoveCommand -> "Undefine the repository or processor with the given 'label'") ::
Nil
print("Processor management commands:\n " + (usage.map{ case (c,d) => prefix + "" + c + " " + d}).mkString("\n "))
}
}

View File

@ -1,41 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
import java.io.File
import java.net.{URL, URLClassLoader}
import xsbt.FileUtilities.read
import xsbt.OpenResource.urlInputStream
import xsbt.Paths._
import xsbt.GlobFilter._
import ProcessorException.error
class Loader extends NotNull
{
def classNameResource = "sbt.processor"
def getProcessor(directory: File): Either[Throwable, Processor] = getProcessor( getLoader(directory) )
private def getProcessor(loader: ClassLoader): Either[Throwable, Processor] =
{
val resource = loader.getResource(classNameResource)
if(resource eq null) Left(new ProcessorException("Processor existed but did not contain '" + classNameResource + "' descriptor."))
else loadProcessor(loader, resource)
}
private def loadProcessor(loader: ClassLoader, resource : URL): Either[Throwable, Processor] =
try { Right(loadProcessor(loader, className(resource))) }
catch { case e: Exception => Left(e) }
private def loadProcessor(loader: ClassLoader, className: String): Processor =
{
val processor = Class.forName(className, true, loader).newInstance
classOf[Processor].cast(processor)
}
private def className(resource: URL): String = urlInputStream(resource) { in => read(in).trim }
private def getLoader(dir: File) =
{
val jars = dir ** "*.jar"
val jarURLs = jars.files.toArray[File].map(_.toURI.toURL)
new URLClassLoader(jarURLs, getClass.getClassLoader)
}
}

View File

@ -1,84 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
import java.io.File
import xsbt.Paths._
import ProcessorException.error
/** Files needed by ManagerImpl.
* `retrieveBaseDirectory` is the directory that processors are retrieved under.
* `retrieveLockFile` is used to synchronize access to that directory.
* `definitionsFile` is the file to save repository and processor definitions to. It is usually per-user instead of per-project.*/
class ManagerFiles(val retrieveBaseDirectory: File, val retrieveLockFile: File, val definitionsFile: File)
class ManagerImpl(files: ManagerFiles, scalaVersion: String, persist: Persist, localOnly: Boolean, log: Logger) extends Manager
{
import files._
def processorDefinition(label: String): Option[ProcessorDefinition] = processors.get(label)
def processor(pdef: ProcessorDefinition): Option[Processor] =
{
def tryProcessor: Either[Throwable, Processor] =
(new Loader).getProcessor( retrieveDirectory(pdef) )
// try to load the processor. It will succeed here if the processor has already been retrieved
tryProcessor.left.flatMap { _ =>
// if it hasn't been retrieved, retrieve the processor and its dependencies
retrieveProcessor(pdef, localOnly)
// try to load the processor now that it has been retrieved
tryProcessor.left.map { // if that fails, log a warning
case p: ProcessorException => log.warn(p.getMessage)
case t => log.trace(t); log.warn(t.toString)
}
}.right.toOption
}
def defineProcessor(p: ProcessorDefinition)
{
checkExisting(p)
retrieveProcessor(p, localOnly)
add(p)
}
def defineRepository(r: RepositoryDefinition)
{
checkExisting(r)
add(r)
}
def removeDefinition(label: String): Definition =
definitions.removeKey(label) match
{
case Some(removed) =>
saveDefinitions()
removed
case None => error("Label '" + label + "' not defined.")
}
private def retrieveProcessor(p: ProcessorDefinition, localOnly: Boolean): Unit =
{
val resolvers = repositories.values.toList.map(toResolver)
val module = p.toModuleID(scalaVersion)
( new Retrieve(retrieveDirectory(p), module, persist.lock, retrieveLockFile, resolvers, log) ).retrieve(localOnly)
}
private def add(d: Definition)
{
definitions(d.label) = d
saveDefinitions()
}
private lazy val definitions = loadDefinitions(definitionsFile)
def repositories = Map() ++ partialMap(definitions) { case (label, d: RepositoryDefinition) => (label, d) }
def processors = Map() ++ partialMap(definitions) { case (label, p: ProcessorDefinition) => (label, p) }
private def checkExisting(p: Definition): Unit = definitions.get(p.label) map { d => error ("Label '" + p.label + "' already in use: " + d) }
private def partialMap[T,S](i: Iterable[T])(f: PartialFunction[T,S]) = i.filter(f.isDefinedAt).map(f)
private def toResolver(repo: RepositoryDefinition): Resolver = new MavenRepository(repo.label, repo.url)
def retrieveDirectory(p: ProcessorDefinition) = retrieveBaseDirectory / p.group / p.module / p.rev
private def saveDefinitions(): Unit = saveDefinitions(definitionsFile)
private def saveDefinitions(file: File): Unit = persist.save(file)(definitions.values.toList)
private def loadDefinitions(file: File): scala.collection.mutable.Map[String, Definition] =
scala.collection.mutable.HashMap( (if(file.exists) rawLoad(file) else Nil) : _*)
private def rawLoad(file: File): Seq[(String, Definition)] = persist.load(definitionsFile).map { d => (d.label, d) }
}

View File

@ -1,50 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
/** Parses commands. `errorMessage` is the String used when a command is invalid.
* There is no detailed error reporting.
* Input Strings are assumed to be trimmed.*/
class CommandParser(errorMessage: String, defParser: DefinitionParsing) extends CommandParsing
{
def parseCommand(line: String): Either[String, Command] =
defParser.parseDefinition(line) match
{
case Some(p: ProcessorDefinition) => Right(new DefineProcessor(p))
case Some(r: RepositoryDefinition) => Right(new DefineRepository(r))
case None => parseOther(line)
}
def parseOther(line: String) =
line match
{
case RemoveRegex(label) => Right(new RemoveDefinition(label))
case HelpCommand | "" => Right(Help)
case ShowCommand => Right(Show)
case _ => Left(errorMessage)
}
val ShowCommand = "show"
val HelpCommand = "help"
val ProcessorCommand = "<label> is <group> <module> <rev>"
val RepositoryCommand = "<label> at <url>"
val RemoveCommand = "remove <label>"
val RemoveRegex = """remove\s+(\w+)""".r
}
/** Parses the String representation of definitions.*/
class DefinitionParser extends DefinitionParsing
{
def parseDefinition(line: String): Option[Definition] =
line match
{
case ProcessorRegex(label, group, name, rev) => Some( new ProcessorDefinition(label, group, name, rev) )
case RepositoryRegex(label, url) => Some( new RepositoryDefinition(label, url) )
case _ => None
}
val ProcessorRegex = """(\w+)\s+is\s+(\S+)\s+(\S+)\s+(\S+)""".r
val RepositoryRegex = """(\w+)\s+at\s+(\S+)""".r
}

View File

@ -1,26 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
import java.io.File
import xsbt.FileUtilities.{readLines, write}
// lock file should be for synchronizing access to the persisted files
class Persist(val lock: xsbti.GlobalLock, lockFile: File, defParser: DefinitionParser) extends Persisting
{
private def withDefinitionsLock[T](f: => T): T = lock(lockFile,Callable(f))
def save(file: File)(definitions: Iterable[Definition])
{
val lines = definitions.mkString(LineSeparator)
withDefinitionsLock { write(file, lines) }
}
def load(file: File): Seq[Definition] =
{
def parseLine(line: String) = defParser.parseDefinition(line).toList
withDefinitionsLock { if(file.exists) readLines(file) else Nil } flatMap(parseLine)
}
private final val LineSeparator = System.getProperty("line.separator", "\n")
}

View File

@ -1,148 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
import java.io.File
/** An interface for code that operates on an sbt `Project`.*/
trait Processor extends NotNull
{
/** Apply this processor's action to the given `project`.
* The arguments are passed unparsed as a single String `args`.
* The return value optionally provides additional commands to run, such as 'reload'.
* Note: `project` is not necessarily the root project. To get the root project, use `project.rootProject`.
* The `label` used to call the processor is provided to allow recursing.*/
def apply(label: String, project: Project, onFailure: Option[String], args: String): ProcessorResult
}
/** An interface for code that operates on an sbt `Project` but doesn't need to modify command processing.*/
abstract class BasicProcessor extends Processor
{
/** Apply this processor's action to the given `project`.
* The arguments are passed unparsed as a single String `args`.
* Note: `project` is not necessarily the root project. To get the root project, use `project.rootProject`.*/
def apply(project: Project, args: String): Unit
override final def apply(label: String, project: Project, onFailure: Option[String], args: String): ProcessorResult =
{
apply(project, args)
new Success(project, onFailure)
}
}
/** The result of a Processor run.*/
sealed trait ProcessorResult extends NotNull
/* Processor success.
* `insertArgs` allows the Processor to insert additional commands to run.
* These commands are run before pending commands.
*
* For example, consider a Processor bound to 'cleanCompile' that returns
* `ProcessorResult("clean", "compile")`
*
* If a user runs:
* `sbt a cleanCompile b `
* This runs `a`, `cleanCompile`, `clean`, `compile`, and finally `b`.
* Commands are processed as if they were entered at the prompt or from the command line.*/
final class Success(val project: Project, val onFailure: Option[String], insertArgs: String*) extends ProcessorResult
{
val insertArguments = insertArgs.toList
}
final class Exit(val code: Int) extends ProcessorResult
final class Reload(insertArgs: String*) extends ProcessorResult
{
val insertArguments = insertArgs.toList
}
/** Manages the processor and repository definitions.*/
trait Manager extends NotNull
{
def defineProcessor(pdef: ProcessorDefinition)
def removeDefinition(label: String): Definition
def defineRepository(repo: RepositoryDefinition)
def processor(pdef: ProcessorDefinition): Option[Processor]
def processorDefinition(label: String): Option[ProcessorDefinition]
def processors: Map[String, ProcessorDefinition]
def repositories: Map[String, RepositoryDefinition]
}
/** Executes a parsed command. */
trait Executing extends NotNull
{
def apply(command: Command)
}
/** Prints information about processors. */
trait Info extends NotNull
{
/** Prints available processors and defined repositories.*/
def show()
/** Prints usage of processor management commands.*/
def help()
}
/** Parses a command String */
trait CommandParsing extends NotNull
{
/** Parses a command String that has been preprocessed.
* It should have any prefix (like the * used by Main) removed
* and whitespace trimmed
*
* If parsing is successful, a `Command` instance is returned wrapped in `Right`.
* Otherwise, an error message is returned wrapped in `Left`.*/
def parseCommand(line: String): Either[String, Command]
}
/** Parses a definition `String`.*/
trait DefinitionParsing extends NotNull
{
/** Parses the given definition `String`.
* The result is wrapped in `Some` if successful, or `None` if the string is not of the correct form. */
def parseDefinition(line: String): Option[Definition]
}
/** Handles serializing `Definition`s.*/
trait Persisting extends NotNull
{
def save(file: File)(definitions: Iterable[Definition])
def load(file: File): Seq[Definition]
}
sealed trait Definition extends NotNull
{
def label: String
}
final class ProcessorDefinition(val label: String, val group: String, val module: String, val rev: String) extends Definition
{
override def toString = Seq(label, "is", group, module, rev).mkString(" ")
def idString = Seq(group, module, rev).mkString(" ")
def toModuleID(scalaVersion: String) = ModuleID(group, module + "_" + scalaVersion, rev)
}
// maven-style repositories only right now
final class RepositoryDefinition(val label: String, val url: String) extends Definition
{
override def toString = Seq(label, "at", url).mkString(" ")
}
/** Data type representing a runnable command related to processor management.*/
sealed trait Command extends NotNull
/** A command to add the given processor definition. */
final class DefineProcessor(val pdef: ProcessorDefinition) extends Command
/** A command to remove the processor or repository definition currently associated with the given `label`.
* If the definition is associated with other labels, those are not affected.*/
final class RemoveDefinition(val label: String) extends Command
/** A command to register the given repository to be used for obtaining `Processor`s. */
final class DefineRepository(val repo: RepositoryDefinition) extends Command
/** A command to show help for processor management command usage. */
object Help extends Command
/** A command to show available processors and repositories.*/
object Show extends Command
/** An exception used when a `Processor` wants to terminate with an error message, but the stack trace is not important.
* If a `cause` is provided, its stack trace is assumed to be important.*/
final class ProcessorException(val message: String, cause: Throwable) extends RuntimeException(message, cause)
{
def this(message: String) = this(message, null)
}
object ProcessorException
{
def error(msg: String): Nothing = throw new ProcessorException(msg)
def error(msg: String, t: Throwable): Nothing = throw new ProcessorException(msg, t)
}

View File

@ -1,33 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt.processor
import java.io.File
class Retrieve(retrieveDirectory: File, module: ModuleID, lock: xsbti.GlobalLock, lockFile: File, repositories: Seq[Resolver], log: IvyLogger) extends NotNull
{
def retrieve(localOnly: Boolean)
{
val paths = new IvyPaths(retrieveDirectory, None)
val ivyScala = new IvyScala("", Nil, false, true)
val fullRepositories = Resolver.withDefaultResolvers(repositories) // TODO: move this somewhere under user control
val configuration = new InlineIvyConfiguration(paths, fullRepositories, Nil, Nil, localOnly, Some(lock), log)
val moduleConfiguration = new InlineConfiguration(thisID, module :: Nil, scala.xml.NodeSeq.Empty, Nil, None, Some(ivyScala), false)
val update = new UpdateConfiguration(retrieveDirectory, retrievePattern, true, logging)
val ivySbt = new IvySbt(configuration)
val ivyModule = new ivySbt.Module(moduleConfiguration)
log.info("Getting processor...")
lock(lockFile, Callable { IvyActions.update(ivyModule, update) } )
}
def thisID = ModuleID("org.scala-tools.sbt", "retrieve-processor", "1.0")
def retrievePattern = "[artifact](-[revision])(-[classifier]).[ext]"
def logging = UpdateLogging.DownloadOnly
}
object Callable
{
def apply[T](f: => T): java.util.concurrent.Callable[T] = new java.util.concurrent.Callable[T] { def call = f }
}

View File

@ -1,39 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008 Mark Harrah */
package sbt
import org.scalacheck._
import Prop._
object NameFilterSpecification extends Properties("NameFilter")
{
specify("All pass accepts everything", (s: String) => AllPassFilter.accept(s))
specify("Exact filter matches provided string",
(s1: String, s2: String) => (new ExactFilter(s1)).accept(s2) == (s1 == s2) )
specify("Exact filter matches valid string", (s: String) => (new ExactFilter(s)).accept(s) )
specify("Glob filter matches provided string if no *s",
(s1: String, s2: String) =>
{
val stripped = stripAsterisksAndControl(s1)
(GlobFilter(stripped).accept(s2) == (stripped == s2))
})
specify("Glob filter matches valid string if no *s",
(s: String) =>
{
val stripped = stripAsterisksAndControl(s)
GlobFilter(stripped).accept(stripped)
})
specify("Glob filter matches valid",
(list: List[String]) =>
{
val stripped = list.map(stripAsterisksAndControl)
GlobFilter(stripped.mkString("*")).accept(stripped.mkString)
})
/** Raw control characters are stripped because they are not allowed in expressions.
* Asterisks are stripped because they are added under the control of the tests.*/
private def stripAsterisksAndControl(s: String) = s.filter(c => !java.lang.Character.isISOControl(c) && c != '*').toString
}

View File

@ -1,61 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008 Mark Harrah
*/
package sbt
import org.scalacheck._
object VersionSpecification extends Properties("Version")
{
import ArbitraryVersion._
specify("Empty or whitespace only string not allowed, all others allowed",
(s: String) => Version.fromString(s).isLeft == s.trim.isEmpty)
specify("BasicVersion round trips", checkRoundTrip _)
specify("BasicVersion increment major", checkIncrementMajor _)
specify("BasicVersion increment minor", checkIncrementMinor _)
specify("BasicVersion increment micro", checkIncrementMicro _)
private def checkRoundTrip(v: BasicVersion) =
{
val v2 = Version.fromString(v.toString)
v2.isRight && v2.right.get == v
}
private def checkIncrementMinor(v: BasicVersion) = checkIncrement(v, _.incrementMinor)
private def checkIncrementMajor(v: BasicVersion) = checkIncrement(v, _.incrementMajor)
private def checkIncrementMicro(v: BasicVersion) = checkIncrement(v, _.incrementMicro)
private def checkIncrement(v: BasicVersion, increment: (BasicVersion => BasicVersion)) =
{
val vNew = increment(v)
checkRoundTrip(vNew) && vNew != v
}
}
object ArbitraryVersion
{
implicit lazy val arbBasicVersion: Arbitrary[BasicVersion] = Arbitrary(genBasicVersion)
implicit lazy val arbOpaqueVersion: Arbitrary[OpaqueVersion] = Arbitrary(genOpaqueVersion)
implicit lazy val arbVersion: Arbitrary[Version] = Arbitrary(genVersion)
import Arbitrary._
import Math.abs
lazy val genBasicVersion =
for{major <- arbInt.arbitrary
minor <- arbOption[Int].arbitrary
micro <- arbOption[Int].arbitrary
extra <- genExtra }
yield
{
if(minor.isEmpty && micro.isDefined)
BasicVersion(abs(major), micro.map(abs), None, extra)
else
BasicVersion(abs(major), minor.map(abs), micro.map(abs), extra)
}
lazy val genOpaqueVersion = for(versionString <- arbString.arbitrary if !versionString.trim.isEmpty) yield OpaqueVersion(versionString)
lazy val genVersion = Gen.frequency((5,genBasicVersion), (1,genOpaqueVersion))
private lazy val genExtra =
for(extra <- arbOption[String].arbitrary;
val trimmedExtra = extra.map(_.trim.filter(c => !java.lang.Character.isISOControl(c)).toString);
if Version.isValidExtra(trimmedExtra))
yield
trimmedExtra
}

View File

@ -1,89 +0,0 @@
package sbt.wrap
import org.scalacheck._
import java.util.{HashSet, LinkedHashSet}
object WrappedHashSetTests extends MutableSetWrapperTests(new HashSet[Int])
object WrappedLinkedHashSetTests extends MutableSetWrapperTests(new LinkedHashSet[Int])
{
specify("toList preserves order", checkAddOrdered _)
private def checkAddOrdered(values: List[Int]) =
{
val set = createWrapped
val check = new scala.collection.mutable.HashSet[Int]
val list = new scala.collection.mutable.ListBuffer[Int]
for(value <- values)
{
set += value
if(!check(value))
{
check += value
list += value
}
}
list.toList sameElements set.toList
}
}
abstract class MutableSetWrapperTests(createUnderlying: => java.util.Set[Int]) extends Properties("Mutable Set wrapper (" + createUnderlying.getClass.getName + ")")
{
protected def createWrapped = new MutableSetWrapper(createUnderlying)
specify("Contains all added at once", checkBatchAddition _)
specify("Contains all added individually", checkSingleAddition _)
specify("toList contains all added at once", checkBatchToList _)
specify("toList contains all added individually", checkSingleToList _)
specify("Contains all added and not removed", checkRemove _)
private def checkSingleAddition(values: List[Int]) =
{
val set = createSingleAdd(values)
values.forall(set.contains)
}
private def checkBatchAddition(values: List[Int]) =
{
val set = createBatchAdd(values)
values.forall(set.contains)
}
private def checkBatchToList(values: List[Int]) =
{
val set = createBatchAdd(values)
val check = scala.collection.mutable.HashSet(set.toList : _*)
values.forall(check.contains)
}
private def checkSingleToList(values: List[Int]) =
{
val set = createSingleAdd(values)
val check = scala.collection.mutable.HashSet(set.toList : _*)
values.forall(check.contains)
}
protected final def createBatchAdd(values: List[Int]) =
{
val set = createWrapped
set ++= values
set
}
protected final def createSingleAdd(values: List[Int]) =
{
val set = createWrapped
values.foreach(set += _)
set
}
private def checkRemove(values: List[(Int, Boolean)]) =
{
val set = createWrapped
val check = new scala.collection.mutable.HashSet[Int]
for( (key, _) <- values)
{
set += key
check += key
}
for( (key, false) <- values)
{
set -= key
check -= key
}
values.forall { case (key, _) => set.contains(key) == check.contains(key) }
}
}