-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
make scalafixEnable less aggressive & more future-proof #292
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package scalafix.internal.sbt | ||
|
||
import sbt.librarymanagement._ | ||
import scalafix.sbt.InvalidArgument | ||
|
||
trait Implicits { | ||
implicit class XtensionModuleID(m: ModuleID) { | ||
def asCoursierCoordinates: String = { | ||
m.crossVersion match { | ||
case _: Disabled => | ||
s"${m.organization}:${m.name}:${m.revision}" | ||
case _: CrossVersion.Binary => | ||
s"${m.organization}::${m.name}:${m.revision}" | ||
case _: CrossVersion.Full => | ||
s"${m.organization}:::${m.name}:${m.revision}" | ||
case other => | ||
throw new InvalidArgument( | ||
s"Unsupported crossVersion $other for dependency $m" | ||
) | ||
} | ||
} | ||
} | ||
} | ||
|
||
object Implicits extends Implicits |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,66 +2,187 @@ package scalafix.sbt | |
|
||
import sbt._ | ||
import sbt.Keys._ | ||
import sbt.VersionNumber.SemVer | ||
|
||
import ScalafixPlugin.autoImport.scalafixResolvers | ||
|
||
import collection.JavaConverters._ | ||
import scala.util._ | ||
import coursierapi.Repository | ||
|
||
/** Command to automatically enable semanticdb compiler output for shell session | ||
*/ | ||
object ScalafixEnable { | ||
|
||
/** If the provided Scala binary version is supported, return the full version | ||
* required for running semanticdb-scalac. | ||
/** If the provided Scala binary version is supported, return the latest scala | ||
* full version for which the recommended semanticdb-scalac is available | ||
*/ | ||
private lazy val semanticdbScalacFullScalaVersion | ||
: PartialFunction[(Long, Long), String] = (for { | ||
private lazy val recommendedSemanticdbScalacScalaVersion | ||
: PartialFunction[(Long, Long), VersionNumber] = (for { | ||
v <- BuildInfo.supportedScalaVersions | ||
p <- CrossVersion.partialVersion(v).toList | ||
} yield p -> v).toMap | ||
} yield p -> VersionNumber(v)).toMap | ||
|
||
/** If the provided Scala binary version is supported, return the full version | ||
* required for running semanticdb-scalac or None if support is built-in in | ||
* the compiler and the full version does not need to be adjusted. | ||
/** If the provided Scala binary version is supported, return the latest scala | ||
* full version for which the recommended semanticdb-scalac is available, or | ||
* None if semanticdb support is built-in in the compiler | ||
*/ | ||
private lazy val maybeSemanticdbScalacFullScalaVersion | ||
: PartialFunction[(Long, Long), Option[String]] = | ||
semanticdbScalacFullScalaVersion.andThen(Some.apply).orElse { | ||
private lazy val maybeRecommendedSemanticdbScalacScalaVersion | ||
: PartialFunction[(Long, Long), Option[VersionNumber]] = | ||
recommendedSemanticdbScalacScalaVersion.andThen(Some.apply).orElse { | ||
// semanticdb is built-in in the Scala 3 compiler | ||
case (major, _) if major == 3 => None | ||
} | ||
|
||
/** Collect projects across the entire build, using the partial function | ||
* accepting a Scala binary version | ||
*/ | ||
private def collectProjects[U]( | ||
extracted: Extracted, | ||
pf: PartialFunction[(Long, Long), U] | ||
): Seq[(ProjectRef, U)] = for { | ||
p <- extracted.structure.allProjectRefs | ||
version <- (p / scalaVersion).get(extracted.structure.data).toList | ||
partialVersion <- CrossVersion.partialVersion(version).toList | ||
res <- pf.lift(partialVersion).toList | ||
} yield p -> res | ||
/** Collect compatible projects across the entire build */ | ||
private def collectProjects(extracted: Extracted): Seq[CompatibleProject] = | ||
for { | ||
p <- extracted.structure.allProjectRefs | ||
scalaV <- (p / scalaVersion).get(extracted.structure.data).toList | ||
partialVersion <- CrossVersion.partialVersion(scalaV).toList | ||
maybeRecommendedSemanticdbScalacV <- | ||
maybeRecommendedSemanticdbScalacScalaVersion.lift(partialVersion).toList | ||
scalafixResolvers0 <- (p / scalafixResolvers) | ||
.get(extracted.structure.data) | ||
.toList | ||
semanticdbCompilerPlugin0 <- (p / semanticdbCompilerPlugin) | ||
.get(extracted.structure.data) | ||
.toList | ||
} yield CompatibleProject( | ||
p, | ||
VersionNumber(scalaV), | ||
semanticdbCompilerPlugin0, | ||
scalafixResolvers0, | ||
maybeRecommendedSemanticdbScalacV | ||
) | ||
|
||
private case class CompatibleProject( | ||
ref: ProjectRef, | ||
scalaVersion0: VersionNumber, | ||
semanticdbCompilerPlugin0: ModuleID, | ||
scalafixResolvers0: Seq[Repository], | ||
maybeRecommendedSemanticdbScalacScalaV: Option[VersionNumber] | ||
) | ||
|
||
lazy val command = Command.command( | ||
"scalafixEnable", | ||
briefHelp = "Configure SemanticdbPlugin for scalafix.", | ||
detail = """1. set semanticdbEnabled & semanticdbVersion | ||
|2. conditionally sets scalaVersion when support is not built-in in the compiler""".stripMargin | ||
briefHelp = | ||
"Configure SemanticdbPlugin for scalafix on supported projects.", | ||
detail = """1. set semanticdbEnabled := true | ||
|2. for scala 2.x, | ||
| - set semanticdbCompilerPlugin to the scalameta version tracked by scalafix if available for scalaVersion, | ||
| - otherwise set semanticdbCompilerPlugin to a compatible version available for scalaVersion, | ||
| - otherwise force scalaVersion to the latest version supported by the scalameta version tracked by scalafix.""".stripMargin | ||
) { s => | ||
val extracted = Project.extract(s) | ||
val scalacOptionsSettings = Seq(Compile, Test).flatMap( | ||
inConfig(_)(ScalafixPlugin.relaxScalacOptionsConfigSettings) | ||
) | ||
val settings = for { | ||
(p, maybeFullVersion) <- collectProjects( | ||
extracted, | ||
maybeSemanticdbScalacFullScalaVersion | ||
) | ||
enableSemanticdbPlugin <- maybeFullVersion.toList.map { fullVersion => | ||
scalaVersion := fullVersion, | ||
} :+ (semanticdbEnabled := true) | ||
project <- collectProjects(extracted) | ||
enableSemanticdbPlugin <- | ||
project.maybeRecommendedSemanticdbScalacScalaV.toList | ||
.flatMap { recommendedSemanticdbScalacScalaV => | ||
|
||
import scalafix.internal.sbt.Implicits._ | ||
val semanticdbScalacModule = | ||
coursierapi.Dependency | ||
.parse( | ||
project.semanticdbCompilerPlugin0.asCoursierCoordinates, | ||
coursierapi.ScalaVersion.of(project.scalaVersion0.toString) | ||
) | ||
.getModule | ||
val recommendedSemanticdbV = | ||
VersionNumber(BuildInfo.scalametaVersion) | ||
val compatibleSemanticdbVs = Try( | ||
coursierapi.Versions.create | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To my knowledge, this is the best Java coursier API to achieve something like suggested in scalacenter/scalafix#1146 (comment) |
||
.withRepositories(project.scalafixResolvers0: _*) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That part is untested, so there is a risk that this fails on corporate environments. The failure fallback keeps the current behavior though, so apart from an added latency it shouldn't be a problem. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this is out of topic in this PR but we could use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked at that in the context of scalacenter/scalafix#1571, but I am not sure it would help as scalafix-core uses the Java coursier API directly, not via what lm/lm-coursier. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's true, but it may not be so hard to translate the sbt |
||
.withModule(semanticdbScalacModule) | ||
.versions() | ||
.getMergedListings | ||
.getAvailable | ||
.asScala | ||
.map(VersionNumber.apply) | ||
// don't use snapshots | ||
.filter(_.extras == Nil) | ||
// https://github.com/scalameta/scalameta/blob/main/COMPATIBILITY.md | ||
.filter(SemVer.isCompatible(_, recommendedSemanticdbV)) | ||
.toList | ||
) | ||
|
||
compatibleSemanticdbVs match { | ||
case Success(Nil) | Failure(_) => | ||
Seq( | ||
scalaVersion := { | ||
val v = recommendedSemanticdbScalacScalaV.toString | ||
sLog.value.warn( | ||
s"Forcing scalaVersion to $v in project " + | ||
s"${project.ref.project} since no semanticdb-scalac " + | ||
s"version binary-compatible with $recommendedSemanticdbV " + | ||
s"and cross-published for scala " + | ||
s"${project.scalaVersion0.toString} was found - " + | ||
s"consider bumping scala" | ||
) | ||
v | ||
}, | ||
semanticdbVersion := recommendedSemanticdbV.toString | ||
) | ||
case Success(available) | ||
if available.contains(recommendedSemanticdbV) => | ||
Seq( | ||
semanticdbVersion := recommendedSemanticdbV.toString | ||
) | ||
case Success(earliestAvailable :: tail) => | ||
val futureVersion = | ||
SemanticSelector.apply(s">${recommendedSemanticdbV}") | ||
|
||
if (earliestAvailable.matchesSemVer(futureVersion)) { | ||
Seq( | ||
semanticdbVersion := { | ||
val v = earliestAvailable.toString | ||
sLog.value.info( | ||
s"Setting semanticdbVersion to $v in project " + | ||
s"${project.ref.project} since the version " + | ||
s"${recommendedSemanticdbV} tracked by scalafix " + | ||
s"${BuildInfo.scalafixVersion} will not be " + | ||
s"published for scala " + | ||
s"${project.scalaVersion0.toString} - " + | ||
s"consider upgrading sbt-scalafix" | ||
) | ||
v | ||
} | ||
) | ||
} else { | ||
val latestAvailable = | ||
tail.lastOption.getOrElse(earliestAvailable) | ||
Seq( | ||
semanticdbVersion := { | ||
val v = latestAvailable.toString | ||
sLog.value.info( | ||
s"Setting semanticdbVersion to $v in project " + | ||
s"${project.ref.project} since the version " + | ||
s"${recommendedSemanticdbV} tracked by scalafix " + | ||
s"${BuildInfo.scalafixVersion} is no longer " + | ||
s"published for scala " + | ||
s"${project.scalaVersion0.toString} - " + | ||
s"consider bumping scala" | ||
) | ||
v | ||
} | ||
) | ||
} | ||
} | ||
} ++ Seq( | ||
semanticdbEnabled := true, | ||
// support sbt 1.3.[0-3] which does not contain | ||
// https://github.com/sbt/sbt/pull/5202 | ||
(semanticdbCompilerPlugin := semanticdbCompilerPlugin.value | ||
.withRevision((semanticdbVersion).value)) | ||
) | ||
settings <- | ||
inScope(ThisScope.copy(project = Select(p)))( | ||
inScope(ThisScope.copy(project = Select(project.ref)))( | ||
scalacOptionsSettings ++ enableSemanticdbPlugin | ||
) :+ (Global / semanticdbVersion := BuildInfo.scalametaVersion) | ||
) | ||
} yield settings | ||
extracted.appendWithoutSession(settings, s) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
resolvers += Resolver.sonatypeRepo("public") | ||
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % sys.props("plugin.version")) | ||
|
||
// https://github.com/scalameta/scalameta/blob/v4.4.10/project/Versions.scala | ||
dependencyOverrides += "ch.epfl.scala" % "scalafix-interfaces" % "0.9.27" // scala-steward:off |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is currently looked up at the
ThisBuild
level elsewhere, but since usage of custom resolvers is not really tested, I chose to be overly defensive