Further refinements to Scala version handling

- override location of resolved Scala jars when scalaInstance is unmanaged
- document current behavior: scalaHome, update, scalaInstance, autoScalaLibrary, managedScalaInstance
This commit is contained in:
Mark Harrah 2012-12-16 18:56:22 -05:00
parent ae211ee33e
commit d156ccfe4e
10 changed files with 243 additions and 12 deletions

View File

@ -271,9 +271,12 @@ object Defaults extends BuildCommon
}
}
}
@deprecated("Use scalaInstanceTask.", "0.13.0")
def scalaInstanceSetting = scalaInstanceTask
def scalaInstanceTask: Initialize[Task[ScalaInstance]] = Def.taskDyn {
// if this logic changes, ensure that `unmanagedScalaInstanceOnly` and `update` are changed
// appropriately to avoid cycles
scalaHome.value match {
case Some(h) => scalaInstanceFromHome(h)
case None =>
@ -285,6 +288,12 @@ object Defaults extends BuildCommon
scalaInstanceFromUpdate
}
}
// Returns the ScalaInstance only if it was not constructed via `update`
// This is necessary to prevent cycles between `update` and `scalaInstance`
private[sbt] def unmanagedScalaInstanceOnly: Initialize[Task[Option[ScalaInstance]]] = Def.taskDyn {
if(scalaHome.value.isDefined) Def.task(Some(scalaInstance.value)) else Def.task(None)
}
private[this] def noToolConfiguration(autoInstance: Boolean): String =
{
val pre = "Missing Scala tool configuration from the 'update' report. "
@ -902,13 +911,8 @@ object Classpaths
ivySbt <<= ivySbt0,
ivyModule := { val is = ivySbt.value; new is.Module(moduleSettings.value) },
transitiveUpdate <<= transitiveUpdateTask,
update <<= (ivyModule, thisProjectRef, updateConfiguration, cacheDirectory, transitiveUpdate, executionRoots, resolvedScoped, skip in update, streams) map {
(module, ref, config, cacheDirectory, reports, roots, resolved, skip, s) =>
val depsUpdated = reports.exists(!_.stats.cached)
val isRoot = roots contains resolved
cachedUpdate(cacheDirectory / "update", Reference.display(ref), module, config, None, skip = skip, force = isRoot, depsUpdated = depsUpdated, log = s.log)
} tag(Tags.Update, Tags.Network),
update <<= (conflictWarning, update, streams) map { (config, report, s) => ConflictWarning(config, report, s.log); report },
update <<= updateTask tag(Tags.Update, Tags.Network),
update := { val report = update.value; ConflictWarning(conflictWarning.value, report, streams.value.log); report },
transitiveClassifiers in GlobalScope :== Seq(SourceClassifier, DocClassifier),
classifiersModule in updateClassifiers := GetClassifiersModule(projectID.value, update.value.allModules, ivyConfigurations.in(updateClassifiers).value, transitiveClassifiers.in(updateClassifiers).value),
updateClassifiers <<= (ivySbt, classifiersModule in updateClassifiers, updateConfiguration, ivyScala, target in LocalRootProject, appConfiguration, streams) map { (is, mod, c, ivyScala, out, app, s) =>
@ -989,7 +993,17 @@ object Classpaths
}})
}
def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, scalaInstance: Option[ScalaInstance], skip: Boolean, force: Boolean, depsUpdated: Boolean, log: Logger): UpdateReport =
def updateTask: Initialize[Task[UpdateReport]] = Def.task {
val depsUpdated = transitiveUpdate.value.exists(!_.stats.cached)
val isRoot = executionRoots.value contains resolvedScoped.value
val log = streams.value.log
val si = Defaults.unmanagedScalaInstanceOnly.value.map(si => (si, scalaOrganization.value))
val show = Reference.display(thisProjectRef.value)
val cache = cacheDirectory.value / "update"
cachedUpdate(cache, show, ivyModule.value, updateConfiguration.value, si, skip = (skip in update).value, force = isRoot, depsUpdated = depsUpdated, log = log)
}
def cachedUpdate(cacheFile: File, label: String, module: IvySbt#Module, config: UpdateConfiguration, scalaInstance: Option[(ScalaInstance, String)], skip: Boolean, force: Boolean, depsUpdated: Boolean, log: Logger): UpdateReport =
{
implicit val updateCache = updateIC
type In = IvyConfiguration :+: ModuleSettings :+: UpdateConfiguration :+: HNil
@ -997,7 +1011,7 @@ object Classpaths
log.info("Updating " + label + "...")
val r = IvyActions.update(module, config, log)
log.info("Done updating.")
scalaInstance match { case Some(si) => substituteScalaFiles(si, r); case None => r }
scalaInstance match { case Some((si,scalaOrg)) => substituteScalaFiles(si, scalaOrg, r); case None => r }
}
def uptodate(inChanged: Boolean, out: UpdateReport): Boolean =
!force &&
@ -1249,15 +1263,24 @@ object Classpaths
)
@deprecated("Doesn't properly handle non-standard Scala organizations.", "0.13.0")
def substituteScalaFiles(scalaInstance: ScalaInstance, report: UpdateReport): UpdateReport =
substituteScalaFiles(scalaInstance, ScalaArtifacts.Organization, report)
def substituteScalaFiles(scalaInstance: ScalaInstance, ScalaOrg: String, report: UpdateReport): UpdateReport =
{
val scalaJars = scalaInstance.jars
report.substitute { (configuration, module, arts) =>
import ScalaArtifacts._
(module.organization, module.name) match
{
case (Organization, LibraryID) => (Artifact(LibraryID), scalaInstance.libraryJar) :: Nil
case (Organization, CompilerID) => (Artifact(CompilerID), scalaInstance.compilerJar) :: Nil
case (ScalaOrg, LibraryID) => (Artifact(LibraryID), scalaInstance.libraryJar) :: Nil
case (ScalaOrg, CompilerID) => (Artifact(CompilerID), scalaInstance.compilerJar) :: Nil
case (ScalaOrg, id) =>
val jarName = id + ".jar"
val replaceWith = scalaJars.filter(_.getName == jarName).map(f => (Artifact(f.getName), f))
if(replaceWith.isEmpty) arts else replaceWith
case _ => arts
}
}
}
// try/catch for supporting earlier launchers
def bootIvyHome(app: xsbti.AppConfiguration): Option[File] =

