Use xsbt for launch, compile, run, console, and doc.

Build Scala version can now be different from the version used by sbt and the project definition.
Remove sbt compiler plugin, which is now provided by xsbt
This commit is contained in:
Mark Harrah 2009-10-09 19:31:55 -04:00
parent 776efa9100
commit 9bb813a2fc
36 changed files with 619 additions and 2815 deletions

View File

@ -1,25 +0,0 @@
Copyright (c) 2009 Mark Harrah
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,35 +0,0 @@
Simple Build Tool (sbt)
Copyright 2009 Mark Harrah
Parts of the Scala library are distributed with sbt:
Copyright 2002-2008 EPFL, Lausanne
Licensed under BSD-style license (see licenses/LICENSE_Scala)
JLine is distributed with sbt:
Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu>
Licensed under BSD-style license (see licenses/LICENSE_JLine)
Apache Ivy, licensed under the Apache License, Version 2.0
(see licenses/LICENSE_Apache) is distributed with sbt and
requires the following notice:
This product includes software developed by
The Apache Software Foundation (http://www.apache.org/).
Portions of Ivy were originally developed by
Jayasoft SARL (http://www.jayasoft.fr/)
and are licensed to the Apache Software Foundation under the
"Software Grant License Agreement"
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Binary file not shown.

View File

@ -1,258 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
------------------------------------------------------------------------------
License for JCraft JSch package
------------------------------------------------------------------------------
Copyright (c) 2002,2003,2004,2005,2006,2007 Atsuhiko Yamanaka, JCraft,Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
3. The names of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------------------------------------------------------------------------
License for jQuery
------------------------------------------------------------------------------
Copyright (c) 2007 John Resig, http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,33 +0,0 @@
Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu>
All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with
the distribution.
Neither the name of JLine nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,35 +0,0 @@
SCALA LICENSE
Copyright (c) 2002-2008 EPFL, Lausanne, unless otherwise specified.
All rights reserved.
This software was developed by the Programming Methods Laboratory of the
Swiss Federal Institute of Technology (EPFL), Lausanne, Switzerland.
Permission to use, copy, modify, and distribute this software in source
or binary form for any purpose with or without fee is hereby granted,
provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the EPFL nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -1,350 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.boot
// This is the main class for the sbt launcher. Its purpose is to ensure the appropriate
// versions of sbt and scala are downloaded to the projects 'project/boot' directory.
// Then, the downloaded version of sbt is started as usual using the right version of
// scala.
// Artifact names must be consistent between the main sbt build and this build.
import java.io.{File, FileFilter}
import java.net.{MalformedURLException, URL, URLClassLoader}
// contains constants and paths
import BootConfiguration._
import UpdateTarget.{UpdateScala, UpdateSbt}
// The exception to use when an error occurs at the launcher level (and not a nested exception).
// This indicates overrides toString because the exception class name is not needed to understand
// the error message.
private class BootException(override val toString: String) extends RuntimeException
// The entry point to the launcher
object Boot
{
def main(args: Array[String])
{
System.setProperty("sbt.boot", true.toString)
val found = Paths.projectSearch().getCanonicalPath
System.setProperty("user.dir", found)
checkProxy()
try { boot(args) }
catch
{
case b: BootException => errorAndExit(b)
case e =>
e.printStackTrace
errorAndExit(e)
}
System.exit(0)
}
private def errorAndExit(e: Throwable)
{
System.out.println("Error during sbt execution: " + e.toString)
System.exit(1)
}
def boot(args: Array[String])
{
// prompt to create project if it doesn't exist.
// will not return if user declines
(new Paths).checkProject()
val loaderCache = new LoaderCache
if(args.length == 0)
load(args, loaderCache) // interactive mode, which can only use one version of scala for a run
else
runBatch(args.toList, Nil, loaderCache) // batch mode, which can reboot with a different scala version
}
private def runBatch(args: List[String], accumulateReversed: List[String], loaderCache: LoaderCache)
{
def doLoad() = if(!accumulateReversed.isEmpty) load(accumulateReversed.reverse.toArray, loaderCache)
args match
{
case Nil => doLoad()
case RebootCommand :: tail =>
doLoad()
runBatch(tail, Nil, loaderCache)
case action :: tail if action.trim.startsWith(CrossBuildPrefix) =>
doLoad()
load(Array(action), loaderCache) // call main with the single cross-build argument, preserving the '+' prefix, with which it knows what to do
runBatch(tail, Nil, loaderCache)
case notReload :: tail => runBatch(tail, notReload :: accumulateReversed, loaderCache)
}
}
/** Loads the project in the current working directory using the version of scala and sbt
* declared in the build. The class loader used prevents the Scala and Ivy classes used by
* this loader from being seen by the loaded sbt/project.*/
private def load(args: Array[String], loaderCache: LoaderCache)
{
val loader = (new Setup(loaderCache)).loader()
val sbtMain = Class.forName(SbtMainClass, true, loader)
val exitCode = run(sbtMain, args)
if(exitCode == NormalExitCode)
()
else if(exitCode == RebootExitCode)
load(args, loaderCache)
else
System.exit(exitCode)
}
private def run(sbtMain: Class[_], args: Array[String]): Int =
{
try {
// Versions newer than 0.3.8 enter through the run method, which does not call System.exit
val runMethod = sbtMain.getMethod(MainMethodName, classOf[Array[String]])
runMethod.invoke(null, Array(args) : _*).asInstanceOf[Int]
} catch {
case e: NoSuchMethodException => runOld(sbtMain, args)
}
}
/** The entry point for version 0.3.8 was the main method. */
private def runOld(sbtMain: Class[_], args: Array[String]): Int =
{
val runMethod = sbtMain.getMethod(OldMainMethodName, classOf[Array[String]])
runMethod.invoke(null, Array(args) : _*)
NormalExitCode
}
private def checkProxy()
{
import ProxyProperties._
val httpProxy = System.getenv(HttpProxyEnv)
if(isDefined(httpProxy) && !isPropertyDefined(ProxyHost) && !isPropertyDefined(ProxyPort))
{
try
{
val proxy = new URL(httpProxy)
setProperty(ProxyHost, proxy.getHost)
val port = proxy.getPort
if(port >= 0)
System.setProperty(ProxyPort, port.toString)
copyEnv(HttpProxyUser, ProxyUser)
copyEnv(HttpProxyPassword, ProxyPassword)
}
catch
{
case e: MalformedURLException =>
System.out.println("Warning: could not parse http_proxy setting: " + e.toString)
}
}
}
private def copyEnv(envKey: String, sysKey: String) { setProperty(sysKey, System.getenv(envKey)) }
private def setProperty(key: String, value: String) { if(value != null) System.setProperty(key, value) }
private def isPropertyDefined(k: String) = isDefined(System.getProperty(k))
private def isDefined(s: String) = s != null && !s.isEmpty
}
object Paths
{
val Only = "only"
val RootFirst = "root-first"
val Nearest = "nearest"
val NoSearch = "none"
val SearchPropertyName = "sbt.boot.search"
def projectSearch() =
{
val current = (new File(".")) getCanonicalFile
lazy val fromRoot = path(current, Nil).filter(hasProject).map(_.getCanonicalFile)
val found: Option[File] =
(System.getProperty(SearchPropertyName, NoSearch).trim.toLowerCase match
{
case RootFirst => fromRoot.headOption
case Nearest => fromRoot.reverse.headOption
case Only =>
if(hasProject(current))
Some(current)
else
fromRoot match
{
case Nil => Some(current)
case head :: Nil => Some(head)
case xs =>
System.err.println("Search method is 'only' and multiple ancestor directories contain project/build.properties:\n\t" + fromRoot.mkString("\n\t"))
System.exit(1)
None
}
case _ => Some(current)
})
found.getOrElse(current)
}
private def hasProject(f: File) = new Paths(f) hasPropertiesFile
private def path(f: File, acc: List[File]): List[File] = if(f eq null) acc else path(f.getParentFile, f :: acc)
}
private class Paths(baseDirectory: File) extends NotNull
{
def this() = this(new File(".") getCanonicalFile)
protected final val ProjectDirectory = new File(baseDirectory, ProjectDirectoryName)
protected final val BootDirectory = new File(ProjectDirectory, BootDirectoryName)
protected final val PropertiesFile = new File(ProjectDirectory, BuildPropertiesName)
final def hasPropertiesFile = PropertiesFile.exists
final def checkProject()
{
if(!ProjectDirectory.exists)
{
val line = SimpleReader.readLine("Project does not exist, create new project? (y/N/s) : ")
if(Setup.isYes(line))
ProjectProperties(PropertiesFile, true)
else if(Setup.isScratch(line))
ProjectProperties.scratch(PropertiesFile)
else
System.exit(1)
}
}
}
/** A class to handle setting up the properties and classpath of the project
* before it is loaded. */
private class Setup(loaderCache: LoaderCache) extends Paths
{
/** Checks that the requested version of sbt and scala have been downloaded.
* It performs a simple check that the appropriate directories exist. It uses Ivy
* to resolve and retrieve any necessary libraries. The classpath to use is returned.*/
final def loader(): ClassLoader = loader(Nil)
private final def loader(forcePrompt: Seq[String]): ClassLoader =
{
val (normalScalaVersion, sbtVersion) = ProjectProperties.forcePrompt(PropertiesFile, forcePrompt : _*)
val scalaVersion = crossScalaVersion(normalScalaVersion)
loaderCache( scalaVersion, sbtVersion ) match
{
case Some(existingLoader) =>
{
setScalaVersion(scalaVersion)
existingLoader
}
case None =>
{
getLoader(scalaVersion, sbtVersion) match
{
case Left(retry) => loader(retry)
case Right(classLoader) => classLoader
}
}
}
}
private def crossScalaVersion(simpleScalaVersion: String): String =
{
val crossScalaVersion = System.getProperty(SbtScalaVersionKey)
if(crossScalaVersion == null || crossScalaVersion.isEmpty)
simpleScalaVersion
else
crossScalaVersion
}
private def getLoader(scalaVersion: String, sbtVersion: String): Either[Seq[String], ClassLoader] =
{
import Setup.{failIfMissing,isYes,needsUpdate}
import ProjectProperties.{ScalaVersionKey, SbtVersionKey}
val baseDirectory = new File(BootDirectory, baseDirectoryName(scalaVersion))
System.setProperty(ScalaHomeProperty, baseDirectory.getAbsolutePath)
val scalaDirectory = new File(baseDirectory, ScalaDirectoryName)
val sbtDirectory = new File(baseDirectory, sbtDirectoryName(sbtVersion))
val classLoader = createLoader(scalaDirectory, sbtDirectory)
val updateTargets = needsUpdate("", classLoader, TestLoadScalaClasses, UpdateScala) ::: needsUpdate(sbtVersion, classLoader, TestLoadSbtClasses, UpdateSbt)
if(updateTargets.isEmpty) // avoid loading Ivy related classes if there is nothing to update
success(classLoader, scalaVersion, sbtVersion)
else
{
Update(baseDirectory, sbtVersion, scalaVersion, updateTargets: _*)
val classLoader = createLoader(scalaDirectory, sbtDirectory)
val sbtFailed = failIfMissing(classLoader, TestLoadSbtClasses, "sbt " + sbtVersion, SbtVersionKey)
val scalaFailed = failIfMissing(classLoader, TestLoadScalaClasses, "Scala " + scalaVersion, ScalaVersionKey)
(scalaFailed +++ sbtFailed) match
{
case Success => success(classLoader, scalaVersion, sbtVersion)
case f: Failure =>
val noRetrieveMessage = "Could not retrieve " + f.label + "."
val getNewVersions = SimpleReader.readLine(noRetrieveMessage + " Select different version? (y/N) : ")
if(isYes(getNewVersions))
Left(f.keys)
else
throw new BootException(noRetrieveMessage)
}
}
}
private def success(classLoader: ClassLoader, scalaVersion: String, sbtVersion: String) =
{
setScalaVersion(scalaVersion)
loaderCache( scalaVersion, sbtVersion ) = classLoader
Right(classLoader)
}
private def createLoader(dirs: File*) =
{
val classpath = Setup.getJars(dirs : _*)
new URLClassLoader(classpath.toArray, new BootFilteredLoader)
}
private def setScalaVersion(scalaVersion: String) { System.setProperty(SbtScalaVersionKey, scalaVersion) }
}
private final class LoaderCache
{
private[this] var cachedSbtVersion: Option[String] = None
private[this] val loaderMap = new scala.collection.mutable.HashMap[String, ClassLoader]
def apply(scalaVersion: String, sbtVersion: String): Option[ClassLoader] =
{
cachedSbtVersion flatMap { currentSbtVersion =>
if(sbtVersion == currentSbtVersion)
loaderMap.get(scalaVersion)
else
None
}
}
def update(scalaVersion: String, sbtVersion: String, loader: ClassLoader)
{
for(currentSbtVersion <- cachedSbtVersion)
{
if(sbtVersion != currentSbtVersion)
loaderMap.clear()
}
cachedSbtVersion = Some(sbtVersion)
loaderMap(scalaVersion) = loader
}
}
private object Setup
{
private def failIfMissing(loader: ClassLoader, classes: Iterable[String], label: String, key: String) = checkTarget(loader, classes, Success, new Failure(label, List(key)))
private def needsUpdate(version: String, loader: ClassLoader, classes: Iterable[String], target: UpdateTarget.Value) =
if(version.endsWith("-SNAPSHOT"))
target :: Nil
else
checkTarget(loader, classes, Nil, target :: Nil)
private def checkTarget[T](loader: ClassLoader, classes: Iterable[String], ifSuccess: => T, ifFailure: => T): T =
{
try
{
for(c <- classes)
Class.forName(c, false, loader)
ifSuccess
}
catch { case e: ClassNotFoundException => ifFailure }
}
def isYes(so: Option[String]) = isValue("y", "yes")(so)
def isScratch(so: Option[String]) = isValue("s", "scratch")(so)
def isValue(values: String*)(so: Option[String]) =
so match
{
case Some(s) => values.contains(s.toLowerCase)
case None => false
}
private def getJars(directories: File*) = directories.flatMap(file => wrapNull(file.listFiles(JarFilter))).map(_.toURI.toURL)
private def wrapNull(a: Array[File]): Array[File] = if(a == null) Array() else a
}
private object JarFilter extends FileFilter
{
def accept(file: File) = !file.isDirectory && file.getName.endsWith(".jar")
}
private sealed trait Checked extends NotNull { def +++(o: Checked): Checked }
private final object Success extends Checked { def +++(o: Checked) = o }
private final class Failure(val label: String, val keys: List[String]) extends Checked
{
def +++(o: Checked) =
o match
{
case Success => this
case f: Failure => new Failure(label + " and " + f.label, keys ::: f.keys)
}
}

View File

@ -1,96 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.boot
// project/boot/ [BootDirectoryName]
// scala-<version>/ [baseDirectoryName]
// lib/ [ScalaDirectoryName]
// sbt-<version>/ [sbtDirectoryName]
//
// see also ProjectProperties for the set of constants that apply to the build.properties file in a project
private object BootConfiguration
{
val SbtMainClass = "sbt.Main"
val MainMethodName = "run"
val OldMainMethodName = "main"
// these are the module identifiers to resolve/retrieve
val ScalaOrg = "org.scala-lang"
val SbtOrg = "sbt"
val CompilerModuleName = "scala-compiler"
val LibraryModuleName = "scala-library"
val SbtModuleName = "simple-build-tool"
/** The Ivy conflict manager to use for updating.*/
val ConflictManagerName = "strict"
/** The name of the local Ivy repository, which is used when compiling sbt from source.*/
val LocalIvyName = "local"
/** The pattern used for the local Ivy repository, which is used when compiling sbt from source.*/
val LocalPattern = "[organisation]/[module]/[revision]/[type]s/[artifact].[ext]"
/** The artifact pattern used for the local Ivy repository.*/
def LocalArtifactPattern = LocalPattern
/** The Ivy pattern used for the local Ivy repository.*/
def LocalIvyPattern = LocalPattern
/** The name of the property declaring the version of scala to use to build the project when not cross-building.*/
val ScalaVersion = "scala.version"
/** The name of the property declaring the version of sbt to use to build the project.*/
val SbtVersion = "sbt.version"
/** The name of the system property containing the version of scala actually used to build a project.
* This might be different from the ScalaVersion property when cross-building.*/
val SbtScalaVersionKey = "sbt.scala.version"
/** The class name prefix used to hide the Scala classes used by this loader from sbt
* and the project definition*/
val ScalaPackage = "scala."
/** The class name prefix used to hide the Ivy classes used by this loader from sbt
* and the project definition*/
val IvyPackage = "org.apache.ivy."
/** The loader will check that these classes can be loaded and will assume that their presence indicates
* sbt and its dependencies have been downloaded.*/
val TestLoadSbtClasses = "sbt.Main" :: "org.apache.ivy.Ivy" :: Nil
/** The loader will check that these classes can be loaded and will assume that their presence indicates
* the Scala compiler and library have been downloaded.*/
val TestLoadScalaClasses = "scala.ScalaObject" :: "scala.tools.nsc.GenericRunnerCommand" :: Nil
val ProjectDirectoryName = "project"
val BootDirectoryName = "boot"
val BuildPropertiesName ="build.properties"
val ScalaHomeProperty = "scala.home"
val UpdateLogName = "update.log"
val CrossBuildPrefix = "+"
val RebootCommand = "reboot"
val RebootExitCode = -1
val NormalExitCode = 0
val DefaultIvyConfiguration = "default"
/** The base URL to use to resolve sbt for download. */
val sbtRootBase = "http://simple-build-tool.googlecode.com/svn/artifacts/"
/** The name of the directory within the boot directory to retrieve scala to. */
val ScalaDirectoryName = "lib"
/** The Ivy pattern to use for retrieving the scala compiler and library. It is relative to the directory
* containing all jars for the requested version of scala. */
val scalaRetrievePattern = ScalaDirectoryName + "/[artifact].[ext]"
/** The Ivy pattern to use for retrieving sbt and its dependencies. It is relative to the directory
* containing all jars for the requested version of scala. */
def sbtRetrievePattern(sbtVersion: String) = sbtDirectoryName(sbtVersion) + "/[artifact]-[revision].[ext]"
/** The Ivy pattern to use for resolving sbt and its dependencies from the Google code project.*/
def sbtResolverPattern(scalaVersion: String) = sbtRootBase + "[revision]/[type]s/[artifact].[ext]"
/** The name of the directory to retrieve sbt and its dependencies to.*/
def sbtDirectoryName(sbtVersion: String) = SbtOrg + "-" + sbtVersion
/** The name of the directory in the boot directory to put all jars for the given version of scala in.*/
def baseDirectoryName(scalaVersion: String) = "scala-" + scalaVersion
}
private object ProxyProperties
{
val HttpProxyEnv = "http_proxy"
val HttpProxyUser = "http_proxy_user"
val HttpProxyPassword = "http_proxy_pass"
val ProxyHost = "http.proxyHost"
val ProxyPort = "http.proxyPort"
val ProxyUser = "http.proxyUser"
val ProxyPassword = "http.proxyPassword"
}

View File

@ -1,20 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.boot
import BootConfiguration._
/** A custom class loader to ensure the main part of sbt doesn't load any Scala or
* Ivy classes from the jar containing the loader. */
private[boot] final class BootFilteredLoader extends ClassLoader with NotNull
{
@throws(classOf[ClassNotFoundException])
override final def loadClass(className: String, resolve: Boolean): Class[_] =
{
if(className.startsWith(ScalaPackage) || className.startsWith(IvyPackage))
throw new ClassNotFoundException(className)
else
super.loadClass(className, resolve)
}
}

View File

@ -1,147 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.boot
/*
Project does not exist, create new project? [y/N/s] y
Name:
Organization []:
Version [1.0]:
Scala version [2.7.5]:
sbt version [0.5.4]:
*/
import java.io.File
/** Constants related to reading/writing the build.properties file in a project.
* See BootConfiguration for general constants used by the loader. */
private object ProjectProperties
{
/** The properties key for storing the name of the project.*/
val NameKey = "project.name"
/** The properties key for storing the organization of the project.*/
val OrganizationKey = "project.organization"
/** The properties key for storing the version of the project.*/
val VersionKey = "project.version"
/** The properties key for storing the version of Scala used with the project.*/
val ScalaVersionKey = "scala.version"
/** The properties key for storing the version of sbt used to build the project.*/
val SbtVersionKey = "sbt.version"
/** The properties key to communicate to the main component of sbt that the project
* should be initialized after being loaded, typically by creating a default directory structure.*/
val InitializeProjectKey = "project.initialize"
/** The properties key that configures the project to be flattened a bit for use by quick throwaway projects.*/
val ScratchKey = "project.scratch"
/** The label used when prompting for the name of the user's project.*/
val NameLabel = "Name"
/** The label used when prompting for the organization of the user's project.*/
val OrganizationLabel = "Organization"
/** The label used when prompting for the version of the user's project.*/
val VersionLabel = "Version"
/** The label used when prompting for the version of Scala to use for the user's project.*/
val ScalaVersionLabel = "Scala version"
/** The label used when prompting for the version of sbt to use for the user's project.*/
val SbtVersionLabel = "sbt version"
/** The default organization of the new user project when the user doesn't explicitly specify one when prompted.*/
val DefaultOrganization = ""
/** The default version of the new user project when the user doesn't explicitly specify a version when prompted.*/
val DefaultVersion = "1.0"
/** The default version of sbt when the user doesn't explicitly specify a version when prompted.*/
val DefaultSbtVersion = DefaultVersions.Sbt //"0.5.4"
/** The default version of Scala when the user doesn't explicitly specify a version when prompted.*/
val DefaultScalaVersion = DefaultVersions.Scala//"2.7.5"
// sets up the project properties for a throwaway project (flattens src and lib to the root project directory)
def scratch(file: File)
{
withProperties(file) { properties =>
for( (key, _, default, _) <- propertyDefinitions(false))
properties(key) = default.getOrElse("scratch")
properties(ScratchKey) = true.toString
}
}
// returns (scala version, sbt version)
def apply(file: File, setInitializeProject: Boolean): (String, String) = applyImpl(file, setInitializeProject, Nil)
def forcePrompt(file: File, propertyKeys: String*) = applyImpl(file, false, propertyKeys)
private def applyImpl(file: File, setInitializeProject: Boolean, propertyKeys: Iterable[String]): (String, String) =
{
val organizationOptional = file.exists
withProperties(file) { properties =>
properties -= propertyKeys
prompt(properties, organizationOptional)
if(setInitializeProject)
properties(InitializeProjectKey) = true.toString
}
}
// (key, label, defaultValue, promptRequired)
private def propertyDefinitions(organizationOptional: Boolean) =
(NameKey, NameLabel, None, true) ::
(OrganizationKey, OrganizationLabel, Some(DefaultOrganization), !organizationOptional) ::
(VersionKey, VersionLabel, Some(DefaultVersion), true) ::
(ScalaVersionKey, ScalaVersionLabel, Some(DefaultScalaVersion), true) ::
(SbtVersionKey, SbtVersionLabel, Some(DefaultSbtVersion), true) ::
Nil
private def prompt(fill: ProjectProperties, organizationOptional: Boolean)
{
for( (key, label, default, promptRequired) <- propertyDefinitions(organizationOptional))
{
val value = fill(key)
if(value == null && promptRequired)
fill(key) = readLine(label, default)
}
}
private def withProperties(file: File)(f: ProjectProperties => Unit) =
{
val properties = new ProjectProperties(file)
f(properties)
properties.save
(properties(ScalaVersionKey), properties(SbtVersionKey))
}
private def readLine(label: String, default: Option[String]): String =
{
val prompt =
default match
{
case Some(d) => "%s [%s]: ".format(label, d)
case None => "%s: ".format(label)
}
SimpleReader.readLine(prompt) orElse default match
{
case Some(line) => line
case None => throw new BootException("Project not loaded: " + label + " not specified.")
}
}
}
import java.io.{FileInputStream, FileOutputStream}
import java.util.Properties
private class ProjectProperties(file: File) extends NotNull
{
private[this] var modified = false
private[this] val properties = new Properties
if(file.exists)
{
val in = new FileInputStream(file)
try { properties.load(in) } finally { in.close() }
}
def update(key: String, value: String)
{
modified = true
properties.setProperty(key, value)
}
def apply(key: String) = properties.getProperty(key)
def save()
{
if(modified)
{
file.getParentFile.mkdirs()
val out = new FileOutputStream(file)
try { properties.store(out, "Project Properties") } finally { out.close() }
modified = false
}
}
def -= (keys: Iterable[String]) { for(key <- keys) properties.remove(key) }
}

View File

@ -1,26 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.boot
import jline.ConsoleReader
object SimpleReader extends NotNull
{
protected[this] val reader =
{
val cr = new ConsoleReader
cr.setBellEnabled(false)
cr
}
def readLine(prompt: String) =
reader.readLine(prompt) match
{
case null => None
case x =>
val trimmed = x.trim
if(trimmed.isEmpty)
None
else
Some(trimmed)
}
}

View File

@ -1,273 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt.boot
import java.io.{File, FileWriter, PrintWriter, Writer}
import org.apache.ivy.{core, plugins, util, Ivy}
import core.LogOptions
import core.cache.DefaultRepositoryCacheManager
import core.event.EventManager
import core.module.id.ModuleRevisionId
import core.module.descriptor.{Configuration, DefaultDependencyDescriptor, DefaultModuleDescriptor, ModuleDescriptor}
import core.report.ResolveReport
import core.resolve.{ResolveEngine, ResolveOptions}
import core.retrieve.{RetrieveEngine, RetrieveOptions}
import core.sort.SortEngine
import core.settings.IvySettings
import plugins.resolver.{ChainResolver, FileSystemResolver, IBiblioResolver, URLResolver}
import util.{DefaultMessageLogger, Message}
import BootConfiguration._
private[boot] object UpdateTarget extends Enumeration
{
val UpdateScala, UpdateSbt = Value
}
import UpdateTarget.{UpdateSbt, UpdateScala}
object Update
{
/** Use Ivy to resolve and retrieve the specified 'targets' for the given versions.*/
def apply(bootDirectory: File, sbtVersion: String, scalaVersion: String, targets: UpdateTarget.Value*) =
synchronized // synchronized because Ivy is not thread-safe
{
val up = new Update(bootDirectory, sbtVersion, scalaVersion, targets : _*)
up.update()
}
}
/** Ensures that the Scala and sbt jars exist for the given versions or else downloads them.*/
private final class Update(bootDirectory: File, sbtVersion: String, scalaVersion: String, targets: UpdateTarget.Value*)
{
private def logFile = new File(bootDirectory, UpdateLogName)
/** A Writer to use to write the full logging information to a file for debugging. **/
lazy val logWriter =
{
bootDirectory.mkdirs
new PrintWriter(new FileWriter(logFile))
}
/** The main entry point of this class for use by the Update module. It runs Ivy */
private def update()
{
Message.setDefaultLogger(new SbtIvyLogger(logWriter))
try { targets.foreach(update) } // runs update on each module separately
catch
{
case e: Exception =>
e.printStackTrace(logWriter)
log(e.toString)
println(" (see " + logFile + " for complete log)")
}
finally { logWriter.close() }
}
/** Runs update for the specified target (updates either the scala or sbt jars for building the project) */
private def update(target: UpdateTarget.Value)
{
val settings = new IvySettings
val ivy = Ivy.newInstance(settings)
ivy.pushContext()
try { update(target, settings) }
finally { ivy.popContext() }
}
private def update(target: UpdateTarget.Value, settings: IvySettings)
{
import Configuration.Visibility.PUBLIC
// the actual module id here is not that important
val moduleID = new DefaultModuleDescriptor(createID(SbtOrg, "boot", "1.0"), "release", null, false)
moduleID.setLastModified(System.currentTimeMillis)
moduleID.addConfiguration(new Configuration(DefaultIvyConfiguration, PUBLIC, "", Array(), true, null))
// add dependencies based on which target needs updating
target match
{
case UpdateScala =>
addDependency(moduleID, ScalaOrg, CompilerModuleName, scalaVersion, "default")
addDependency(moduleID, ScalaOrg, LibraryModuleName, scalaVersion, "default")
update(settings, moduleID, target, false)
case UpdateSbt =>
addDependency(moduleID, SbtOrg, SbtModuleName, sbtVersion, scalaVersion)
try { update(settings, moduleID, target, false) }
catch
{
// unfortunately, there is not a more specific exception thrown when a configuration does not exist,
// so we always retry after cleaning the ivy file for this version of sbt on in case it is a newer version
// of Scala than when this version of sbt was initially published
case e: RuntimeException =>
update(settings, moduleID, target, true)
}
}
}
/** Runs the resolve and retrieve for the given moduleID, which has had its dependencies added already. */
private def update(settings: IvySettings, moduleID: DefaultModuleDescriptor, target: UpdateTarget.Value, cleanExisting: Boolean)
{
val eventManager = new EventManager
addResolvers(settings, scalaVersion, target)
settings.setDefaultConflictManager(settings.getConflictManager(ConflictManagerName))
settings.setBaseDir(bootDirectory)
if(cleanExisting)
{
val sbtID = createID(SbtOrg, SbtModuleName, sbtVersion)
onDefaultRepositoryCacheManager(settings) { cache =>
val ivyFile = cache.getIvyFileInCache(sbtID)
ivyFile.delete()
val original = new File(ivyFile.getParentFile, ivyFile.getName + ".original")
original.delete()
}
}
resolve(settings, eventManager, moduleID)
retrieve(settings, 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)
{
val dep = new DefaultDependencyDescriptor(moduleID, createID(organization, name, revision), false, false, true)
dep.addDependencyConfiguration(DefaultIvyConfiguration, conf)
moduleID.addDependency(dep)
}
private def resolve(settings: IvySettings, 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)
val resolveEngine = new ResolveEngine(settings, eventManager, new SortEngine(settings))
val resolveReport = resolveEngine.resolve(module, resolveOptions)
if(resolveReport.hasError)
{
logExceptions(resolveReport)
println(Set(resolveReport.getAllProblemMessages.toArray: _*).mkString(System.getProperty("line.separator")))
throw new BootException("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(settings: IvySettings, eventManager: EventManager, module: ModuleDescriptor, target: UpdateTarget.Value)
{
val retrieveOptions = new RetrieveOptions
val retrieveEngine = new RetrieveEngine(settings, eventManager)
val pattern =
target match
{
// see BuildConfiguration
case UpdateSbt => sbtRetrievePattern(sbtVersion)
case UpdateScala => scalaRetrievePattern
}
retrieveEngine.retrieve(module.getModuleRevisionId, pattern, retrieveOptions);
}
/** Add the scala tools repositories and a URL resolver to download sbt from the Google code project.*/
private def addResolvers(settings: IvySettings, scalaVersion: String, target: UpdateTarget.Value)
{
val newDefault = new ChainResolver
newDefault.setName("redefined-public")
newDefault.add(localResolver(settings.getDefaultIvyUserDir.getAbsolutePath))
newDefault.add(mavenLocal)
newDefault.add(mavenMainResolver)
target match
{
case UpdateSbt =>
newDefault.add(sbtResolver(scalaVersion))
case UpdateScala =>
newDefault.add(mavenResolver("Scala-Tools Maven2 Repository", "http://scala-tools.org/repo-releases"))
newDefault.add(scalaSnapshots(scalaVersion))
}
onDefaultRepositoryCacheManager(settings)(_.setUseOrigin(true))
settings.addResolver(newDefault)
settings.setDefaultResolver(newDefault.getName)
}
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 sbtResolver(scalaVersion: String) =
{
val pattern = sbtResolverPattern(scalaVersion)
val resolver = new URLResolver
resolver.setName("Sbt Repository")
resolver.addIvyPattern(pattern)
resolver.addArtifactPattern(pattern)
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 val SnapshotPattern = """(\d+).(\d+).(\d+)-(\d{8})\.(\d{6})-(\d+|\+)""".r.pattern
private def scalaSnapshots(scalaVersion: String) =
{
val m = SnapshotPattern.matcher(scalaVersion)
if(m.matches)
{
val base = Seq(1,2,3).map(m.group).mkString(".")
val pattern = "http://scala-tools.org/repo-snapshots/[organization]/[module]/" + base + "-SNAPSHOT/[artifact]-[revision].[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")
}
private def localResolver(ivyUserDirectory: String) =
{
val localIvyRoot = ivyUserDirectory + "/local"
val artifactPattern = localIvyRoot + "/" + LocalArtifactPattern
val ivyPattern = localIvyRoot + "/" + LocalIvyPattern
val resolver = new FileSystemResolver
resolver.setName(LocalIvyName)
resolver.addIvyPattern(ivyPattern)
resolver.addArtifactPattern(artifactPattern)
resolver
}
/** 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) }
println(msg)
}
}
/** A custom logger for Ivy to ignore the messages about not finding classes
* intentionally filtered using proguard. */
private final class SbtIvyLogger(logWriter: PrintWriter) extends DefaultMessageLogger(Message.MSG_INFO) with NotNull
{
private val ignorePrefix = "impossible to define"
override def log(msg: String, level: Int)
{
logWriter.println(msg)
if(level <= getLevel && msg != null && !msg.startsWith(ignorePrefix))
System.out.println(msg)
}
override def rawlog(msg: String, level: Int) { log(msg, level) }
}

View File

@ -1,20 +0,0 @@
<!-- to be replace by inline ModuleConfigurations when 0.5.5 is released -->
<ivysettings>
<settings defaultResolver="sbt-chain"/>
<include url="${ivy.default.settings.dir}/ivysettings-public.xml"/>
<include url="${ivy.default.settings.dir}/ivysettings-local.xml"/>
<resolvers>
<ibiblio name="scala-tools" m2compatible="true" root="http://scala-tools.org/repo-releases/"/>
<url name="scala-snapshots" m2compatible="true" >
<artifact pattern="http://scala-tools.org/repo-snapshots/[organization]/[module]/2.8.0-SNAPSHOT/[artifact]-[revision].[ext]" />
</url>
<chain name="sbt-chain" returnFirst="true" checkmodified="true">
<resolver ref="local"/>
<resolver ref="public"/>
<resolver ref="scala-tools"/>
</chain>
</resolvers>
<modules>
<module organisation="org.scala-lang" revision="2.8.0-.*" resolver="scala-snapshots"/>
</modules>
</ivysettings>

View File

@ -1,7 +1,7 @@
#Project properties
#Tue Sep 29 20:46:06 EDT 2009
project.organization=sbt
#Thu Oct 01 17:51:28 EDT 2009
project.organization=org.scala-tools.sbt
project.name=Simple Build Tool
sbt.version=0.5.5
project.version=0.5.6-SNAPSHOT
scala.version=2.7.2
scala.version=2.7.5

View File

@ -1,217 +0,0 @@
import sbt._
import java.io.File
import scala.xml.NodeSeq
/** Support for compiling sbt across multiple versions of Scala. The scala compiler is run in a
* separate JVM and no partial compilation is done.*/
abstract class CrossCompileProject extends BasicScalaProject with MavenStyleScalaPaths
{
/* The base configuration names for the versions of Scala*/
private val version2_7_2 = "2.7.2"
private val version2_7_3 = "2.7.3"
private val version2_7_4 = "2.7.4"
private val version2_7_5 = "2.7.5"
private val version2_7_6 = "2.7.6"
private val version2_8_0 = "2.8.0-20090929.004247-+"
private val base = "base"
/* The configurations for the versions of Scala.*/
private val conf_2_7_2 = config(version2_7_2)
private val conf_2_7_3 = config(version2_7_3)
private val conf_2_7_4 = config(version2_7_4)
private val conf_2_7_5 = config(version2_7_5)
private val conf_2_7_6 = config(version2_7_6)
private val conf_2_8_0 = config(version2_8_0)
private val conf_base = config(base)
// the list of all configurations cross-compile supports
private val allConfigurations = conf_2_7_2 :: conf_2_7_3 :: conf_2_7_4 :: conf_2_7_5 :: conf_2_7_6 :: conf_2_8_0 :: Nil
// the list of configurations to actually build against
private val buildConfigurations = allConfigurations//conf_2_7_2 :: conf_2_8_0 :: Nil//conf_2_7_2 :: conf_2_7_3 :: conf_2_7_4 :: conf_2_7_5 :: Nil
// the configuration to use for normal development (when cross-building is not done)
private def developmentVersion = buildConfigurations.first
/* Methods to derive the configuration name from the base name 'v'.*/
private def optional(v: Configuration) = config("optional-" + v.toString)
private def scalac(v: Configuration) = config("scalac-" + v.toString)
private def sbt(v: Configuration) = config("sbt_" + v.toString)
private def depConf(v: Configuration) = v.toString + "->default"
// =========== Cross-compilation across scala versions ===========
// The dependencies that should go in each configuration are:
// base Required dependencies that are the same across all scala versions.
// <version> Required dependencies to use with Scala <version>
// optional-base Optional dependencies that are the same for all scala versions
// optional-<version> Optional dependencies to use with Scala <version>
// compile Used for normal development, it should extend a specific <version> and optional-<version>
// scalac-<version> The scala compiler for Scala <version>
// There should be a jar publication for each version of scala. The artifact should be named sbt_<version>.
override def ivyXML =
(<configurations>
<conf name={conf_base.toString}/>
<conf name={optional(conf_base).toString}/>
{ variableConfigurations }
<!-- The configuration used for normal development (actions other than cross-*) -->
<conf name="default" extends={developmentVersion + "," + optional(developmentVersion).toString} visibility="private"/>
</configurations>
<publications>
{ publications }
</publications>
<dependencies>
<!-- Dependencies that are the same across all Scala versions -->
<dependency org="org.apache.ivy" name="ivy" rev="2.0.0" transitive="false" conf={depConf(conf_base)}/>
<dependency org="com.jcraft" name="jsch" rev="0.1.31" transitive="false" conf={depConf(conf_base)}/>
<dependency org="org.mortbay.jetty" name="jetty" rev="6.1.14" transitive="true" conf={depConf(optional(conf_base))}/>
<!-- the dependencies that are different depending on the version of Scala -->
{ variableDependencies(conf_2_7_2, /*ScalaTest*/"0.9.3", /*Specs*/"1.4.0", false) }
{ variableDependencies(conf_2_7_3, /*ScalaTest*/"0.9.4", /*Specs*/"1.4.3", true) }
{ variableDependencies(conf_2_7_4, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true) }
{ variableDependencies(conf_2_7_5, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true) }
{ variableDependencies(conf_2_7_6, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true) }
{ variableDependencies(conf_2_8_0, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true) }
</dependencies>)
/** Creates a publication (an 'artifact' element) for each Scala version */
private def publications: NodeSeq =
{
for(conf <- buildConfigurations) yield
<artifact name={sbt(conf).toString} conf={conf.toString}/>
}
/** Creates the main, optional, and scalac configurations for each Scala version*/
private def variableConfigurations: NodeSeq =
{
buildConfigurations flatMap
{ conf =>
scalaComment(conf) ++
(<conf name={conf.toString} extends={conf_base.toString}/>
<conf name={optional(conf).toString} extends={optional(conf_base).toString}/>
<conf name={scalac(conf).toString} visibility="private"/>)
}
}
/** Defines the dependencies for the given version of Scala, ScalaTest, and Specs. If uniformTestOrg is true,
* the 'org.scala-tools.testing' organization is used. Otherwise, 'org.' is prefixed to the module name. */
private def variableDependencies(scalaVersion: Configuration, scalaTestVersion: String, specsVersion: String, uniformTestOrg: Boolean) =
{
if(buildConfigurations.contains(scalaVersion))
{
scalaComment(scalaVersion) ++
{
if(scalaVersion eq conf_2_8_0)
Nil
else
{
testDependency("scalatest", scalaTestVersion, uniformTestOrg, scalaVersion) ++
testDependency("specs", specsVersion, uniformTestOrg, scalaVersion) ++
testDependency("scalacheck", "1.5", false, scalaVersion)
}
} ++
scalaDependency("scala-compiler", scalaVersion) ++ scalaDependency("scala-library", scalaVersion) ++
{
if(scalaVersion == conf_2_8_0)
<dependency org="jline" name="jline" rev="0.9.94" transitive="false" conf={depConf(conf_2_8_0)}/>
else
NodeSeq.Empty
}
}
else
Nil
}
private def scalaDependency(name: String, scalaVersion: Configuration) =
<dependency org="org.scala-lang" name={name} rev={scalaVersion.toString} conf={depConf(scalac(scalaVersion))}/>
/** Creates a comment containing the version of Scala*/
private def scalaComment(scalaVersion: Configuration) = scala.xml.Comment("Scala " + scalaVersion)
/** Creates a dependency element for a test. See 'testOrg' for a description of uniformTestOrg.*/
private def testDependency(name: String, version: String, uniformTestOrg: Boolean, baseConf: Configuration) =
<dependency org={testOrg(name, uniformTestOrg)} name={name} rev={version} transitive="false" conf={depConf(optional(baseConf))}/>
/** Returns the organization for the given test library. If uniform is true,
* the 'org.scala-tools.testing' organization is used. Otherwise, 'org.' is prefixed to the module name.*/
private def testOrg(name: String, uniform: Boolean) =
if(uniform) "org.scala-tools.testing"
else "org." + name
/** Disable filtering Scala jars from dependency management, because we need them and are putting them
* in custom configurations and are using them in a separate process than sbt runs in.*/
override def filterScalaJars = false
/** The lib directory is now only for building using the 'build' script.*/
override def unmanagedClasspath = path("ignore_lib_directory")
/** When cross-compiling, replace mainCompilePath with the classes directory for the version being compiled.*/
override def fullUnmanagedClasspath(config: Configuration) =
if( (Configurations.Default :: Configurations.defaultMavenConfigurations) contains config)
super.fullUnmanagedClasspath(config)
else
classesPath(config) +++ mainResourcesPath
// include the optional-<version> dependencies as well as the ones common across all scala versions
def optionalClasspath(version: Configuration) = fullClasspath(optional(version)) +++ super.optionalClasspath
private val CompilerMainClass = "scala.tools.nsc.Main"
// use a publish configuration that publishes the 'base' + all <version> configurations (base is required because
// the <version> configurations extend it)
private val conf = new DefaultPublishConfiguration("local", "release")
{
override def configurations: Option[Iterable[Configuration]] = Some(config(base) :: buildConfigurations)
}
// the actions for cross-version packaging and publishing
lazy val crossPackage = buildConfigurations.map(packageForScala)
lazy val crossDeliverLocal = deliverTask(conf, updateOptions) dependsOn(crossPackage : _*)
lazy val crossPublishLocal = publishTask(conf, updateOptions) dependsOn(crossDeliverLocal)
// Creates a task that produces a packaged sbt compiled against Scala scalaVersion.
// The jar is named 'sbt_<scala-version>-<sbt-version>.jar'
private def packageForScala(scalaVersion: Configuration) =
{
val classes = classesPath(scalaVersion) ** "*"
val jarName = crossJarName(scalaVersion)
val packageActionName = crossActionName("package", scalaVersion)
val compileAction = compileForScala(scalaVersion) named(crossActionName("compile", scalaVersion))
packageTask(classes +++ mainResources, outputPath, jarName, packageOptions) dependsOn(compileAction) named(packageActionName)
}
private def crossActionName(base: String, scalaVersion: Configuration) = base + " [ " + scalaVersion.toString + " ] "
private def crossJarName(scalaVersion: Configuration) = sbt(scalaVersion) + "-" + version.toString + ".jar"
// This creates a task that compiles sbt against the given version of scala. Classes are put in classes-<scalaVersion>.
private def compileForScala(version: Configuration)=
{
def filteredSources =
if(version eq conf_2_8_0) // cannot compile against test libraries currently
Path.lazyPathFinder { mainSources.get.filter(!_.asFile.getName.endsWith("TestFrameworkImpl.scala")) }
else
mainSources
task
{
val classes = classesPath(version)
val toClean = (outputPath / crossJarName(version)) +++ (classes ** "*")
val setupResult =
FileUtilities.clean(toClean.get, true, log) orElse
FileUtilities.createDirectory(classes, log)
for(err <- setupResult) log.error(err)
// the classpath containing the scalac compiler
val compilerClasspath = concatPaths(fullClasspath(scalac(version)))
// The libraries to compile sbt against
val classpath = fullClasspath(version) +++ optionalClasspath(version)
val sources: List[String] = pathListStrings(filteredSources)
val compilerOptions = List("-cp", concatPaths(classpath), "-d", classes.toString)
val compilerArguments: List[String] = compilerOptions ::: sources
// the compiler classpath has to be appended to the boot classpath to work properly
val allArguments = "-Xmx512M" :: ("-Xbootclasspath/a:" + compilerClasspath) :: CompilerMainClass :: compilerArguments
log.debug("Running external compiler with command: java " + allArguments.mkString(" "))
val start = System.currentTimeMillis
val exitValue = Process("java", allArguments) ! log
val stop = System.currentTimeMillis
log.info("Compiled sbt with Scala " + version.name + " in " + ((stop-start)/1000.0) + " s")
if(exitValue == 0)
None
else
Some("Nonzero exit value (" + exitValue + ") when calling scalac " + version + " with options: \n" + compilerOptions.mkString(" "))
}
}
private def concatPaths(p: PathFinder): String = Path.makeString(p.get)
private def pathListStrings(p: PathFinder): List[String] = p.get.map(_.absolutePath).toList
private def classesPath(scalaVersion: Configuration) = ("target" / ("classes-" + scalaVersion.toString)) ##
}

View File

@ -1,151 +0,0 @@
import sbt._
import LoaderProject._
import java.io.File
// a project for the sbt launcher
// the main content of this project definition is setting up and running proguard
// to combine and compact all dependencies into a single jar
protected/* removes the ambiguity as to which project is the entry point by making this class non-public*/
class LoaderProject(info: ProjectInfo) extends DefaultProject(info)
{
val mainClassName = "sbt.boot.Boot"
val baseName = "sbt-launcher"
val proguardConfigurationPath: Path = outputPath / "proguard.pro"
lazy val outputJar: Path = rootProject.outputPath / (baseName + "-" + version + ".jar")
def rootProjectDirectory = rootProject.info.projectPath
override lazy val release = task { None }
override def mainClass = Some(mainClassName)
override def defaultJarBaseName = baseName + "-" + version.toString
/****** Resources *****/
def extraResources = descendents(info.projectPath / "licenses", "*") +++ "LICENSE" +++ "NOTICE"
override def mainResources = super.mainResources +++ extraResources
/****** Dependencies *******/
val defaultConfig = config("default")
val toolsConfig = config("tools")
val ivy = "org.apache.ivy" % "ivy" % "2.0.0"
val proguardJar = "net.sf.proguard" % "proguard" % "4.3" % "tools->default"
/******** Proguard *******/
lazy val proguard = proguardTask dependsOn(`package`, writeProguardConfiguration) describedAs(ProguardDescription)
lazy val writeProguardConfiguration = writeProguardConfigurationTask dependsOn `package` describedAs WriteProguardDescription
private def proguardTask =
task
{
FileUtilities.clean(outputJar :: Nil, log)
val proguardClasspath = managedClasspath(toolsConfig)
val proguardClasspathString = Path.makeString(proguardClasspath.get)
val configFile = proguardConfigurationPath.asFile.getAbsolutePath
val exitValue = Process("java", List("-Xmx128M", "-cp", proguardClasspathString, "proguard.ProGuard", "@" + configFile)) ! log
if(exitValue == 0) None else Some("Proguard failed with nonzero exit code (" + exitValue + ")")
}
private def writeProguardConfigurationTask =
task
{
// these are classes that need to be explicitly kept because they are loaded reflectively
val ivyKeepResolvers =
"org.apache.ivy.plugins.resolver.URLResolver" ::
"org.apache.ivy.plugins.resolver.IBiblioResolver" ::
Nil
// the template for the proguard configuration file
val outTemplate = """
|-dontoptimize
|-dontobfuscate
|-dontnote
|-dontwarn
|-libraryjars %s
|-injars %s(!META-INF/**,!fr/**,!**/antlib.xml,!**/*.png)
|-injars %s(!META-INF/**)
|%s
|-outjars %s
|-ignorewarnings
|%s
|%s
|-keep public class %s {
| public static void main(java.lang.String[]);
|}"""
val defaultJar = mkpath((outputPath / defaultJarName).asFile)
log.debug("proguard configuration using main jar " + defaultJar)
val ivyKeepOptions = ivyKeepResolvers.map("-keep public class " + _ + allPublic).mkString("\n")
val runtimeClasspath = runClasspath.get.map(_.asFile).toList
val jlineJars = runtimeClasspath.filter(isJLineJar)
val externalDependencies = (mainCompileConditional.analysis.allExternals).map(_.getAbsoluteFile).filter(_.getName.endsWith(".jar"))
log.debug("proguard configuration external dependencies: \n\t" + externalDependencies.mkString("\n\t"))
// partition jars from the external jar dependencies of this project by whether they are located in the project directory
// if they are, they are specified with -injars, otherwise they are specified with -libraryjars
val (externalJars, libraryJars) = externalDependencies.toList.partition(jar => Path.relativize(rootProjectDirectory, jar).isDefined)
log.debug("proguard configuration library jars locations: " + libraryJars.mkString(", "))
// pull out Ivy in order to exclude resources inside
val (ivyJars, externalJarsNoIvy) = externalJars.partition(_.getName.startsWith("ivy"))
log.debug("proguard configuration ivy jar location: " + ivyJars.mkString(", "))
// the loader uses JLine, so there is a dependency on the compiler (because JLine is distributed with the compiler,
// it finds the JLine classes from the compiler jar instead of the jline jar on the classpath), but we don't want to
// include the version of JLine from the compiler.
val includeExternalJars = externalJarsNoIvy.filter(jar => !isJarX(jar, "scala-compiler"))
// exclude properties files and manifests from scala-library jar
val inJars = (defaultJar :: includeExternalJars.map(f => mkpath(f) + "(!META-INF/**,!*.properties)")).map("-injars " + _).mkString("\n")
withJar(ivyJars, "Ivy") { ivyJar =>
withJar(jlineJars, "JLine") { jlineJar =>
val proguardConfiguration =
outTemplate.stripMargin.format(mkpaths(libraryJars),
mkpath(ivyJar), mkpath(jlineJar),
inJars, mkpath(outputJar.asFile), ivyKeepOptions, keepJLine, mainClassName)
log.debug("Proguard configuration written to " + proguardConfigurationPath)
FileUtilities.write(proguardConfigurationPath.asFile, proguardConfiguration, log)
}
}
}
private def mkpaths(f: Seq[File]) = f.map(mkpath).mkString(File.separator)
private def mkpath(f: File) = '\"' + f.getAbsolutePath + '\"'
private def withJar(files: List[File], name: String)(f: File => Option[String]): Option[String] =
files match
{
case Nil => Some(name + " not present (try running update)")
case jar :: _ => f(jar)
}
private def isJLineJar(file: File) = isJarX(file, "jline")
private def isJarX(file: File, x: String) =
{
val name = file.getName
name.startsWith(x) && name.endsWith(".jar")
}
// class body declaration for proguard that keeps all public members
private val allPublic = " {\n public * ;\n}"
private val keepJLine =
"""
|-keep public class jline.** {
| public protected *;
|}
""".stripMargin
def managedSrc = path("src_managed")
def defaultVersionPath = managedSrc / "DefaultVersions.scala"
override def mainSourceRoots = super.mainSourceRoots +++ managedSrc
override def compileAction = super.compileAction dependsOn(versionProperties)
override def cleanAction = cleanTask(outputPath +++ managedSrc, cleanOptions)
lazy val versionProperties = task { writeVersionProperties(projectVersion.value.toString, "2.7.5") }//scalaVersion.value.toString) }
def writeVersionProperties(sbtVersion: String, scalaVersion: String) =
FileUtilities.write(defaultVersionPath.asFile, defaultVersions(sbtVersion, scalaVersion), log)
def defaultVersions(sbtVersion: String, scalaVersion: String) =
"""
package sbt.boot
object DefaultVersions
{
val Sbt = "%s"
val Scala = "%s"
}
""".format(sbtVersion, scalaVersion)
}
object LoaderProject
{
val ProguardDescription = "Produces the final compacted jar that contains only the minimum classes needed using proguard."
val WriteProguardDescription = "Creates the configuration file to use with proguard."
}

View File

@ -1,7 +1,7 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
import sbt._
/*import sbt._
import java.io.File
@ -93,4 +93,4 @@ package sbt {
implicit def elemToPB(command: scala.xml.Elem): ProcessBuilder =
impl.CommandParser.parse(command.text.trim).fold(error, Function.tupled(Process.apply))
}
}
}*/

View File

@ -5,33 +5,91 @@ import sbt._
import java.io.File
class SbtProject(info: ProjectInfo) extends ParentProject(info) with ReleaseProject
class SbtProject(info: ProjectInfo) extends DefaultProject(info) //with test.SbtScripted
{
// Launcher sub project.
lazy val boot = project("boot", "Simple Build Tool Loader", new LoaderProject(_))
// Main builder sub project
lazy val main = project(info.projectPath, "Simple Build Tool", new MainProject(_))
// One-shot build for users building from trunk
lazy val fullBuild = task { None } dependsOn(boot.proguard, main.crossPublishLocal) describedAs
"Builds the loader and builds main sbt against all supported versions of Scala and installs to the local repository."
override def shouldCheckOutputDirectories = false
override def baseUpdateOptions = QuietUpdate :: Nil
//override def parallelExecution = true
override def deliverLocalAction = noAction
private def noAction = task { None }
override def publishLocalAction = noAction
}
protected class MainProject(val info: ProjectInfo) extends CrossCompileProject with test.SbtScripted
{
override def defaultJarBaseName = "sbt_" + version.toString
/** Additional resources to include in the produced jar.*/
def extraResources = descendents(info.projectPath / "licenses", "*") +++ "LICENSE" +++ "NOTICE"
override def mainResources = super.mainResources +++ extraResources
override def mainClass = Some("sbt.Main")
override def testOptions = ExcludeTests("sbt.ReflectiveSpecification" :: "sbt.ProcessSpecification" :: Nil) :: super.testOptions.toList
override def scriptedDependencies = testCompile :: `package` :: Nil
override lazy val release = task { None }
override def normalizedName = "sbt"
//override def scriptedDependencies = testCompile :: `package` :: Nil
def specificSnapshotRepo =
Resolver.url("scala-nightly") artifacts("http://scala-tools.org/repo-snapshots/[organization]/[module]/2.8.0-SNAPSHOT/[artifact]-[revision].[ext]") mavenStyle()
val nightlyScala = ModuleConfiguration("org.scala-lang", "*", "2.8.0-.*", specificSnapshotRepo)
override def compileOptions = Nil
def scalaVersionString = ScalaVersion.current.getOrElse(scalaVersion.value)
override def mainSources =
{
if(scalaVersionString == Version2_8_0)
Path.lazyPathFinder( super.mainSources.get.filter( !_.asFile.getName.endsWith("TestFrameworkImpl.scala") ))
else
super.mainSources
}
override def useDefaultConfigurations = false
val default = Configurations.Default
val optional = Configurations.Optional
val provided = Configurations.Provided
/* The base configuration names for the versions of Scala*/
private val Version2_7_2 = "2.7.2"
private val Version2_7_3 = "2.7.3"
private val Version2_7_4 = "2.7.4"
private val Version2_7_5 = "2.7.5"
private val Version2_7_6 = "2.7.6"
private val Version2_8_0 = "2.8.0-20090929.004247-+"
// the list of all supported versions
private def allVersions = Version2_7_2 :: Version2_7_3 :: Version2_7_4 :: Version2_7_5 :: Version2_7_6 :: Version2_8_0 :: Nil
override def crossScalaVersions = Set(Version2_7_2, Version2_7_5)//, Version2_8_0)
val ivy = "org.apache.ivy" % "ivy" % "2.0.0" intransitive()
val jsch = "com.jcraft" % "jsch" % "0.1.31" intransitive()
val jetty = "org.mortbay.jetty" % "jetty" % "6.1.14" % "optional"
// xsbt components
val xsbti = "org.scala-tools.sbt" % "launcher-interface" % "0.7.0_13" % "provided"
val compiler = "org.scala-tools.sbt" %% "compile" % "0.7.0_13"
override def libraryDependencies = super.libraryDependencies ++ getDependencies(scalaVersionString)
def getDependencies(scalaVersion: String) =
scalaVersion match
{
case Version2_7_2 => variableDependencies(false, scalaVersion, /*ScalaTest*/"0.9.3", /*Specs*/"1.4.0", false)
case Version2_7_3 => variableDependencies(false, scalaVersion, /*ScalaTest*/"0.9.4", /*Specs*/"1.4.3", true)
case Version2_7_4 => variableDependencies(false, scalaVersion, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true)
case Version2_7_5 => variableDependencies(false, scalaVersion, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true)
case Version2_7_6 => variableDependencies(false, scalaVersion, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true)
case Version2_8_0 => variableDependencies(true, scalaVersion, /*ScalaTest*/"0.9.5", /*Specs*/"1.4.3", true)
case _ => error("Unsupported Scala version: " + scalaVersion)
}
/** Defines the dependencies for the given version of Scala, ScalaTest, and Specs. If uniformTestOrg is true,
* the 'org.scala-tools.testing' organization is used. Otherwise, 'org.' is prefixed to the module name. */
private def variableDependencies(is28: Boolean, scalaVersion: String, scalaTestVersion: String, specsVersion: String, uniformTestOrg: Boolean) =
{
( if(is28) Nil else testDependencies(scalaTestVersion, specsVersion, uniformTestOrg)) ++
( if(is28) Seq("jline" % "jline" % "0.9.94" intransitive()) else Nil)
}
private def testDependencies(scalaTestVersion: String, specsVersion: String, uniformTestOrg: Boolean) =
{
testDependency("scalatest", scalaTestVersion, uniformTestOrg) ::
testDependency("specs", specsVersion, uniformTestOrg) ::
testDependency("scalacheck", "1.5", false) ::
Nil
}
/** Creates a dependency element for a test. See 'testOrg' for a description of uniformTestOrg.*/
private def testDependency(name: String, version: String, uniformTestOrg: Boolean) =
testOrg(name, uniformTestOrg) % name % version % "optional" intransitive()
/** Returns the organization for the given test library. If uniform is true,
* the 'org.scala-tools.testing' organization is used. Otherwise, 'org.' is prefixed to the module name.*/
private def testOrg(name: String, uniform: Boolean) = if(uniform) "org.scala-tools.testing" else "org." + name
}

View File

@ -1,6 +0,0 @@
import sbt._
class Plugins(info: ProjectInfo) extends PluginDefinition(info)
{
val scripted = "org.scala-tools.sbt" % "test" % "0.5.3"
}

View File

@ -1,246 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah
*/
package sbt
import scala.tools.nsc.{io, plugins, symtab, Global, Phase}
import io.{AbstractFile, PlainFile, ZipArchive}
import plugins.{Plugin, PluginComponent}
import symtab.Flags
import scala.collection.mutable.{HashMap, HashSet, Map, Set}
import java.io.File
object Analyzer
{
val PluginName = "sbt-analyzer"
val CallbackIDOptionName = "callback:"
}
class Analyzer(val global: Global) extends Plugin
{
import global._
import Analyzer._
val name = PluginName
val description = "A plugin to find all concrete instances of a given class and extract dependency information."
val components = List[PluginComponent](Component)
private var callbackOption: Option[AnalysisCallback] = None
override def processOptions(options: List[String], error: String => Unit)
{
for(option <- options)
{
if(option.startsWith(CallbackIDOptionName))
callbackOption = AnalysisCallback(option.substring(CallbackIDOptionName.length).toInt)
else
error("Option for sbt analyzer plugin not understood: " + option)
}
if(callbackOption.isEmpty)
error("Callback ID not specified for sbt analyzer plugin.")
}
override val optionsHelp: Option[String] =
{
val prefix = " -P:" + name + ":"
Some(prefix + CallbackIDOptionName + "<callback-id> Set the callback id.\n")
}
/* ================================================== */
// These two templates abuse scope for source compatibility between Scala 2.7.x and 2.8.x so that a single
// sbt codebase compiles with both series of versions.
// In 2.8.x, PluginComponent.runsAfter has type List[String] and the method runsBefore is defined on
// PluginComponent with default value Nil.
// In 2.7.x, runsBefore does not exist on PluginComponent and PluginComponent.runsAfter has type String.
//
// Therefore, in 2.8.x, object runsBefore is shadowed by PluginComponent.runsBefore (which is Nil) and so
// afterPhase :: runsBefore
// is equivalent to List[String](afterPhase)
// In 2.7.x, object runsBefore is not shadowed and so runsAfter has type String.
private object runsBefore { def :: (s: String) = s }
private abstract class CompatiblePluginComponent(afterPhase: String) extends PluginComponent
{
override val runsAfter = afterPhase :: runsBefore
}
/* ================================================== */
private object Component extends CompatiblePluginComponent("jvm")
{
val global = Analyzer.this.global
val phaseName = Analyzer.this.name
def newPhase(prev: Phase) = new AnalyzerPhase(prev)
}
private class AnalyzerPhase(prev: Phase) extends Phase(prev)
{
def name = Analyzer.this.name
def run
{
val callback = callbackOption.get
val projectPath = callback.basePath
val projectPathString = Path.basePathString(projectPath).getOrElse({error("Could not determine base path for " + projectPath); ""})
def relativize(file: File) = Path.relativize(projectPath, projectPathString, file)
val outputDir = new File(global.settings.outdir.value)
val outputPathOption = relativize(outputDir)
if(outputPathOption.isEmpty)
error("Output directory " + outputDir.getAbsolutePath + " must be in the project directory.")
val outputPath = outputPathOption.get
val superclassNames = callback.superclassNames.map(newTermName)
val superclassesAll =
for(name <- superclassNames) yield
{
try { Some(global.definitions.getClass(name)) }
catch { case fe: scala.tools.nsc.FatalError => callback.superclassNotFound(name.toString); None }
}
val superclasses = superclassesAll.filter(_.isDefined).map(_.get)
for(unit <- currentRun.units)
{
// build dependencies structure
val sourceFile = unit.source.file.file
val sourcePathOption = relativize(sourceFile)
if(sourcePathOption.isEmpty)
error("Source file " + sourceFile.getAbsolutePath + " must be in the project directory.")
val sourcePath = sourcePathOption.get
callback.beginSource(sourcePath)
for(on <- unit.depends)
{
val onSource = on.sourceFile
if(onSource == null)
{
classFile(on) match
{
case Some(f) =>
{
f match
{
case ze: ZipArchive#Entry => callback.jarDependency(new File(ze.getArchive.getName), sourcePath)
case pf: PlainFile =>
{
Path.relativize(outputPath, pf.file) match
{
case None => // dependency is a class file outside of the output directory
callback.classDependency(pf.file, sourcePath)
case Some(relativeToOutput) => // dependency is a product of a source not included in this compilation
callback.productDependency(relativeToOutput, sourcePath)
}
}
case _ => ()
}
}
case None => ()
}
}
else
{
for(depPath <- relativize(onSource.file))
callback.sourceDependency(depPath, sourcePath)
}
}
// find subclasses and modules with main methods
for(clazz @ ClassDef(mods, n, _, _) <- unit.body)
{
val sym = clazz.symbol
if(sym != NoSymbol && mods.isPublic && !mods.isAbstract && !mods.isTrait &&
!sym.isImplClass && sym.isStatic && !sym.isNestedClass)
{
val isModule = sym.isModuleClass
for(superclass <- superclasses.filter(sym.isSubClass))
callback.foundSubclass(sourcePath, sym.fullNameString, superclass.fullNameString, isModule)
if(isModule && hasMainMethod(sym))
callback.foundApplication(sourcePath, sym.fullNameString)
}
}
// build list of generated classes
for(iclass <- unit.icode)
{
val sym = iclass.symbol
def addGenerated(separatorRequired: Boolean)
{
val classPath = pathOfClass(outputPath, sym, separatorRequired)
if(classPath.asFile.exists)
callback.generatedClass(sourcePath, classPath)
}
if(sym.isModuleClass && !sym.isImplClass)
{
if(isTopLevelModule(sym) && sym.linkedClassOfModule == NoSymbol)
addGenerated(false)
addGenerated(true)
}
else
addGenerated(false)
}
callback.endSource(sourcePath)
}
}
}
private def classFile(sym: Symbol): Option[AbstractFile] =
{
import scala.tools.nsc.symtab.Flags
val name = sym.fullNameString(java.io.File.separatorChar) + (if (sym.hasFlag(Flags.MODULE)) "$" else "")
val entry = classPath.root.find(name, false)
if (entry ne null)
Some(entry.classFile)
else if(isTopLevelModule(sym))
{
val linked = sym.linkedClassOfModule
if(linked == NoSymbol)
None
else
classFile(linked)
}
else
None
}
private def isTopLevelModule(sym: Symbol): Boolean =
atPhase (currentRun.picklerPhase.next) {
sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass
}
private def pathOfClass(outputPath: Path, s: Symbol, separatorRequired: Boolean): Path =
pathOfClass(outputPath, s, separatorRequired, ".class")
private def pathOfClass(outputPath: Path, s: Symbol, separatorRequired: Boolean, postfix: String): Path =
{
if(s.owner.isPackageClass && s.isPackageClass)
packagePath(outputPath, s) / postfix
else
pathOfClass(outputPath, s.owner.enclClass, true, s.simpleName + (if(separatorRequired) "$" else "") + postfix)
}
private def packagePath(outputPath: Path, s: Symbol): Path =
{
if(s.isEmptyPackageClass || s.isRoot)
outputPath
else
packagePath(outputPath, s.owner.enclClass) / s.simpleName.toString
}
private def hasMainMethod(sym: Symbol): Boolean =
{
val main = sym.info.nonPrivateMember(newTermName("main"))//nme.main)
main.tpe match
{
case OverloadedType(pre, alternatives) => alternatives.exists(alt => isVisible(alt) && isMainType(pre.memberType(alt)))
case tpe => isVisible(main) && isMainType(main.owner.thisType.memberType(main))
}
}
private def isVisible(sym: Symbol) = sym != NoSymbol && sym.isPublic && !sym.isDeferred
private def isMainType(tpe: Type) =
{
tpe match
{
// singleArgument is of type Symbol in 2.8.0 and type Type in 2.7.x
case MethodType(List(singleArgument), result) => isUnitType(result) && isStringArray(singleArgument)
case _ => false
}
}
private lazy val StringArrayType = appliedType(definitions.ArrayClass.typeConstructor, definitions.StringClass.tpe :: Nil)
// isStringArray is overloaded to handle the incompatibility between 2.7.x and 2.8.0
private def isStringArray(tpe: Type): Boolean = tpe.typeSymbol == StringArrayType.typeSymbol
private def isStringArray(sym: Symbol): Boolean = isStringArray(sym.tpe)
private def isUnitType(tpe: Type) = tpe.typeSymbol == definitions.UnitClass
}

View File

@ -184,7 +184,7 @@ trait ManagedProject extends ClasspathProject
implicit def toGroupID(groupID: String): GroupID =
{
nonEmpty(groupID, "Group ID")
new GroupID(groupID, ScalaVersion.currentString)
new GroupID(groupID, buildScalaVersion)
}
implicit def toRepositoryName(name: String): RepositoryName =
{

View File

@ -22,7 +22,7 @@ private sealed abstract class BasicBuilderProject extends InternalProject with S
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)
{
@ -31,10 +31,10 @@ private sealed abstract class BasicBuilderProject extends InternalProject with S
action
log.setLevel(oldLevel)
}
def projectClasspath = compilePath +++ libraries +++ sbtJarPath
def sbtJarPath = Path.lazyPathFinder { Path.fromFile(FileUtilities.sbtJar) :: Nil }
abstract class BuilderCompileConfiguration extends AbstractCompileConfiguration
{
def projectPath = info.projectPath
@ -54,11 +54,13 @@ private sealed abstract class BasicBuilderProject extends InternalProject with S
def classpath = projectClasspath
def analysisPath = outputPath / DefaultMainAnalysisDirectoryName
}
def tpe: String
val definitionCompileConditional = new BuilderCompileConditional(definitionCompileConfiguration, tpe)
final class BuilderCompileConditional(config: BuilderCompileConfiguration, tpe: String) extends AbstractCompileConditional(config)
import xsbt.{ComponentManager, ScalaInstance}
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) =
@ -94,12 +96,12 @@ private sealed abstract class BasicBuilderProject extends InternalProject with S
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 =>
case Nil =>
log.debug("No " + tpe + "s detected using default project.")
Right(None)
case singleDefinition :: Nil => Right(Some(singleDefinition))
@ -114,7 +116,7 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path,
private lazy val pluginProject =
{
if(pluginPath.exists)
Some(new PluginBuilderProject(ProjectInfo(pluginPath.asFile, Nil, None)(rawLogger)))
Some(new PluginBuilderProject(ProjectInfo(pluginPath.asFile, Nil, None)(rawLogger, info.app, info.buildScalaVersion)))
else
None
}
@ -137,13 +139,13 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path,
pluginUptodate() = flag
saveEnvironment()
}
private def pluginTask(f: => Option[String]) = task { if(!pluginUptodate.value) f else None }
lazy val syncPlugins = pluginTask(sync()) dependsOn(extractSources)
lazy val extractSources = pluginTask(extract()) dependsOn(update)
lazy val update = pluginTask(loadAndUpdate()) dependsOn(compile)
private def sync() = pluginCompileConditional.run orElse { setUptodate(true); None }
private def extract() =
{
@ -161,7 +163,7 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path,
Control.thread(projectDefinition) {
case Some(definition) =>
logInfo("\nUpdating plugins...")
val pluginInfo = ProjectInfo(info.projectPath.asFile, Nil, None)(rawLogger)
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))
pluginBuilder.projectName() = "Plugin builder"
pluginBuilder.projectVersion() = OpaqueVersion("1.0")
@ -184,8 +186,8 @@ private final class BuilderProject(val info: ProjectInfo, val pluginPath: Path,
}
def plugins = descendents(managedDependencyPath, jarFilter)
def pluginClasspath: PathFinder = plugins +++ pluginCompileConfiguration.outputDirectory
lazy val pluginCompileConditional = new BuilderCompileConditional(pluginCompileConfiguration, "plugin")
lazy val pluginCompileConditional = new BuilderCompileConditional(pluginCompileConfiguration, buildCompiler, "plugin")
lazy val pluginCompileConfiguration =
new BuilderCompileConfiguration
{

View File

@ -4,102 +4,57 @@
package sbt
import java.io.File
import xsbt.{AnalyzingCompiler, CompileFailed, CompilerArguments, ComponentManager, ScalaInstance}
object CompileOrder extends Enumeration
{
val Mixed, JavaThenScala, ScalaThenJava = Value
}
private object CompilerCore
{
def scalaClasspathForJava = FileUtilities.scalaJars.map(_.getAbsolutePath).mkString(File.pathSeparator)
}
sealed abstract class CompilerCore
{
val ClasspathOptionString = "-classpath"
val OutputOptionString = "-d"
final def apply(label: String, sources: Iterable[Path], classpath: Iterable[Path], outputDirectory: Path, scalaOptions: Seq[String], log: Logger): Option[String] =
apply(label, sources, classpath, outputDirectory, scalaOptions, Nil, CompileOrder.Mixed, log)
final def apply(label: String, sources: Iterable[Path], classpath: Iterable[Path], outputDirectory: Path, scalaOptions: Seq[String], javaOptions: Seq[String], order: CompileOrder.Value, log: Logger): Option[String] =
{
def filteredSources(extension: String) = sources.filter(_.asFile.getName.endsWith(extension))
def fileSet(sources: Iterable[Path]) = Set() ++ sources.map(_.asFile)
def process(label: String, sources: Iterable[_], act: => Unit) =
() => if(sources.isEmpty) log.debug("No " + label + " sources.") else act
// Returns false if there were errors, true if there were not.
protected def process(args: List[String], log: Logger): Boolean
// Returns false if there were errors, true if there were not.
protected def processJava(args: List[String], log: Logger): Boolean = true
protected def scalaClasspathForJava: String
def actionStartMessage(label: String): String
def actionNothingToDoMessage: String
def actionSuccessfulMessage: String
def actionUnsuccessfulMessage: String
private def classpathString(rawClasspathString: String, includeScala: Boolean) =
if(includeScala)
List(rawClasspathString, scalaClasspathForJava).mkString(File.pathSeparator)
else
rawClasspathString
final def apply(label: String, sources: Iterable[Path], classpathString: String, outputDirectory: Path, options: Seq[String], log: Logger): Option[String] =
apply(label, sources, classpathString, outputDirectory, options, Nil, CompileOrder.Mixed, log)
final def apply(label: String, sources: Iterable[Path], rawClasspathString: String, outputDirectory: Path, options: Seq[String], javaOptions: Seq[String], order: CompileOrder.Value, log: Logger): Option[String] =
val javaSources = fileSet(filteredSources(".java"))
val scalaSources = fileSet( if(order == CompileOrder.Mixed) sources else filteredSources(".scala") )
val classpathSet = fileSet(classpath)
val scalaCompile = process("Scala", scalaSources, processScala(scalaSources, classpathSet, outputDirectory.asFile, scalaOptions, log) )
val javaCompile = process("Java", javaSources, processJava(javaSources, classpathSet, outputDirectory.asFile, javaOptions, log))
try { doCompile(label, sources, outputDirectory, order, log)(javaCompile, scalaCompile) }
catch { case e: xsbti.CompileFailed => log.trace(e); Some(e.toString) }
}
protected def doCompile(label: String, sources: Iterable[Path], outputDirectory: Path, order: CompileOrder.Value, log: Logger)(javaCompile: () => Unit, scalaCompile: () => Unit) =
{
log.info(actionStartMessage(label))
def classpathOption(includeScala: Boolean): List[String] =
if(sources.isEmpty)
{
val classpath = classpathString(rawClasspathString, includeScala)
if(classpath.isEmpty)
Nil
else
List(ClasspathOptionString, classpath)
log.info(actionNothingToDoMessage)
None
}
val outputDir = outputDirectory.asFile
FileUtilities.createDirectory(outputDir, log) orElse
else
{
def classpathAndOut(javac: Boolean): List[String] = OutputOptionString :: outputDir.getAbsolutePath :: classpathOption(javac)
Control.trapUnit("Compiler error: ", log)
FileUtilities.createDirectory(outputDirectory.asFile, log) orElse Control.trapUnit("Compiler error: ", log)
{
val sourceList = sources.map(_.asFile.getAbsolutePath).toList
if(sourceList.isEmpty)
{
log.info(actionNothingToDoMessage)
None
}
else
{
def filteredSources(extension: String) = sourceList.filter(_.endsWith(extension))
def compile(label: String, sources: List[String], options: Seq[String], includeScala: Boolean)(process: (List[String], Logger) => Boolean) =
{
if(sources.isEmpty)
{
log.debug("No "+label+" sources to compile.")
true
}
else
{
val arguments = (options ++ classpathAndOut(includeScala) ++ sources).toList
log.debug(label + " arguments: " + arguments.mkString(" "))
process(arguments, log)
}
}
def scalaCompile = () =>
{
val scalaSourceList = if(order == CompileOrder.Mixed) sourceList else filteredSources(".scala")
compile("Scala", scalaSourceList, options, false)(process)
}
def javaCompile = () =>
{
val javaSourceList = filteredSources(".java")
compile("Java", javaSourceList, javaOptions, true)(processJava)
}
val (first, second) = if(order == CompileOrder.JavaThenScala) (javaCompile, scalaCompile) else (scalaCompile, javaCompile)
if(first() && second())
{
log.info(actionSuccessfulMessage)
None
}
else
Some(actionUnsuccessfulMessage)
}
val (first, second) = if(order == CompileOrder.JavaThenScala) (javaCompile, scalaCompile) else (scalaCompile, javaCompile)
first()
second()
log.info(actionSuccessfulMessage)
None
}
}
}
def actionStartMessage(label: String): String
def actionNothingToDoMessage: String
def actionSuccessfulMessage: String
protected def processScala(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], log: Logger): Unit
protected def processJava(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], log: Logger): Unit
}
sealed abstract class CompilerBase extends CompilerCore
@ -107,199 +62,75 @@ sealed abstract class CompilerBase extends CompilerCore
def actionStartMessage(label: String) = "Compiling " + label + " sources..."
val actionNothingToDoMessage = "Nothing to compile."
val actionSuccessfulMessage = "Compilation successful."
val actionUnsuccessfulMessage = "Compilation unsuccessful."
}
final class ForkCompile(config: ForkScalaCompiler) extends CompilerBase
{
import java.io.File
protected def process(arguments: List[String], log: Logger) =
Fork.scalac(config.javaHome, config.compileJVMOptions, config.scalaJars, arguments, log) == 0
override protected def processJava(args: List[String], log: Logger) =
Fork.javac(config.javaHome, args, log) == 0
override protected def scalaClasspathForJava = config.scalaJars.mkString(File.pathSeparator)
}
object ForkCompile
{
def apply(config: ForkScalaCompiler, conditional: CompileConditional) =
{
import conditional.config.{compileOrder, classpath, javaOptions, label, log, options, outputDirectory, sources}
// recompile only if any sources were modified after any classes or no classes exist
val sourcePaths = sources.get
val newestSource = (0L /: sourcePaths)(_ max _.lastModified)
val products = (outputDirectory ** GlobFilter("*.class")).get
val oldestClass = (java.lang.Long.MAX_VALUE /: products)(_ min _.lastModified)
if(products.isEmpty || newestSource > oldestClass)
{
// full recompile, since we are not doing proper dependency tracking
FileUtilities.clean(outputDirectory :: Nil, log)
val compiler = new ForkCompile(config)
FileUtilities.createDirectory(outputDirectory.asFile, log)
compiler(label, sourcePaths, Path.makeString(classpath.get), outputDirectory, options, javaOptions, compileOrder, log)
}
else
{
log.info("Compilation up to date.")
None
}
}
}
// The following code is based on scala.tools.nsc.Main and scala.tools.nsc.ScalaDoc
// Copyright 2005-2008 LAMP/EPFL
// Original author: Martin Odersky
final class Compile(maximumErrors: Int) extends CompilerBase
final class Compile(maximumErrors: Int, compiler: AnalyzingCompiler, analysisCallback: AnalysisCallback, baseDirectory: Path) extends CompilerBase
{
protected def process(arguments: List[String], log: Logger) =
protected def processScala(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], log: Logger)
{
import scala.tools.nsc.{CompilerCommand, FatalError, Global, Settings, reporters, util}
import util.FakePos
var reporter = new LoggerReporter(maximumErrors, log)
val settings = new Settings(reporter.error)
val command = new CompilerCommand(arguments, settings, error, false)
object compiler extends Global(command.settings, reporter)
if(!reporter.hasErrors)
{
val run = new compiler.Run
run compile command.files
reporter.printSummary()
}
!reporter.hasErrors
val callbackInterface = new AnalysisInterface(analysisCallback, baseDirectory, outputDirectory)
compiler(Set() ++ sources, Set() ++ classpath, outputDirectory, options, true, callbackInterface, maximumErrors, log)
}
protected def processJava(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], log: Logger)
{
val arguments = (new CompilerArguments(compiler.scalaInstance))(sources, classpath, outputDirectory, options, true)
val code = Process("javac", arguments) ! log
if( code != 0 ) throw new CompileFailed(arguments.toArray, "javac returned nonzero exit code")
}
override protected def processJava(args: List[String], log: Logger) =
(Process("javac", args) ! log) == 0
protected def scalaClasspathForJava = CompilerCore.scalaClasspathForJava
}
final class Scaladoc(maximumErrors: Int) extends CompilerCore
final class Scaladoc(maximumErrors: Int, compiler: AnalyzingCompiler) extends CompilerCore
{
protected def scalaClasspathForJava = CompilerCore.scalaClasspathForJava
protected def process(arguments: List[String], log: Logger) =
{
import scala.tools.nsc.{doc, CompilerCommand, FatalError, Global, reporters, util}
import util.FakePos
val reporter = new LoggerReporter(maximumErrors, log)
val docSettings: doc.Settings = new doc.Settings(reporter.error)
val command = new CompilerCommand(arguments, docSettings, error, false)
object compiler extends Global(command.settings, reporter)
{
override val onlyPresentation = true
}
if(!reporter.hasErrors)
{
val run = new compiler.Run
run compile command.files
val generator = new doc.DefaultDocDriver
{
lazy val global: compiler.type = compiler
lazy val settings = docSettings
}
generator.process(run.units)
reporter.printSummary()
}
!reporter.hasErrors
}
protected def processScala(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], log: Logger): Unit =
compiler.doc(sources, classpath, outputDirectory, options, maximumErrors, log)
protected def processJava(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], log: Logger) = ()
def actionStartMessage(label: String) = "Generating API documentation for " + label + " sources..."
val actionNothingToDoMessage = "No sources specified."
val actionSuccessfulMessage = "API documentation generation successful."
def actionUnsuccessfulMessage = "API documentation generation unsuccessful."
}
// The following code is based on scala.tools.nsc.reporters.{AbstractReporter, ConsoleReporter}
// Copyright 2002-2009 LAMP/EPFL
// Original author: Martin Odersky
final class LoggerReporter(maximumErrors: Int, log: Logger) extends scala.tools.nsc.reporters.Reporter
final class Console(compiler: AnalyzingCompiler) extends NotNull
{
import scala.tools.nsc.util.{FakePos,NoPosition,Position}
private val positions = new scala.collection.mutable.HashMap[Position, Severity]
def error(msg: String) { error(FakePos("scalac"), msg) }
def printSummary()
/** Starts an interactive scala interpreter session with the given classpath.*/
def apply(classpath: Iterable[Path], log: Logger): Option[String] =
apply(classpath, "", log)
def apply(classpath: Iterable[Path], initialCommands: String, log: Logger): Option[String] =
{
if(WARNING.count > 0)
log.warn(countElementsAsString(WARNING.count, "warning") + " found")
if(ERROR.count > 0)
log.error(countElementsAsString(ERROR.count, "error") + " found")
def console0 = compiler.console(Set() ++ classpath.map(_.asFile), initialCommands, log)
JLine.withJLine( Run.executeTrapExit(console0, log) )
}
}
def display(pos: Position, msg: String, severity: Severity)
private final class AnalysisInterface(delegate: AnalysisCallback, basePath: Path, outputDirectory: File) extends xsbti.AnalysisCallback with NotNull
{
val outputPath = Path.fromFile(outputDirectory)
def superclassNames = delegate.superclassNames.toSeq.toArray[String]
def superclassNotFound(superclassName: String) = delegate.superclassNotFound(superclassName)
def beginSource(source: File) = delegate.beginSource(srcPath(source))
def foundSubclass(source: File, subclassName: String, superclassName: String, isModule: Boolean) =
delegate.foundSubclass(srcPath(source), subclassName, superclassName, isModule)
def sourceDependency(dependsOn: File, source: File) =
delegate.sourceDependency(srcPath(dependsOn), srcPath(source))
def jarDependency(jar: File, source: File) = delegate.jarDependency(jar, srcPath(source))
def generatedClass(source: File, clazz: File) = delegate.generatedClass(srcPath(source), classPath(clazz))
def endSource(source: File) = delegate.endSource(srcPath(source))
def foundApplication(source: File, className: String) = delegate.foundApplication(srcPath(source), className)
def classDependency(clazz: File, source: File) =
{
severity.count += 1
if(severity != ERROR || maximumErrors < 0 || severity.count <= maximumErrors)
print(severityToLevel(severity), pos, msg)
}
private def severityToLevel(severity: Severity): Level.Value =
severity match
val sourcePath = srcPath(source)
Path.relativize(outputPath, clazz) match
{
case ERROR => Level.Error
case WARNING => Level.Warn
case INFO => Level.Info
}
private def print(level: Level.Value, posIn: Position, msg: String)
{
// the implicits keep source compatibility with the changes in 2.8 : Position.{source,line,column} are no longer Options
implicit def anyToOption[T <: AnyRef](t: T): Option[T] = Some(t)
implicit def intToOption(t: Int): Option[Int] = Some(t)
val pos =
posIn match
{
case null | NoPosition => NoPosition
case x: FakePos => x
case x =>
posIn.inUltimateSource(posIn.source.get)
}
pos match
{
case NoPosition => log.log(level, msg)
case FakePos(fmsg) => log.log(level, fmsg+" "+msg)
case _ =>
val sourcePrefix = pos.source.map(_.file.path).getOrElse("")
val lineNumberString = pos.line.map(line => ":" + line + ":").getOrElse(":") + " "
log.log(level, sourcePrefix + lineNumberString + msg)
if (!pos.line.isEmpty)
{
val lineContent = pos.lineContent.stripLineEnd
log.log(level, lineContent) // source line with error/warning
for(offset <- pos.offset; src <- pos.source)
{
val pointer = offset - src.lineToOffset(src.offsetToLine(offset))
val pointerSpace = (lineContent: Seq[Char]).take(pointer).map { case '\t' => '\t'; case _ => ' ' }
log.log(level, pointerSpace.mkString + "^") // pointer to the column position of the error/warning
}
}
}
}
override def reset =
{
super.reset
positions.clear
}
protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean)
{
severity match
{
case WARNING | ERROR =>
{
if(!testAndLog(pos, severity))
display(pos, msg, severity)
}
case _ => display(pos, msg, severity)
}
}
private def testAndLog(pos: Position, severity: Severity): Boolean =
{
if(pos == null || pos.offset.isEmpty)
false
else if(positions.get(pos).map(_ >= severity).getOrElse(false))
true
else
{
positions(pos) = severity
false
case None => // dependency is a class file outside of the output directory
delegate.classDependency(clazz, sourcePath)
case Some(relativeToOutput) => // dependency is a product of a source not included in this compilation
delegate.productDependency(relativeToOutput, sourcePath)
}
}
def relativizeOrAbs(base: Path, file: File) = Path.relativize(base, file).getOrElse(Path.fromFile(file))
def classPath(file: File) = relativizeOrAbs(outputPath, file)
def srcPath(file: File) = relativizeOrAbs(basePath, file)
}

