mirror of https://github.com/sbt/sbt.git
cleanup
This commit is contained in:
parent
50f1bd73d6
commit
3821239b43
|
|
@ -1,4 +0,0 @@
|
|||
<plugin>
|
||||
<name>sbt-analyze</name>
|
||||
<classname>sbt.Analyzer</classname>
|
||||
</plugin>
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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._")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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("")
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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>"
|
||||
}
|
||||
|
|
@ -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 "" }
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 "))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue