Skip to content

Commit

Permalink
allow usage of rules compiled against 2.11 & 2.13
Browse files Browse the repository at this point in the history
Benefits
1. Rules using the presentation compiler can now work on 2.11 and 2.13
   input as long as they are cross-compiled
2. Advanced users with local rules targeting 2.11 or 2.13 code will be
   able to simplify their build

2.12 remains the default until 1.0 to be backward compatible with
external rules (i.e loaded through `scalafixDependencies` or
`dependency:`) that are not (yet) cross-compiled
  • Loading branch information
github-brice-jaglin committed Jun 2, 2020
1 parent 3c62af2 commit e6c40bd
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 20 deletions.
29 changes: 17 additions & 12 deletions src/main/scala/scalafix/internal/sbt/ScalafixCoursier.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,30 @@ import scalafix.sbt.BuildInfo
import scala.collection.JavaConverters._

object ScalafixCoursier {
private def scalafixCliModule: Module =
private def scalafixCliModule(scalaVersion: String): Module =
Module.of(
"ch.epfl.scala",
s"scalafix-cli_${BuildInfo.scala212}"
s"scalafix-cli_$scalaVersion"
)
private def scalafixCli: Dependency =
private def scalafixCli(scalaVersion: String): Dependency =
Dependency.of(
scalafixCliModule,
scalafixCliModule(scalaVersion),
BuildInfo.scalafixVersion
)

def scalafixCliJars(
scalaVersion: String,
repositories: Seq[cs.Repository]
): List[Path] = {
runFetch(
newFetch()
.addDependencies(scalafixCli)
newFetch(scalaVersion)
.addDependencies(scalafixCli(scalaVersion))
.addRepositories(toCoursierRepositories(repositories): _*)
)
}

def scalafixToolClasspath(
scalaVersion: String,
deps: Seq[ModuleID],
customResolvers: Seq[cs.Repository]
): Seq[URL] = {
Expand All @@ -41,7 +43,7 @@ object ScalafixCoursier {
} else {
val jars = dependencyCache.computeIfAbsent(
deps,
fetchScalafixDependencies(customResolvers)
fetchScalafixDependencies(scalaVersion, customResolvers)
)
jars.map(_.toUri.toURL)
}
Expand All @@ -51,13 +53,16 @@ object ScalafixCoursier {
jutil.Collections.synchronizedMap(new jutil.HashMap())
}
private[scalafix] def fetchScalafixDependencies(
scalaVersion: String,
customResolvers: Seq[cs.Repository]
): function.Function[Seq[ModuleID], List[Path]] =
new function.Function[Seq[ModuleID], List[Path]] {
override def apply(t: Seq[ModuleID]): List[Path] = {
val binaryScalaVersion = CrossVersion.binaryScalaVersion(scalaVersion)
val dependencies = t.map { module =>
val binarySuffix =
if (module.crossVersion.isInstanceOf[CrossVersion.Binary]) "_2.12"
if (module.crossVersion.isInstanceOf[CrossVersion.Binary])
s"_$binaryScalaVersion"
else ""
Dependency.of(
module.organization,
Expand All @@ -66,9 +71,9 @@ object ScalafixCoursier {
)
}
runFetch(
newFetch()
newFetch(scalaVersion)
.addRepositories(toCoursierRepositories(customResolvers): _*)
.addDependencies(scalafixCli)
.addDependencies(scalafixCli(scalaVersion))
.addDependencies(dependencies.toArray: _*)
)
}
Expand All @@ -84,7 +89,7 @@ object ScalafixCoursier {
}.toArray
def runFetch(fetch: Fetch): List[Path] =
fetch.fetch().asScala.iterator.map(_.toPath()).toList
def newFetch(): Fetch =
def newFetch(scalaVersion: String): Fetch =
Fetch
.create()
.withRepositories()
Expand All @@ -93,7 +98,7 @@ object ScalafixCoursier {
.create()
.withForceVersions(
Map(
scalafixCliModule -> scalafixCli.getVersion()
scalafixCliModule(scalaVersion) -> BuildInfo.scalafixVersion
).asJava
)
)
Expand Down
12 changes: 9 additions & 3 deletions src/main/scala/scalafix/internal/sbt/ScalafixInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ object Arg {
}

class ScalafixInterface private (
scalaVersion: String,
scalafixArguments: ScalafixArguments, // hide it to force usage of withArgs so we can intercept arguments
val args: Seq[Arg]
) {
Expand All @@ -88,11 +89,13 @@ class ScalafixInterface private (
.classLoader

private def this(
scalaVersion: String,
api: ScalafixAPI,
toolClasspath: URLClassLoader,
mainCallback: ScalafixMainCallback
) =
this(
scalaVersion,
api
.newArguments()
.withMainCallback(mainCallback)
Expand Down Expand Up @@ -125,6 +128,7 @@ class ScalafixInterface private (
): ScalafixInterface = {
val extraURLs = ScalafixCoursier
.scalafixToolClasspath(
scalaVersion,
extraExternalDeps,
customResolvers
) ++ extraInternalDeps.map(_.toURI.toURL)
Expand All @@ -141,7 +145,7 @@ class ScalafixInterface private (
try arg(acc)
catch { case NonFatal(e) => throw new InvalidArgument(e.getMessage) }
}
new ScalafixInterface(newScalafixArguments, this.args ++ args)
new ScalafixInterface(scalaVersion, newScalafixArguments, this.args ++ args)
}

def run(): Seq[ScalafixError] =
Expand All @@ -165,19 +169,21 @@ object ScalafixInterface {
override def apply(): T = _value.get
}
def fromToolClasspath(
scalafixScalaVersion: String,
scalafixDependencies: Seq[ModuleID],
scalafixCustomResolvers: Seq[Repository],
logger: Logger = Compat.ConsoleLogger(System.out)
): () => ScalafixInterface =
new LazyValue({ () =>
val jars = ScalafixCoursier.scalafixCliJars(scalafixCustomResolvers)
val jars = ScalafixCoursier
.scalafixCliJars(scalafixScalaVersion, scalafixCustomResolvers)
val urls = jars.map(_.toUri.toURL).toArray
val interfacesParent =
new ScalafixInterfacesClassloader(this.getClass.getClassLoader)
val classloader = new URLClassLoader(urls, interfacesParent)
val api = ScalafixAPI.classloadInstance(classloader)
val callback = new ScalafixLogger(logger)
new ScalafixInterface(api, classloader, callback)
new ScalafixInterface(scalafixScalaVersion, api, classloader, callback)
.addToolClasspath(
scalafixDependencies,
scalafixCustomResolvers,
Expand Down
8 changes: 8 additions & 0 deletions src/main/scala/scalafix/sbt/ScalafixPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ object ScalafixPlugin extends AutoPlugin {
"Optional list of custom rules to install from Maven Central. " +
"This setting is read from the global scope so it only needs to be defined once in the build."
)
val scalafixScalaVersion: SettingKey[String] =
settingKey[String](
"The full scala version used for the scalafix classpath. Rules must be compiled against that binary version." +
s"Defaults to the latest supported 2.12 version (${BuildInfo.scala212}). " +
"This setting is read from the global scope so it only needs to be defined once in the build."
)
val scalafixConfig: SettingKey[Option[File]] =
settingKey[Option[File]](
"Optional location to .scalafix.conf file to specify which scalafix rules should run. " +
Expand Down Expand Up @@ -111,6 +117,7 @@ object ScalafixPlugin extends AutoPlugin {
// https://github.com/sbt/sbt/issues/3572#issuecomment-417582703
workingDirectory = baseDirectory.in(ThisBuild).value.toPath
scalafixInterface = ScalafixInterface.fromToolClasspath(
scalafixScalaVersion = scalafixScalaVersion.in(ThisBuild).value,
scalafixDependencies = scalafixDependencies.in(ThisBuild).value,
scalafixCustomResolvers = scalafixResolvers.in(ThisBuild).value
)
Expand All @@ -119,6 +126,7 @@ object ScalafixPlugin extends AutoPlugin {
scalafixCaching := false,
scalafixResolvers := ScalafixCoursier.defaultResolvers,
scalafixDependencies := Nil,
scalafixScalaVersion := BuildInfo.scala212, // will follow scalafixVersion once community rules are cross-built
commands += ScalafixEnable.command
)

Expand Down
10 changes: 6 additions & 4 deletions src/sbt-test/sbt-scalafix/local-rules/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,29 @@ inThisBuild(
// Custom rule published to Maven Central https://github.com/olafurpg/example-scalafix-rule
"com.geirsson" %% "example-scalafix-rule" % "1.3.0"
)
// scalaVersion := Versions.scala213,
// scalafixScalaVersion := scalaVersion.version
)
)

val rules = project
.disablePlugins(ScalafixPlugin)
.settings(
scalaVersion := Versions.scala212,
crossPaths := false,
scalaVersion := Versions.scala212, // TODO: remove
crossPaths := false, // TODO: remove
libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % Versions.scalafixVersion,
libraryDependencies += "joda-time" % "joda-time" % "2.10.6"
)

val service = project
.dependsOn(rules % ScalafixConfig)
.settings(
scalaVersion := Versions.scala213,
scalaVersion := Versions.scala213, // TODO: remove
libraryDependencies += "com.nequissimus" % "sort-imports_2.12" % "0.5.0" % ScalafixConfig
)

val sameproject = project
.settings(
scalaVersion := Versions.scala212, // the project scala version MUST match the one used by Scalafix
scalaVersion := Versions.scala212, // TODO: remove
libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % Versions.scalafixVersion % ScalafixConfig
)
10 changes: 10 additions & 0 deletions src/sbt-test/sbt-scalafix/scalafixScalaVersion/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import _root_.scalafix.sbt.{BuildInfo => V}

inThisBuild(
List(
addCompilerPlugin(scalafixSemanticdb),
scalacOptions += "-Yrangepos",
scalaVersion := V.scala211,
scalafixScalaVersion := scalaVersion.value // this should be the default in 1.x
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
resolvers += Resolver.sonatypeRepo("public")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % sys.props("plugin.version"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
object NeedExplicitResultTypes {
def a = {
println("foobar")
123
}
}
5 changes: 5 additions & 0 deletions src/sbt-test/sbt-scalafix/scalafixScalaVersion/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# ExplicitResultTypes uses the presentation compiler so the rule must run with the same scala binary version as the target
-> scalafix --check ExplicitResultTypes
> scalafix ExplicitResultTypes
> scalafix --check ExplicitResultTypes

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.eclipse.jgit.lib.AbbreviatedObjectId
import org.scalatest.Tag
import sbt.complete.Parser
import org.scalatest.funsuite.AnyFunSuite
import scalafix.sbt.BuildInfo

class SbtCompletionsSuite extends AnyFunSuite {
val fs = new Fs()
Expand All @@ -29,6 +30,7 @@ class SbtCompletionsSuite extends AnyFunSuite {
val mainArgs =
ScalafixInterface
.fromToolClasspath(
BuildInfo.scala212,
Seq(exampleDependency),
ScalafixCoursier.defaultResolvers
)()
Expand Down
4 changes: 3 additions & 1 deletion src/test/scala/scalafix/internal/sbt/ScalafixAPISuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import org.scalactic.source.Position
import sbt._
import sbt.internal.sbtscalafix.Compat
import scalafix.interfaces.ScalafixError

import org.scalatest.funsuite.AnyFunSuite
import scalafix.sbt.BuildInfo

import scala.util.Properties

class ScalafixAPISuite extends AnyFunSuite {
Expand All @@ -28,6 +29,7 @@ class ScalafixAPISuite extends AnyFunSuite {
val logger = Compat.ConsoleLogger(new PrintStream(baos))
val interface = ScalafixInterface
.fromToolClasspath(
BuildInfo.scala212,
List("com.geirsson" %% "example-scalafix-rule" % "1.3.0"),
ScalafixCoursier.defaultResolvers,
logger
Expand Down

0 comments on commit e6c40bd

Please sign in to comment.