Skip to content

WIP - adding example to cross-rewrite #466

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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 115 additions & 48 deletions examples/cross-rewrite-example/build/build.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
6 changes: 5 additions & 1 deletion examples/cross-rewrite-example/src/Main.scala
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
import scala.concurrent.Future
object Main {
def main( args: Array[String] ): Unit = {
Disjunction.intOrString.map( println )
}
}
3 changes: 3 additions & 0 deletions examples/cross-rewrite-example/src/disjunction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Disjunction {
val intOrString: Either[Int, String] = Right( "It works!" )
}
9 changes: 9 additions & 0 deletions examples/cross-rewrite-example/test/disjunction_spec.scala
Original file line number Diff line number Diff line change
@@ -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!" )
}
}
}
45 changes: 25 additions & 20 deletions stage1/resolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion stage2/BasicBuild.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down
2 changes: 2 additions & 0 deletions stage2/GitDependency.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ object GitDependency{
++ "(" ++ url ++ subDirectory.map("/" ++ _).getOrElse("") ++ "#" ++ ref
++ ", "
++ subBuild.mkString(", ")
++ ", "
++ context.scalaVersion.toString
++ ")"
)

Expand Down
2 changes: 1 addition & 1 deletion stage2/LazyDependency.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down