diff --git a/src/main/scala/higherkindness/rules_scala/used_deps/BUILD b/src/main/scala/higherkindness/rules_scala/used_deps/BUILD new file mode 100644 index 000000000..7d425f4f0 --- /dev/null +++ b/src/main/scala/higherkindness/rules_scala/used_deps/BUILD @@ -0,0 +1,43 @@ +load("//rules:scalafmt.bzl", "scala_format_test") +load("//rules:scala.bzl", "scala_library") + +scala_library( + name = "used_deps", + srcs = glob(["**/*.scala"]), + scala = "//external:scala_annex_scala", + visibility = ["//visibility:public"], + deps = [ + "@scala_annex_scala_2_12_scala_compiler//jar", + "@scala_annex_scala_2_12_scala_reflect//jar", + ], + resource_jars = [ + ":resources", + ], +) + +_gen_plugin_xml_cmd = """ +cat > $@ << EOF + + name + higherkindness.rules_scala.used_deps.UsedDepsPlugin + +""" + +genrule( + name = "gen-scalac-plugin.xml", + outs = ["scalac-plugin.xml"], + cmd = _gen_plugin_xml_cmd, +) + +java_binary( + name = "resources", + classpath_resources = [ + ":gen-scalac-plugin.xml", + ], + create_executable = False, +) + +scala_format_test( + name = "format", + srcs = glob(["**/*.scala"]), +) diff --git a/src/main/scala/higherkindness/rules_scala/used_deps/plugin.scala b/src/main/scala/higherkindness/rules_scala/used_deps/plugin.scala new file mode 100644 index 000000000..4ca81bfc6 --- /dev/null +++ b/src/main/scala/higherkindness/rules_scala/used_deps/plugin.scala @@ -0,0 +1,94 @@ +package higherkindness.rules_scala +package used_deps + +import scala.reflect.io.AbstractFile +import scala.reflect.io.NoAbstractFile +import scala.reflect.internal.SymbolTable +import scala.tools.nsc.Global +import scala.tools.nsc.Phase +import scala.tools.nsc.plugins.Plugin +import scala.tools.nsc.plugins.PluginComponent +import scala.util.matching.Regex + +import java.io.File + +final class UsedDepsPlugin(override val global: Global) extends Plugin { + + override val name: String = "used-deps" + override val description: String = "tracks used dependencies" + override val components: List[PluginComponent] = usedDeps :: Nil + + private object usedDeps extends PluginComponent { + override val global: Global = UsedDepsPlugin.this.global + override val phaseName: String = "unused-deps" + override val runsAfter: List[String] = global.refChecks.phaseName :: Nil + + private[used_deps] var outputFile: Option[File] = None + + override def newPhase(prev: Phase): Phase = + new StdPhase(prev) with UsedDepsPhase[global.type] { + val global = usedDeps.this.global + } + } + + override val optionsHelp: Option[String] = Some( + " -P:unused-deps:out= Path to write output file (default: stdout)" + ) + + private implicit final class RegexInterpolator(sc: StringContext) { + def r = new Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*) + } + + override def init(options: List[String], error: String => Unit): Boolean = { + options.foreach { + case r"out=(.*)$out" => + usedDeps.outputFile = Some(new File(out)) + case arg => + error(s"Unexpected argument: $arg") + } + true + } + + { + hijackField(classOf[SymbolTable], "traceSymbolActivity", true) + } + + if (global.traceSymbolActivity != true) + sys.error("unable to force tracing of symbol activity") + + private[this] def hijackField[T](clazz: Class[_], name: String, newValue: T): T = { + val field = clazz.getDeclaredField(name) + field.setAccessible(true) + val oldValue = field.get(global).asInstanceOf[T] + field.set(global, newValue) + oldValue + } + +} + + +trait UsedDepsPhase[G <: Global] { + val global: G + import global._ + + def apply(unit: CompilationUnit): Unit = { + val map = traceSymbols.allSymbols.values + .filter(_.associatedFile != NoAbstractFile) + .filter(_.associatedFile.file != null) + .foldLeft(Map.empty[File, List[Symbol]]) { (acc, sym) => + val k = sym.associatedFile.file + acc.get(k) match { + case Some(vv) => acc + ((k, sym :: vv)) + case None => acc + ((k, sym :: Nil)) + } + } + + map.foreach { case (k, v) => + println(k) + v.foreach(sym => println(" " + sym)) + } + + () + } + +} diff --git a/src/test/scala/higherkindness/rules_scala/used_deps/BUILD b/src/test/scala/higherkindness/rules_scala/used_deps/BUILD new file mode 100644 index 000000000..673513659 --- /dev/null +++ b/src/test/scala/higherkindness/rules_scala/used_deps/BUILD @@ -0,0 +1,47 @@ +load("//rules:scalafmt.bzl", "scala_format_test") +load("//rules:scala.bzl", "scala_library") + +scala_library( + name = "LibA1", + srcs = [ + "LibA1.scala", + ], + scala = "//external:scala_annex_scala", + plugins = [ + "//src/main/scala/higherkindness/rules_scala/used_deps", + ], + visibility = ["//visibility:public"], +) + +scala_library( + name = "LibA2", + srcs = [ + "LibA2.scala", + ], + scala = "//external:scala_annex_scala", + plugins = [ + "//src/main/scala/higherkindness/rules_scala/used_deps", + ], + visibility = ["//visibility:public"], +) + +scala_library( + name = "LibB", + srcs = [ + "LibB.scala", + ], + deps = [ + ":LibA1", + ":LibA2", + ], + scala = "//external:scala_annex_scala", + plugins = [ + "//src/main/scala/higherkindness/rules_scala/used_deps", + ], + visibility = ["//visibility:public"], +) + +scala_format_test( + name = "format", + srcs = glob(["**/*.scala"]), +) diff --git a/src/test/scala/higherkindness/rules_scala/used_deps/LibA1.scala b/src/test/scala/higherkindness/rules_scala/used_deps/LibA1.scala new file mode 100644 index 000000000..769dce933 --- /dev/null +++ b/src/test/scala/higherkindness/rules_scala/used_deps/LibA1.scala @@ -0,0 +1,4 @@ +package higherkindness.rules_scala +package used_deps + +trait LibA1 diff --git a/src/test/scala/higherkindness/rules_scala/used_deps/LibA2.scala b/src/test/scala/higherkindness/rules_scala/used_deps/LibA2.scala new file mode 100644 index 000000000..0c07e941b --- /dev/null +++ b/src/test/scala/higherkindness/rules_scala/used_deps/LibA2.scala @@ -0,0 +1,4 @@ +package higherkindness.rules_scala +package used_deps + +trait LibA2 diff --git a/src/test/scala/higherkindness/rules_scala/used_deps/LibB.scala b/src/test/scala/higherkindness/rules_scala/used_deps/LibB.scala new file mode 100644 index 000000000..53e38d6ba --- /dev/null +++ b/src/test/scala/higherkindness/rules_scala/used_deps/LibB.scala @@ -0,0 +1,4 @@ +package higherkindness.rules_scala +package used_deps + +trait LibB extends LibA1 with LibA2 diff --git a/src/test/scala/higherkindness/rules_scala/used_deps/test1.scala b/src/test/scala/higherkindness/rules_scala/used_deps/test1.scala new file mode 100644 index 000000000..66d179a51 --- /dev/null +++ b/src/test/scala/higherkindness/rules_scala/used_deps/test1.scala @@ -0,0 +1,6 @@ +package higherkindness.rules_scala +package test1 + +class Test1 { + +}