View File

@ -3,29 +3,31 @@
*/
package sbt
import xsbt.AnalyzingCompiler
trait Conditional[Source, Product, External] extends NotNull
{
type AnalysisType <: TaskAnalysis[Source, Product, External]
val analysis: AnalysisType = loadAnalysis
protected def loadAnalysis: AnalysisType
protected def log: Logger
protected def productType: String
protected def productTypePlural: String
protected def sourcesToProcess: Iterable[Source]
protected def sourceExists(source: Source): Boolean
protected def sourceLastModified(source: Source): Long
protected def productExists(product: Product): Boolean
protected def productLastModified(product: Product): Long
protected def externalInfo(externals: Iterable[External]): Iterable[(External, ExternalInfo)]
protected def execute(cAnalysis: ConditionalAnalysis): Option[String]
final case class ExternalInfo(available: Boolean, lastModified: Long) extends NotNull
trait ConditionalAnalysis extends NotNull
{
@ -35,7 +37,7 @@ trait Conditional[Source, Product, External] extends NotNull
def invalidatedSourcesCount: Int
def removedSourcesCount: Int
}
final def run =
{
val result = execute(analyze)
@ -45,7 +47,7 @@ trait Conditional[Source, Product, External] extends NotNull
private def analyze =
{
import scala.collection.mutable.HashSet
val sourcesSnapshot = sourcesToProcess
val removedSources = new HashSet[Source]
removedSources ++= analysis.allSources
@ -56,10 +58,10 @@ trait Conditional[Source, Product, External] extends NotNull
log.debug("Source " + removed + " removed.")
analysis.removeDependent(removed)
}
val unmodified = new HashSet[Source]
val modified = new HashSet[Source]
for(source <- sourcesSnapshot)
{
if(isSourceModified(source))
@ -102,7 +104,7 @@ trait Conditional[Source, Product, External] extends NotNull
analysis.removeExternalDependency(external)
}
}
val handled = new scala.collection.mutable.HashSet[Source]
val transitive = !java.lang.Boolean.getBoolean("sbt.intransitive")
def markModified(changed: Iterable[Source]) { for(c <- changed if !handled.contains(c)) markSourceModified(c) }
@ -119,10 +121,10 @@ trait Conditional[Source, Product, External] extends NotNull
markModified(modified.toList)
if(transitive)
removedSources.foreach(markDependenciesModified)
for(changed <- removedSources ++ modified)
analysis.removeSource(changed)
new ConditionalAnalysis
{
def dirtySources = wrap.Wrappers.readOnly(modified)
@ -138,7 +140,7 @@ trait Conditional[Source, Product, External] extends NotNull
}
}
}
protected def checkLastModified = true
protected def noProductsImpliesModified = true
protected def isSourceModified(source: Source) =
@ -155,7 +157,7 @@ trait Conditional[Source, Product, External] extends NotNull
val sourceModificationTime = sourceLastModified(source)
def isOutofdate(p: Product) =
!productExists(p) || (checkLastModified && productLastModified(p) < sourceModificationTime)
sourceProducts.find(isOutofdate) match
{
case Some(modifiedProduct) =>
@ -206,7 +208,7 @@ abstract class CompileConfiguration extends AbstractCompileConfiguration
def testDefinitionClassNames: Iterable[String]
}
import java.io.File
class CompileConditional(override val config: CompileConfiguration) extends AbstractCompileConditional(config)
class CompileConditional(override val config: CompileConfiguration, compiler: AnalyzingCompiler) extends AbstractCompileConditional(config, compiler)
{
import config._
type AnalysisType = CompileAnalysis
@ -220,7 +222,7 @@ class CompileConditional(override val config: CompileConfiguration) extends Abst
analysis.addTest(sourcePath, TestDefinition(isModule, subclassName, superclassName))
}
}
abstract class AbstractCompileConditional(val config: AbstractCompileConfiguration) extends Conditional[Path, Path, File]
abstract class AbstractCompileConditional(val config: AbstractCompileConfiguration, val compiler: AnalyzingCompiler) extends Conditional[Path, Path, File]
{
import config._
type AnalysisType <: BasicCompileAnalysis
@ -232,31 +234,32 @@ abstract class AbstractCompileConditional(val config: AbstractCompileConfigurati
a
}
protected def constructAnalysis(analysisPath: Path, projectPath: Path, log: Logger): AnalysisType
protected def log = config.log
protected def productType = "class"
protected def productTypePlural = "classes"
protected def sourcesToProcess = sources.get
protected def sourceExists(source: Path) = source.asFile.exists
protected def sourceLastModified(source: Path) = source.asFile.lastModified
protected def productExists(product: Path) = product.asFile.exists
protected def productLastModified(product: Path) = product.asFile.lastModified
private def libraryJar = compiler.scalaInstance.libraryJar
protected def externalInfo(externals: Iterable[File]) =
{
val (classpathJars, classpathDirs) = ClasspathUtilities.buildSearchPaths(classpath.get)
for(external <- externals) yield
{
val available = external.exists && ClasspathUtilities.onClasspath(classpathJars, classpathDirs, external)
val available = external.exists && (external == libraryJar || ClasspathUtilities.onClasspath(classpathJars, classpathDirs, external) )
if(!available)
log.debug("External " + external + (if(external.exists) " not on classpath." else " does not exist."))
(external, ExternalInfo(available, external.lastModified))
}
}
import ChangeDetection.{LastModifiedOnly, HashOnly, HashAndLastModified, HashAndProductsExist}
protected def changeDetectionMethod: ChangeDetection.Value = HashAndProductsExist
override protected def checkLastModified = changeDetectionMethod != HashAndProductsExist
@ -271,7 +274,7 @@ abstract class AbstractCompileConditional(val config: AbstractCompileConfigurati
case HashOnly => hashModified(source)
case LastModifiedOnly => super.isSourceModified(source)
}
import scala.collection.mutable.{Buffer, ListBuffer}
private val newHashes: Buffer[(Path, Option[Array[Byte]])] = new ListBuffer
private def warnHashError(source: Path, message: String)
@ -312,22 +315,21 @@ abstract class AbstractCompileConditional(val config: AbstractCompileConfigurati
log.info(executeAnalysis.toString)
finishHashes()
import executeAnalysis.dirtySources
// the output directory won't show up in the classpath unless it exists, so do this before classpath.get
val outputDir = outputDirectory.asFile
FileUtilities.createDirectory(outputDir, log)
val cp = classpath.get
if(!dirtySources.isEmpty)
checkClasspath(cp)
val classpathString = Path.makeString(cp)
val id = AnalysisCallback.register(analysisCallback)
val allOptions = (("-Xplugin:" + FileUtilities.sbtJar.getAbsolutePath) ::
("-P:sbt-analyzer:callback:" + id.toString) :: Nil) ++ options
def run = (new Compile(config.maxErrors))(label, dirtySources, classpathString, outputDirectory, allOptions, javaOptions, compileOrder, log)
def run =
{
val compile = new Compile(config.maxErrors, compiler, analysisCallback, projectPath)
compile(label, dirtySources, cp, outputDirectory, options, javaOptions, compileOrder, log)
}
val loader = ClasspathUtilities.toLoader(cp)
val r = classfile.Analyze(projectPath, outputDirectory, dirtySources, sourceRoots.get, log)(analysis.allProducts, analysisCallback, loader)(run)
AnalysisCallback.unregister(id)
if(log.atLevel(Level.Debug))
{
/** This checks that the plugin accounted for all classes in the output directory.*/
@ -377,7 +379,7 @@ abstract class AbstractCompileConditional(val config: AbstractCompileConfigurati
}
}
}
protected def analysisCallback: AnalysisCallback
}
object ChangeDetection extends Enumeration

