diff --git a/build.sbt b/build.sbt index e8cbdb4..1e9c6b0 100644 --- a/build.sbt +++ b/build.sbt @@ -8,9 +8,15 @@ addCommandAlias( ";scalafmtCheckAll; scalafmtSbtCheck; +test; +publishLocal; scripted" ) -// ScalaTest -libraryDependencies += "org.scalactic" %% "scalactic" % "3.2.9" % Test -libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.9" % Test +lazy val root = project.aggregate(compiler, `sbt-source-extract`) + +lazy val dependencies = Seq( + "org.scalactic" %% "scalactic" % "3.2.9" % Test, + "org.scalatest" %% "scalatest" % "3.2.9" % Test, + "org.typelevel" %% "cats-core" % "2.4.0" +) + +lazy val compilerClasspath = TaskKey[Classpath]("compiler-classpath") inThisBuild( List( @@ -23,7 +29,7 @@ inThisBuild( Developer( "jisantuc", "James Santucci", - "james.santucci@47deg.com", + "james.santucci@47d1eg.com", url("https://github.com/jisantuc") ) ) @@ -32,13 +38,33 @@ inThisBuild( (console / initialCommands) := """import io.github.jisantuc.sbtse._""" -enablePlugins(ScriptedPlugin, SbtPlugin) -// set up 'scripted; sbt plugin for testing sbt plugins -scriptedLaunchOpts ++= - Seq("-Xmx1024M", "-Dplugin.version=" + version.value) - ThisBuild / githubWorkflowBuild := Seq( WorkflowStep.Sbt(List("ci-test")) ) ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty + +lazy val `sbt-source-extract` = (project in file("sbtse")) + .settings( + compilerClasspath := { (compiler / Compile / fullClasspath) }.value, + buildInfoObject := "Meta", + buildInfoPackage := "io.github.jisantuc.sbtse", + buildInfoKeys := Seq( + version, + BuildInfoKey.map(compilerClasspath) { case (_, classFiles) ⇒ + ("compilerClasspath", classFiles.map(_.data)) + } + ), + libraryDependencies ++= dependencies, + // set up 'scripted; sbt plugin for testing sbt plugins + scriptedLaunchOpts ++= + Seq("-Xmx1024M", "-Dplugin.version=" + version.value) + ) + .dependsOn(compiler) + .enablePlugins(BuildInfoPlugin, SbtPlugin, ScriptedPlugin) + +lazy val compiler = (project in file("compiler")) + .settings( + libraryDependencies ++= dependencies + ) + .enablePlugins(BuildInfoPlugin, SbtPlugin) diff --git a/compiler/src/main/scala/io/github/jisantuc/sbtse/Compiler.scala b/compiler/src/main/scala/io/github/jisantuc/sbtse/Compiler.scala new file mode 100644 index 0000000..29537cd --- /dev/null +++ b/compiler/src/main/scala/io/github/jisantuc/sbtse/Compiler.scala @@ -0,0 +1,14 @@ +package io.github.jisantuc.sbtse + +class CompilerJava { + def compile(): Array[String] = { + locally(Compiler().test) + println("ok!") + Array.empty + } +} + +final case class Compiler() { + lazy val extracter = new Extracter() + val test = extracter.testFn() +} diff --git a/compiler/src/main/scala/io/github/jisantuc/sbtse/Extracter.scala b/compiler/src/main/scala/io/github/jisantuc/sbtse/Extracter.scala new file mode 100644 index 0000000..b403520 --- /dev/null +++ b/compiler/src/main/scala/io/github/jisantuc/sbtse/Extracter.scala @@ -0,0 +1,67 @@ +package io.github.jisantuc.sbtse + +import java.io.File + +import scala.annotation.tailrec +import scala.collection.compat._ +import scala.reflect.internal.util.BatchSourceFile +import scala.tools.nsc._ +import scala.tools.nsc.doc.{Settings => _, _} +import scala.tools.nsc.doc.base.comment.Comment + +class Extracter { + private def classPathOfClass(className: String): List[String] = { + val resource = className.split('.').mkString("/", "/", ".class") + val path = getClass.getResource(resource).getPath + if (path.indexOf("file:") >= 0) { + val indexOfFile = path.indexOf("file:") + 5 + val indexOfSeparator = path.lastIndexOf('!') + List(path.substring(indexOfFile, indexOfSeparator)) + } else { + require(path.endsWith(resource)) + List(path.substring(0, path.length - resource.length + 1)) + } + } + + private lazy val compilerPath = + try classPathOfClass("scala.tools.nsc.Interpreter") + catch { + case e: Throwable => + throw new RuntimeException( + "Unable to load Scala interpreter from classpath (scala-compiler jar is missing?)", + e + ) + } + + private lazy val libPath = + try classPathOfClass("scala.AnyVal") + catch { + case e: Throwable => + throw new RuntimeException( + "Unable to load scala base object from classpath (scala-library jar is missing?)", + e + ) + } + + lazy val paths: List[String] = compilerPath ::: libPath + + // Same settings as + // https://github.com/scala-exercises/sbt-exercise/blob/v0.6.7/compiler/src/main/scala/org/scalaexercises/exercises/compiler/SourceTextExtraction.scal + // except usejavacp.value is set to false. + // that setting controls whether to "Utilize the java.class.path in classpath resolution." + // clearly that was interacting with _something_ poorly (buildinfo plugin? I don't know!) + // but it'll probably be a bit before I understand what. + val defaultSettings = new Settings { + embeddedDefaults[Global.type] + Yrangepos.value = true + usejavacp.value = false + + bootclasspath.value = paths.mkString(File.pathSeparator) + classpath.value = paths.mkString(File.pathSeparator) + } + val global = new Global(defaultSettings) + + def testFn(): global.Run = { + new global.Run() + } +} diff --git a/compiler/src/test/scala/io/github/jisantuc/sbtse/SourceExtractSpec.scala b/compiler/src/test/scala/io/github/jisantuc/sbtse/SourceExtractSpec.scala new file mode 100644 index 0000000..9053c3c --- /dev/null +++ b/compiler/src/test/scala/io/github/jisantuc/sbtse/SourceExtractSpec.scala @@ -0,0 +1,12 @@ +package io.github.jisantuc.sbtse + +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers + +class SourceExtractTest extends AnyFunSpec with Matchers { + describe("run without crashing when initialized without classpath hackery") { + it("should do that") { + new CompilerJava().compile() + } + } +} diff --git a/project/plugins.sbt b/project/plugins.sbt index c73413a..83274ec 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ libraryDependencies += "org.scala-sbt" %% "scripted-plugin" % sbtVersion.value addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.13.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") diff --git a/sbtse/src/main/scala/io/github/jisantuc/sbtse/SourceExtractPlugin.scala b/sbtse/src/main/scala/io/github/jisantuc/sbtse/SourceExtractPlugin.scala new file mode 100644 index 0000000..98dc016 --- /dev/null +++ b/sbtse/src/main/scala/io/github/jisantuc/sbtse/SourceExtractPlugin.scala @@ -0,0 +1,62 @@ +package io.github.jisantuc.sbtse + +import sbt._ +import sbt.Keys._ +import sbt.plugins.JvmPlugin + +import cats.data.Ior +import cats.syntax.either._ +import cats.syntax.monadError._ +import scala.util.Random +import sbt.internal.inc.classpath.ClasspathUtil +import java.nio.file.Paths + +object SourceExtractPlugin extends AutoPlugin { + + override def trigger = allRequirements + override def requires = JvmPlugin + + object autoImport { + val testInit = + TaskKey[Unit]("test-init", "run initialization to see if it crashes") + } + + import autoImport._ + + override lazy val projectSettings = Seq( + testInit := locally(testTask.value) + ) + + override lazy val buildSettings = Seq() + + override lazy val globalSettings = Seq() + + private def catching[A](f: => A)(msg: => String) = + Either.catchNonFatal(f).leftMap(e => Ior.both(msg, e)) + + private val COMPILER_CLASS = "io.github.jisantuc.sbtse.CompilerJava" + + private type COMPILER = { + def compile(): Array[String] + } + + def testTask = Def.task { + val libraryClasspath = Attributed.data((Compile / fullClasspath).value) + val classpath = (Meta.compilerClasspath ++ libraryClasspath).distinct + val loader = ClasspathUtil.toLoader( + classpath.map(file => Paths.get(file.getAbsolutePath())), + null, + ClasspathUtil.createClasspathResources( + appPaths = + Meta.compilerClasspath.map(file => Paths.get(file.getAbsolutePath())), + bootPaths = scalaInstance.value.allJars.map(file => + Paths.get(file.getAbsolutePath()) + ) + ) + ) + + val compilerClass = loader.loadClass(COMPILER_CLASS) + val compiler = compilerClass.newInstance.asInstanceOf[COMPILER] + compiler.compile() + } +} diff --git a/sbtse/src/sbt-test/sbt-source-extract/simple/build.sbt b/sbtse/src/sbt-test/sbt-source-extract/simple/build.sbt new file mode 100644 index 0000000..eeac86b --- /dev/null +++ b/sbtse/src/sbt-test/sbt-source-extract/simple/build.sbt @@ -0,0 +1,11 @@ +version := "0.1" +scalaVersion := "2.12.15" + +lazy val simple = (project in file(".")) + .settings( + resolvers ++= Seq( + Resolver.sonatypeRepo("snapshots"), + Resolver.defaultLocal + ) + ) + .enablePlugins(SourceExtractPlugin) diff --git a/src/sbt-test/sbt-source-extract/simple/project/build.properties b/sbtse/src/sbt-test/sbt-source-extract/simple/project/build.properties similarity index 100% rename from src/sbt-test/sbt-source-extract/simple/project/build.properties rename to sbtse/src/sbt-test/sbt-source-extract/simple/project/build.properties diff --git a/src/sbt-test/sbt-source-extract/simple/project/plugins.sbt b/sbtse/src/sbt-test/sbt-source-extract/simple/project/plugins.sbt similarity index 100% rename from src/sbt-test/sbt-source-extract/simple/project/plugins.sbt rename to sbtse/src/sbt-test/sbt-source-extract/simple/project/plugins.sbt diff --git a/src/sbt-test/sbt-source-extract/simple/src/main/scala/Main.scala b/sbtse/src/sbt-test/sbt-source-extract/simple/src/main/scala/Main.scala similarity index 100% rename from src/sbt-test/sbt-source-extract/simple/src/main/scala/Main.scala rename to sbtse/src/sbt-test/sbt-source-extract/simple/src/main/scala/Main.scala diff --git a/sbtse/src/sbt-test/sbt-source-extract/simple/test b/sbtse/src/sbt-test/sbt-source-extract/simple/test new file mode 100644 index 0000000..8f138a9 --- /dev/null +++ b/sbtse/src/sbt-test/sbt-source-extract/simple/test @@ -0,0 +1,2 @@ +# check whether the test task runs without crashing +> simple / testInit \ No newline at end of file diff --git a/src/main/scala/io/github/jisantuc/sbtse/SourceextractPlugin.scala b/src/main/scala/io/github/jisantuc/sbtse/SourceextractPlugin.scala deleted file mode 100644 index 2c3062a..0000000 --- a/src/main/scala/io/github/jisantuc/sbtse/SourceextractPlugin.scala +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.jisantuc.sbtse - -import sbt._ -import sbt.Keys._ -import sbt.plugins.JvmPlugin - -import scala.util.Random - -object SourceExtractPlugin extends AutoPlugin { - - override def trigger = allRequirements - override def requires = JvmPlugin - - object autoImport { - val randomGenerator = - SettingKey[Random]("random-generator", "random number generator") - val randomNumber = TaskKey[Int]("random-number", "generate a random number") - } - - import autoImport._ - - override lazy val projectSettings = Seq( - randomGenerator := new Random(), - randomNumber := randomGenerator.value.nextInt(100) - ) - - override lazy val buildSettings = Seq() - - override lazy val globalSettings = Seq() -} diff --git a/src/sbt-test/sbt-source-extract/simple/build.sbt b/src/sbt-test/sbt-source-extract/simple/build.sbt deleted file mode 100644 index a62047e..0000000 --- a/src/sbt-test/sbt-source-extract/simple/build.sbt +++ /dev/null @@ -1,4 +0,0 @@ -version := "0.1" -scalaVersion := "2.12.15" - -enablePlugins(SourceExtractPlugin) diff --git a/src/sbt-test/sbt-source-extract/simple/test b/src/sbt-test/sbt-source-extract/simple/test deleted file mode 100644 index 69f0628..0000000 --- a/src/sbt-test/sbt-source-extract/simple/test +++ /dev/null @@ -1,2 +0,0 @@ -# check if we can compile -> show randomNumber \ No newline at end of file diff --git a/src/test/scala/io/github/jisantuc/sbtse/SourceextractSpec.scala b/src/test/scala/io/github/jisantuc/sbtse/SourceextractSpec.scala deleted file mode 100644 index 20f0f13..0000000 --- a/src/test/scala/io/github/jisantuc/sbtse/SourceextractSpec.scala +++ /dev/null @@ -1,5 +0,0 @@ -package io.github.jisantuc.sbtse - -class SourceExtractTest { - // write tests with your preferred framework -}