resolver keys, javaHome, scalaHome, and change Java source resolution

- rename resolvers to fullResolvers, baseResolvers to resolvers
- if set, use javaHome for compilation as well as running
- add scalaHome configuration for easily configuring scalaInstance
   for a local Scala installation
- no longer require source roots for Java sources.  Instead, resolve
   ambiguities by package name and then distance to root.  Common
   package suffixes in different directories within the same project
   are a potential problem.
This commit is contained in:
Mark Harrah 2011-03-09 18:04:53 -05:00
parent fc32a31abf
commit 77092b7888
6 changed files with 93 additions and 40 deletions

View File

@ -516,7 +516,7 @@ object Load
def build(classpath: Seq[File], sources: Seq[File], target: File, compilers: Compilers, log: Logger): (Inputs, inc.Analysis) =
{
val inputs = Compiler.inputs(classpath, sources, target, Nil, Nil, Nil, Compiler.DefaultMaxErrors)(compilers, log)
val inputs = Compiler.inputs(classpath, sources, target, Nil, Nil, Compiler.DefaultMaxErrors)(compilers, log)
val analysis = Compiler(inputs, log)
(inputs, analysis)
}

View File

@ -24,22 +24,22 @@ object Compiler
final class Inputs(val compilers: Compilers, val config: Options, val incSetup: IncSetup)
final class Options(val classpath: Seq[File], val sources: Seq[File], val classesDirectory: File, val options: Seq[String], val javacOptions: Seq[String], val maxErrors: Int)
final class IncSetup(val srcBases: Seq[File], val analysisMap: Map[File, Analysis], val cacheDirectory: File)
final class IncSetup(val analysisMap: Map[File, Analysis], val cacheDirectory: File)
final class Compilers(val scalac: AnalyzingCompiler, val javac: JavaCompiler)
def inputs(classpath: Seq[File], sources: Seq[File], outputDirectory: File, options: Seq[String], javacOptions: Seq[String], srcBases: Seq[File], maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
def inputs(classpath: Seq[File], sources: Seq[File], outputDirectory: File, options: Seq[String], javacOptions: Seq[String], maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
{
import Path._
val classesDirectory = outputDirectory / "classes"
val cacheDirectory = outputDirectory / "cache"
val augClasspath = classesDirectory.asFile +: classpath
inputs(augClasspath, sources, classesDirectory, options, javacOptions, srcBases, Map.empty, cacheDirectory, maxErrors)
inputs(augClasspath, sources, classesDirectory, options, javacOptions, Map.empty, cacheDirectory, maxErrors)
}
def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], srcBases: Seq[File], analysisMap: Map[File, Analysis], cacheDirectory: File, maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
def inputs(classpath: Seq[File], sources: Seq[File], classesDirectory: File, options: Seq[String], javacOptions: Seq[String], analysisMap: Map[File, Analysis], cacheDirectory: File, maxErrors: Int)(implicit compilers: Compilers, log: Logger): Inputs =
new Inputs(
compilers,
new Options(classpath, sources, classesDirectory, options, javacOptions, maxErrors),
new IncSetup(srcBases, analysisMap, cacheDirectory)
new IncSetup(analysisMap, cacheDirectory)
)
def compilers(implicit app: AppConfiguration, log: Logger): Compilers =
@ -49,14 +49,14 @@ object Compiler
}
def compilers(instance: ScalaInstance)(implicit app: AppConfiguration, log: Logger): Compilers =
compilers(instance, ClasspathOptions.auto)
compilers(instance, ClasspathOptions.auto, None)
def compilers(instance: ScalaInstance, javac: (Seq[String], Logger) => Int)(implicit app: AppConfiguration, log: Logger): Compilers =
compilers(instance, ClasspathOptions.auto, javac)
def compilers(instance: ScalaInstance, cpOptions: ClasspathOptions)(implicit app: AppConfiguration, log: Logger): Compilers =
def compilers(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File])(implicit app: AppConfiguration, log: Logger): Compilers =
{
val javac = JavaCompiler.directOrFork(cpOptions, instance)( (args: Seq[String], log: Logger) => Process("javac", args) ! log )
val javac = directOrFork(instance, cpOptions, javaHome)
compilers(instance, cpOptions, javac)
}
def compilers(instance: ScalaInstance, cpOptions: ClasspathOptions, javac: (Seq[String], Logger) => Int)(implicit app: AppConfiguration, log: Logger): Compilers =
@ -75,6 +75,21 @@ object Compiler
val componentManager = new ComponentManager(launcher.globalLock, app.provider.components, log)
new AnalyzingCompiler(instance, componentManager, cpOptions, log)
}
def directOrFork(instance: ScalaInstance, cpOptions: ClasspathOptions, javaHome: Option[File]): JavaCompiler =
if(javaHome.isDefined)
JavaCompiler.fork(cpOptions, instance)(forkJavac(javaHome))
else
JavaCompiler.directOrFork(cpOptions, instance)( forkJavac(None) )
def forkJavac(javaHome: Option[File]): (Seq[String], Logger) => Int =
{
import Path._
val exec = javaHome match { case None => "javac"; case Some(jh) => (jh / "bin" / "javac").absolutePath }
(args: Seq[String], log: Logger) => {
log.debug("Forking javac: " + exec + " " + args.mkString(" "))
Process(exec, args) ! log
}
}
def apply(in: Inputs, log: Logger): Analysis =
{
@ -83,6 +98,6 @@ object Compiler
import in.incSetup._
val agg = new build.AggressiveCompile(cacheDirectory)
agg(scalac, javac, sources, classpath, classesDirectory, srcBases, options, javacOptions, analysisMap, maxErrors)(log)
agg(scalac, javac, sources, classpath, classesDirectory, options, javacOptions, analysisMap, maxErrors)(log)
}
}