View File

@ -43,8 +43,8 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
def manifestClassPath: Option[String] = None
def dependencies = info.dependencies ++ subProjects.values.toList
val mainCompileConditional = new CompileConditional(mainCompileConfiguration)
val testCompileConditional = new CompileConditional(testCompileConfiguration)
val mainCompileConditional = new CompileConditional(mainCompileConfiguration, buildCompiler)
val testCompileConditional = new CompileConditional(testCompileConfiguration, buildCompiler)
def compileOrder = CompileOrder.Mixed
@ -95,6 +95,8 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
TestFilter(new impl.TestQuickFilter(analysis, failedOnly, path, log)) :: TestListeners(new impl.TestStatusReporter(path, log) :: Nil) :: Nil
}
def consoleInit = ""
protected def includeTest(test: String): Boolean = true
/** This is called to create the initial directories when a user makes a new project from
@ -212,31 +214,26 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
/** Configures forking the compiler and runner. Use ForkScalaCompiler, ForkScalaRun or mix together.*/
def fork: Option[ForkScala] = None
private def doCompile(conditional: CompileConditional) =
{
fork match
{
case Some(fc: ForkScalaCompiler) => ForkCompile(fc, conditional)
case _ => conditional.run
}
}
private def getRunner =
private def doCompile(conditional: CompileConditional) = conditional.run
implicit def defaultRunner: ScalaRun =
{
fork match
{
case Some(fr: ForkScalaRun) => new ForkRun(fr)
case _ => Run
case _ => new Run(buildCompiler)
}
}
def basicConsoleTask = consoleTask(consoleClasspath, consoleInit)
protected def runTask(mainClass: String): MethodTask = task { args => runTask(Some(mainClass), runClasspath, args) dependsOn(compile, copyResources) }
protected def compileAction = task { doCompile(mainCompileConditional) } describedAs MainCompileDescription
protected def testCompileAction = task { doCompile(testCompileConditional) } dependsOn compile describedAs TestCompileDescription
protected def cleanAction = cleanTask(outputPath, cleanOptions) describedAs CleanDescription
protected def runAction = task { args => runTask(getMainClass(true), runClasspath, args, getRunner) dependsOn(compile, copyResources) } describedAs RunDescription
protected def consoleQuickAction = consoleTask(consoleClasspath, Run) describedAs ConsoleQuickDescription
protected def consoleAction = consoleTask(consoleClasspath, Run).dependsOn(testCompile, copyResources, copyTestResources) describedAs ConsoleDescription
protected def runAction = task { args => runTask(getMainClass(true), runClasspath, args) dependsOn(compile, copyResources) } describedAs RunDescription
protected def consoleQuickAction = basicConsoleTask describedAs ConsoleQuickDescription
protected def consoleAction = basicConsoleTask.dependsOn(testCompile, copyResources, copyTestResources) describedAs ConsoleDescription
protected def docAction = scaladocTask(mainLabel, mainSources, mainDocPath, docClasspath, documentOptions).dependsOn(compile) describedAs DocDescription
protected def docTestAction = scaladocTask(testLabel, testSources, testDocPath, docClasspath, documentOptions).dependsOn(testCompile) describedAs TestDocDescription
protected def testAction = defaultTestTask(testOptions)
@ -315,6 +312,7 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
mapScalaModule(snapshot.scalaCompiler, ScalaArtifacts.CompilerID)
}
override def watchPaths = mainSources +++ testSources +++ mainResources +++ testResources
private def mapScalaModule(in: Iterable[_], id: String) = in.map(jar => ModuleID(ScalaArtifacts.Organization, id, buildScalaVersion))
}
abstract class BasicWebScalaProject extends BasicScalaProject with WebScalaProject with WebScalaPaths
{ p =>
@ -327,7 +325,7 @@ abstract class BasicWebScalaProject extends BasicScalaProject with WebScalaProje
lazy val jettyInstance = new JettyRunner(jettyConfiguration)
def jettyConfiguration =
def jettyConfiguration: JettyConfiguration =
new DefaultJettyConfiguration
{
def classpath = jettyRunClasspath
@ -335,6 +333,7 @@ abstract class BasicWebScalaProject extends BasicScalaProject with WebScalaProje
def war = jettyWebappPath
def contextPath = jettyContextPath
def classpathName = "test"
def parentLoader = buildScalaInstance.loader
def scanDirectories = p.scanDirectories.map(_.asFile)
def scanInterval = p.scanInterval
def port = jettyPort
@ -443,12 +442,6 @@ object BasicScalaProject
log.warn("Multiple classes with a main method were detected. Specify main class explicitly with:")
log.warn(" override mainClass = Some(\"className\")")
}
private def mapScalaModule(in: Iterable[_], id: String) =
{
ScalaVersion.current.toList.flatMap { scalaVersion =>
in.map(jar => ModuleID(ScalaArtifacts.Organization, id, scalaVersion))
}
}
}
object BasicWebScalaProject
{

View File

@ -19,10 +19,10 @@ trait Environment
def get: Option[T] = resolve.toOption
/** Returns full information about this property's current value. */
def resolve: PropertyResolution[T]
def foreach(f: T => Unit): Unit = resolve.foreach(f)
}
/** Creates a system property with the given name and no default value.*/
def system[T](propName: String)(implicit format: Format[T]): Property[T]
/** Creates a system property with the given name and the given default value to use if no value is explicitly specified.*/
@ -67,12 +67,12 @@ trait BasicEnvironment extends Environment
protected def parentEnvironment: Option[BasicEnvironment] = None
/** The identifier used in messages to refer to this environment. */
def environmentLabel = envBackingPath.absolutePath
private[this] var isModified = false
private[sbt] def setEnvironmentModified(modified: Boolean) { synchronized { isModified = modified } }
private[this] def isEnvironmentModified = synchronized { isModified }
implicit val IntFormat: Format[Int] = new SimpleFormat[Int] { def fromString(s: String) = java.lang.Integer.parseInt(s) }
implicit val LongFormat: Format[Long] = new SimpleFormat[Long] { def fromString(s: String) = java.lang.Long.parseLong(s) }
implicit val DoubleFormat: Format[Double] = new SimpleFormat[Double] { def fromString(s: String) = java.lang.Double.parseDouble(s) }
@ -94,8 +94,8 @@ trait BasicEnvironment extends Environment
def fromString(s: String) = Version.fromString(s).fold(msg => error(msg), x => x)
}
implicit val FileFormat = Format.file
/** Implementation of 'Property' for user-defined properties. */
private[sbt] class UserProperty[T](lazyDefaultValue: => Option[T], format: Format[T], inheritEnabled: Boolean,
inheritFirst: Boolean, private[BasicEnvironment] val manifest: Manifest[T]) extends Property[T]
@ -126,22 +126,26 @@ trait BasicEnvironment extends Environment
case None =>
val inherited = inheritedValue
// note that the following means the default value will not be used if an exception occurs inheriting
inherited orElse
{
defaultValue match
{
case Some(v) => DefinedValue(v, false, true)
case None => inherited
}
}
inherited orElse getDefault(inherited)
}
private def resolveDefaultFirst =
(explicitValue() orElse defaultValue) match
explicitValue() match
{
case Some(v) => DefinedValue(v, false, explicitValue().isEmpty)
case None => inheritedValue
case Some(v) => DefinedValue(v, false, false)
case None => getDefault(inheritedValue)
}
private def getDefault(orElse: => PropertyResolution[T]): PropertyResolution[T] =
try
{
defaultValue match
{
case Some(v) => DefinedValue(v, false, true)
case None => orElse
}
} catch { case e: Exception =>
ResolutionException("Error while evaluating default value for property", Some(e))
}
private def inheritedValue: PropertyResolution[T] =
{
val propOption = if(inheritEnabled) parentProperty else None
@ -152,7 +156,7 @@ trait BasicEnvironment extends Environment
}
}
private def parentProperty = for(parent <- parentEnvironment; n <- name; prop <- parent.propertyMap.get(n)) yield prop
private def tryToInherit[R](prop: BasicEnvironment#UserProperty[R]): PropertyResolution[T] =
{
if(prop.manifest <:< manifest)
@ -167,9 +171,9 @@ trait BasicEnvironment extends Environment
case DefinedValue(v, isInherited, isDefault) => DefinedValue(v, true, isDefault)
case x => x
}
override def toString = nameString + "=" + resolve
/** Gets the explicitly set value converted to a 'String'.*/
private[sbt] def getStringValue: Option[String] = explicitValue().map(format.toString)
/** Explicitly sets the value for this property by converting the given string value.*/
@ -217,12 +221,12 @@ trait BasicEnvironment extends Environment
}
override def toString = name + "=" + resolve
}
def system[T](propertyName: String)(implicit format: Format[T]): Property[T] =
new SystemProperty[T](propertyName, None, format)
def systemOptional[T](propertyName: String, defaultValue: => T)(implicit format: Format[T]): Property[T] =
new SystemProperty[T](propertyName, Some(defaultValue), format)
def property[T](implicit manifest: Manifest[T], format: Format[T]): Property[T] =
new UserProperty[T](None, format, true, false, manifest)
def propertyLocal[T](implicit manifest: Manifest[T], format: Format[T]): Property[T] =
@ -231,7 +235,7 @@ trait BasicEnvironment extends Environment
propertyOptional(defaultValue, false)(manifest, format)
def propertyOptional[T](defaultValue: => T, inheritFirst: Boolean)(implicit manifest: Manifest[T], format: Format[T]): Property[T] =
new UserProperty[T](Some(defaultValue), format, true, inheritFirst, manifest)
private type AnyUserProperty = UserProperty[_]
/** Maps property name to property. The map is constructed by reflecting vals defined on this object,
* so it should not be referenced during initialization or else subclass properties will be missed.**/
@ -254,7 +258,7 @@ trait BasicEnvironment extends Environment
log.error("Error loading properties from " + environmentLabel + " : " + errorMsg)
map //.readOnly (not currently in 2.8)
}
def propertyNames: Iterable[String] = propertyMap.keys.toList
def getPropertyNamed(name: String): Option[UserProperty[_]] = propertyMap.get(name)
def propertyNamed(name: String): UserProperty[_] = propertyMap(name)

