mirror of https://github.com/sbt/sbt.git
335 lines
14 KiB
Scala
335 lines
14 KiB
Scala
/* sbt -- Simple Build Tool
|
|
* Copyright 2009, 2010 Mark Harrah
|
|
*/
|
|
package xsbt.boot
|
|
|
|
import Pre._
|
|
import java.io.{File, FileWriter, PrintWriter, Writer}
|
|
import java.util.concurrent.Callable
|
|
import java.util.regex.Pattern
|
|
|
|
import org.apache.ivy.{core, plugins, util, Ivy}
|
|
import core.LogOptions
|
|
import core.cache.DefaultRepositoryCacheManager
|
|
import core.event.EventManager
|
|
import core.module.id.{ArtifactId, ModuleId, ModuleRevisionId}
|
|
import core.module.descriptor.{Configuration => IvyConfiguration, DefaultDependencyArtifactDescriptor, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor}
|
|
import core.module.descriptor.{DefaultExcludeRule, ExcludeRule}
|
|
import core.report.ResolveReport
|
|
import core.resolve.{ResolveEngine, ResolveOptions}
|
|
import core.retrieve.{RetrieveEngine, RetrieveOptions}
|
|
import core.sort.SortEngine
|
|
import core.settings.IvySettings
|
|
import plugins.matcher.{ExactPatternMatcher, PatternMatcher}
|
|
import plugins.resolver.{ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver}
|
|
import util.{DefaultMessageLogger, Message, MessageLoggerEngine}
|
|
|
|
import BootConfiguration._
|
|
|
|
sealed trait UpdateTarget extends NotNull { def tpe: String; def classifiers: List[String] }
|
|
final class UpdateScala(val classifiers: List[String]) extends UpdateTarget { def tpe = "scala" }
|
|
final class UpdateApp(val id: Application, val classifiers: List[String]) extends UpdateTarget { def tpe = "app" }
|
|
|
|
final class UpdateConfiguration(val bootDirectory: File, val ivyCacheDirectory: Option[File], val scalaVersion: String, val repositories: List[Repository]) extends NotNull
|
|
|
|
/** Ensures that the Scala and application jars exist for the given versions or else downloads them.*/
|
|
final class Update(config: UpdateConfiguration)
|
|
{
|
|
import config.{bootDirectory, ivyCacheDirectory, repositories, scalaVersion}
|
|
bootDirectory.mkdirs
|
|
|
|
private def logFile = new File(bootDirectory, UpdateLogName)
|
|
private val logWriter = new PrintWriter(new FileWriter(logFile))
|
|
|
|
private lazy val settings =
|
|
{
|
|
val settings = new IvySettings
|
|
addResolvers(settings)
|
|
settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
|
|
settings.setBaseDir(bootDirectory)
|
|
settings.setVariable("scala", scalaVersion)
|
|
settings
|
|
}
|
|
private lazy val ivy =
|
|
{
|
|
val ivy = new Ivy() { private val loggerEngine = new SbtMessageLoggerEngine; override def getLoggerEngine = loggerEngine }
|
|
ivy.setSettings(settings)
|
|
ivy.bind()
|
|
ivy
|
|
}
|
|
// should be the same file as is used in the Ivy module
|
|
private lazy val ivyLockFile = new File(settings.getDefaultIvyUserDir, ".sbt.ivy.lock")
|
|
|
|
/** The main entry point of this class for use by the Update module. It runs Ivy */
|
|
def apply(target: UpdateTarget): Boolean =
|
|
{
|
|
Message.setDefaultLogger(new SbtIvyLogger(logWriter))
|
|
val action = new Callable[Boolean] { def call = lockedApply(target) }
|
|
Locks(ivyLockFile, action)
|
|
}
|
|
private def lockedApply(target: UpdateTarget) =
|
|
{
|
|
ivy.pushContext()
|
|
try { update(target); true }
|
|
catch
|
|
{
|
|
case e: Exception =>
|
|
e.printStackTrace(logWriter)
|
|
log(e.toString)
|
|
System.out.println(" (see " + logFile + " for complete log)")
|
|
false
|
|
}
|
|
finally
|
|
{
|
|
logWriter.close()
|
|
ivy.popContext()
|
|
}
|
|
}
|
|
/** Runs update for the specified target (updates either the scala or appliciation jars for building the project) */
|
|
private def update(target: UpdateTarget)
|
|
{
|
|
import IvyConfiguration.Visibility.PUBLIC
|
|
// the actual module id here is not that important
|
|
val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot-" + target.tpe, "1.0"), "release", null, false)
|
|
moduleID.setLastModified(System.currentTimeMillis)
|
|
moduleID.addConfiguration(new IvyConfiguration(DefaultIvyConfiguration, PUBLIC, "", new Array(0), true, null))
|
|
// add dependencies based on which target needs updating
|
|
target match
|
|
{
|
|
case u: UpdateScala =>
|
|
addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default", u.classifiers)
|
|
addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default", u.classifiers)
|
|
System.out.println("Getting Scala " + scalaVersion + " ...")
|
|
case u: UpdateApp =>
|
|
val app = u.id
|
|
val resolvedName = if(app.crossVersioned) app.name + "_" + scalaVersion else app.name
|
|
addDependency(moduleID, app.groupID, resolvedName, app.getVersion, "default(compile)", u.classifiers)
|
|
excludeScala(moduleID)
|
|
System.out.println("Getting " + app.groupID + " " + resolvedName + " " + app.getVersion + " ...")
|
|
}
|
|
update(moduleID, target)
|
|
}
|
|
/** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */
|
|
private def update(moduleID: DefaultModuleDescriptor, target: UpdateTarget)
|
|
{
|
|
val eventManager = new EventManager
|
|
resolve(eventManager, moduleID)
|
|
retrieve(eventManager, moduleID, target)
|
|
}
|
|
private def createID(organization: String, name: String, revision: String) =
|
|
ModuleRevisionId.newInstance(organization, name, revision)
|
|
/** Adds the given dependency to the default configuration of 'moduleID'. */
|
|
private def addDependency(moduleID: DefaultModuleDescriptor, organization: String, name: String, revision: String, conf: String, classifiers: List[String])
|
|
{
|
|
val dep = new DefaultDependencyDescriptor(moduleID, createID(organization, name, revision), false, false, true)
|
|
dep.addDependencyConfiguration(DefaultIvyConfiguration, conf)
|
|
for(classifier <- classifiers)
|
|
addClassifier(dep, name, classifier)
|
|
moduleID.addDependency(dep)
|
|
}
|
|
private def addClassifier(dep: DefaultDependencyDescriptor, name: String, classifier: String)
|
|
{
|
|
val extraMap = new java.util.HashMap[String,String]
|
|
if(!classifier.isEmpty)
|
|
extraMap.put("e:classifier", classifier)
|
|
val ivyArtifact = new DefaultDependencyArtifactDescriptor(dep, name, artifactType(classifier), "jar", null, extraMap)
|
|
for(conf <- dep.getModuleConfigurations)
|
|
dep.addDependencyArtifact(conf, ivyArtifact)
|
|
}
|
|
private def excludeScala(module: DefaultModuleDescriptor)
|
|
{
|
|
def excludeScalaJar(name: String): Unit = module.addExcludeRule(excludeRule(ScalaOrg, name))
|
|
excludeScalaJar(LibraryModuleName)
|
|
excludeScalaJar(CompilerModuleName)
|
|
}
|
|
private def excludeRule(organization: String, name: String): ExcludeRule =
|
|
{
|
|
val artifact = new ArtifactId(ModuleId.newInstance(organization, name), "*", "*", "*")
|
|
val rule = new DefaultExcludeRule(artifact, ExactPatternMatcher.INSTANCE, java.util.Collections.emptyMap[AnyRef,AnyRef])
|
|
rule.addConfiguration(DefaultIvyConfiguration)
|
|
rule
|
|
}
|
|
private def resolve(eventManager: EventManager, module: ModuleDescriptor)
|
|
{
|
|
val resolveOptions = new ResolveOptions
|
|
// this reduces the substantial logging done by Ivy, including the progress dots when downloading artifacts
|
|
resolveOptions.setLog(LogOptions.LOG_DOWNLOAD_ONLY)
|
|
resolveOptions.setCheckIfChanged(false)
|
|
val resolveEngine = new ResolveEngine(settings, eventManager, new SortEngine(settings))
|
|
val resolveReport = resolveEngine.resolve(module, resolveOptions)
|
|
if(resolveReport.hasError)
|
|
{
|
|
logExceptions(resolveReport)
|
|
val seen = new java.util.LinkedHashSet[Any]
|
|
seen.addAll(resolveReport.getAllProblemMessages)
|
|
System.out.println(seen.toArray.mkString(System.getProperty("line.separator")))
|
|
error("Error retrieving required libraries")
|
|
}
|
|
}
|
|
/** Exceptions are logged to the update log file. */
|
|
private def logExceptions(report: ResolveReport)
|
|
{
|
|
for(unresolved <- report.getUnresolvedDependencies)
|
|
{
|
|
val problem = unresolved.getProblem
|
|
if(problem != null)
|
|
problem.printStackTrace(logWriter)
|
|
}
|
|
}
|
|
/** Retrieves resolved dependencies using the given target to determine the location to retrieve to. */
|
|
private def retrieve(eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget)
|
|
{
|
|
val retrieveOptions = new RetrieveOptions
|
|
val retrieveEngine = new RetrieveEngine(settings, eventManager)
|
|
val pattern =
|
|
target match
|
|
{
|
|
case _: UpdateScala => scalaRetrievePattern
|
|
case u: UpdateApp => appRetrievePattern(u.id.toID)
|
|
}
|
|
retrieveEngine.retrieve(module.getModuleRevisionId, baseDirectoryName(scalaVersion) + "/" + pattern, retrieveOptions)
|
|
}
|
|
/** Add the scala tools repositories and a URL resolver to download sbt from the Google code project.*/
|
|
private def addResolvers(settings: IvySettings)
|
|
{
|
|
val newDefault = new ChainResolver
|
|
newDefault.setName("redefined-public")
|
|
if(repositories.isEmpty) error("No repositories defined.")
|
|
for(repo <- repositories if includeRepo(repo))
|
|
newDefault.add(toIvyRepository(settings, repo))
|
|
configureCache(settings, ivyCacheDirectory)
|
|
settings.addResolver(newDefault)
|
|
settings.setDefaultResolver(newDefault.getName)
|
|
}
|
|
// exclude the local Maven repository for Scala -SNAPSHOTs
|
|
private def includeRepo(repo: Repository) = !(Repository.isMavenLocal(repo) && isSnapshot(scalaVersion) )
|
|
private def isSnapshot(scalaVersion: String) = scalaVersion.endsWith(Snapshot)
|
|
private[this] val Snapshot = "-SNAPSHOT"
|
|
private[this] val ChangingPattern = ".*" + Snapshot
|
|
private[this] val ChangingMatcher = PatternMatcher.REGEXP
|
|
private def configureCache(settings: IvySettings, dir: Option[File])
|
|
{
|
|
val cacheDir = dir.getOrElse(settings.getDefaultRepositoryCacheBasedir())
|
|
val manager = new DefaultRepositoryCacheManager("default-cache", settings, cacheDir)
|
|
manager.setUseOrigin(true)
|
|
manager.setChangingMatcher(ChangingMatcher)
|
|
manager.setChangingPattern(ChangingPattern)
|
|
settings.addRepositoryCacheManager(manager)
|
|
settings.setDefaultRepositoryCacheManager(manager)
|
|
dir.foreach(dir => settings.setDefaultResolutionCacheBasedir(dir.getAbsolutePath))
|
|
}
|
|
private def toIvyRepository(settings: IvySettings, repo: Repository) =
|
|
{
|
|
import Repository.{Ivy, Maven, Predefined}
|
|
import Predefined._
|
|
repo match
|
|
{
|
|
case Maven(id, url) => mavenResolver(id, url.toString)
|
|
case Ivy(id, url, pattern) => urlResolver(id, url.toString, pattern)
|
|
case Predefined(Local) => localResolver(settings.getDefaultIvyUserDir.getAbsolutePath)
|
|
case Predefined(MavenLocal) => mavenLocal
|
|
case Predefined(MavenCentral) => mavenMainResolver
|
|
case Predefined(ScalaToolsReleases) => mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases")
|
|
case Predefined(ScalaToolsSnapshots) => scalaSnapshots(scalaVersion)
|
|
}
|
|
}
|
|
private def onDefaultRepositoryCacheManager(settings: IvySettings)(f: DefaultRepositoryCacheManager => Unit)
|
|
{
|
|
settings.getDefaultRepositoryCacheManager match
|
|
{
|
|
case manager: DefaultRepositoryCacheManager => f(manager)
|
|
case _ => ()
|
|
}
|
|
}
|
|
/** Uses the pattern defined in BuildConfiguration to download sbt from Google code.*/
|
|
private def urlResolver(id: String, base: String, pattern: String) =
|
|
{
|
|
val resolver = new URLResolver
|
|
resolver.setName(id)
|
|
val adjusted = (if(base.endsWith("/")) base else (base + "/") ) + pattern
|
|
resolver.addIvyPattern(adjusted)
|
|
resolver.addArtifactPattern(adjusted)
|
|
resolver
|
|
}
|
|
private def mavenLocal = mavenResolver("Maven2 Local", "file://" + System.getProperty("user.home") + "/.m2/repository/")
|
|
/** Creates a maven-style resolver.*/
|
|
private def mavenResolver(name: String, root: String) =
|
|
{
|
|
val resolver = defaultMavenResolver(name)
|
|
resolver.setRoot(root)
|
|
resolver
|
|
}
|
|
/** Creates a resolver for Maven Central.*/
|
|
private def mavenMainResolver = defaultMavenResolver("Maven Central")
|
|
/** Creates a maven-style resolver with the default root.*/
|
|
private def defaultMavenResolver(name: String) =
|
|
{
|
|
val resolver = new IBiblioResolver
|
|
resolver.setName(name)
|
|
resolver.setM2compatible(true)
|
|
resolver
|
|
}
|
|
private def localResolver(ivyUserDirectory: String) =
|
|
{
|
|
val localIvyRoot = ivyUserDirectory + "/local"
|
|
val resolver = new FileSystemResolver
|
|
resolver.setName(LocalIvyName)
|
|
resolver.addIvyPattern(localIvyRoot + "/" + LocalIvyPattern)
|
|
resolver.addArtifactPattern(localIvyRoot + "/" + LocalArtifactPattern)
|
|
resolver
|
|
}
|
|
private val SnapshotPattern = Pattern.compile("""(\d+).(\d+).(\d+)-(\d{8})\.(\d{6})-(\d+|\+)""")
|
|
private def scalaSnapshots(scalaVersion: String) =
|
|
{
|
|
val m = SnapshotPattern.matcher(scalaVersion)
|
|
if(m.matches)
|
|
{
|
|
val base = List(1,2,3).map(m.group).mkString(".")
|
|
val pattern = "http://scala-tools.org/repo-snapshots/[organization]/[module]/" + base + "-SNAPSHOT/[artifact]-[revision](-[classifier]).[ext]"
|
|
|
|
val resolver = new URLResolver
|
|
resolver.setName("Scala Tools Snapshots")
|
|
resolver.setM2compatible(true)
|
|
resolver.addArtifactPattern(pattern)
|
|
resolver
|
|
}
|
|
else
|
|
mavenResolver("Scala-Tools Maven2 Snapshots Repository", "http://scala-tools.org/repo-snapshots")
|
|
}
|
|
/** Logs the given message to a file and to the console. */
|
|
private def log(msg: String) =
|
|
{
|
|
try { logWriter.println(msg) }
|
|
catch { case e: Exception => System.err.println("Error writing to update log file: " + e.toString) }
|
|
System.out.println(msg)
|
|
}
|
|
}
|
|
|
|
import SbtIvyLogger.{acceptError, acceptMessage}
|
|
|
|
/** A custom logger for Ivy to ignore the messages about not finding classes
|
|
* intentionally filtered using proguard and about 'unknown resolver'. */
|
|
private final class SbtIvyLogger(logWriter: PrintWriter) extends DefaultMessageLogger(Message.MSG_INFO)
|
|
{
|
|
override def log(msg: String, level: Int)
|
|
{
|
|
logWriter.println(msg)
|
|
if(level <= getLevel && acceptMessage(msg))
|
|
System.out.println(msg)
|
|
}
|
|
override def rawlog(msg: String, level: Int) { log(msg, level) }
|
|
/** This is a hack to filter error messages about 'unknown resolver ...'. */
|
|
override def error(msg: String) = if(acceptError(msg)) super.error(msg)
|
|
}
|
|
private final class SbtMessageLoggerEngine extends MessageLoggerEngine
|
|
{
|
|
/** This is a hack to filter error messages about 'unknown resolver ...'. */
|
|
override def error(msg: String) = if(acceptError(msg)) super.error(msg)
|
|
}
|
|
private object SbtIvyLogger
|
|
{
|
|
val IgnorePrefix = "impossible to define"
|
|
val UnknownResolver = "unknown resolver"
|
|
def acceptError(msg: String) = acceptMessage(msg) && !msg.startsWith(UnknownResolver)
|
|
def acceptMessage(msg: String) = (msg ne null) && !msg.startsWith(IgnorePrefix)
|
|
} |