View File

@ -0,0 +1,7 @@
val makeHome = taskKey[Unit]("Populates the 'home/lib' directory with Scala jars from the default ScalaInstance")
makeHome := {
val lib = baseDirectory.value / "home" / "lib"
for(jar <- scalaInstance.value.jars)
IO.copyFile(jar, lib / jar.getName)
}

View File

@ -0,0 +1,10 @@
scalaHome := Some(baseDirectory.value / "home")
val checkUpdate = taskKey[Unit]("Ensures that resolved Scala artifacts are replaced with ones from the configured Scala home directory")
checkUpdate := {
val report = update.value
val lib = (scalaHome.value.get / "lib").getCanonicalFile
for(f <- report.allFiles)
assert(f.getParentFile == lib, "Artifact not in Scala home directory: " + f.getAbsolutePath)
}

View File

@ -0,0 +1,4 @@
> makeHome
$ copy-file changes/real-build.sbt build.sbt
> reload
> checkUpdate

View File

@ -0,0 +1,3 @@
autoScalaLibrary := false
libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value % "test"

View File

@ -0,0 +1 @@
class A

View File

@ -0,0 +1 @@
public class B {}

View File

@ -0,0 +1,9 @@
$ copy-file changes/B.java B.java
> compile
$ copy-file changes/A.scala A.scala
-> compile
$ delete A.scala
$ copy-file changes/A.scala src/test/scala/A.scala
> compile

View File