View File

@ -28,30 +28,30 @@ trait BasicIntegrationTesting extends ScalaIntegrationTesting with IntegrationTe
self: BasicScalaProject =>
import BasicScalaIntegrationTesting._
lazy val integrationTestCompile = integrationTestCompileAction
lazy val integrationTest = integrationTestAction
val integrationTestCompileConditional = new CompileConditional(integrationTestCompileConfiguration)
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] =
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

View File

@ -18,15 +18,16 @@ object ControlEvent extends Enumeration
val Start, Header, Finish = Value
}
abstract class Logger extends NotNull
abstract class Logger extends xsbt.CompileLogger with xsbt.IvyLogger
{
def getLevel: Level.Value
def setLevel(newLevel: Level.Value)
def enableTrace(flag: Boolean)
def traceEnabled: Boolean
def atLevel(level: Level.Value) = level.id >= getLevel.id
def trace(t: => Throwable): Unit
final def verbose(message: => String): Unit = debug(message)
final def debug(message: => String): Unit = log(Level.Debug, message)
final def info(message: => String): Unit = log(Level.Info, message)
final def warn(message: => String): Unit = log(Level.Warn, message)
@ -34,7 +35,7 @@ abstract class Logger extends NotNull
def success(message: => String): Unit
def log(level: Level.Value, message: => String): Unit
def control(event: ControlEvent.Value, message: => String): Unit
def logAll(events: Seq[LogEvent]): Unit
/** Defined in terms of other methods in Logger and should not be called from them. */
final def log(event: LogEvent)
@ -49,6 +50,14 @@ abstract class Logger extends NotNull
case c: ControlEvent => control(c.event, c.msg)
}
}
import xsbti.F0
def debug(msg: F0[String]): Unit = log(Level.Debug, msg)
def warn(msg: F0[String]): Unit = log(Level.Warn, msg)
def info(msg: F0[String]): Unit = log(Level.Info, msg)
def error(msg: F0[String]): Unit = log(Level.Error, msg)
def trace(msg: F0[Throwable]) = trace(msg.apply)
def log(level: Level.Value, msg: F0[String]): Unit = log(level, msg.apply)
}
/** Implements the level-setting methods of Logger.*/
@ -68,7 +77,7 @@ final class SynchronizedLogger(delegate: Logger) extends Logger
def setLevel(newLevel: Level.Value) { synchronized { delegate.setLevel(newLevel) } }
def enableTrace(enabled: Boolean) { synchronized { delegate.enableTrace(enabled) } }
def traceEnabled: Boolean = { synchronized { delegate.traceEnabled } }
def trace(t: => Throwable) { synchronized { delegate.trace(t) } }
def log(level: Level.Value, message: => String) { synchronized { delegate.log(level, message) } }
def success(message: => String) { synchronized { delegate.success(message) } }
@ -139,12 +148,12 @@ final class BufferedLogger(delegate: Logger) extends Logger
{
private[this] val buffers = wrap.Wrappers.weakMap[Thread, Buffer[LogEvent]]
private[this] var recordingAll = false
private[this] def getOrCreateBuffer = buffers.getOrElseUpdate(key, createBuffer)
private[this] def buffer = if(recordingAll) Some(getOrCreateBuffer) else buffers.get(key)
private[this] def createBuffer = new ListBuffer[LogEvent]
private[this] def key = Thread.currentThread
@deprecated def startRecording() = recordAll()
/** Enables buffering for logging coming from the current Thread. */
def record(): Unit = synchronized { buffers(key) = createBuffer }
@ -162,7 +171,7 @@ final class BufferedLogger(delegate: Logger) extends Logger
try { f }
finally { Control.trap(stopAll()) }
}
/** Flushes the buffer to the delegate logger for the current thread. This method calls logAll on the delegate
* so that the messages are written consecutively. The buffer is cleared in the process. */
def play(): Unit =
@ -194,7 +203,7 @@ final class BufferedLogger(delegate: Logger) extends Logger
playAll()
clearAll()
}
def setLevel(newLevel: Level.Value): Unit =
synchronized
{
@ -209,7 +218,7 @@ final class BufferedLogger(delegate: Logger) extends Logger
buffer.foreach{_ += new SetTrace(flag) }
delegate.enableTrace(flag)
}
def trace(t: => Throwable): Unit =
doBufferableIf(traceEnabled, new Trace(t), _.trace(t))
def success(message: => String): Unit =
@ -246,18 +255,18 @@ final class BufferedLogger(delegate: Logger) extends Logger
object ConsoleLogger
{
private val formatEnabled = ansiSupported && !formatExplicitlyDisabled
private[this] def formatExplicitlyDisabled = java.lang.Boolean.getBoolean("sbt.log.noformat")
private[this] def ansiSupported =
try { jline.Terminal.getTerminal.isANSISupported }
catch { case e: Exception => !isWindows }
private[this] def os = System.getProperty("os.name")
private[this] def isWindows = os.toLowerCase.indexOf("windows") >= 0
}
/** A logger that logs to the console. On supported systems, the level labels are
* colored.
* colored.
*
* This logger is not thread-safe.*/
class ConsoleLogger extends BasicLogger
@ -311,7 +320,7 @@ class ConsoleLogger extends BasicLogger
System.out.println()
}
}
def logAll(events: Seq[LogEvent]) = System.out.synchronized { events.foreach(log) }
def control(event: ControlEvent.Value, message: => String)
{ log(labelColor(Level.Info), Level.Info.toString, Console.BLUE, message) }
@ -329,7 +338,7 @@ object Level extends Enumeration with NotNull
* uses this label. Because the label for levels is defined in this module, the success
* label is also defined here. */
val SuccessLabel = "success"
// added because elements was renamed to iterator in 2.8.0 nightly
def levels = Debug :: Info :: Warn :: Error :: Nil
/** Returns the level with the given name wrapped in Some, or None if no level exists for that name. */

