diff --git a/org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/TestsSuite.java b/org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/TestsSuite.java index f36b4b17a2..eae85b3fe2 100644 --- a/org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/TestsSuite.java +++ b/org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/TestsSuite.java @@ -1,5 +1,6 @@ package scala.tools.eclipse; +import scala.tools.eclipse.classpath.ClasspathTests; import scala.tools.eclipse.completion.CompletionTests; import scala.tools.eclipse.hyperlinks.HyperlinkDetectorTests; @@ -36,5 +37,6 @@ SbtBuilderTest.class, PresentationCompilerTest.class, OutputFoldersTest.class, + ClasspathTests.class, }) class TestsSuite { } diff --git a/org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/classpath/ClasspathTests.scala b/org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/classpath/ClasspathTests.scala new file mode 100644 index 0000000000..74aa3f72d3 --- /dev/null +++ b/org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/classpath/ClasspathTests.scala @@ -0,0 +1,244 @@ +package scala.tools.eclipse.classpath + +import scala.tools.eclipse.testsetup.TestProjectSetup +import org.junit.Assert._ +import org.junit.Test +import org.eclipse.jdt.core.JavaCore +import org.eclipse.core.resources.IResource +import org.eclipse.core.resources.IncrementalProjectBuilder +import org.eclipse.core.runtime.NullProgressMonitor +import org.eclipse.core.runtime.Path +import org.junit.Before +import org.eclipse.jdt.core.IClasspathEntry +import org.eclipse.core.resources.IMarker +import scala.tools.eclipse.ScalaPlugin +import org.junit.After +import org.junit.Ignore + +object ClasspathTests extends TestProjectSetup("classpath") + +class ClasspathTests { + + import ClasspathTests._ + + /** + * The default classpath, with the eclipse scala container. + */ + val baseRawClasspath= project.javaProject.getRawClasspath() + + /** + * The classpath, with the eclipse scala container removed. + */ + def cleanRawClasspath= for (classpathEntry <- baseRawClasspath + if classpathEntry.getPath().toPortableString() != "org.scala-ide.sdt.launching.SCALA_CONTAINER") + yield classpathEntry + + @After + def resetClasspath() { + setRawClasspathAndCheckMarkers(baseRawClasspath, 0, 0) + } + + /** + * The scala library is defined as part of the eclipse container in the classpath (default case) + */ + @Test + def eclipseContainerScalaLibrary() { + setRawClasspathAndCheckMarkers(baseRawClasspath, 0, 0) + } + + /** + * No scala library defined in the classpath + */ + @Test + def noScalaLibrary() { + setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1) + } + + /** + * Two scala library defined in the classpath, the eclipse container one, and one from the lib folder + */ + @Test + def twoScalaLibraries() { + setRawClasspathAndCheckMarkers(baseRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/2.10.x/scala-library.jar"), null, null), 0, 1) + } + + /** + * Two scala library defined in the classpath, the eclipse container one, and one with a different name. + */ + @Test + def twoScalaLibrariesWithDifferentName() { + setRawClasspathAndCheckMarkers(baseRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/2.10.x/my-scala-library.jar"), null, null), 0, 1) + } + + /** + * The scala library is defined using a classpath variable, with a different but compatible version + */ + @Test + def usingClasspathVariable() { + // create a classpath variable + JavaCore.setClasspathVariable("CLASSPATH_TEST_LIB", new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/"), new NullProgressMonitor) + setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newVariableEntry(new Path("CLASSPATH_TEST_LIB/scala-library.jar"), null, null), 1, 0) + } + + /** + * The scala-library.jar from the lib folder is marked as being a different version, but compatible + */ + @Test + def differentButCompatibleVersion() { + setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/scala-library.jar"), null, null), 1, 0) + } + + /** + * The scala-library.jar is marked as being a different, incompatible version + */ + @Test + def differentAndIncompatibleVersion() { + val newRawClasspath= cleanRawClasspath :+ + JavaCore.newLibraryEntry(new Path("/classpath/lib/" + + (ScalaPlugin.plugin.shortScalaVer match { + case "2.8" => "2.9" + case "2.9" => "2.10" + case "2.10" => "2.8" + case _ => + fail("Unsupported embedded scala library version " + ScalaPlugin.plugin.scalaVer +". Please update the test.") + "" + }) + ".x/scala-library.jar"), null, null) + + setRawClasspathAndCheckMarkers(newRawClasspath, 0, 1) + } + + /** + * The properties file in scala-library.jar doesn't contain the version information + */ + @Test + def noVersionInPropertiesFile() { + setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/noversion/scala-library.jar"), null, null), 0, 1) + } + + /** + * The scala-library.jar doesn't contain a properties file. + */ + @Test + def noPropertiesFile() { + setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/noproperties/scala-library.jar"), null, null), 0, 1) + } + + /** + * The library has a different name, but with a compatible version and contains scala.Predef + */ + @Test + def differentNameWithCompatibleVersion() { + setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/my-scala-library.jar"), null, null), 1, 0) + } + + /** + * The library has a different name, but with a compatible version and contains scala.Predef + */ + @Test + def differentNameWithIncompatibleVersion() { + val newRawClasspath= cleanRawClasspath :+ + JavaCore.newLibraryEntry(new Path("/classpath/lib/" + + (ScalaPlugin.plugin.shortScalaVer match { + case "2.8" => "2.9" + case "2.9" => "2.10" + case "2.10" => "2.8" + case _ => + fail("Unsupported embedded scala library version " + ScalaPlugin.plugin.scalaVer +". Please update the test.") + "" + }) + ".x/my-scala-library.jar"), null, null) + + setRawClasspathAndCheckMarkers(newRawClasspath, 0, 1) + } + + /** + * + */ + + /** + * check that the error marker is kept even after a clean + */ + @Test + def errorKeptAfterClean() { + setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1) + + project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor) + project.underlying.build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor) + + checkMarkers(0, 1) + } + + /** + * check the code is not compiled if the classpath is not right (no error reported in scala files) + */ + @Test + def errorInClasspathStopBuild() { + project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor) + project.underlying.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor) + + // no error on the project itself + checkMarkers(0, 0) + + // two excepted code errors + var markers= project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_INFINITE) + assertEquals("Unexpected number of scala problems in project", 2, markers.length) + + // switch to an invalid classpath + setRawClasspathAndCheckMarkers(cleanRawClasspath, 0, 1) + + project.underlying.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor) + project.underlying.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new NullProgressMonitor) + + // one error on the project + checkMarkers(0, 1) + + // no additional code errors + markers= project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_INFINITE) + assertEquals("Unexpected number of scala problems in project", 1, markers.length) + } + + /** + * Set the new classpath and check the number of errors and warnings attached to the project. + */ + private def setRawClasspathAndCheckMarkers(newRawClasspath: Array[IClasspathEntry], expectedNbOfWarningMarker: Int, expectedNbOfErrorMarker: Int) { + project.javaProject.setRawClasspath(newRawClasspath, new NullProgressMonitor) + checkMarkers(expectedNbOfWarningMarker, expectedNbOfErrorMarker) + } + + /** + * Check the number of errors and warnings attached to the project. + */ + private def checkMarkers(expectedNbOfWarningMarker: Int, expectedNbOfErrorMarker: Int) { + val TIMEOUT= 5000 + + // check the classpathValid state + assertEquals("Unexpected classpath validity state", expectedNbOfErrorMarker == 0, project.isClasspathValid()) + + var nbOfWarningMarker= 0 + var nbOfErrorMarker= 0 + + for (i <- 1 to (TIMEOUT / 200)) { + // count the markers on the project + nbOfWarningMarker= 0 + nbOfErrorMarker= 0 + for (marker <- project.underlying.findMarkers("org.scala-ide.sdt.core.problem", false, IResource.DEPTH_ZERO)) + marker.getAttribute(IMarker.SEVERITY, 0) match { + case IMarker.SEVERITY_ERROR => nbOfErrorMarker+=1 + case IMarker.SEVERITY_WARNING => nbOfWarningMarker+=1 + case _ => + } + + if (nbOfWarningMarker == expectedNbOfWarningMarker && nbOfErrorMarker == expectedNbOfErrorMarker) { + // markers are fine, we're done + return + } + + // wait a bit before trying again + Thread.sleep(200) + } + + // after TIMEOUT, we didn't get the expected value + assertEquals("Unexpected nb of warning markers", expectedNbOfWarningMarker, nbOfWarningMarker) + assertEquals("Unexpected nb of error markers", expectedNbOfErrorMarker, nbOfErrorMarker) + } + +} \ No newline at end of file diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/.classpath b/org.scala-ide.sdt.core.tests/test-workspace/classpath/.classpath new file mode 100644 index 0000000000..bc01bff4ca --- /dev/null +++ b/org.scala-ide.sdt.core.tests/test-workspace/classpath/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/.project b/org.scala-ide.sdt.core.tests/test-workspace/classpath/.project new file mode 100644 index 0000000000..bc9a97d178 --- /dev/null +++ b/org.scala-ide.sdt.core.tests/test-workspace/classpath/.project @@ -0,0 +1,18 @@ + + + classpath + + + + + + org.scala-ide.sdt.core.scalabuilder + + + + + + org.scala-ide.sdt.core.scalanature + org.eclipse.jdt.core.javanature + + diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.10.x/my-scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.10.x/my-scala-library.jar new file mode 100644 index 0000000000..dae1b1d591 Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.10.x/my-scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.10.x/scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.10.x/scala-library.jar new file mode 100644 index 0000000000..dae1b1d591 Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.10.x/scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.8.x/my-scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.8.x/my-scala-library.jar new file mode 100644 index 0000000000..2e990c53e6 Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.8.x/my-scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.8.x/scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.8.x/scala-library.jar new file mode 100644 index 0000000000..2e990c53e6 Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.8.x/scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.9.x/my-scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.9.x/my-scala-library.jar new file mode 100644 index 0000000000..44c2bc65e8 Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.9.x/my-scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.9.x/scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.9.x/scala-library.jar new file mode 100644 index 0000000000..44c2bc65e8 Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/2.9.x/scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/noproperties/scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/noproperties/scala-library.jar new file mode 100644 index 0000000000..75e2a37dfd Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/noproperties/scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/noversion/scala-library.jar b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/noversion/scala-library.jar new file mode 100644 index 0000000000..d09df06d74 Binary files /dev/null and b/org.scala-ide.sdt.core.tests/test-workspace/classpath/lib/noversion/scala-library.jar differ diff --git a/org.scala-ide.sdt.core.tests/test-workspace/classpath/src/test/Test.scala b/org.scala-ide.sdt.core.tests/test-workspace/classpath/src/test/Test.scala new file mode 100644 index 0000000000..db194937ed --- /dev/null +++ b/org.scala-ide.sdt.core.tests/test-workspace/classpath/src/test/Test.scala @@ -0,0 +1,12 @@ +package test + +class Test { + + var x: ArrayList= null + + def foo() { + var s= "s" + s= 2 + } + +} \ No newline at end of file diff --git a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaBuilder.scala b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaBuilder.scala index e474e9759f..d6144c205a 100644 --- a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaBuilder.scala +++ b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaBuilder.scala @@ -6,16 +6,18 @@ package scala.tools.eclipse import scala.collection.mutable.HashSet + import java.{ lang => jl, util => ju } + import org.eclipse.core.resources.{ IFile, IncrementalProjectBuilder, IProject, IResource, IResourceDelta, IResourceDeltaVisitor, IResourceVisitor } import org.eclipse.core.runtime.{ IProgressMonitor, IPath, SubMonitor } import org.eclipse.jdt.internal.core.JavaModelManager import org.eclipse.jdt.internal.core.builder.{ JavaBuilder, State } + import scala.tools.eclipse.javaelements.JDTUtils import scala.tools.eclipse.util.{ FileUtils, ReflectionUtils } -import util.HasLogger -class ScalaBuilder extends IncrementalProjectBuilder with HasLogger { +class ScalaBuilder extends IncrementalProjectBuilder { def plugin = ScalaPlugin.plugin private val scalaJavaBuilder = new GeneralScalaJavaBuilder @@ -34,6 +36,12 @@ class ScalaBuilder extends IncrementalProjectBuilder with HasLogger { override def build(kind : Int, ignored : ju.Map[_, _], monitor : IProgressMonitor) : Array[IProject] = { import IncrementalProjectBuilder._ import buildmanager.sbtintegration.EclipseSbtBuildManager + + // check the classpath + if (!plugin.getScalaProject(getProject).isClasspathValid()) { + // bail out is the classpath in not valid + return new Array[IProject](0) + } val project = plugin.getScalaProject(getProject) @@ -66,17 +74,8 @@ class ScalaBuilder extends IncrementalProjectBuilder with HasLogger { // Only for sbt which is able to track external dependencies properly project.buildManager match { case _: EclipseSbtBuildManager => - - def hasChanges(prj: IProject): Boolean = { - val delta = getDelta(prj) - delta == null || delta.getKind != IResourceDelta.NO_CHANGE - } - - if (project.externalDepends.exists(hasChanges)) { - // reset presentation compilers if a dependency has been rebuilt - logger.debug("Resetting presentation compiler for %s due to dependent project change".format(project.underlying.getName())) - project.resetPresentationCompiler - + if (project.externalDepends.exists( + x => { val delta = getDelta(x); delta == null || delta.getKind != IResourceDelta.NO_CHANGE})) { // in theory need to be able to identify the exact dependencies // but this is deeply rooted inside the sbt dependency tracking mechanism // so we just tell it to have a look at all the files diff --git a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPlugin.scala b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPlugin.scala index 32ce9f64b2..33c96b79df 100644 --- a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPlugin.scala +++ b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPlugin.scala @@ -84,7 +84,7 @@ class ScalaPlugin extends AbstractUIPlugin with IResourceChangeListener with IEl val javaFileExtn = ".java" val jarFileExtn = ".jar" - private def cutVersion(version: String): String = { + def cutVersion(version: String): String = { val pattern = "(\\d)\\.(\\d+)\\..*".r version match { case pattern(major, minor)=> @@ -135,12 +135,12 @@ class ScalaPlugin extends AbstractUIPlugin with IResourceChangeListener with IEl if (!headlessMode) { ResourcesPlugin.getWorkspace.addResourceChangeListener(this, IResourceChangeEvent.PRE_CLOSE) - JavaCore.addElementChangedListener(this) PlatformUI.getWorkbench.getEditorRegistry.setDefaultEditor("*.scala", editorId) ScalaPlugin.getWorkbenchWindow map (_.getPartService().addPartListener(ScalaPlugin.this)) diagnostic.StartupDiagnostics.run } - logger.info("Scala compiler bundle: " + scalaCompilerBundle.getLocation) + JavaCore.addElementChangedListener(this) + println("Scala compiler bundle: " + scalaCompilerBundle.getLocation) } override def stop(context: BundleContext) = { @@ -195,12 +195,29 @@ class ScalaPlugin extends AbstractUIPlugin with IResourceChangeListener with IEl override def elementChanged(event: ElementChangedEvent) { import scala.collection.mutable.ListBuffer + import IJavaElement._ + import IJavaElementDelta._ + + // check if the changes are linked with the build path + val modelDelta= event.getDelta() + if (JAVA_MODEL == modelDelta.getElement().getElementType() && modelDelta.getKind() == CHANGED && (modelDelta.getFlags() & F_CHILDREN) != 0) { + val innerDelta= modelDelta.getAffectedChildren()(0) + if (innerDelta.getKind() == IJavaElementDelta.CHANGED && (innerDelta.getFlags() & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0) { + innerDelta.getElement() match { + case javaProject: IJavaProject => { + if (isScalaProject(javaProject)) { + getScalaProject(javaProject.getProject()).classpathHasChanged() + } + } + case _ => + } + } + } + + // process deleted files val buff = new ListBuffer[ScalaSourceFile] def findRemovedSources(delta: IJavaElementDelta) { - import IJavaElement._ - import IJavaElementDelta._ - val isChanged = delta.getKind == CHANGED val isRemoved = delta.getKind == REMOVED def hasFlag(flag: Int) = (delta.getFlags & flag) != 0 diff --git a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaProject.scala b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaProject.scala index 583efd130d..f78c5a62e3 100644 --- a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaProject.scala +++ b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaProject.scala @@ -25,9 +25,13 @@ import util.SWTUtils.asyncExec import EclipseUtils.workspaceRunnableIn import scala.tools.eclipse.properties.CompilerSettings import scala.tools.eclipse.util.HasLogger +import scala.collection.mutable.ListBuffer +import scala.actors.Actor +import org.eclipse.jdt.core.IJarEntryResource +import java.util.Properties +import org.eclipse.jdt.core.IPackageFragmentRoot - -trait BuildSuccessListener { +trait BuildSuccessListener { def buildSuccessful(): Unit } @@ -37,8 +41,10 @@ class ScalaProject(val underlying: IProject) extends HasLogger { private var classpathUpdate: Long = IResource.NULL_STAMP private var buildManager0: EclipseBuildManager = null private var hasBeenBuilt = false - private val resetPendingLock = new Object - private var resetPending = false + + private var classpathCheckLock= new Object + private var classpathHasBeenChecked= false + private var classpathValid= false; private val buildListeners = new mutable.HashSet[BuildSuccessListener] @@ -48,7 +54,6 @@ class ScalaProject(val underlying: IProject) extends HasLogger { private val presentationCompiler = new Cached[Option[ScalaPresentationCompiler]] { override def create() = { - checkClasspathTimeStamp(shouldReset = false) try { val settings = new Settings settings.printtypes.tryToSet(Nil) @@ -342,22 +347,121 @@ class ScalaProject(val underlying: IProject) extends HasLogger { } } - /** Check if the .classpath file has been changed since the last check. - * If the saved timestamp does not match the file timestamp, reset the - * two compilers. +// /** Check if the .classpath file has been changed since the last check. +// * If the saved timestamp does not match the file timestamp, reset the +// * two compilers. +// */ +// def checkClasspathTimeStamp(shouldReset: Boolean): Unit = plugin.check { +// val cp = underlying.getFile(".classpath") +// if (cp.exists) +// classpathUpdate match { +// case IResource.NULL_STAMP => classpathUpdate = cp.getModificationStamp() +// case stamp if stamp == cp.getModificationStamp() => +// case _ => +// classpathUpdate = cp.getModificationStamp() +// if (shouldReset) resetCompilers() +// } +// } + + /** + * Manage the possible classpath error/warning reported on the project. */ - def checkClasspathTimeStamp(shouldReset: Boolean): Unit = plugin.check { - val cp = underlying.getFile(".classpath") - if (cp.exists) - classpathUpdate match { - case IResource.NULL_STAMP => classpathUpdate = cp.getModificationStamp() - case stamp if stamp == cp.getModificationStamp() => - case _ => - classpathUpdate = cp.getModificationStamp() - if (shouldReset) resetCompilers() + private def setClasspathError(severity: Int, message: String) { + // set the state + classpathValid= severity != IMarker.SEVERITY_ERROR + classpathHasBeenChecked= true + new Thread() { + override def run() { + // clean the markers + underlying.deleteMarkers(plugin.problemMarkerId, false, IResource.DEPTH_ZERO) + + // add a new marker if needed + severity match { + case IMarker.SEVERITY_ERROR | IMarker.SEVERITY_WARNING => + val marker= underlying.createMarker(plugin.problemMarkerId) + marker.setAttribute(IMarker.MESSAGE, message) + marker.setAttribute(IMarker.SEVERITY, severity) + case _ => + } + } + }.start() } - + + /** + * Return true if the classpath is deemed valid. + * Check the classpath if it has not been checked yet. + */ + def isClasspathValid(): Boolean = { + classpathCheckLock.synchronized { + if (!classpathHasBeenChecked) + checkClasspath() + classpathValid + } + } + + /** + * Check if the classpath is valid for scala. + * It is said valid if it contains one and only scala library jar, with a version compatible + * with the one from the scala-ide plug-in + */ + def classpathHasChanged() { + classpathCheckLock.synchronized { + try { + resetCompilers() + // mark as in progress + classpathHasBeenChecked= false + checkClasspath() + } + } + } + + private def checkClasspath() { + // look for all package fragment roots containing instances of scala.Predef + val fragmentRoots= new ListBuffer[IPackageFragmentRoot] + for (fragmentRoot <- javaProject.getAllPackageFragmentRoots()) { + val fragment= fragmentRoot.getPackageFragment("scala") + fragmentRoot.getKind() match { + case IPackageFragmentRoot.K_BINARY => + if (fragment.getClassFile("Predef.class").exists()) + fragmentRoots+= fragmentRoot + case _ => // look only in jars. SBT doesn't start without one, and refined is not really happy either + } + } + + // check the found package fragment roots + fragmentRoots.length match { + case 0 => // unable to find any trace of scala library + setClasspathError(IMarker.SEVERITY_ERROR, "Unable to find a scala library. Please add the scala container or a scala library jar to the build path.") + case 1 => // one and only one, now check if the version number is contained in library.properties + for (resource <- fragmentRoots(0).getNonJavaResources()) + resource match { + case jarEntry: IJarEntryResource if jarEntry.isFile() && "library.properties".equals(jarEntry.getName) => + val properties= new Properties() + properties.load(jarEntry.getContents()) + val version= properties.getProperty("version.number") + if (version != null && version == plugin.scalaVer) { + // exactly the same version, should be from the container. Perfect + setClasspathError(0, null) + } else { + if (version != null && plugin.cutVersion(version) == plugin.shortScalaVer) { + // compatible version. Still, add warning message + setClasspathError(IMarker.SEVERITY_WARNING, "The version of scala library found in the build path is different from the one provided by scala IDE: " + version + ". Expected: " + plugin.scalaVer + ". Make sure you know what you are doing.") + } else { + // incompatible version + setClasspathError(IMarker.SEVERITY_ERROR, "The version of scala library found in the build path is incompatible with the one provided by scala IDE: " + version + ". Expected: " + plugin.scalaVer + ". Please replace the scala library with the scala container or a compatible scala library jar.") + } + } + return + case _ => + } + // no library.properties, not good + setClasspathError(IMarker.SEVERITY_ERROR, "The scala library found in the build path doesn't contain a library.properties file. Please replace the scala library with the scala container or a valid scala library jar") + case _ => // 2 or more of them, not good + setClasspathError(IMarker.SEVERITY_ERROR, "More than one scala library found in the build path. Please update the project build path so it contains only one scala library reference") + } + } + private def refreshOutput: Unit = { val res = plugin.workspaceRoot.findMember(javaProject.getOutputLocation) if (res ne null) @@ -393,7 +497,7 @@ class ScalaProject(val underlying: IProject) extends HasLogger { setting <- box.userSettings; if filter(setting) ) { val value0 = store.getString(SettingConverterUtil.convertNameToProperty(setting.name)) -// logger.info("[%s] initializing %s to %s".format(underlying.getName(), setting.name, value0.toString)) + logger.info("[%s] initializing %s to %s".format(underlying.getName(), setting.name, value0.toString)) try { val value = if (setting ne settings.pluginsDir) value0 else { ScalaPlugin.plugin.continuationsClasses map { @@ -484,7 +588,6 @@ class ScalaProject(val underlying: IProject) extends HasLogger { } def buildManager = { - checkClasspathTimeStamp(shouldReset = true) if (buildManager0 == null) { val settings = new Settings initialize(settings, _ => true) @@ -544,6 +647,10 @@ class ScalaProject(val underlying: IProject) extends HasLogger { def clean(implicit monitor: IProgressMonitor) = { underlying.deleteMarkers(plugin.problemMarkerId, true, IResource.DEPTH_INFINITE) + // mark the classpath as not checked + classpathCheckLock.synchronized { + classpathHasBeenChecked= false + } resetCompilers if (buildManager0 != null) buildManager0.clean(monitor)