From 86058fb7a5790616b81d891954c8c428916624e3 Mon Sep 17 00:00:00 2001 From: Iulian Dragos Date: Wed, 11 Jul 2012 16:13:18 +0200 Subject: [PATCH 1/2] Don't report nonexistent projects in `directDependencies`. The JDT has a very annoying habit of caching dependent projects, and there's no way to clear that cache. I've seen the build fail because it thought it depends on a nonexistent project. Neither closing, refreshing or restarting Eclipse helped, so we filter it explicitly here. --- .../src/scala/tools/eclipse/ScalaProject.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 69114007c9..90f69dc3d6 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 @@ -145,9 +145,9 @@ class ScalaProject private (val underlying: IProject) extends ClasspathManagemen } - /** The direct dependencies of this project. */ + /** The direct dependencies of this project. It only returns existing projects. */ def directDependencies: Seq[IProject] = - underlying.getReferencedProjects + underlying.getReferencedProjects.filter(_.exists) /** All direct and indirect dependencies of this project. * From 98918102ffa4f41584be8c8100a3b7e4b0113b44 Mon Sep 17 00:00:00 2001 From: Iulian Dragos Date: Wed, 11 Jul 2012 16:14:43 +0200 Subject: [PATCH 2/2] Added a test for working with the Scala library coming from a file, and made the class path checker more lenient when the library comes from a project. In such a case the version is assumed to be compatible (there is no library.properties file inside the project, so we can't reliably guess the version). --- .../eclipse/classpath/ClasspathTests.scala | 17 +++++++ .../tools/eclipse/ClasspathManagement.scala | 47 ++++++++++++++----- 2 files changed, 53 insertions(+), 11 deletions(-) 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 index 26b454f495..570dc72581 100644 --- 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 @@ -17,6 +17,7 @@ import org.junit.Ignore import scala.tools.eclipse.EclipseUserSimulator import scala.tools.eclipse.ScalaProject import scala.tools.eclipse.properties.CompilerSettings +import scala.tools.eclipse.testsetup.SDTTestUtils object ClasspathTests extends TestProjectSetup("classpath") @@ -186,6 +187,22 @@ class ClasspathTests { def binaryClassFolderLibrary() { setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newLibraryEntry(new Path("/classpath/lib/" + ScalaPlugin.plugin.shortScalaVer + ".x/binary-scala-library"), null, null), 1, 0) } + + @Test + def dependentProjectLibrary() { + import SDTTestUtils._ + + val Seq(scalaLibProject) = createProjects("scala-library") + try { + val packScala = createSourcePackage("scala")(scalaLibProject) + + val unitA = packScala.createCompilationUnit("Predef.scala", "class Predef", true, null) + + setRawClasspathAndCheckMarkers(cleanRawClasspath :+ JavaCore.newProjectEntry(scalaLibProject.underlying.getFullPath, true), 0, 0) + } finally { + deleteProjects(scalaLibProject) + } + } /** * check that the error marker is kept even after a clean diff --git a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ClasspathManagement.scala b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ClasspathManagement.scala index c11c034a03..8beb8454ed 100644 --- a/org.scala-ide.sdt.core/src/scala/tools/eclipse/ClasspathManagement.scala +++ b/org.scala-ide.sdt.core/src/scala/tools/eclipse/ClasspathManagement.scala @@ -63,6 +63,15 @@ case class ScalaClasspath(val jdkPaths: Seq[IPath], // JDK classpath toPath(jdkPaths) ++ scalaLibraryFile.toSeq ++ toPath(userCp) } +/** A Scala library definition. + * + * @param location The file-system absolute path to the root of the Scala library + * @param version An option version, retrieved from library.properties, if present + * @param isProject Whether the library is provided by a project inside the workspace + * + */ +case class ScalaLibrary(location: IPath, version: Option[String], isProject: Boolean) + /** Scala project classpath management. This class is responsible for breaking down the classpath in * JDK entries, Scala library entries, and user entries. It also validates the classpath and * manages the classpath error markers for the given Scala project. @@ -75,8 +84,10 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => val cp = classpath.filterNot(jdkEntries.toSet) scalaLibraries match { - case Seq((pf, version), _*) => new ScalaClasspath(jdkEntries, Some(pf), cp.filterNot(_ == pf), version) - case _ => new ScalaClasspath(jdkEntries, None, cp, None) + case Seq(ScalaLibrary(pf, version, _), _*) => + new ScalaClasspath(jdkEntries, Some(pf), cp.filterNot(_ == pf), version) + case _ => + new ScalaClasspath(jdkEntries, None, cp, None) } } @@ -201,7 +212,7 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => * @return the absolute file-system path to package fragments that define `scala.Predef`. * If it contains path variables or is a linked resources, the path is resolved. */ - def scalaLibraries: Seq[(IPath, Option[String])] = { + def scalaLibraries: Seq[ScalaLibrary] = { val pathToPredef = new Path("scala/Predef.class") def isZipFileScalaLib(p: IPath): Boolean = { @@ -213,7 +224,7 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => } // look for all package fragment roots containing instances of scala.Predef - val fragmentRoots = new mutable.ListBuffer[(IPath, Option[String])] + val fragmentRoots = new mutable.ListBuffer[ScalaLibrary] for (fragmentRoot <- javaProject.getAllPackageFragmentRoots() if fragmentRoot.getPackageFragment("scala").exists) { fragmentRoot.getKind() match { @@ -231,7 +242,7 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => } } - if (foundIt) fragmentRoots += ((fragmentRoot.getPath, getVersionNumber(fragmentRoot))) + if (foundIt) fragmentRoots += ScalaLibrary(fragmentRoot.getPath, getVersionNumber(fragmentRoot), isProject = false) case IPackageFragmentRoot.K_SOURCE => for { @@ -242,7 +253,7 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => (srcPath, binFolder) <- dependentPrj.sourceOutputFolders if srcPath.getProjectRelativePath == folder.getProjectRelativePath } { - fragmentRoots += ((binFolder.getLocation, getVersionNumber(fragmentRoot))) + fragmentRoots += ScalaLibrary(binFolder.getLocation, getVersionNumber(fragmentRoot), isProject = true) } case _ => @@ -253,6 +264,11 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => } private def checkClasspath() { + def incompatibleScalaLibrary(scalaLib: ScalaLibrary) = scalaLib match { + case ScalaLibrary(_, version, false) => !plugin.isCompatibleVersion(version) + case _ => false + } + // look for all package fragment roots containing instances of scala.Predef val fragmentRoots = scalaLibraries @@ -261,7 +277,10 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => 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 - fragmentRoots(0)._2 match { + if (fragmentRoots(0).isProject) { + // if the library is provided by a project in the workspace, disable the warning (the version file is missing anyway) + setClasspathError(0, null) + } else fragmentRoots(0).version match { case Some(v) if v == plugin.scalaVer => // exactly the same version, should be from the container. Perfect setClasspathError(0, null) @@ -275,13 +294,19 @@ trait ClasspathManagement extends HasLogger { self: ScalaProject => // no version found setClasspathError(IMarker.SEVERITY_ERROR, "The scala library found in the build path doesn't expose its version. Please replace the scala library with the scala container or a valid scala library jar") } - case _ => // 2 or more of them, not great - if (fragmentRoots.exists { case (_, version) => !plugin.isCompatibleVersion(version) }) - setClasspathError(IMarker.SEVERITY_ERROR, "More than one scala library found in the build path, including at least one with an incompatible version. Please update the project build path so it contains only compatible scala libraries") + case _ => // 2 or more of them, not great, but warn only if the library is not a project + if (fragmentRoots.exists(incompatibleScalaLibrary)) + setClasspathError(IMarker.SEVERITY_ERROR, moreThanOneLibraryError(fragmentRoots.map(_.location), compatible = false)) else - setClasspathError(IMarker.SEVERITY_WARNING, "More than one scala library found in the build path, all with compatible versions. This is not an optimal configuration, try to limit to one scala library in the build path.") + setClasspathError(IMarker.SEVERITY_WARNING, moreThanOneLibraryError(fragmentRoots.map(_.location), compatible = true)) } } + + private def moreThanOneLibraryError(libs: Seq[IPath], compatible: Boolean): String = { + val first = "More than one scala library found in the build path (%s).".format(libs.mkString(", ")) + if (compatible) first + "This is not an optimal configuration, try to limit to one Scala library in the build path." + else first + "At least one has an incompatible version. Please update the project build path so it contains only compatible scala libraries." + } /** Return the version number contained in library.properties if it exists. */