View File

@ -55,7 +55,8 @@ object Defaults
(cp map extractAnalysis).toMap
def buildCore: Seq[Setting[_]] = inScope(GlobalScope)(Seq(
pollInterval :== 1,
pollInterval :== 500,
scalaHome :== None,
javaHome :== None,
outputStrategy :== None,
fork :== false,
@ -94,9 +95,8 @@ object Defaults
sources <<= (sourceDirectories, sourceFilter, defaultExcludes) map { (d,f,excl) => d.descendentsExcept(f,excl).getFiles.toSeq },
scalaSource <<= sourceDirectory / "scala",
javaSource <<= sourceDirectory / "java",
javaSourceRoots <<= toSeq(javaSource),
resourceDirectory <<= sourceDirectory / "resources",
sourceDirectories <<= (scalaSource, javaSourceRoots) { _ +: _ },
sourceDirectories <<= (scalaSource, javaSource) { _ :: _ :: Nil },
resourceDirectories <<= toSeq(resourceDirectory),
resources <<= (resourceDirectories, defaultExcludes) map { (dirs, excl) => dirs.descendentsExcept("*",excl).getFiles.toSeq }
)
@ -110,10 +110,10 @@ object Defaults
def compileBase = Seq(
classpathOptions in GlobalScope :== ClasspathOptions.auto,
compilers <<= (scalaInstance, appConfiguration, streams, classpathOptions) map { (si, app, s, co) => Compiler.compilers(si, co)(app, s.log) },
compilers <<= (scalaInstance, appConfiguration, streams, classpathOptions, javaHome) map { (si, app, s, co, jh) => Compiler.compilers(si, co, jh)(app, s.log) },
javacOptions in GlobalScope :== Nil,
scalacOptions in GlobalScope :== Nil,
scalaInstance <<= (appConfiguration, scalaVersion){ (app, version) => ScalaInstance(version, app.provider.scalaProvider.launcher) },
scalaInstance <<= scalaInstanceSetting,
scalaVersion <<= appConfiguration( _.provider.scalaProvider.version),
target <<= (target, scalaInstance, crossPaths)( (t,si,cross) => if(cross) t / ("scala-" + si.actualVersion) else t )
)
@ -165,6 +165,13 @@ object Defaults
override def watchPaths(s: State) = EvaluateTask.evaluateTask(Project structure s, key, s, base) match { case Some(Value(ps)) => ps; case _ => Nil }
}
}
def scalaInstanceSetting = (appConfiguration, scalaVersion, scalaHome){ (app, version, home) =>
val launcher = app.provider.scalaProvider.launcher
home match {
case None => ScalaInstance(version, launcher)
case Some(h) => ScalaInstance(h, launcher)
}
}
lazy val testTasks = Seq(
testLoader <<= (fullClasspath, scalaInstance) map { (cp, si) => TestFramework.createTestLoader(data(cp), si) },
@ -296,12 +303,12 @@ object Defaults
def compileTask = (compileInputs, streams) map { (i,s) => Compiler(i,s.log) }
def compileInputsTask =
(dependencyClasspath, sources, javaSourceRoots, compilers, javacOptions, scalacOptions, cacheDirectory, classDirectory, streams) map {
(cp, srcs, javaRoots, cs, javacOpts, scalacOpts, cacheDir, classes, s) =>
(dependencyClasspath, sources, compilers, javacOptions, scalacOptions, cacheDirectory, classDirectory, streams) map {
(cp, srcs, cs, javacOpts, scalacOpts, cacheDir, classes, s) =>
val classpath = classes +: data(cp)
val analysis = analysisMap(cp)
val cache = cacheDir / "compile"
Compiler.inputs(classpath, srcs, classes, scalacOpts, javacOpts, javaRoots, analysis, cache, 100)(cs, s.log)
Compiler.inputs(classpath, srcs, classes, scalacOpts, javacOpts, analysis, cache, 100)(cs, s.log)
}
def copyResourcesTask =
@ -416,7 +423,7 @@ object Classpaths
normalizedName <<= name(StringUtilities.normalize),
organization :== normalizedName,
classpathFilter in GlobalScope :== "*.jar",
resolvers <<= (projectResolver,baseResolvers).map( (pr,rs) => pr +: Resolver.withDefaultResolvers(rs)),
fullResolvers <<= (projectResolver,resolvers).map( (pr,rs) => pr +: Resolver.withDefaultResolvers(rs)),
offline in GlobalScope :== false,
moduleID :== normalizedName,
defaultConfiguration in GlobalScope :== Some(Configurations.Compile),
@ -440,12 +447,12 @@ object Classpaths
pomArtifact <<= (publishMavenStyle, moduleID)( (mavenStyle, name) => if(mavenStyle) Artifact(name, "pom", "pom") :: Nil else Nil),
artifacts <<= (pomArtifact,moduleID)( (pom,name) => Artifact(name) +: pom),
projectID <<= (organization,moduleID,version,artifacts){ (org,module,version,as) => ModuleID(org, module, version).cross(true).artifacts(as : _*) },
baseResolvers in GlobalScope :== Nil,
resolvers in GlobalScope :== Nil,
projectDescriptors <<= depMap,
retrievePattern :== "[type]/[organisation]/[module]/[artifact](-[revision])(-[classifier]).[ext]",
updateConfiguration <<= (retrieveConfiguration, ivyLoggingLevel)((conf,level) => new UpdateConfiguration(conf, level) ),
retrieveConfiguration :== None, //Some(new RetrieveConfiguration(managedDependencyPath asFile, retrievePattern, true))
ivyConfiguration <<= (resolvers, ivyPaths, otherResolvers, moduleConfigurations, offline, appConfiguration) map { (rs, paths, other, moduleConfs, off, app) =>
ivyConfiguration <<= (fullResolvers, ivyPaths, otherResolvers, moduleConfigurations, offline, appConfiguration) map { (rs, paths, other, moduleConfs, off, app) =>
// todo: pass logger from streams directly to IvyActions
val lock = app.provider.scalaProvider.launcher.globalLock
new InlineIvyConfiguration(paths, rs, other, moduleConfs, off, Some(lock), ConsoleLogger())

View File

@ -50,7 +50,6 @@ object Keys
val sourceManaged = SettingKey[File]("source-managed")
val scalaSource = SettingKey[File]("scala-source")
val javaSource = SettingKey[File]("java-source")
val javaSourceRoots = SettingKey[Seq[File]]("java-source-roots")
val resourceDirectory = SettingKey[File]("resource-directory")
val sourceDirectories = SettingKey[Seq[File]]("source-directories")
val resourceDirectories = SettingKey[Seq[File]]("resource-directories")
@ -70,6 +69,7 @@ object Keys
val javacOptions = SettingKey[Seq[String]]("javac-options")
val initialCommands = SettingKey[String]("initial-commands")
val compileInputs = TaskKey[Compiler.Inputs]("compile-inputs")
val scalaHome = SettingKey[Option[File]]("scala-home")
val scalaInstance = SettingKey[ScalaInstance]("scala-instance")
val scalaVersion = SettingKey[String]("scala-version")
val classpathOptions = SettingKey[ClasspathOptions]("classpath-options")
@ -164,9 +164,9 @@ object Keys
val moduleID = SettingKey[String]("module-id")
val version = SettingKey[String]("version")
val projectID = SettingKey[ModuleID]("project-id")
val baseResolvers = SettingKey[Seq[Resolver]]("base-resolvers")
val resolvers = SettingKey[Seq[Resolver]]("resolvers")
val projectResolver = TaskKey[Resolver]("project-resolver")
val resolvers = TaskKey[Seq[Resolver]]("resolvers")
val fullResolvers = TaskKey[Seq[Resolver]]("full-resolvers")
val otherResolvers = SettingKey[Seq[Resolver]]("other-resolvers")
val moduleConfigurations = SettingKey[Seq[ModuleConfiguration]]("module-configurations")
val retrievePattern = SettingKey[String]("retrieve-pattern")

View File

@ -15,25 +15,25 @@ import inc._
import CompileSetup._
import sbinary.DefaultProtocol.{ immutableMapFormat, immutableSetFormat, StringFormat }
final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File], val javaSrcBases: Seq[File],
final class CompileConfiguration(val sources: Seq[File], val classpath: Seq[File],
val previousAnalysis: Analysis, val previousSetup: Option[CompileSetup], val currentSetup: CompileSetup, val getAnalysis: File => Option[Analysis],
val maxErrors: Int, val compiler: AnalyzingCompiler, val javac: JavaCompiler)
class AggressiveCompile(cacheDirectory: File)
{
def apply(compiler: AnalyzingCompiler, javac: JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, javaSrcBases: Seq[File] = Nil, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: Map[File, Analysis] = Map.empty, maxErrors: Int = 100)(implicit log: Logger): Analysis =
def apply(compiler: AnalyzingCompiler, javac: JavaCompiler, sources: Seq[File], classpath: Seq[File], outputDirectory: File, options: Seq[String] = Nil, javacOptions: Seq[String] = Nil, analysisMap: Map[File, Analysis] = Map.empty, maxErrors: Int = 100)(implicit log: Logger): Analysis =
{
val setup = new CompileSetup(outputDirectory, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, CompileOrder.Mixed)
compile1(sources, classpath, javaSrcBases, setup, store, analysisMap, compiler, javac, maxErrors)
compile1(sources, classpath, setup, store, analysisMap, compiler, javac, maxErrors)
}
def withBootclasspath(args: CompilerArguments, classpath: Seq[File]): Seq[File] =
args.bootClasspath ++ args.finishClasspath(classpath)
def compile1(sources: Seq[File], classpath: Seq[File], javaSrcBases: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], compiler: AnalyzingCompiler, javac: JavaCompiler, maxErrors: Int)(implicit log: Logger): Analysis =
def compile1(sources: Seq[File], classpath: Seq[File], setup: CompileSetup, store: AnalysisStore, analysis: Map[File, Analysis], compiler: AnalyzingCompiler, javac: JavaCompiler, maxErrors: Int)(implicit log: Logger): Analysis =
{
val (previousAnalysis, previousSetup) = extract(store.get())
val config = new CompileConfiguration(sources, classpath, javaSrcBases, previousAnalysis, previousSetup, setup, analysis.get _, maxErrors, compiler, javac)
val config = new CompileConfiguration(sources, classpath, previousAnalysis, previousSetup, setup, analysis.get _, maxErrors, compiler, javac)
val (modified, result) = compile2(config)
if(modified)
store.set(result, setup)
@ -68,7 +68,7 @@ class AggressiveCompile(cacheDirectory: File)
import Path._
val loader = ClasspathUtilities.toLoader(absClasspath, compiler.scalaInstance.loader)
def readAPI(source: File, classes: Seq[Class[_]]) { callback.api(source, ClassToAPI(classes)) }
Analyze(outputDirectory, javaSrcs, javaSrcBases, log)(callback, loader, readAPI) {
Analyze(outputDirectory, javaSrcs, log)(callback, loader, readAPI) {
javac(javaSrcs, absClasspath, outputDirectory, options.javacOptions)
}
}

View File

@ -13,9 +13,9 @@ import java.lang.reflect.Modifier.{STATIC, PUBLIC, ABSTRACT}
private[sbt] object Analyze
{
def apply[T](outputDirectory: Path, sources: Seq[Path], roots: Seq[Path], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)(compile: => Unit)
def apply[T](outputDirectory: Path, sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File,Seq[Class[_]]) => Unit)(compile: => Unit)
{
val sourceSet = Set(sources.toSeq : _*)
val sourceMap = sources.groupBy(_.getName)
val classesFinder = outputDirectory ** GlobFilter("*.class")
val existingClasses = classesFinder.get
@ -38,7 +38,7 @@ private[sbt] object Analyze
path <- Path.relativize(outputDirectory, newClass);
classFile = Parser(newClass.asFile);
sourceFile <- classFile.sourceFile orElse guessSourceName(newClass.asFile.getName);
source <- guessSourcePath(sourceSet, roots, classFile, log))
source <- guessSourcePath(sourceMap, classFile, log))
{
analysis.beginSource(source)
analysis.generatedClass(source, path)
@ -102,23 +102,54 @@ private[sbt] object Analyze
private final val ClassExt = ".class"
private def trimClassExt(name: String) = if(name.endsWith(ClassExt)) name.substring(0, name.length - ClassExt.length) else name
private def resolveClassFile(file: File, className: String): File = (file /: (className.replace('.','/') + ClassExt).split("/"))(new File(_, _))
private def guessSourcePath(sources: scala.collection.Set[Path], roots: Iterable[Path], classFile: ClassFile, log: Logger) =
private def guessSourcePath(sourceNameMap: Map[String, Iterable[File]], classFile: ClassFile, log: Logger) =
{
val classNameParts = classFile.className.split("""\.""")
val lastIndex = classNameParts.length - 1
val pkg = classNameParts.take(lastIndex)
val simpleClassName = classNameParts(lastIndex)
val pkg = classNameParts.init
val simpleClassName = classNameParts.last
val sourceFileName = classFile.sourceFile.getOrElse(simpleClassName.takeWhile(_ != '$').mkString("", "", ".java"))
val relativeSourceFile = (pkg ++ (sourceFileName :: Nil)).mkString("/")
val candidates = roots.map(root => Path.fromString(root, relativeSourceFile)).filter(sources.contains).toList
val candidates = findSource(sourceNameMap, pkg.toList, sourceFileName)
candidates match
{
case Nil => log.warn("Could not determine source for class " + classFile.className)
case head :: Nil => ()
case _ =>log.warn("Multiple sources matched for class " + classFile.className + ": " + candidates.mkString(", "))
case _ => log.warn("Multiple sources matched for class " + classFile.className + ": " + candidates.mkString(", "))
}
candidates
}
private def findSource(sourceNameMap: Map[String, Iterable[File]], pkg: List[String], sourceFileName: String): List[File] =
refine( (sourceNameMap get sourceFileName).toList.flatten map { x => (x,x) }, pkg.reverse)
private def refine(sources: List[(File, File)], pkgRev: List[String]): List[File] =
{
def make = sources.map(_._1)
if(sources.isEmpty || sources.tail.isEmpty)
make
else
pkgRev match
{
case Nil => shortest(make)
case x :: xs =>
val retain = sources flatMap { case (src, pre) =>
if(pre != null && pre.getName == x)
(src, pre.getParentFile) :: Nil
else
Nil
}
refine(retain, xs)
}
}
private def shortest(files: List[File]): List[File] =
if(files.isEmpty) files
else
{
val fs = files.groupBy(distanceToRoot(0))
fs(fs.keys.min)
}
private def distanceToRoot(acc: Int)(file: File): Int =
if(file == null) acc else distanceToRoot(acc + 1)(file.getParentFile)
private def isTopLevel(classFile: ClassFile) = classFile.className.indexOf('$') < 0
private lazy val unit = classOf[Unit]
private lazy val strArray = List(classOf[Array[String]])