diff --git a/examples/cross-rewrite-example/build/build.scala b/examples/cross-rewrite-example/build/build.scala index 6d2ffac5..1b5b5161 100644 --- a/examples/cross-rewrite-example/build/build.scala +++ b/examples/cross-rewrite-example/build/build.scala @@ -1,72 +1,139 @@ package cbt_examples_build.cross_rewrite import cbt._ import java.io.File -import scala.meta._ +import scala.collection.immutable +import scalafix.syntax._ +import scalafix.rewrite._ import scalafix.util._ import scalafix.util.TreePatch._ import scalafix.util.TokenPatch._ +import scala.meta._ +import scala.meta.contrib._ -class Build(val context: Context) extends BaseBuild{ outer => - override def defaultScalaVersion = "2.11.8" +class TestBuild( val context: Context, mainBuild: BaseBuild ) extends ScalaTest with Scalameta { + override def defaultScalaVersion = mainBuild.scalaVersion + override def projectDirectory = CrossRewrite.mkdirIfNotExists( mainBuild.projectDirectory / "test" ) + override def dependencies = mainBuild +: super.dependencies +} - def versions = Seq[(String, Seq[Patch])]( - scalaVersion -> Seq(), - "2.12.1" -> Seq( - RemoveGlobalImport( - importer"scala.concurrent.Future" - ), - AddGlobalImport( - importer"scala.util.Try" +class Build(val context: Context) extends BaseBuild with Scalameta with PackageJars { defaultMainBuild => + override def defaultScalaVersion: String = "2.12.1" + override def groupId = "org.cvogt" + override def artifactId = "cbt-examples-cross-rewrite" + override def version = "0.1" + override def scaladoc = None // Scalameta breaks Scaladoc + + override def test: BaseBuild = new TestBuild( context, this ) + + def cross = for{ + ( v, version_patches, version_rewrites ) <- CrossRewrite.versions + ( label, dep, lib_patches, lib_rewrites ) <- CrossRewrite.libs + } yield { + new Build(context) with Scalafix{ patchedMainBuild => + override def defaultScalaVersion = v + override def artifactId = super.artifactId ~ "-" ~ label + override def projectDirectory = CrossRewrite.mkdirIfNotExists( + defaultMainBuild.target / "rewrites" / label ++ "-" ++ v ) - ) + override def dependencies = + super.dependencies ++ Resolver(mavenCentral).bind( + // hack because using ScalaDependency in the outer build binds it + // to THAT builds initial scalaVersion, which we are overriding + // here, but we are looping over libs outside of that, so + // the override doesn't affect it + // So we use MavenDependency instead and append the id here. + dep.copy(artifactId = dep.artifactId + "_" + scalaMajorVersion) + ) + override def sources = CrossRewrite.patchesSources( + defaultMainBuild.sources, + projectDirectory / "src", + defaultMainBuild.classpath, + lib_patches ++ version_patches, + lib_rewrites ++ version_rewrites, + lib + ) + + override def test = new TestBuild( context, this ){ + override def sources = CrossRewrite.patchesSources( + defaultMainBuild.test.sources, + projectDirectory / "src", + defaultMainBuild.test.classpath, + lib_patches ++ version_patches, + lib_rewrites ++ version_rewrites, + lib + ) + } + } + } +} + +object CrossRewrite{ + def versions = Seq[(String, Seq[Patch], Seq[Rewrite[ScalafixMirror]])]( + ("2.12.1", Seq(), Seq()), + ("2.11.8", Seq(), Seq()) ) - def libs = Seq[(String, MavenDependency, Seq[Patch])]( + def libs = Seq[(String, MavenDependency, Seq[Patch], Seq[Rewrite[ScalafixMirror]])]( ( "scalaz", - ScalaDependency( "org.scalaz", "scalaz-core", "7.2.2" ), + MavenDependency( "org.scalaz", "scalaz-core", "7.2.10" ), Seq( - ) + AddGlobalImport(importer"scalaz._"), + Replace(Symbol("_root_.scala.package.Either."), q"\/"), + Replace(Symbol("_root_.scala.util.Right."), q"\/-"), + RemoveGlobalImport(importer"cats.implicits._") + ), + Seq() ), ( "cats", - ScalaDependency( "org.typelevel", "cats", "0.9.0" ), - Seq( - ) + MavenDependency( "org.typelevel", "cats", "0.9.0" ), + Seq(), + Seq(addCatsImplicitsIfNeeded) ) ) - def cross = versions.flatMap{ case ( v, version_rewrites ) => - libs.map{ - case ( label, dep, lib_rewrites ) => - val d = outer.target / "rewrites" / label ++ "-" ++ v - d.mkdirs - new Build(context) with Scalafix with PackageJars{ - override def groupId = "org.cvogt" - override def artifactId = "cbt-examples-cross-rewrite-" + label - override def version = "0.1" - override def defaultScalaVersion = v - override def dependencies = super.dependencies ++ Resolver( mavenCentral ).bind( dep ) - override def projectDirectory = d - override def scaladoc = None - override def sources = { - val fromTo = lib.autoRelative( outer.sources ).collect{ - case (location, relative) if location.isFile - => location -> projectDirectory / "src" / relative - } - - val to = fromTo.map(_._2) - assert( ( to diff to.distinct ).isEmpty ) + //This is an example of adding inmport only if a specific method is used in the source file. + val addCatsImplicitsIfNeeded = Rewrite[ScalafixMirror] { ctx => + implicit val mirror = ctx.mirror //ctx.tree methods using the semantic mirror rely on an implicit mirror in scope + val symbolForEitherMap = Symbol("_root_.scala.util.Either.map.") //The symbol we are looking for in the file + val symbolFoundInCurrentFile = ctx.tree.exists { + case t: Term.Name //Use the semantic mirror to compare symbols in the file with the symbol we are looking for + if mirror.symbol(t).toOption.exists(_.normalized == symbolForEitherMap) => true + case _ => false + } + //Only add the import to files containing the symbol we are looking for, Either.map in this case. + immutable.Seq(AddGlobalImport(importer"cats.implicits._")).filter(_ => symbolFoundInCurrentFile) + } - Scalafix.apply(lib).config( - outer.classpath, - files = fromTo, - patches = lib_rewrites ++ version_rewrites, - allowEmpty = true - ).apply + def mkdirIfNotExists( d: File ): File = { + d.mkdirs + d + } - to - } - } + def patchesSources( + sources: Seq[File], + destination: File, + semanticDbClassPath: ClassPath, + patches: Seq[Patch], + rewrites: Seq[Rewrite[ScalafixMirror]], + lib: Lib + ) = { + val fromTo = lib.autoRelative( sources ).collect{ + case (location, relative) if location.isFile + => location -> destination / relative } + + val to = fromTo.map(_._2) + assert( ( to diff to.distinct ).isEmpty ) + + Scalafix.apply(lib).config( + semanticDbClassPath, + files = fromTo, + patches = patches, + rewrites = rewrites, + allowEmpty = true + ).apply + + to } } diff --git a/examples/cross-rewrite-example/src/Main.scala b/examples/cross-rewrite-example/src/Main.scala index 27ea3ff1..d052a6d2 100644 --- a/examples/cross-rewrite-example/src/Main.scala +++ b/examples/cross-rewrite-example/src/Main.scala @@ -1 +1,5 @@ -import scala.concurrent.Future +object Main { + def main( args: Array[String] ): Unit = { + Disjunction.intOrString.map( println ) + } +} diff --git a/examples/cross-rewrite-example/src/disjunction.scala b/examples/cross-rewrite-example/src/disjunction.scala new file mode 100644 index 00000000..97686b80 --- /dev/null +++ b/examples/cross-rewrite-example/src/disjunction.scala @@ -0,0 +1,3 @@ +object Disjunction { + val intOrString: Either[Int, String] = Right( "It works!" ) +} diff --git a/examples/cross-rewrite-example/test/disjunction_spec.scala b/examples/cross-rewrite-example/test/disjunction_spec.scala new file mode 100644 index 00000000..58c63fdd --- /dev/null +++ b/examples/cross-rewrite-example/test/disjunction_spec.scala @@ -0,0 +1,9 @@ +import org.scalatest._ + +class Test extends FunSpec with Matchers { + describe( "Disjunction" ) { + it( "should work in all versions" ) { + Disjunction.intOrString shouldBe Right( "It works!" ) + } + } +} diff --git a/stage1/resolver.scala b/stage1/resolver.scala index f4a9b13d..e60260fe 100644 --- a/stage1/resolver.scala +++ b/stage1/resolver.scala @@ -86,7 +86,10 @@ trait DependencyImplementation extends Dependency{ java_exe.string +: "-cp" +: classpath.string +: className +: args ) } else { - lib.getMain( classLoader.loadClass( className ) )( args ) + val cl = if( !flatClassLoader ) classLoader else { + new java.net.URLClassLoader(classpath.strings.map(f => new URL("file://" ++ f)).toArray) + } + lib.getMain( cl.loadClass( className ) )( args ) } } @@ -109,30 +112,26 @@ trait DependencyImplementation extends Dependency{ def flatClassLoader: Boolean = false override def classLoader: ClassLoader = taskCache[DependencyImplementation]( "classLoader" ).memoize{ - if( flatClassLoader ){ - new java.net.URLClassLoader(classpath.strings.map(f => new URL("file://" ++ f)).toArray) - } else { - /* - if( concurrencyEnabled ){ - // trigger concurrent building / downloading dependencies - exportClasspathConcurrently - } - */ - lib.classLoaderRecursion( - this, - (this +: transitiveDependencies).collect{ - case d: ArtifactInfo => d - }.groupBy( - d => (d.groupId,d.artifactId) - ).mapValues(_.head) - ) + /* + if( concurrencyEnabled ){ + // trigger concurrent building / downloading dependencies + exportClasspathConcurrently } + */ + lib.classLoaderRecursion( + this, + (this +: transitiveDependencies).collect{ + case d: ArtifactInfo => d + }.groupBy( + d => (d.groupId,d.artifactId) + ).mapValues(_.head) + ) } // FIXME: these probably need to update outdated as well def classpath: ClassPath = exportedClasspath ++ dependencyClasspath def verifyClasspath: Unit = classpath.verify(lib) - def dependencyClasspath: ClassPath = taskCache[DependencyImplementation]( "dependencyClasspath" ).memoize{ + def dependencyClasspath: ClassPath = { ClassPath( transitiveDependencies .flatMap(_.exportedClasspath.files) @@ -369,7 +368,13 @@ case class BoundMavenDependency( cbtLastModified, mavenCache ++ basePath(true) ++ ".pom.dependencies", classLoaderCache.hashMap )( MavenDependency.deserialize )( _.serialize )( MavenDependency.dejavafy )( _.map(_.javafy).toArray ){ (pomXml \ "dependencies" \ "dependency").collect{ - case xml if ( (xml \ "scope").text == "" || (xml \ "scope").text == "compile" ) && (xml \ "optional").text != "true" => + case xml if ( + (xml \ "scope").text == "" + // these are probably not right like this, but should be better than not having them for now + || (xml \ "scope").text == "runtime" + || (xml \ "scope").text == "compile" + || (xml \ "scope").text == "provided" + ) && (xml \ "optional").text != "true" => val artifactId = lookup(xml,_ \ "artifactId").get val groupId = lookup(xml,_ \ "groupId").getOrElse( diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index 05e3c41c..a28c8609 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -17,7 +17,7 @@ trait BaseBuild extends BuildInterface with DependencyImplementation with SbtDep // will create new instances given the context, which means operations in the // overrides will happen multiple times and if they are not idempotent stuff likely breaks def context: Context - override lazy val moduleKey: String = "BaseBuild("+target.string+")" + override lazy val moduleKey: String = "BaseBuild("+target.string+","+context.scalaVersion+")" implicit def transientCache: java.util.Map[AnyRef,AnyRef] = context.transientCache object libraries extends libraries( context, scalaVersion, scalaMajorVersion ) diff --git a/stage2/GitDependency.scala b/stage2/GitDependency.scala index 8a4a4413..26d758a0 100644 --- a/stage2/GitDependency.scala +++ b/stage2/GitDependency.scala @@ -22,6 +22,8 @@ object GitDependency{ ++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref ++ ", " ++ subBuild.mkString(", ") + ++ ", " + ++ context.scalaVersion.toString ++ ")" ) diff --git a/stage2/LazyDependency.scala b/stage2/LazyDependency.scala index 2822638c..2cfcd273 100644 --- a/stage2/LazyDependency.scala +++ b/stage2/LazyDependency.scala @@ -5,7 +5,7 @@ class LazyDependency( _dependency: => Dependency )( implicit logger: Logger, tra def dependenciesArray = Array( dependency ) def exportedClasspathArray = Array() override def lastModified = dependency.lastModified - override lazy val moduleKey = show + override lazy val moduleKey = "LazyDependency:" + dependency.moduleKey def show = s"LazyDependency(${dependency.show})" override def toString = show override def equals( other: Any ) = other match {