@ -0,0 +1,167 @@
=================
Configuring Scala
=================
sbt needs to obtain Scala for a project and it can do this automatically or you can configure it explicitly.
The Scala version that is configured for a project will compile, run, document, and provide a REPL for the project code.
When compiling a project, sbt needs to run the Scala compiler as well as provide the compiler with a classpath, which may include several Scala jars, like the reflection jar.
Automatically managed Scala
===========================
The most common case is when you want to use a version of Scala that is available in a repository.
The only required configuration is the Scala version you want to use.
For example,
::
scalaVersion := "2.10.0"
This will retrieve Scala from the repositories configured via the ``resolvers`` setting.
It will use this version for building your project: compiling, running, scaladoc, and the REPL.
Configuring the scala-library dependency
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, the standard Scala library is automatically added as a dependency.
If you want to configure it differently than the default or you have a project with only Java sources, set:
::
autoScalaLibrary := false
In order to compile Scala sources, the Scala library needs to be on the classpath.
When ``autoScalaLibrary`` is true, the Scala library will be on all classpaths: test, runtime, and compile.
Otherwise, you need to add it like any other dependency.
For example, the following dependency definition uses Scala only for tests:
::
autoScalaLibrary := false
libraryDependencies += "org.scala-lang" % "scala-library" % scalaVersion.value % "test"
Configuring additional Scala dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When using a Scala dependency other than the standard library, add it as a normal managed dependency.
For example, to depend on the Scala compiler,
::
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
Note that this is necessary regardless of the value of the ``autoScalaLibrary`` setting described in the previous section.
Configuring Scala tool dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In order to compile Scala code, run scaladoc, and provide a Scala REPL, sbt needs the ``scala-compiler`` jar.
This should not be a normal dependency of the project, so sbt adds a dependency on ``scala-compiler`` in the special, private ``scala-tool`` configuration.
It may be desirable to have more control over this in some situations.
Disable this automatic behavior with the ``autoScalaInstance`` key:
::
managedScalaInstance := false
This will also disable the automatic dependency on ``scala-library``.
If you do not need the Scala compiler for anything (compiling, the REPL, scaladoc, etc...), you can stop here.
sbt does not need an instance of Scala for your project in that case.
Otherwise, sbt will still need access to the jars for the Scala compiler for compilation and other tasks.
You can provide them by either declaring a dependency in the ``scala-tool`` configuration or by explicitly defining ``scalaInstance``.
In the first case, add the ``scala-tool`` configuration and add a dependency on ``scala-compiler`` in this configuration.
The organization is not important, but sbt needs the module name to be ``scala-compiler`` and ``scala-library`` in order to handle those jars appropriately.
For example,
::
managedScalaInstance := false
// Add the configuration for the dependencies on Scala tool jars
// You can also use a manually constructed configuration like:
// config("scala-tool").hide
ivyConfigurations += Configurations.ScalaTool
// Add the usual dependency on the library as well on the compiler in the
// 'scala-tool' configuration
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-library" % scalaVersion.value,
"org.scala-lang" % "scala-compiler" % scalaVersion.value % "scala-tool"
)
In the second case, directly construct a value of type `ScalaInstance <../../api/sbt/ScalaInstance.html>`_, typically using a method in the `companion object <../../api/sbt/ScalaInstance$.html>`_, and assign it to ``scalaInstance``.
You will also need to add the ``scala-library`` jar to the classpath to compile and run Scala sources.
For example,
::
managedScalaInstance := false
scalaInstance := ...
unmanagedJars in Compile += scalaInstance.value.libraryJar
Switching to a local Scala version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To use a locally built Scala version, configure Scala home as described in the following section.
Scala will still be resolved as before, but the jars will come from the configured Scala home directory.
Using Scala from a local directory
==================================
The result of building Scala from source is a Scala home directory ``<base>/build/pack/`` that contains a subdirectory ``lib/`` containing the Scala library, compiler, and other jars.
The same directory layout is obtained by downloading and extracting a Scala distribution.
Such a Scala home directory may be used as the source for jars by setting ``scalaHome``.
For example,
::
scalaHome := Some(file("/home/user/scala-2.10/"))
By default, ``lib/scala-library.jar`` will be added to the unmanaged classpath and ``lib/scala-compiler.jar`` will be used to compile Scala sources and provide a Scala REPL.
No managed dependency is recorded on ``scala-library``.
This means that Scala will only be resolved from a repository if you explicitly define a dependency on Scala or if Scala is depended on indirectly via a dependency.
In these cases, the artifacts for the resolved dependencies will be substituted with jars in the Scala home ``lib/`` directory.
Mixing with managed dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As an example, consider adding a dependency on ``scala-reflect`` when ``scalaHome`` is configured:
::
scalaHome := Some(file("/home/user/scala-2.10/"))
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
This will be resolved as normal, except that sbt will see if ``/home/user/scala-2.10/lib/scala-reflect.jar`` exists.
If it does, that file will be used in place of the artifact from the managed dependency.
Using unmanaged dependencies only
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instead of adding managed dependencies on Scala jars, you can directly add them.
The ``scalaInstance`` task provides structured access to the Scala distribution.
For example, to add all jars in the Scala home ``lib/`` directory,
::
scalaHome := Some(file("/home/user/scala-2.10/"))
unmanagedJars in Compile ++= scalaInstance.value.jars
To add only some jars, filter the jars from ``scalaInstance`` before adding them.
sbt's Scala version
===================
sbt needs Scala jars to run itself since it is written in Scala.
sbt uses that same version of Scala to compile the build definitions that you write for your project because they use sbt APIs.
This version of Scala is fixed for a specific sbt release and cannot be changed.
For sbt |version|, this version is Scala |scalaVersion|.
Because this Scala version is needed before sbt runs, the repositories used to retrieve this version are configured in the sbt :doc:`launcher </Detailed-Topics/Launcher>`.

View File

@ -10,6 +10,8 @@ extensions = ['sphinxcontrib.issuetracker', 'sphinx.ext.extlinks', 'howto']
project = 'sbt'
version = '0.13'
release = '0.13.0-SNAPSHOT'
scalaVersion = "2.10"
scalaRelease = "2.10.0"
# General settings
@ -72,6 +74,8 @@ sbt_native_package_base = 'http://scalasbt.artifactoryonline.com/scalasbt/sbt-na
rst_epilog = """
.. |scalaVersion| replace:: %(scalaVersion)s
.. |scalaRelease| replace:: %(scalaRelease)s
.. _typesafe-snapshots: %(typesafe_ivy_snapshots)s
.. |typesafe-snapshots| replace:: Typesafe Snapshots
.. _sbt-launch.jar: %(launcher_release_base)s/%(version)s/sbt-launch.jar
@ -88,6 +92,8 @@ rst_epilog = """
'launcher_snapshots_base': launcher_snapshots_base,
'version': release,
'typesafe_ivy_snapshots': typesafe_ivy_snapshots,
'sbt_native_package_base': sbt_native_package_base
'sbt_native_package_base': sbt_native_package_base,
'scalaRelease': scalaRelease,
'scalaVersion': scalaVersion
}