Skip to content

Commit

Permalink
improvement: if no binary version in jar path try using build target …
Browse files Browse the repository at this point in the history
…info (#6698)

* improvement: if no binary version in jar path try using build target info

* special case for scala lib and look for tasty files when discovering scala version for source jar
  • Loading branch information
kasiaMarek authored Dec 2, 2024
1 parent 7001e6d commit 1e0d88e
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,8 @@ final class ImplementationProvider(
}

private def semanticdbForJarFile(fileSource: AbsolutePath) = {
val dialect = ScalaVersions.dialectForDependencyJar(fileSource.filename)
val dialect =
ScalaVersions.dialectForDependencyJar(fileSource, buildTargets)
FileIO.slurp(fileSource, StandardCharsets.UTF_8)
val textDocument = Mtags.index(fileSource, dialect)
textDocument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,14 +430,31 @@ final class BuildTargets private (
* in other capacity than as a fallback, since both source jar
* and normal jar might not be in the same directory.
*
* @param sourceJarPath path to the nromaljar
* @param sourceJarPath path to the normal jar
* @return path to the source jar for that jar
*/
private def sourceJarPathFallback(
sourceJarPath: AbsolutePath
): Option[AbsolutePath] = {
val fallback = sourceJarPath.parent.resolve(
sourceJarPath.filename.replace(".jar", "-sources.jar")
): Option[AbsolutePath] =
findInTheSameDirectoryWithReplace(sourceJarPath, ".jar", "-sources.jar")

/**
* Try to resolve path to normal jar from source jar,
* should only be used as fallback, since jar and source jar
* do not have to be in the same directory.
*/
private def jarFromSourceJarFallback(
jarPath: AbsolutePath
): Option[AbsolutePath] =
findInTheSameDirectoryWithReplace(jarPath, "-sources.jar", ".jar")

private def findInTheSameDirectoryWithReplace(
path: AbsolutePath,
fromPattern: String,
toPattern: String,
) = {
val fallback = path.parent.resolve(
path.filename.replace(fromPattern, toPattern)
)
if (fallback.exists) Some(fallback)
else None
Expand Down Expand Up @@ -578,18 +595,29 @@ final class BuildTargets private (
jar: AbsolutePath,
): Option[AbsolutePath] = {
data
.fromOptions(_.findSourceJarOf(jar, Some(id)))
.fromOptions(_.findConnectedArtifact(jar, Some(id)))
.orElse(sourceJarPathFallback(jar))
}

def sourceJarFor(
jar: AbsolutePath
): Option[AbsolutePath] = {
data
.fromOptions(_.findSourceJarOf(jar, targetId = None))
.fromOptions(_.findConnectedArtifact(jar, targetId = None))
.orElse(sourceJarPathFallback(jar))
}

def findJarFor(
id: BuildTargetIdentifier,
sourceJar: AbsolutePath,
): Option[AbsolutePath] = {
data
.fromOptions(
_.findConnectedArtifact(sourceJar, Some(id), classifier = null)
)
.orElse(jarFromSourceJarFallback(sourceJar))
}

def inverseDependencySource(
sourceJar: AbsolutePath
): collection.Set[BuildTargetIdentifier] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ case class Indexer(indexProviders: IndexProviders)(implicit rc: ReportContext) {
case Right(zip) =>
scribe.debug(s"Indexing JDK sources from $zip")
usedJars += zip
val dialect = ScalaVersions.dialectForDependencyJar(zip.filename)
val dialect = ScalaVersions.dialectForDependencyJar(zip, buildTargets)
sharedIndices.jvmTypeHierarchy.getTypeHierarchy(zip) match {
case Some(overrides) =>
definitionIndex.addIndexedSourceJar(zip, Nil, dialect)
Expand Down Expand Up @@ -540,7 +540,7 @@ case class Indexer(indexProviders: IndexProviders)(implicit rc: ReportContext) {
* @param path JAR path
*/
private def addSourceJarSymbols(path: AbsolutePath): Unit = {
val dialect = ScalaVersions.dialectForDependencyJar(path.filename)
val dialect = ScalaVersions.dialectForDependencyJar(path, buildTargets)
tables.jarSymbols.getTopLevels(path) match {
case Some(toplevels) =>
tables.jarSymbols.getTypeHierarchy(path) match {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package scala.meta.internal.metals

import java.io.UncheckedIOException

import scala.collection.concurrent.TrieMap

import scala.meta.Dialect
import scala.meta.dialects._
import scala.meta.internal.io.FileIO
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.semver.SemVer
import scala.meta.io.AbsolutePath

class ScalaVersions(
deprecatedScalaVersions: Seq[String],
Expand All @@ -13,6 +20,8 @@ class ScalaVersions(
scala3: String,
) {

private val jarScalaVersionIndex = TrieMap[String, String]()

def isScala3Milestone(version: String): Boolean =
version.startsWith("3.0.0-M") || version.startsWith("3.0.0-RC")

Expand Down Expand Up @@ -150,7 +159,9 @@ class ScalaVersions(
}

private val scalaVersionRegex =
raw"(_)(\d)(\.\d{1,2})?(\.\d(-(RC|M)\d)?)?".r
raw"_(\d)(\.\d{1,2})?(\.\d(-(RC|M)\d)?)?".r
private val scalaLibraryRegex =
raw"scala-library-(\d)(\.\d{1,2})(\.\d(-(RC|M)\d)?)".r

/**
* Extract scala binary version from dependency jar name.
Expand All @@ -160,35 +171,66 @@ class ScalaVersions(
* `some-library_2.13-4.5.0` -> 2.13
* `some-library_2.13-2.11` -> 2.13
*/
def scalaBinaryVersionFromJarName(filename: String): String = {
def scalaBinaryVersionFromJarName(filename: String): Option[String] = {
val dropEnding = filename
.stripSuffix(".jar")

scalaVersionRegex
.findAllMatchIn(dropEnding)
.toList
List(scalaLibraryRegex, scalaVersionRegex)
.flatMap(_.findAllMatchIn(dropEnding).toList)
.flatMap { m =>
val hasUnderscorePrefix = Option(m.group(1)).isDefined
val major = m.group(2)
val minor = Option(m.group(3)).getOrElse("")
val ending = Option(m.group(4)).getOrElse("")
val major = m.group(1)
val minor = Option(m.group(2)).getOrElse("")
val ending = Option(m.group(3)).getOrElse("")
val version = s"$major$minor$ending"

if (isSupportedScalaBinaryVersion(version))
Some(version -> hasUnderscorePrefix)
Some(version)
else None
}
.sortBy(_._2)(Ordering.Boolean.reverse)
.headOption
.map { case (version, _) => scalaBinaryVersionFromFullVersion(version) }
.getOrElse("2.13")
.map(scalaBinaryVersionFromFullVersion)
}

def dialectForDependencyJar(filename: String): Dialect =
dialectForScalaVersion(
scalaBinaryVersionFromJarName(filename),
includeSource3 = true,
)
def dialectForDependencyJar(
jar: AbsolutePath,
buildTargets: BuildTargets,
): Dialect = {
lazy val buildTargetAndScalaVersion =
buildTargets
.inverseDependencySource(jar)
.flatMap(id => buildTargets.scalaTarget(id))
.map(target => (target.scalaBinaryVersion, target.id))
.toList
.sortBy(_._1)
.headOption

def fromTastyExistance = {
val fromTasty = buildTargetAndScalaVersion
.flatMap { case (_, id) => buildTargets.findJarFor(id, jar) }
.flatMap(
FileIO.withJarFileSystem(_, create = false) { root =>
try {
root.listRecursive
.find(f => f.isFile && f.filename.endsWith(".tasty"))
.map(_ => "3")
.orElse(Some("2.13"))
} catch {
case _: UncheckedIOException => None
}
}
)
fromTasty.foreach(jarScalaVersionIndex.put(jar.toURI.toString(), _))
fromTasty
}

val scalaVersion =
scalaBinaryVersionFromJarName(jar.toNIO.getFileName().toString())
.orElse(jarScalaVersionIndex.get(jar.toURI.toString()))
.orElse(fromTastyExistance)
.orElse(buildTargetAndScalaVersion.map(_._1))
.getOrElse("2.13")
dialectForScalaVersion(scalaVersion, includeSource3 = true)
}

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ class StandaloneSymbolSearch(

private val index = OnDemandSymbolIndex.empty()
sources.foreach(s =>
index.addSourceJar(s, ScalaVersions.dialectForDependencyJar(s.filename))
index.addSourceJar(
s,
ScalaVersions.dialectForDependencyJar(s, buildTargets),
)
)

private val docs = new Docstrings(index)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,10 @@ final class TargetData {
}
}

def findSourceJarOf(
def findConnectedArtifact(
jar: AbsolutePath,
targetId: Option[BuildTargetIdentifier],
classifier: String = "sources",
): Option[AbsolutePath] = {
val jarUri = jar.toURI.toString()
def depModules: Iterator[MavenDependencyModule] = targetId match {
Expand All @@ -228,10 +229,10 @@ final class TargetData {
module <- depModules
artifacts = module.getArtifacts().asScala
if artifacts.exists(artifact => isUriEqual(artifact.getUri(), jarUri))
sourceJar <- artifacts.find(_.getClassifier() == "sources")
sourceJarPath = sourceJar.getUri().toAbsolutePath
if sourceJarPath.exists
} yield sourceJarPath
foundJar <- artifacts.find(_.getClassifier() == classifier)
foundJarPath = foundJar.getUri().toAbsolutePath
if foundJarPath.exists
} yield foundJarPath
allFound.headOption
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,8 @@ class MetalsTreeViewProvider(
val dialect =
if (path.isJarFileSystem) {
path.jarPath
.map { p =>
ScalaVersions.dialectForDependencyJar(p.filename)
}
.flatMap(p => ScalaVersions.scalaBinaryVersionFromJarName(p.filename))
.map(ScalaVersions.dialectForScalaVersion(_, includeSource3 = true))
.getOrElse(dialects.Scala3)
} else dialectFromWorkspace
val input = path.toInput
Expand Down Expand Up @@ -264,7 +263,7 @@ class FolderTreeViewProvider(
valueTooltip = _.toString,
toplevels = () => buildTargets.allSourceJars.filter(_.exists),
loadSymbols = (path, symbol) => {
val dialect = ScalaVersions.dialectForDependencyJar(path.filename)
val dialect = ScalaVersions.dialectForDependencyJar(path, buildTargets)
classpath.jarSymbols(path, symbol, dialect)
},
toplevelIcon = "package",
Expand Down
6 changes: 5 additions & 1 deletion tests/unit/src/main/scala/tests/MetalsTestEnrichments.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ object MetalsTestEnrichments {
}
libraries.foreach(
_.sources.entries.foreach { s =>
val dialect = ScalaVersions.dialectForDependencyJar(s.filename)
val version = ScalaVersions
.scalaBinaryVersionFromJarName(s.filename)
.getOrElse("2.13")
val dialect =
ScalaVersions.dialectForScalaVersion(version, includeSource3 = true)
wsp.index.addSourceJar(s, dialect)
}
)
Expand Down
8 changes: 7 additions & 1 deletion tests/unit/src/test/scala/tests/DefinitionSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,15 @@ abstract class DefinitionSuiteBase(
// Step 2. Index dependency sources
index.addSourceJar(JdkSources().right.get, dialect)
input.dependencySources.entries.foreach { jar =>
val scalaVersion = ScalaVersions
.scalaBinaryVersionFromJarName(jar.filename)
.getOrElse("2.13")
index.addSourceJar(
jar,
ScalaVersions.dialectForDependencyJar(jar.filename),
ScalaVersions.dialectForScalaVersion(
scalaVersion,
includeSource3 = true,
),
)
}

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/src/test/scala/tests/ScalaVersionsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ class ScalaVersionsSuite extends BaseSuite {
checkJar("tested-3.0-sources.jar", "2.13")

def checkJar(jar: String, expected: String): Unit = test(jar) {
val out = ScalaVersions.scalaBinaryVersionFromJarName(jar)
val out = ScalaVersions.scalaBinaryVersionFromJarName(jar).getOrElse("2.13")
assertEquals(out, expected, jar)
}

Expand Down

0 comments on commit 1e0d88e

Please sign in to comment.