View File

@ -5,47 +5,56 @@ package sbt
import scala.collection.immutable.TreeSet
private trait RunCompleteAction extends NotNull
private case class Exit(code: Int) extends RunCompleteAction
private object Reload extends RunCompleteAction
private case class Exit(code: Int) extends xsbti.Exit
{
require(code >= 0)
}
private case class Reboot(val scalaVersion: String, argsList: List[String], configuration: xsbti.AppConfiguration) extends xsbti.Reboot
{
def app = configuration.provider.id
def arguments = argsList.toArray
def baseDirectory = configuration.baseDirectory
}
/** 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
{
/** The entry point for sbt. If arguments are specified, they are interpreted as actions, executed,
* and then the program terminates. If no arguments are specified, the program enters interactive
* mode. Call run if you need to run sbt in the same JVM.*/
def main(args: Array[String])
{
val exitCode = run(args)
if(exitCode == RebootExitCode)
{
println("Rebooting is not supported when the sbt loader is not used.")
println("Please manually restart sbt.")
}
System.exit(exitCode)
}
val RebootExitCode = -1
val NormalExitCode = 0
val SetupErrorExitCode = 1
val SetupDeclinedExitCode = 2
val LoadErrorExitCode = 3
val UsageErrorExitCode = 4
val BuildErrorExitCode = 5
def run(args: Array[String]): Int =
val ProgramErrorExitCode = 6
}
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 =
{
try { run(configuration, remainingArguments, buildScalaVersion) }
catch { case re: ReloadException => run0(re.remainingArguments, re.buildScalaVersion) }
}
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 match
Project.loadProject(configuration.provider, buildScalaVersion) match
{
case err: LoadSetupError =>
println("\n" + err.message)
ExitHooks.runExitHooks(Project.bootLogger)
SetupErrorExitCode
Exit(SetupErrorExitCode)
case LoadSetupDeclined =>
ExitHooks.runExitHooks(Project.bootLogger)
SetupDeclinedExitCode
Exit(SetupDeclinedExitCode)
case err: LoadError =>
{
val log = Project.bootLogger
@ -63,81 +72,138 @@ object Main
}
line match
{
case Some(l) => if(!isTerminateAction(l)) run(args) else NormalExitCode
case None => LoadErrorExitCode
case Some(l) => if(!isTerminateAction(l)) run(configuration, remainingArguments, buildScalaVersion) else Exit(NormalExitCode)
case None => Exit(LoadErrorExitCode)
}
}
case success: LoadSuccess =>
{
import success.project
val doNext: RunCompleteAction =
// in interactive mode, fill all undefined properties
if(args.length > 0 || fillUndefinedProjectProperties(project.projectClosure.toList.reverse))
startProject(project, args, startTime)
else
new Exit(NormalExitCode)
ExitHooks.runExitHooks(project.log)
doNext match
try
{
case Reload => run(args)
case x: Exit => x.code
// 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) }
}
}
}
private def startProject(project: Project, args: Array[String], startTime: Long): RunCompleteAction =
{
project.log.info("Building project " + project.name + " " + project.version.toString + " using " + project.getClass.getName)
val scalaVersionOpt = ScalaVersion.current orElse project.scalaVersion.get
for(sbtVersion <- project.sbtVersion.get; scalaVersion <- scalaVersionOpt if !sbtVersion.isEmpty && !scalaVersion.isEmpty)
project.log.info(" with sbt " + sbtVersion + " and Scala " + scalaVersion)
args match
private def initialize(args: List[String]): List[String] =
args.lastOption match
{
case Array() =>
CrossBuild.load() match
{
case None =>
project.log.info("No actions specified, interactive session started. Execute 'help' for more information.")
val doNext = interactive(project)
printTime(project, startTime, "session")
doNext
case Some(cross) =>
crossBuildNext(project, cross)
new Exit(RebootExitCode)
}
case CrossBuild(action) =>
val exitCode =
CrossBuild.load() match
{
case None => if(startCrossBuild(project, action)) RebootExitCode else BuildErrorExitCode
case Some(cross) => if(crossBuildNext(project, cross)) RebootExitCode else NormalExitCode
}
new Exit(exitCode)
case _ =>
val source = args.toList.elements
def nextCommand = if(source.hasNext) Right(source.next) else Left(new Exit(NormalExitCode) )
val result = loop(project, project, p => nextCommand, false)
result match
{
case Exit(NormalExitCode) => project.log.success("Build completed successfully.")
case Exit(_) => project.log.error("Error during build.")
case _ => ()
}
printTime(project, startTime, "build")
result
case None => InteractiveCommand :: Nil
case Some(InteractiveCommand | 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 " + project.sbtVersion.value + " and Scala " + project.scalaVersion.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
}
}
private def crossBuildNext(project: Project, cross: CrossBuild) =
private def processArguments(baseProject: Project, arguments: List[String], configuration: xsbti.AppConfiguration, startTime: Long): xsbti.MainResult =
{
val setScalaVersion = (newVersion: String) => { System.setProperty(ScalaVersion.LiveKey, newVersion); () }
val complete =
if(handleAction(project, cross.command))
cross.versionComplete(setScalaVersion)
def process(project: Project, arguments: List[String], isInteractive: Boolean): xsbti.MainResult =
{
def saveProject(newArgs: List[String]) = if(baseProject.name != project.name) (ProjectAction + " " + project.name) :: newArgs else newArgs
arguments match
{
case "" :: tail => process(project, tail, isInteractive)
case (ExitCommand | QuitCommand) :: _ => Exit(NormalExitCode)
case RebootCommand :: tail => Reboot(project.scalaVersion.value, saveProject(tail), configuration)
case InteractiveCommand :: _ => process(project, prompt(baseProject, project) :: arguments, true)
case SpecificBuild(version, action) :: tail =>
if(Some(version) != baseProject.info.buildScalaVersion)
throw new ReloadException(saveProject(action :: tail), Some(version))
else
process(project, tail, isInteractive)
case CrossBuild(action) :: tail =>
if(checkAction(project, action)) process(project, CrossBuild(project, action) ::: tail, isInteractive)
else if(isInteractive) process(project, tail, isInteractive)
else Exit(UsageErrorExitCode)
case SetProject(name) :: tail =>
SetProject(baseProject, name, project) match
{
case Some(newProject) => process(newProject, tail, isInteractive)
case None => process(project, if(isInteractive) tail else ExitCommand :: tail, isInteractive)
}
case action :: tail =>
val success = processAction(baseProject, project, action, isInteractive)
if(success || isInteractive) process(project, tail, isInteractive) else Exit(BuildErrorExitCode)
case Nil => project.log.error("Invalid internal sbt state: no arguments"); Exit(ProgramErrorExitCode)
}
}
process(baseProject, arguments, false)
}
object SetProject
{
def unapply(s: String) =
if(s.startsWith(ProjectAction + " "))
Some(s.substring(ProjectAction.length + 1))
else
cross.error(setScalaVersion)
if(complete)
printTime(project, cross.startTime, "cross-build")
!complete
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
{
val pattern = """\+\+(\S+)\s*(.*)""".r.pattern
def unapply(s: String) =
{
val m = pattern.matcher(s)
if(m.matches)
Some(m.group(1).trim, m.group(2).trim)
else
None
}
}
object CrossBuild
{
def unapply(s: String) = if(s.startsWith("+") && !s.startsWith("++")) Some(s.substring(1)) else None
def apply(project: Project, action: 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...")
action :: Nil
}
else
againstScalaVersions.toList.map("++" + _ + " " + action)
}
}
// todo: project.log.info("No actions specified, interactive session started. Execute 'help' for more information.")
private def prompt(baseProject: Project, project: Project): String =
{
val projectNames = baseProject.projectClosure.map(_.name)
val prefixes = ContinuousExecutePrefix :: CrossBuildPrefix :: Nil
val completors = new Completors(ProjectAction, projectNames, interactiveCommands, List(GetAction, SetAction), prefixes)
val reader = new JLineReader(baseProject.historyPath, completors, baseProject.log)
val methodCompletions = for( (name, method) <- project.methods) yield (name, method.completions)
reader.setVariableCompletions(project.taskNames, project.propertyNames, methodCompletions)
reader.readLine("> ").getOrElse(ExitCommand)
}
/** The name of the command that loads a console with access to the current project through the variable 'project'.*/
@ -150,18 +216,19 @@ object Main
val ProjectAction = "project"
/** The name of the command that shows all available projects.*/
val ShowProjectsAction = "projects"
val ExitCommand = "exit"
val QuitCommand = "quit"
val InteractiveCommand = "shell"
/** The list of lowercase command names that may be used to terminate the program.*/
val TerminateActions: Iterable[String] = "exit" :: "quit" :: Nil
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 rebooting sbt. Requires sbt to have been launched by the loader.*/
val RebootCommand = "reboot"
/** The name of the command that reloads a project. This is useful for when the project definition has changed. */
val ReloadAction = "reload"
/** 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. */
@ -170,8 +237,6 @@ object Main
val ContinuousExecutePrefix = "~"
/** The prefix used to identify a request to execute the remaining input across multiple Scala versions.*/
val CrossBuildPrefix = "+"
/** Error message for when the user tries to prefix an action with CrossBuildPrefix but the loader is not used.*/
val CrossBuildUnsupported = "Cross-building is not supported when the loader is not used."
/** The number of seconds between polling by the continuous compile command.*/
val ContinuousCompilePollDelaySeconds = 1
@ -183,96 +248,24 @@ object Main
private def logLevels: Iterable[String] = TreeSet.empty[String] ++ Level.levels.map(_.toString)
/** The list of all interactive commands other than logging level.*/
private def basicCommands: Iterable[String] = TreeSet(ShowProjectsAction, ShowActions, ShowCurrent, HelpAction,
RebootCommand, ReloadAction, TraceCommand, ContinuousCompileCommand, ProjectConsoleAction)
RebootCommand, TraceCommand, ContinuousCompileCommand, ProjectConsoleAction)
/** Enters interactive mode for the given root project. It uses JLine for tab completion and
* history. It returns normally when the user terminates or reloads the interactive session. That is,
* it does not call System.exit to quit.
**/
private def interactive(baseProject: Project): RunCompleteAction =
{
val projectNames = baseProject.projectClosure.map(_.name)
val prefixes = ContinuousExecutePrefix :: CrossBuildPrefix :: Nil
val completors = new Completors(ProjectAction, projectNames, interactiveCommands, List(GetAction, SetAction), prefixes)
val reader = new JLineReader(baseProject.historyPath, completors, baseProject.log)
def updateTaskCompletions(project: Project)
private def processAction(baseProject: Project, currentProject: Project, action: String, isInteractive: Boolean): Boolean =
action match
{
val methodCompletions = for( (name, method) <- project.methods) yield (name, method.completions)
reader.setVariableCompletions(project.taskNames, project.propertyNames, methodCompletions)
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
handleCommand(currentProject, action)
}
def readCommand(currentProject: Project) =
{
updateTaskCompletions(currentProject) // this is done before every command because the completions could change due to the action previously invoked
reader.readLine("> ").toRight(new Exit(NormalExitCode))
}
loop(baseProject, baseProject, readCommand, true)
}
/** Prompts the user for the next command using 'currentProject' as context.
* If the command indicates that the user wishes to terminate or reload the session,
* the function returns the appropriate value.
* Otherwise, the command is handled and this function is called again
* (tail recursively) to prompt for the next command. */
private def loop(baseProject: Project, currentProject: Project, nextCommand: Project => Either[RunCompleteAction, String], isInteractive: Boolean): RunCompleteAction =
nextCommand(currentProject).right.flatMap{ line => process(baseProject, currentProject, line, isInteractive) } match
{
case Left(complete) => complete
case Right(project) => loop(baseProject, project, nextCommand, isInteractive)
}
private def process(baseProject: Project, currentProject: Project, line: String, isInteractive: Boolean): Either[RunCompleteAction, Project] =
{
def keepCurrent(success: Boolean) = if(success) Right(currentProject) else Left(new Exit(BuildErrorExitCode) )
def interactiveKeepCurrent(success: Boolean) = keepCurrent(success || isInteractive)
def keep(u: Unit) = Right(currentProject)
val trimmed = line.trim
if(trimmed.isEmpty)
Right(currentProject)
else if(isTerminateAction(trimmed))
Left(new Exit(NormalExitCode))
else if(ReloadAction == trimmed)
Left(Reload)
else if(RebootCommand == trimmed)
{
if(!isInteractive) currentProject.log.warn("'reboot' does not pick up changes to 'scala.version' in batch mode.")
System.setProperty(ScalaVersion.LiveKey, "")
Left(new Exit(RebootExitCode))
}
else if(trimmed.startsWith(CrossBuildPrefix))
{
if(startCrossBuild(currentProject, trimmed.substring(CrossBuildPrefix.length).trim))
Left(new Exit(RebootExitCode))
else
Right(currentProject)
}
else if(trimmed.startsWith(ProjectAction + " "))
{
val projectName = trimmed.substring(ProjectAction.length + 1)
baseProject.projectClosure.find(_.name == projectName) match
{
case Some(newProject) =>
printProject("Set current project to ", newProject)
Right(newProject)
case None =>
currentProject.log.error("Invalid project name '" + projectName + "' (type 'projects' to list available projects).")
keepCurrent(isInteractive)
}
}
else if(trimmed == HelpAction)
keep(displayHelp(isInteractive))
else if(trimmed == ShowProjectsAction)
keep(baseProject.projectClosure.foreach(listProject))
else if(trimmed.startsWith(SetAction + " "))
interactiveKeepCurrent( setProperty(currentProject, trimmed.substring(SetAction.length + 1)) )
else if(trimmed.startsWith(GetAction + " "))
interactiveKeepCurrent( getProperty(currentProject, trimmed.substring(GetAction.length + 1)) )
else if(trimmed == ProjectConsoleAction)
interactiveKeepCurrent(showResult(Run.projectConsole(currentProject), currentProject.log))
else
interactiveKeepCurrent( handleCommand(currentProject, trimmed) )
}
private def printCmd(name:String, desc:String) = Console.println("\t" + 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 " +
@ -288,9 +281,8 @@ object Main
printCmd(ContinuousExecutePrefix + " <command>", "Executes the project specified action or method whenever source files change.")
printCmd(CrossBuildPrefix + " <command>", "Executes the project specified action or method for all versions of Scala defined in crossScalaVersions.")
printCmd(ShowActions, "Shows all available actions.")
printCmd(RebootCommand, "Changes to scala.version or sbt.version are processed and the project definition is reloaded.")
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(ReloadAction, "Reloads sbt, recompiling modified project definitions if necessary.")
printCmd(ShowCurrent, "Shows the current project and logging level of that project.")
printCmd(Level.levels.mkString(", "), "Set logging for the current project to the specified level.")
printCmd(TraceCommand, "Toggles whether logging stack traces is enabled.")
@ -300,31 +292,13 @@ object Main
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 bound to the variable 'current' and all members imported.")
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)
private def startCrossBuild(project: Project, action: String) =
{
checkBooted && checkAction(project, action) &&
{
val againstScalaVersions = project.crossScalaVersions
val versionsDefined = !againstScalaVersions.isEmpty
if(versionsDefined)
CrossBuild(againstScalaVersions, action, System.currentTimeMillis)
else
Console.println("Project does not declare any Scala versions to cross-build against.")
versionsDefined
}
}
private def checkBooted =
Project.booted ||
{
Console.println(CrossBuildUnsupported)
false
}
/** 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 =
{
@ -530,7 +504,7 @@ object Main
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 'reboot' command or restarting sbt.")
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) =>
@ -586,77 +560,9 @@ object Main
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 class CrossBuild(val remainingScalaVersions: Set[String], val command: String, val startTime: Long)
{
def error(setScalaVersion: String => Unit) = clearScalaVersion(setScalaVersion)
private def clearScalaVersion(setScalaVersion: String => Unit) =
private final class ReloadException(val remainingArguments: List[String], val buildScalaVersion: Option[String]) extends RuntimeException
{
CrossBuild.clear()
setScalaVersion("")
true
override def fillInStackTrace = this
}
def versionComplete(setScalaVersion: String => Unit) =
{
val remaining = remainingScalaVersions - ScalaVersion.currentString
if(remaining.isEmpty)
clearScalaVersion(setScalaVersion)
else
{
CrossBuild.setProperties(remaining, command, startTime.toString)
setScalaVersion(remaining.toSeq.first)
false
}
}
}
private object CrossBuild
{
private val RemainingScalaVersionsKey = "sbt.remaining.scala.versions"
private val CrossCommandKey = "sbt.cross.build.command"
private val StartTimeKey = "sbt.cross.start.time"
private def setProperties(remainingScalaVersions: Set[String], command: String, startTime: String)
{
System.setProperty(RemainingScalaVersionsKey, remainingScalaVersions.mkString(" "))
System.setProperty(CrossCommandKey, command)
System.setProperty(StartTimeKey, startTime)
}
private def getProperty(key: String) =
{
val value = System.getProperty(key)
if(value == null)
""
else
value.trim
}
private def clear() { setProperties(Set.empty, "", "") }
def load() =
{
val command = getProperty(CrossCommandKey)
val remaining = getProperty(RemainingScalaVersionsKey)
val startTime = getProperty(StartTimeKey)
if(command.isEmpty || remaining.isEmpty || startTime.isEmpty)
None
else
Some(new CrossBuild(Set(remaining.split(" ") : _*), command, startTime.toLong))
}
def apply(remainingScalaVersions: Set[String], command: String, startTime: Long) =
{
setProperties(remainingScalaVersions, command, startTime.toString)
new CrossBuild(remainingScalaVersions, command, startTime)
}
import Main.CrossBuildPrefix
def unapply(s: String): Option[String] =
{
val trimmed = s.trim
if(trimmed.startsWith(CrossBuildPrefix))
Some(trimmed.substring(CrossBuildPrefix.length).trim)
else
None
}
def unapply(s: Array[String]): Option[String] =
s match
{
case Array(CrossBuild(crossBuildAction)) => Some(crossBuildAction)
case _ => None
}
}

View File

@ -3,6 +3,8 @@
*/
package sbt
import xsbti.{AppProvider, ScalaProvider}
import xsbt.{AnalyzingCompiler, ComponentManager, ScalaInstance}
import java.io.File
import java.net.URLClassLoader
import scala.collection._
@ -20,9 +22,9 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
lg
}
protected def defaultLoggingLevel = Level.Info
trait ActionOption extends NotNull
/** Basic project information. */
def info: ProjectInfo
/** The project name. */
@ -33,7 +35,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
final 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. */
@ -51,7 +53,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
/** 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
@ -83,7 +85,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
* 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
@ -98,7 +100,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
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
@ -128,7 +130,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
}
}
}
/** Logs the list of projects at the debug level.*/
private def showBuildOrder(order: Iterable[Project])
{
@ -136,22 +138,22 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
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), log), path)
def project(path: Path, deps: Project*): Project = getProject(Project.loadProject(path, deps, Some(this), log, 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.*/
@ -164,8 +166,8 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
* 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))(log)), Some(new SetupInfo(name, None, None, false)), log)
initialize(construct(ProjectInfo(path.asFile, deps, Some(this))(log, 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.
@ -177,11 +179,11 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
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
@ -193,7 +195,7 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
def outputPath = crossPath(outputRootPath)
def outputRootPath: Path = outputDirectoryName
def outputDirectoryName = DefaultOutputDirectoryName
private def getProject(result: LoadResult, path: Path): Project =
result match
{
@ -202,31 +204,50 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
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 build this project with by default. This property is only
* ready by `sbt` on startup and reboot. When cross-building, this value may be different from the actual
* version of Scala being used to build the project. ScalaVersion.current and ScalaVersion.cross should be used
* to read the version of Scala building the project. This should only be used to change the version of Scala used
* for normal development (not cross-building)*/
final val scalaVersion = propertyOptional[String]("", true)
final val sbtVersion = propertyOptional[String]("", 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 (defined initially by buildInitScalaVersion).
* This property is only read by `sbt` on startup and reload. It is the definitive source for the version of Scala
* that sbt and the project definition are using.*/
final val scalaVersion = property[String]
final val sbtVersion = property[String]
final val projectInitialize = propertyOptional[Boolean](false)
final val projectScratch = propertyOptional[Boolean](false, true)
/** The property that defines the version of Scala to build this project with by default. This can be
* different from the version of Scala used to build and run the project definition (defined by scalaVersion).
* This property is only read by `sbt` on startup and reload. When cross-building, this value may be different from the actual
* version of Scala being used to build the project. info.scalaVersion is always the definitive source for the current Scala version.
* This property should only be used to change the version of Scala used for normal development (not cross-building).*/
final val buildInitScalaVersion = propertyOptional[String](scalaVersion.value, true)
/** The definitive source for the version of Scala being used to *build* the project.*/
def buildScalaVersion = info.buildScalaVersion.getOrElse(buildInitScalaVersion.value)
/** If this project is cross-building, returns `base` with an additional path component containing the scala version.
* Otherwise, this returns `base`.
def componentManager = new xsbt.ComponentManager(info.app.components, log)
def buildScalaInstance =
localScalaInstances.find(_.version == buildScalaVersion) getOrElse
xsbt.ScalaInstance(buildScalaVersion, info.launcher)
lazy val localScalaInstances: Seq[ScalaInstance] = localScala ++ info.parent.toList.flatMap(_.localScalaInstances)
def localScala: Seq[ScalaInstance] = Nil
def buildCompiler = new AnalyzingCompiler(buildScalaInstance, componentManager)
def defineScala(home: File): ScalaInstance = ScalaInstance(home, info.launcher)
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) = ScalaVersion.withCross(disableCrossPaths)(base / ScalaVersion.crossString(_), base)
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 ScalaVersion.currentString
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 empty and cross-building is disabled. Overriding this to a Set of Scala versions
@ -241,15 +262,15 @@ trait Project extends TaskManager with Dag[Project] with BasicEnvironment
* 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
@ -265,14 +286,14 @@ object Project
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 =
{
@ -283,20 +304,22 @@ object Project
}
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: LoadResult = loadProject(bootLogger)
/** Loads the project in the current working directory.*/
private[sbt] def loadProject(log: Logger): LoadResult = checkOutputDirectories(loadProject(new File("."), Nil, None, log))
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): LoadResult =
loadProject(path.asFile, deps, parent, log)
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): LoadResult =
loadProject(projectDirectory, deps, parent, getClass.getClassLoader, log)
private[sbt] def loadProject(projectDirectory: File, deps: Iterable[Project], parent: Option[Project], additional: ClassLoader, log: Logger): LoadResult =
private[sbt] def loadProject(projectDirectory: File, deps: Iterable[Project], parent: Option[Project], log: Logger, app: AppProvider, buildScalaVersion: Option[String]): LoadResult =
loadProject(projectDirectory, deps, parent, getClass.getClassLoader, log, app, buildScalaVersion)
private[sbt] def loadProject(projectDirectory: File, deps: Iterable[Project], parent: Option[Project], additional: ClassLoader, log: Logger, app: AppProvider, buildScalaVersion: Option[String]): LoadResult =
{
val info = ProjectInfo(projectDirectory, deps, parent)(log)
val info = ProjectInfo(projectDirectory, deps, parent)(log, app, buildScalaVersion)
ProjectInfo.setup(info, log) match
{
case err: SetupError => new LoadSetupError(err.message)
@ -377,7 +400,8 @@ object Project
{
val pluginProjectPath = info.builderPath / PluginProjectDirectoryName
val additionalPaths = additional match { case u: URLClassLoader => u.getURLs.map(url => Path.fromFile(FileUtilities.toFile(url))); case _ => Array[Path]() }
val builderProject = new BuilderProject(ProjectInfo(builderProjectPath.asFile, Nil, None)(buildLog), pluginProjectPath, additionalPaths, buildLog)
val builderInfo = ProjectInfo(builderProjectPath.asFile, Nil, None)(buildLog, info.app, Some(info.definitionScalaVersion))
val builderProject = new BuilderProject(builderInfo, pluginProjectPath, additionalPaths, buildLog)
builderProject.compile.run.toLeft(()).right.flatMap { ignore =>
builderProject.projectDefinition.right.map {
case Some(definition) => getProjectClass[Project](definition, builderProject.projectClasspath, additional)
@ -447,7 +471,7 @@ object Project
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)
{
@ -456,7 +480,7 @@ object Project
project.log.info(projectHeader)
project.log.info("=" * projectHeader.length)
}
def rootProject(p: Project): Project =
p.info.parent match
{

View File

@ -4,10 +4,16 @@
package sbt
import java.io.File
import xsbti.{AppProvider, ScalaProvider}
import FileUtilities._
final case class ProjectInfo(projectDirectory: File, dependencies: Iterable[Project], parent: Option[Project])(log: Logger) extends NotNull
// provider is for the build, not the build definition
final case class ProjectInfo(projectDirectory: File, dependencies: Iterable[Project], parent: Option[Project])
(log: Logger, val app: AppProvider, val buildScalaVersion: Option[String]) extends NotNull
{
def definitionScalaVersion = app.scalaProvider.version
def launcher = app.scalaProvider.launcher
val logger = new FilterLogger(log)
val projectPath: Path =
{
@ -34,7 +40,7 @@ object ProjectInfo
{
val MetadataDirectoryName = "project"
private val DefaultOrganization = "empty"
def setup(info: ProjectInfo, log: Logger): SetupResult =
{
val builderDirectory = info.builderPath.asFile
@ -81,7 +87,7 @@ object ProjectInfo
}
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) "
@ -89,7 +95,7 @@ object ProjectInfo
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: "))

View File

@ -4,32 +4,34 @@
package sbt
import java.io.File
import xsbti.AppProvider
import FileUtilities._
object Resources
{
def apply(basePath: String) =
def apply(basePath: String, provider: AppProvider, buildScalaVersion: Option[String]) =
{
require(basePath.startsWith("/"))
val resource = getClass.getResource(basePath)
if(resource == null)
throw new Exception("Resource base directory '" + basePath + "' not on classpath.")
error("Resource base directory '" + basePath + "' not on classpath.")
else
{
val file = toFile(resource)
if(file.exists)
new Resources(file)
new Resources(file, provider, buildScalaVersion)
else
throw new Exception("Resource base directory '" + basePath + "' does not exist.")
error("Resource base directory '" + basePath + "' does not exist.")
}
}
private val LoadErrorPrefix = "Error loading initial project: "
}
class Resources(val baseDirectory: File, additional: ClassLoader)
class Resources(val baseDirectory: File, additional: ClassLoader, app: AppProvider, buildScalaVersion: Option[String])
{
def this(baseDirectory: File) = this(baseDirectory, getClass.getClassLoader)
def this(baseDirectory: File, provider: AppProvider, buildScalaVersion: Option[String]) =
this(baseDirectory, getClass.getClassLoader, provider, buildScalaVersion)
import Resources._
// The returned directory is not actually read-only, but it should be treated that way
def readOnlyResourceDirectory(group: String, name: String): Either[String, File] =
@ -62,7 +64,7 @@ class Resources(val baseDirectory: File, additional: ClassLoader)
}
doInTemporaryDirectory(log)(readWrite(readOnly))
}
def withProject[T](projectDirectory: File, log: Logger)(f: Project => WithProjectResult[T]): Either[String, T] =
readWriteResourceDirectory(projectDirectory, log)(withProject(log)(f))
def withProject[T](group: String, name: String, log: Logger)(f: Project => WithProjectResult[T]): Either[String, T] =
@ -87,7 +89,7 @@ class Resources(val baseDirectory: File, additional: ClassLoader)
Left(msg)
}
buffered.recordAll()
resultToEither(Project.loadProject(dir, Nil, None, additional, buffered)) match
resultToEither(Project.loadProject(dir, Nil, None, additional, buffered, app, buildScalaVersion)) match
{
case Left(msg) =>
reload match

View File

@ -13,17 +13,10 @@ import java.net.{URL, URLClassLoader}
trait ScalaRun
{
def console(classpath: Iterable[Path], log: Logger): Option[String]
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger): Option[String]
}
class ForkRun(config: ForkScalaRun) extends ScalaRun
{
def console(classpath: Iterable[Path], log: Logger): Option[String] =
{
error("Forking the interpreter is not implemented.")
//val exitCode = Fork.scala(config.javaHome, config.runJVMOptions, config.scalaJars, classpathOption(classpath), config.workingDirectory, log)
//processExitCode(exitCode, "interpreter")
}
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger): Option[String] =
{
val scalaOptions = classpathOption(classpath) ::: mainClass :: options.toList
@ -42,31 +35,21 @@ class ForkRun(config: ForkScalaRun) extends ScalaRun
Some("Nonzero exit code returned from " + label + ": " + exitCode)
}
}
class Run(compiler: xsbt.AnalyzingCompiler) extends ScalaRun
{
/** Runs the class 'mainClass' using the given classpath and options using the scala runner.*/
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger) =
{
def execute = compiler.run(Set() ++ classpath.map(_.asFile), mainClass, options, log)
Run.executeTrapExit( execute, log )
}
}
/** This module is an interface to starting the scala interpreter or runner.*/
object Run extends ScalaRun
object Run
{
/** Starts an interactive scala interpreter session with the given classpath.*/
def console(classpath: Iterable[Path], log: Logger) =
createSettings(log)
{
(settings: Settings) =>
{
settings.classpath.value = Path.makeString(classpath)
log.info("Starting scala interpreter...")
log.debug(" Classpath: " + settings.classpath.value)
log.info("")
Control.trapUnit("Error during session: ", log)
{
JLine.withJLine {
val loop = new InterpreterLoop
executeTrapExit(loop.main(settings), log)
}
}
}
}
/** Executes the given function, trapping calls to System.exit. */
private def executeTrapExit(f: => Unit, log: Logger): Option[String] =
private[sbt] def executeTrapExit(f: => Unit, log: Logger): Option[String] =
{
val exitCode = TrapExit(f, log)
if(exitCode == 0)
@ -77,37 +60,6 @@ object Run extends ScalaRun
else
Some("Nonzero exit code: " + exitCode)
}
/** Runs the class 'mainClass' using the given classpath and options using the scala runner.*/
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger) =
{
createSettings(log) { (settings: Settings) =>
Control.trapUnit("Error during run: ", log)
{
val classpathURLs = classpath.map(_.asURL).toList
val bootClasspath = FileUtilities.pathSplit(settings.bootclasspath.value)
val extraURLs =
for(pathString <- bootClasspath if pathString.length > 0) yield
(new java.io.File(pathString)).toURI.toURL
log.info("Running " + mainClass + " ...")
log.debug(" Classpath:" + (classpathURLs ++ extraURLs).mkString("\n\t", "\n\t",""))
def execute =
try { ObjectRunner.run(classpathURLs ++ extraURLs, mainClass, options.toList) }
catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
executeTrapExit( execute, log )
}
}
}
/** If mainClassOption is None, then the interactive scala interpreter is started with the given classpath.
* Otherwise, the class wrapped by Some is run using the scala runner with the given classpath and
* options. */
def apply(mainClassOption: Option[String], classpath: Iterable[Path], options: Seq[String], log: Logger) =
{
mainClassOption match
{
case Some(mainClass) => run(mainClass, classpath, options, log)
case None => console(classpath, log)
}
}
/** Create a settings object and execute the provided function if the settings are created ok.*/
private def createSettings(log: Logger)(f: Settings => Option[String]) =
{
@ -140,8 +92,9 @@ object Run extends ScalaRun
}}
}
/** A custom InterpreterLoop with the purpose of creating an interpreter with Project 'project' bound to the value 'current',
* and the following two lines interpreted:
* 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 classpath

View File

@ -11,14 +11,14 @@ import scala.collection.mutable.ListBuffer
trait SimpleScalaProject extends ExecProject
{
def errorTask(message: String) = task{ Some(message) }
trait CleanOption extends ActionOption
case class ClearAnalysis(analysis: TaskAnalysis[_, _, _]) extends CleanOption
case class Preserve(paths: PathFinder) extends CleanOption
case class CompileOption(val asString: String) extends ActionOption
case class JavaCompileOption(val asString: String) extends ActionOption
val Deprecation = CompileOption("-deprecation")
val ExplainTypes = CompileOption("-explaintypes")
val Optimize = CompileOption("-optimise")
@ -33,7 +33,7 @@ trait SimpleScalaProject extends ExecProject
val Java1_4 = Value("jvm-1.4")
val Msil = Value("msil")
}
def cleanTask(paths: PathFinder, options: CleanOption*): Task =
cleanTask(paths, options)
def cleanTask(paths: PathFinder, options: => Seq[CleanOption]): Task =
@ -57,17 +57,17 @@ trait SimpleScalaProject extends ExecProject
trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProject with Exec
{
import ScalaProject._
final case class MaxCompileErrors(val value: Int) extends CompileOption("") with ScaladocOption { def asList = Nil }
trait PackageOption extends ActionOption
trait TestOption extends ActionOption
case class TestSetup(setup: () => Option[String]) extends TestOption
case class TestCleanup(cleanup: () => Option[String]) extends TestOption
case class ExcludeTests(tests: Iterable[String]) extends TestOption
case class TestListeners(listeners: Iterable[TestReportListener]) extends TestOption
case class TestFilter(filterTest: String => Boolean) extends TestOption
case class JarManifest(m: Manifest) extends PackageOption
{
assert(m != null)
@ -80,8 +80,8 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
val converted = for( (name,value) <- attributes ) yield (new Attributes.Name(name), value)
new ManifestAttributes(converted : _*)
}
trait ScaladocOption extends ActionOption
{
def asList: List[String]
@ -105,7 +105,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
def stylesheetFile(path: Path) = CompoundDocOption("-stylesheetfile", path.asFile.getAbsolutePath)
def documentTop(topText: String) = CompoundDocOption("-top", topText)
def windowTitle(title: String) = CompoundDocOption("-windowtitle", title)
object Access extends Enumeration
{
val Public = Value("public")
@ -123,17 +123,16 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
val classes = conditional.analysis.allProducts.flatMap(path => Path.relativize(compilePath.asFile, path.asFile))
classes.map(_.replace(java.io.File.separatorChar, '.').toList.dropRight(".class".length).mkString).toSeq
}
def consoleTask(classpath : PathFinder): Task =
consoleTask(classpath, Run)
def consoleTask(classpath : PathFinder, runner: ScalaRun): Task =
interactiveTask { runner.console(classpath.get, log) }
def runTask(mainClass: => Option[String], classpath: PathFinder, options: String*): Task =
def consoleTask(classpath: PathFinder): Task = consoleTask(classpath, "")
def consoleTask(classpath: PathFinder, initialCommands: => String): Task =
interactiveTask {
(new Console(buildCompiler))(classpath.get, initialCommands, log)
}
def runTask(mainClass: => Option[String], classpath: PathFinder, options: String*)(implicit runner: ScalaRun): Task =
runTask(mainClass, classpath, options)
def runTask(mainClass: => Option[String], classpath: PathFinder, options: => Seq[String]): Task =
runTask(mainClass, classpath, options, Run)
def runTask(mainClass: => Option[String], classpath: PathFinder, options: => Seq[String], runner: ScalaRun): Task =
def runTask(mainClass: => Option[String], classpath: PathFinder, options: => Seq[String])(implicit runner: ScalaRun): Task =
task
{
mainClass match
@ -142,7 +141,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
case None => Some("No main class specified.")
}
}
def syncPathsTask(sources: PathFinder, destinationDirectory: Path): Task =
task { FileUtilities.syncPaths(sources, destinationDirectory, log) }
def syncTask(sourceDirectory: Path, destinationDirectory: Path): Task =
@ -177,10 +176,9 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
def scaladocTask(label: String, sources: PathFinder, outputDirectory: Path, classpath: PathFinder, options: => Seq[ScaladocOption]): Task =
task
{
val classpathString = Path.makeString(classpath.get)
val optionsLocal = options
val maxErrors = maximumErrors(optionsLocal)
(new Scaladoc(maxErrors))(label, sources.get, classpathString, outputDirectory, optionsLocal.flatMap(_.asList), log)
(new Scaladoc(maxErrors, buildCompiler))(label, sources.get, classpath.get, outputDirectory, optionsLocal.flatMap(_.asList), log)
}
def packageTask(sources: PathFinder, outputDirectory: Path, jarName: => String, options: PackageOption*): Task =
@ -206,7 +204,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
{
option match
{
case JarManifest(mergeManifest) =>
case JarManifest(mergeManifest) =>
{
mergeAttributes(manifest.getMainAttributes, mergeManifest.getMainAttributes)
val entryMap = new MutableMapWrapper(manifest.getEntries)
@ -258,7 +256,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
val excludeTestsSet = new HashSet[String]
val setup, cleanup = new ListBuffer[() => Option[String]]
val testListeners = new ListBuffer[TestReportListener]
for(option <- options)
{
option match
@ -271,7 +269,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
}
() // 2.8.0-SNAPSHOT bug in type inference
}
if(excludeTestsSet.size > 0 && log.atLevel(Level.Debug))
{
log.debug("Excluding tests: ")
@ -282,12 +280,12 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
TestFramework.testTasks(frameworks, classpath.get, tests, log, testListeners.readOnly, false, setup.readOnly, cleanup.readOnly)
}
private def flatten[T](i: Iterable[Iterable[T]]) = i.flatMap(x => x)
protected def testQuickMethod(testAnalysis: CompileAnalysis, options: => Seq[TestOption])(toRun: Seq[TestOption] => Task) =
multiTask(testAnalysis.allTests.map(_.testClassName).toList) { includeFunction =>
toRun(TestFilter(includeFunction) :: options.toList)
}
protected final def maximumErrors[T <: ActionOption](options: Seq[T]) =
(for( MaxCompileErrors(maxErrors) <- options) yield maxErrors).firstOption.getOrElse(DefaultMaximumCompileErrors)
}
@ -301,12 +299,12 @@ trait WebScalaProject extends ScalaProject
val webInfPath = warPath / "WEB-INF"
val webLibDirectory = webInfPath / "lib"
val classesTargetDirectory = webInfPath / "classes"
val (libs, directories) = classpath.get.toList.partition(ClasspathUtilities.isArchive)
val classesAndResources = descendents(Path.lazyPathFinder(directories) ##, "*")
if(log.atLevel(Level.Debug))
directories.foreach(d => log.debug(" Copying the contents of directory " + d + " to " + classesTargetDirectory))
import FileUtilities.{copy, copyFlat, copyFilesFlat, clean}
(copy(webappContents.get, warPath, log).right flatMap { copiedWebapp =>
copy(classesAndResources.get, classesTargetDirectory, log).right flatMap { copiedClasses =>
@ -370,7 +368,7 @@ trait MultiTaskProject extends Project
filterInclude
run(includeFunction)
} completeWith allTests
}
trait ExecProject extends Project
{

View File

@ -1,47 +0,0 @@
/* sbt -- Simple Build Tool
* Copyright 2009 Mark Harrah
*/
package sbt
/** Provides access to the current version of Scala being used to build a project. These methods typically
* return None or the empty string when the loader is not used. */
object ScalaVersion
{
/** The name of the system property containing the Scala version used for this project.*/
private[sbt] val LiveKey = "sbt.scala.version"
private[sbt] def crossString(v: String) = "scala_" + v
/** Returns the current version of Scala being used to build the project, unless the sbt loader is not being used,
* in which case this is the empty string.*/
def currentString =
{
val v = System.getProperty(LiveKey)
if(v == null)
""
else
v.trim
}
/** Returns the current version of Scala being used to build the project. If the sbt loader is not being
* used, this returns None. Otherwise, the value returned by this method is fixed for the duration of
* a Project's existence. It only changes on reboot (during which a Project is recreated).*/
val current: Option[String] =
{
val sv = currentString
if(sv.isEmpty)
None
else
Some(sv)
}
private[sbt] def withCross[T](crossDisabled: Boolean)(withVersion: String => T, disabled: => T): T =
{
if(crossDisabled)
disabled
else
{
current match
{
case Some(scalaV) => withVersion(scalaV)
case _ => disabled
}
}
}
}

View File

@ -15,7 +15,7 @@ object JettyRunner
class JettyRunner(configuration: JettyConfiguration) extends ExitHook
{
ExitHooks.register(this)
def name = "jetty-shutdown"
def runBeforeExiting() { stop() }
private var running: Option[Stoppable] = None
@ -37,7 +37,7 @@ class JettyRunner(configuration: JettyConfiguration) extends ExitHook
val runner = ModuleUtilities.getObject(implClassName, lazyLoader).asInstanceOf[JettyRun]
runner(configuration)
}
if(running.isDefined)
Some("This instance of Jetty is already running.")
else
@ -55,7 +55,7 @@ class JettyRunner(configuration: JettyConfiguration) extends ExitHook
}
}
private val implClassName = "sbt.LazyJettyRun"
private def runError(e: Throwable, messageBase: String, log: Logger) =
{
log.trace(e)
@ -78,8 +78,6 @@ sealed trait JettyConfiguration extends NotNull
def scanInterval: Int
/** The classpath to get Jetty from. */
def jettyClasspath: PathFinder
/** The classpath containing the classes, jars, and resources for the web application. */
def classpath: PathFinder
def classpathName: String
def log: Logger
}
@ -87,6 +85,9 @@ trait DefaultJettyConfiguration extends JettyConfiguration
{
def contextPath: String
def port: Int
/** The classpath containing the classes, jars, and resources for the web application. */
def classpath: PathFinder
def parentLoader: ClassLoader
}
abstract class CustomJettyConfiguration extends JettyConfiguration
{
@ -108,17 +109,17 @@ private object LazyJettyRun extends JettyRun
import org.mortbay.log.Log
import org.mortbay.util.Scanner
import org.mortbay.xml.XmlConfiguration
import java.lang.ref.{Reference, WeakReference}
val DefaultMaxIdleTime = 30000
def apply(configuration: JettyConfiguration): Stoppable =
{
val oldLog = Log.getLog
Log.setLog(new JettyLogger(configuration.log))
val server = new Server
val listener =
configuration match
{
@ -126,11 +127,11 @@ private object LazyJettyRun extends JettyRun
import c._
configureDefaultConnector(server, port)
def classpathURLs = classpath.get.map(_.asURL).toSeq
def createLoader = new URLClassLoader(classpathURLs.toArray, this.getClass.getClassLoader)
def createLoader = new URLClassLoader(classpathURLs.toArray, parentLoader)
val webapp = new WebAppContext(war.absolutePath, contextPath)
webapp.setClassLoader(createLoader)
server.setHandler(webapp)
Some(new Scanner.BulkListener {
def filesChanged(files: java.util.List[_]) {
reload(server, webapp.setClassLoader(createLoader), log)
@ -143,7 +144,7 @@ private object LazyJettyRun extends JettyRun
(new XmlConfiguration(file.toURI.toURL)).configure(server)
None
}
def configureScanner() =
{
val scanDirectories = configuration.scanDirectories
@ -164,7 +165,7 @@ private object LazyJettyRun extends JettyRun
Some(new WeakReference(scanner))
}
}
try
{
server.start()
@ -219,7 +220,7 @@ private object LazyJettyRun extends JettyRun
{
def isDebugEnabled = delegate.atLevel(Level.Debug)
def setDebugEnabled(enabled: Boolean) = delegate.setLevel(if(enabled) Level.Debug else Level.Info)
def getLogger(name: String) = this
def info(msg: String, arg0: AnyRef, arg1: AnyRef) { delegate.info(format(msg, arg0, arg1)) }
def debug(msg: String, arg0: AnyRef, arg1: AnyRef) { delegate.debug(format(msg, arg0, arg1)) }