mirror of https://github.com/sbt/sbt.git
Add sbt-launcher
This commit is contained in:
parent
e5a6a609e3
commit
88f54cc356
14
build.sbt
14
build.sbt
|
|
@ -382,6 +382,19 @@ lazy val `sbt-shading` = project
|
|||
libraryDependencies += "org.anarres.jarjar" % "jarjar-core" % "1.0.0"
|
||||
)
|
||||
|
||||
lazy val `sbt-launcher` = project
|
||||
.dependsOn(cache)
|
||||
.settings(commonSettings)
|
||||
.settings(packAutoSettings)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"com.github.alexarchambault" %% "case-app" % "1.1.3",
|
||||
"org.scala-sbt" % "launcher-interface" % "1.0.0",
|
||||
"com.typesafe" % "config" % "1.3.1"
|
||||
),
|
||||
packExcludeArtifactTypes += "pom"
|
||||
)
|
||||
|
||||
val http4sVersion = "0.8.6"
|
||||
|
||||
lazy val `http-server` = project
|
||||
|
|
@ -426,6 +439,7 @@ lazy val `coursier` = project.in(file("."))
|
|||
cli,
|
||||
`sbt-coursier`,
|
||||
`sbt-shading`,
|
||||
`sbt-launcher`,
|
||||
web,
|
||||
doc,
|
||||
`http-server`,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
COURSIER_VERSION=1.0.0-SNAPSHOT
|
||||
|
||||
"$(dirname "$0")/../coursier" bootstrap \
|
||||
"io.get-coursier:sbt-launcher_2.12:$COURSIER_VERSION" \
|
||||
-i launcher \
|
||||
-I launcher:org.scala-sbt:launcher-interface:1.0.0 \
|
||||
-o csbt \
|
||||
-J -Djline.shutdownhook=false
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.File
|
||||
|
||||
final case class AppConfiguration(
|
||||
arguments: Array[String],
|
||||
baseDirectory: File,
|
||||
provider: xsbti.AppProvider
|
||||
) extends xsbti.AppConfiguration
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.File
|
||||
|
||||
final case class AppProvider(
|
||||
scalaProvider: xsbti.ScalaProvider,
|
||||
id: xsbti.ApplicationID,
|
||||
loader: ClassLoader,
|
||||
mainClass: Class[_ <: xsbti.AppMain],
|
||||
createMain: () => xsbti.AppMain,
|
||||
mainClasspath: Array[File],
|
||||
components: xsbti.ComponentProvider
|
||||
) extends xsbti.AppProvider {
|
||||
def entryPoint: Class[_] =
|
||||
mainClass
|
||||
def newMain(): xsbti.AppMain =
|
||||
createMain()
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.File
|
||||
|
||||
final case class ApplicationID(
|
||||
groupID: String,
|
||||
name: String,
|
||||
version: String,
|
||||
mainClass: String,
|
||||
mainComponents: Array[String],
|
||||
crossVersioned: Boolean,
|
||||
crossVersionedValue: xsbti.CrossValue,
|
||||
classpathExtra: Array[File]
|
||||
) extends xsbti.ApplicationID {
|
||||
|
||||
assert(crossVersioned == (crossVersionedValue != xsbti.CrossValue.Disabled))
|
||||
|
||||
def disableCrossVersion(scalaVersion: String): ApplicationID =
|
||||
crossVersionedValue match {
|
||||
case xsbti.CrossValue.Disabled =>
|
||||
this
|
||||
case xsbti.CrossValue.Binary =>
|
||||
val scalaBinaryVersion = scalaVersion.split('.').take(2).mkString(".")
|
||||
copy(
|
||||
crossVersioned = false,
|
||||
crossVersionedValue = xsbti.CrossValue.Disabled,
|
||||
version = s"${version}_$scalaBinaryVersion"
|
||||
)
|
||||
case xsbti.CrossValue.Full =>
|
||||
copy(
|
||||
crossVersioned = false,
|
||||
crossVersionedValue = xsbti.CrossValue.Disabled,
|
||||
version = s"${version}_$scalaVersion"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object ApplicationID {
|
||||
def apply(id: xsbti.ApplicationID): ApplicationID =
|
||||
id match {
|
||||
case id0: ApplicationID => id0
|
||||
case _ =>
|
||||
ApplicationID(
|
||||
id.groupID(),
|
||||
id.name(),
|
||||
id.version(),
|
||||
id.mainClass(),
|
||||
id.mainComponents(),
|
||||
id.crossVersionedValue() != xsbti.CrossValue.Disabled,
|
||||
id.crossVersionedValue(),
|
||||
id.classpathExtra()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.{Files, StandardCopyOption}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
final class ComponentProvider(cacheDir: File) extends xsbti.ComponentProvider {
|
||||
|
||||
private val components0 = new mutable.HashMap[String, Array[File]]
|
||||
|
||||
def componentLocation(id: String): File =
|
||||
new File(cacheDir, id)
|
||||
|
||||
def component(componentId: String): Array[File] = {
|
||||
val res = components0.getOrElse[Array[File]](
|
||||
componentId,
|
||||
{
|
||||
val dir = componentLocation(componentId)
|
||||
if (dir.exists())
|
||||
Option(dir.listFiles()).getOrElse(Array())
|
||||
else
|
||||
Array()
|
||||
}
|
||||
)
|
||||
res
|
||||
}
|
||||
|
||||
private def clear(componentId: String): Unit = {
|
||||
|
||||
def deleteRecursively(f: File): Unit =
|
||||
if (f.isFile)
|
||||
f.delete()
|
||||
else
|
||||
Option(f.listFiles())
|
||||
.getOrElse(Array())
|
||||
.foreach(deleteRecursively)
|
||||
|
||||
val dir = componentLocation(componentId)
|
||||
deleteRecursively(dir)
|
||||
}
|
||||
|
||||
private def copying(componentId: String, f: File): File = {
|
||||
|
||||
// TODO Use some locking mechanisms here
|
||||
|
||||
val dir = componentLocation(componentId)
|
||||
dir.mkdirs()
|
||||
val dest = new File(dir, f.getName)
|
||||
Files.copy(f.toPath, dest.toPath, StandardCopyOption.REPLACE_EXISTING)
|
||||
dest
|
||||
}
|
||||
|
||||
def defineComponentNoCopy(componentId: String, components: Array[File]): Unit = {
|
||||
components0 += componentId -> components.distinct
|
||||
}
|
||||
def defineComponent(componentId: String, components: Array[File]): Unit = {
|
||||
clear(componentId)
|
||||
components0 += componentId -> components.distinct.map(copying(componentId, _))
|
||||
}
|
||||
def addToComponent(componentId: String, components: Array[File]): Boolean = {
|
||||
val previousFiles = components0.getOrElse(componentId, Array.empty[File])
|
||||
val newFiles = (previousFiles ++ components.distinct.map(copying(componentId, _))).distinct
|
||||
components0 += componentId -> newFiles
|
||||
newFiles.length != previousFiles.length
|
||||
}
|
||||
|
||||
def lockFile: File = new File("/component-lock")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.{Callable, ConcurrentHashMap}
|
||||
|
||||
case object DummyGlobalLock extends xsbti.GlobalLock {
|
||||
|
||||
private val locks = new ConcurrentHashMap[File, AnyRef]
|
||||
|
||||
def apply[T](lockFile: File, run: Callable[T]): T =
|
||||
Option(lockFile) match {
|
||||
case None => run.call()
|
||||
case Some(lockFile0) =>
|
||||
val lock0 = new AnyRef
|
||||
val lock = Option(locks.putIfAbsent(lockFile0, lock0)).getOrElse(lock0)
|
||||
|
||||
lock.synchronized {
|
||||
run.call()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,638 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.{File, OutputStreamWriter}
|
||||
import java.net.{URL, URLClassLoader}
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import coursier.Cache.Logger
|
||||
import coursier._
|
||||
import coursier.ivy.IvyRepository
|
||||
import coursier.maven.MavenSource
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scalaz.{-\/, \/-}
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
class Launcher(
|
||||
scalaVersion: String,
|
||||
componentsCache: File,
|
||||
val ivyHome: File
|
||||
) extends xsbti.Launcher {
|
||||
|
||||
val componentProvider = new ComponentProvider(componentsCache)
|
||||
|
||||
lazy val baseLoader = {
|
||||
|
||||
@tailrec
|
||||
def rootLoader(cl: ClassLoader): ClassLoader =
|
||||
if (cl == null)
|
||||
sys.error("Cannot find base loader")
|
||||
else {
|
||||
val isLauncherLoader =
|
||||
try {
|
||||
cl
|
||||
.asInstanceOf[AnyRef { def getIsolationTargets: Array[String] }]
|
||||
.getIsolationTargets
|
||||
.contains("launcher")
|
||||
} catch {
|
||||
case _: Throwable => false
|
||||
}
|
||||
|
||||
if (isLauncherLoader)
|
||||
cl
|
||||
else
|
||||
rootLoader(cl.getParent)
|
||||
}
|
||||
|
||||
rootLoader(Thread.currentThread().getContextClassLoader)
|
||||
}
|
||||
|
||||
val repositoryIdPrefix = "coursier-launcher-"
|
||||
|
||||
val repositories = Seq(
|
||||
// mmh, ID "local" seems to be required for publishLocal to be fine if we're launching sbt
|
||||
"local" -> Cache.ivy2Local,
|
||||
s"${repositoryIdPrefix}central" -> MavenRepository("https://repo1.maven.org/maven2", sbtAttrStub = true),
|
||||
s"${repositoryIdPrefix}typesafe-ivy-releases" -> IvyRepository.parse(
|
||||
"https://repo.typesafe.com/typesafe/ivy-releases/[organization]/[module]/[revision]/[type]s/[artifact](-[classifier]).[ext]"
|
||||
).leftMap(sys.error).merge,
|
||||
s"${repositoryIdPrefix}sbt-plugin-releases" -> IvyRepository.parse(
|
||||
"https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/[organization]/[module](/scala_[scalaVersion])(/sbt_[sbtVersion])/[revision]/[type]s/[artifact](-[classifier]).[ext]"
|
||||
).leftMap(sys.error).merge
|
||||
)
|
||||
|
||||
assert(!repositories.groupBy(_._1).exists(_._2.lengthCompare(1) > 0))
|
||||
|
||||
val cachePolicies = CachePolicy.default
|
||||
|
||||
def fetch(logger: Option[Logger]) = {
|
||||
def helper(policy: CachePolicy) =
|
||||
Cache.fetch(cachePolicy = policy, logger = logger)
|
||||
|
||||
val f = cachePolicies.map(helper)
|
||||
|
||||
Fetch.from(repositories.map(_._2), f.head, f.tail: _*)
|
||||
}
|
||||
|
||||
val keepArtifactTypes = Set("jar", "bundle")
|
||||
|
||||
def tasks(res: Resolution, logger: Option[Logger], classifiersOpt: Option[Seq[String]] = None) = {
|
||||
val a = classifiersOpt
|
||||
.fold(res.dependencyArtifacts.map(_._2))(res.dependencyClassifiersArtifacts(_).map(_._2))
|
||||
|
||||
val keepArtifactTypes = classifiersOpt.fold(Set("jar", "bundle"))(c => c.map(c => MavenSource.classifierExtensionDefaultTypes.getOrElse((c, "jar"), ???)).toSet)
|
||||
|
||||
a.collect {
|
||||
case artifact if keepArtifactTypes(artifact.`type`) =>
|
||||
def file(policy: CachePolicy) = Cache.file(
|
||||
artifact,
|
||||
cachePolicy = policy,
|
||||
logger = logger
|
||||
)
|
||||
|
||||
(file(cachePolicies.head) /: cachePolicies.tail)(_ orElse file(_))
|
||||
.run
|
||||
.map(artifact.->)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def isOverrideRepositories = false // ???
|
||||
|
||||
def bootDirectory: File = ???
|
||||
|
||||
def getScala(version: String): xsbti.ScalaProvider =
|
||||
getScala(version, "")
|
||||
|
||||
def getScala(version: String, reason: String): xsbti.ScalaProvider =
|
||||
getScala(version, reason, "org.scala-lang")
|
||||
|
||||
def getScala(version: String, reason: String, scalaOrg: String): xsbti.ScalaProvider = {
|
||||
|
||||
val key = (version, scalaOrg)
|
||||
|
||||
Option(scalaProviderCache.get(key)).getOrElse {
|
||||
val prov = getScala0(version, reason, scalaOrg)
|
||||
val previous = Option(scalaProviderCache.putIfAbsent(key, prov))
|
||||
previous.getOrElse(prov)
|
||||
}
|
||||
}
|
||||
|
||||
private val scalaProviderCache = new ConcurrentHashMap[(String, String), xsbti.ScalaProvider]
|
||||
|
||||
private def getScala0(version: String, reason: String, scalaOrg: String): xsbti.ScalaProvider = {
|
||||
|
||||
val files = getScalaFiles(version, reason, scalaOrg)
|
||||
|
||||
val libraryJar = files.find(_.getName.startsWith("scala-library")).getOrElse {
|
||||
throw new NoSuchElementException("scala-library JAR")
|
||||
}
|
||||
val compilerJar = files.find(_.getName.startsWith("scala-compiler")).getOrElse {
|
||||
throw new NoSuchElementException("scala-compiler JAR")
|
||||
}
|
||||
|
||||
val scalaLoader = new URLClassLoader(files.map(_.toURI.toURL).toArray, baseLoader)
|
||||
|
||||
ScalaProvider(
|
||||
this,
|
||||
version,
|
||||
scalaLoader,
|
||||
files.toArray,
|
||||
libraryJar,
|
||||
compilerJar,
|
||||
id => app(id, id.version())
|
||||
)
|
||||
}
|
||||
|
||||
private def getScalaFiles(version: String, reason: String, scalaOrg: String): Seq[File] = {
|
||||
|
||||
val initialRes = Resolution(
|
||||
Set(
|
||||
Dependency(Module(scalaOrg, "scala-library"), version),
|
||||
Dependency(Module(scalaOrg, "scala-compiler"), version)
|
||||
),
|
||||
forceVersions = Map(
|
||||
Module(scalaOrg, "scala-library") -> version,
|
||||
Module(scalaOrg, "scala-compiler") -> version,
|
||||
Module(scalaOrg, "scala-reflect") -> version
|
||||
)
|
||||
)
|
||||
|
||||
val logger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
logger.foreach(_.init {
|
||||
System.err.println(s"Resolving Scala $version (organization $scalaOrg)")
|
||||
})
|
||||
|
||||
val res = initialRes.process.run(fetch(logger)).unsafePerformSync
|
||||
|
||||
logger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Resolved Scala $version (organization $scalaOrg)")
|
||||
}
|
||||
|
||||
if (res.errors.nonEmpty) {
|
||||
Console.err.println(s"Errors:\n${res.errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (res.conflicts.nonEmpty) {
|
||||
Console.err.println(s"Conflicts:\n${res.conflicts.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (!res.isDone) {
|
||||
Console.err.println("Did not converge")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
val artifactLogger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
artifactLogger.foreach(_.init {
|
||||
System.err.println(s"Fetching Scala $version artifacts (organization $scalaOrg)")
|
||||
})
|
||||
|
||||
val results = Task.gatherUnordered(tasks(res, artifactLogger)).unsafePerformSync
|
||||
|
||||
artifactLogger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Fetched Scala $version artifacts (organization $scalaOrg)")
|
||||
}
|
||||
|
||||
val errors = results.collect { case (a, -\/(err)) => (a, err) }
|
||||
val files = results.collect { case (_, \/-(f)) => f }
|
||||
|
||||
if (errors.nonEmpty) {
|
||||
Console.err.println(s"Error downloading artifacts:\n${errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
def topLoader: ClassLoader = baseLoader
|
||||
|
||||
def appRepositories: Array[xsbti.Repository] =
|
||||
repositories.map {
|
||||
case (id, m: MavenRepository) =>
|
||||
Repository.Maven(id, new URL(m.root))
|
||||
case (id, i: IvyRepository) =>
|
||||
|
||||
assert(i.metadataPatternOpt.forall(_ == i.pattern))
|
||||
|
||||
val (base, pat) = i.pattern.string.span(c => c != '[' && c != '$' && c != '(')
|
||||
|
||||
assert(base.nonEmpty, i.pattern.string)
|
||||
|
||||
Repository.Ivy(
|
||||
id,
|
||||
new URL(base),
|
||||
pat,
|
||||
pat,
|
||||
mavenCompatible = false,
|
||||
skipConsistencyCheck = true, // ???
|
||||
descriptorOptional = true // ???
|
||||
)
|
||||
}.toArray
|
||||
|
||||
def ivyRepositories: Array[xsbti.Repository] =
|
||||
appRepositories // ???
|
||||
|
||||
def globalLock = DummyGlobalLock
|
||||
|
||||
// See https://github.com/sbt/ivy/blob/2cf13e211b2cb31f0d3b317289dca70eca3362f6/src/java/org/apache/ivy/util/ChecksumHelper.java
|
||||
def checksums: Array[String] = Array("sha1", "md5")
|
||||
|
||||
def app(id: xsbti.ApplicationID, version: String): xsbti.AppProvider =
|
||||
app(ApplicationID(id).copy(version = version))
|
||||
|
||||
def app(id: xsbti.ApplicationID, extra: Dependency*): xsbti.AppProvider = {
|
||||
|
||||
val (scalaFiles, files) = appFiles(id, extra: _*)
|
||||
|
||||
val scalaLoader = new URLClassLoader(scalaFiles.map(_.toURI.toURL).toArray, baseLoader)
|
||||
|
||||
val libraryJar = scalaFiles.find(_.getName.startsWith("scala-library")).getOrElse {
|
||||
throw new NoSuchElementException("scala-library JAR")
|
||||
}
|
||||
val compilerJar = scalaFiles.find(_.getName.startsWith("scala-compiler")).getOrElse {
|
||||
throw new NoSuchElementException("scala-compiler JAR")
|
||||
}
|
||||
|
||||
val scalaProvider = ScalaProvider(
|
||||
this,
|
||||
scalaVersion,
|
||||
scalaLoader,
|
||||
scalaFiles.toArray,
|
||||
libraryJar,
|
||||
compilerJar,
|
||||
id => app(id, id.version())
|
||||
)
|
||||
|
||||
val loader = new URLClassLoader(files.filterNot(scalaFiles.toSet).map(_.toURI.toURL).toArray, scalaLoader)
|
||||
val mainClass0 = loader.loadClass(id.mainClass).asSubclass(classOf[xsbti.AppMain])
|
||||
|
||||
AppProvider(
|
||||
scalaProvider,
|
||||
id,
|
||||
loader,
|
||||
mainClass0,
|
||||
() => mainClass0.newInstance(),
|
||||
files.toArray,
|
||||
componentProvider
|
||||
)
|
||||
}
|
||||
|
||||
private def appFiles(id: xsbti.ApplicationID, extra: Dependency*): (Seq[File], Seq[File]) = {
|
||||
|
||||
val id0 = ApplicationID(id).disableCrossVersion(scalaVersion)
|
||||
|
||||
val initialRes = Resolution(
|
||||
Set(
|
||||
Dependency(Module("org.scala-lang", "scala-library"), scalaVersion),
|
||||
Dependency(Module("org.scala-lang", "scala-compiler"), scalaVersion),
|
||||
Dependency(Module(id0.groupID, id0.name), id0.version)
|
||||
) ++ extra,
|
||||
forceVersions = Map(
|
||||
Module("org.scala-lang", "scala-library") -> scalaVersion,
|
||||
Module("org.scala-lang", "scala-compiler") -> scalaVersion,
|
||||
Module("org.scala-lang", "scala-reflect") -> scalaVersion
|
||||
)
|
||||
)
|
||||
|
||||
val logger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
val extraMsg =
|
||||
if (extra.isEmpty)
|
||||
""
|
||||
else
|
||||
s" (plus ${extra.length} dependencies)"
|
||||
|
||||
logger.foreach(_.init {
|
||||
System.err.println(s"Resolving ${id0.groupID}:${id0.name}:${id0.version}$extraMsg")
|
||||
})
|
||||
|
||||
val res = initialRes.process.run(fetch(logger)).unsafePerformSync
|
||||
|
||||
logger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Resolved ${id0.groupID}:${id0.name}:${id0.version}$extraMsg")
|
||||
}
|
||||
|
||||
if (res.errors.nonEmpty) {
|
||||
Console.err.println(s"Errors:\n${res.errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (res.conflicts.nonEmpty) {
|
||||
Console.err.println(s"Conflicts:\n${res.conflicts.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (!res.isDone) {
|
||||
Console.err.println("Did not converge")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
val artifactLogger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
artifactLogger.foreach(_.init {
|
||||
System.err.println(s"Fetching ${id0.groupID}:${id0.name}:${id0.version} artifacts")
|
||||
})
|
||||
|
||||
val results = Task.gatherUnordered(tasks(res, artifactLogger)).unsafePerformSync
|
||||
|
||||
artifactLogger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Fetched ${id0.groupID}:${id0.name}:${id0.version} artifacts")
|
||||
}
|
||||
|
||||
val errors = results.collect { case (a, -\/(err)) => (a, err) }
|
||||
val files = results.collect { case (_, \/-(f)) => f }
|
||||
|
||||
if (errors.nonEmpty) {
|
||||
Console.err.println(s"Error downloading artifacts:\n${errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
val scalaSubRes = res.subset(
|
||||
Set(
|
||||
Dependency(Module("org.scala-lang", "scala-library"), scalaVersion),
|
||||
Dependency(Module("org.scala-lang", "scala-compiler"), scalaVersion)
|
||||
)
|
||||
)
|
||||
|
||||
val scalaArtifactLogger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
scalaArtifactLogger.foreach(_.init {
|
||||
System.err.println(s"Fetching ${id0.groupID}:${id0.name}:${id0.version} Scala artifacts")
|
||||
})
|
||||
|
||||
val scalaResults = Task.gatherUnordered(tasks(scalaSubRes, scalaArtifactLogger)).unsafePerformSync
|
||||
|
||||
scalaArtifactLogger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Fetched ${id0.groupID}:${id0.name}:${id0.version} Scala artifacts")
|
||||
}
|
||||
|
||||
val scalaErrors = scalaResults.collect { case (a, -\/(err)) => (a, err) }
|
||||
val scalaFiles = scalaResults.collect { case (_, \/-(f)) => f }
|
||||
|
||||
if (scalaErrors.nonEmpty) {
|
||||
Console.err.println(s"Error downloading artifacts:\n${scalaErrors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
(scalaFiles, files)
|
||||
}
|
||||
|
||||
def registerScalaComponents(scalaVersion: String = scalaVersion): Unit = {
|
||||
|
||||
lazy val prov = getScala(scalaVersion)
|
||||
lazy val jars = prov.jars()
|
||||
|
||||
lazy val libraryJar = jars.find(_.getName.startsWith("scala-library")).getOrElse {
|
||||
throw new NoSuchElementException("scala-library JAR")
|
||||
}
|
||||
lazy val compilerJar = jars.find(_.getName.startsWith("scala-compiler")).getOrElse {
|
||||
throw new NoSuchElementException("scala-compiler JAR")
|
||||
}
|
||||
lazy val reflectJar = jars.find(_.getName.startsWith("scala-reflect")).getOrElse {
|
||||
throw new NoSuchElementException("scala-reflect JAR")
|
||||
}
|
||||
|
||||
if (componentProvider.component("library").isEmpty)
|
||||
componentProvider.defineComponentNoCopy("library", Array(libraryJar))
|
||||
if (componentProvider.component("compiler").isEmpty)
|
||||
componentProvider.defineComponentNoCopy("compiler", Array(compilerJar))
|
||||
if (componentProvider.component("reflect").isEmpty)
|
||||
componentProvider.defineComponentNoCopy("reflect", Array(reflectJar))
|
||||
}
|
||||
|
||||
def registerSbtInterfaceComponents(sbtVersion: String): Unit = {
|
||||
|
||||
lazy val (interfaceJar, _) = sbtInterfaceComponentFiles(sbtVersion)
|
||||
lazy val compilerInterfaceSourceJar = sbtCompilerInterfaceSrcComponentFile(sbtVersion)
|
||||
|
||||
if (componentProvider.component("xsbti").isEmpty)
|
||||
componentProvider.defineComponentNoCopy("xsbti", Array(interfaceJar))
|
||||
if (componentProvider.component("compiler-interface").isEmpty)
|
||||
componentProvider.defineComponentNoCopy("compiler-interface", Array(compilerInterfaceSourceJar))
|
||||
if (componentProvider.component("compiler-interface-src").isEmpty)
|
||||
componentProvider.defineComponentNoCopy("compiler-interface-src", Array(compilerInterfaceSourceJar))
|
||||
}
|
||||
|
||||
private def sbtInterfaceComponentFiles(sbtVersion: String): (File, File) = {
|
||||
|
||||
lazy val res = {
|
||||
|
||||
val initialRes = Resolution(
|
||||
Set(
|
||||
Dependency(Module("org.scala-sbt", "interface"), sbtVersion, transitive = false)
|
||||
)
|
||||
)
|
||||
|
||||
val logger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
logger.foreach(_.init {
|
||||
System.err.println(s"Resolving org.scala-sbt:interface:$sbtVersion")
|
||||
})
|
||||
|
||||
val res = initialRes.process.run(fetch(logger)).unsafePerformSync
|
||||
|
||||
logger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Resolved org.scala-sbt:interface:$sbtVersion")
|
||||
}
|
||||
|
||||
if (res.errors.nonEmpty) {
|
||||
Console.err.println(s"Errors:\n${res.errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (res.conflicts.nonEmpty) {
|
||||
Console.err.println(s"Conflicts:\n${res.conflicts.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (!res.isDone) {
|
||||
Console.err.println("Did not converge")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
lazy val interfaceJar = {
|
||||
|
||||
val artifactLogger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
artifactLogger.foreach(_.init {
|
||||
System.err.println(s"Fetching org.scala-sbt:interface:$sbtVersion artifacts")
|
||||
})
|
||||
|
||||
val results = Task.gatherUnordered(tasks(res, artifactLogger)).unsafePerformSync
|
||||
|
||||
artifactLogger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Fetched org.scala-sbt:interface:$sbtVersion artifacts")
|
||||
}
|
||||
|
||||
val errors = results.collect { case (a, -\/(err)) => (a, err) }
|
||||
val files = results.collect { case (_, \/-(f)) => f }
|
||||
|
||||
if (errors.nonEmpty) {
|
||||
Console.err.println(s"Error downloading artifacts:\n${errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
files match {
|
||||
case Nil =>
|
||||
throw new NoSuchElementException(s"interface JAR for sbt $sbtVersion")
|
||||
case List(jar) =>
|
||||
jar
|
||||
case _ =>
|
||||
sys.error(s"Too many interface JAR for sbt $sbtVersion: ${files.mkString(", ")}")
|
||||
}
|
||||
}
|
||||
|
||||
lazy val compilerInterfaceSourcesJar = {
|
||||
|
||||
val artifactLogger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
artifactLogger.foreach(_.init {
|
||||
System.err.println(s"Fetching org.scala-sbt:interface:$sbtVersion source artifacts")
|
||||
})
|
||||
|
||||
val results = Task.gatherUnordered(tasks(res, artifactLogger, Some(Seq("sources")))).unsafePerformSync
|
||||
|
||||
artifactLogger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Fetched org.scala-sbt:interface:$sbtVersion source artifacts")
|
||||
}
|
||||
|
||||
val errors = results.collect { case (a, -\/(err)) => (a, err) }
|
||||
val files = results.collect { case (_, \/-(f)) => f }
|
||||
|
||||
if (errors.nonEmpty) {
|
||||
Console.err.println(s"Error downloading artifacts:\n${errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
files match {
|
||||
case Nil =>
|
||||
throw new NoSuchElementException(s"compiler-interface source JAR for sbt $sbtVersion")
|
||||
case List(jar) =>
|
||||
jar
|
||||
case _ =>
|
||||
sys.error(s"Too many compiler-interface source JAR for sbt $sbtVersion: ${files.mkString(", ")}")
|
||||
}
|
||||
}
|
||||
|
||||
(interfaceJar, compilerInterfaceSourcesJar)
|
||||
}
|
||||
|
||||
private def sbtCompilerInterfaceSrcComponentFile(sbtVersion: String): File = {
|
||||
|
||||
val res = {
|
||||
|
||||
val initialRes = Resolution(
|
||||
Set(
|
||||
Dependency(Module("org.scala-sbt", "compiler-interface"), sbtVersion, transitive = false)
|
||||
)
|
||||
)
|
||||
|
||||
val logger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
logger.foreach(_.init {
|
||||
System.err.println(s"Resolving org.scala-sbt:compiler-interface:$sbtVersion")
|
||||
})
|
||||
|
||||
val res = initialRes.process.run(fetch(logger)).unsafePerformSync
|
||||
|
||||
logger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Resolved org.scala-sbt:compiler-interface:$sbtVersion")
|
||||
}
|
||||
|
||||
if (res.errors.nonEmpty) {
|
||||
Console.err.println(s"Errors:\n${res.errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (res.conflicts.nonEmpty) {
|
||||
Console.err.println(s"Conflicts:\n${res.conflicts.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
if (!res.isDone) {
|
||||
Console.err.println("Did not converge")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
val files = {
|
||||
|
||||
val artifactLogger =
|
||||
Some(new TermDisplay(
|
||||
new OutputStreamWriter(System.err)
|
||||
))
|
||||
|
||||
artifactLogger.foreach(_.init {
|
||||
System.err.println(s"Fetching org.scala-sbt:compiler-interface:$sbtVersion source artifacts")
|
||||
})
|
||||
|
||||
val results = Task.gatherUnordered(
|
||||
tasks(res, artifactLogger, None) ++
|
||||
tasks(res, artifactLogger, Some(Seq("sources")))
|
||||
).unsafePerformSync
|
||||
|
||||
artifactLogger.foreach { l =>
|
||||
if (l.stopDidPrintSomething())
|
||||
System.err.println(s"Fetched org.scala-sbt:compiler-interface:$sbtVersion source artifacts")
|
||||
}
|
||||
|
||||
val errors = results.collect { case (a, -\/(err)) => (a, err) }
|
||||
|
||||
if (errors.nonEmpty) {
|
||||
Console.err.println(s"Error downloading artifacts:\n${errors.map(" " + _).mkString("\n")}")
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
results.collect { case (_, \/-(f)) => f }
|
||||
}
|
||||
|
||||
files.find(f => f.getName == "compiler-interface-src.jar" || f.getName == "compiler-interface-sources.jar").getOrElse {
|
||||
sys.error("compiler-interface-src not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
|
||||
import caseapp._
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import coursier.Dependency
|
||||
|
||||
final case class MainApp(
|
||||
@ExtraName("org")
|
||||
organization: String,
|
||||
name: String,
|
||||
version: String,
|
||||
scalaVersion: String,
|
||||
sbtVersion: String,
|
||||
mainClass: String,
|
||||
mainComponents: List[String],
|
||||
classpathExtra: List[String],
|
||||
extra: List[String]
|
||||
) extends App {
|
||||
|
||||
val sbtPropFile = new File(sys.props("user.dir") + "/sbt.properties")
|
||||
val buildPropFile = new File(sys.props("user.dir") + "/project/build.properties")
|
||||
|
||||
val propFileOpt = Some(sbtPropFile).filter(_.exists())
|
||||
.orElse(Some(buildPropFile).filter(_.exists()))
|
||||
|
||||
val (org0, name0, ver0, scalaVer0, extraDeps0, mainClass0, sbtVersion0) =
|
||||
propFileOpt match {
|
||||
case Some(propFile) =>
|
||||
// can't get ConfigFactory.parseFile to work fine here
|
||||
val conf = ConfigFactory.parseString(new String(Files.readAllBytes(propFile.toPath), StandardCharsets.UTF_8))
|
||||
.withFallback(ConfigFactory.defaultReference(Thread.currentThread().getContextClassLoader))
|
||||
.resolve()
|
||||
val sbtConfig = SbtConfig.fromConfig(conf)
|
||||
|
||||
(sbtConfig.organization, sbtConfig.moduleName, sbtConfig.version, sbtConfig.scalaVersion, sbtConfig.dependencies, sbtConfig.mainClass, sbtConfig.version)
|
||||
case None =>
|
||||
require(scalaVersion.nonEmpty, "No scala version specified")
|
||||
(organization, name, version, scalaVersion, Nil, mainClass, sbtVersion)
|
||||
}
|
||||
|
||||
val (extraParseErrors, extraModuleVersions) = coursier.util.Parse.moduleVersions(extra, scalaVersion)
|
||||
|
||||
if (extraParseErrors.nonEmpty) {
|
||||
???
|
||||
}
|
||||
|
||||
val extraDeps = extraModuleVersions.map {
|
||||
case (mod, ver) =>
|
||||
Dependency(mod, ver)
|
||||
}
|
||||
|
||||
val launcher = new Launcher(
|
||||
scalaVer0,
|
||||
// FIXME Add org & moduleName in this path
|
||||
new File(s"${sys.props("user.dir")}/target/sbt-components/components_scala$scalaVer0${if (sbtVersion0.isEmpty) "" else "_sbt" + sbtVersion0}"),
|
||||
new File(s"${sys.props("user.dir")}/target/ivy2")
|
||||
)
|
||||
|
||||
launcher.registerScalaComponents()
|
||||
|
||||
if (sbtVersion0.nonEmpty)
|
||||
launcher.registerSbtInterfaceComponents(sbtVersion0)
|
||||
|
||||
val appId = ApplicationID(
|
||||
org0,
|
||||
name0,
|
||||
ver0,
|
||||
mainClass0,
|
||||
mainComponents.toArray,
|
||||
crossVersioned = false,
|
||||
xsbti.CrossValue.Disabled,
|
||||
classpathExtra.map(new File(_)).toArray
|
||||
)
|
||||
|
||||
val appProvider = launcher.app(appId, extraDeps0 ++ extraDeps: _*)
|
||||
|
||||
val appMain = appProvider.newMain()
|
||||
|
||||
val appConfig = AppConfiguration(
|
||||
remainingArgs.toArray,
|
||||
new File(sys.props("user.dir")),
|
||||
appProvider
|
||||
)
|
||||
|
||||
val thread = Thread.currentThread()
|
||||
val previousLoader = thread.getContextClassLoader
|
||||
|
||||
val result =
|
||||
try {
|
||||
thread.setContextClassLoader(appProvider.loader())
|
||||
appMain.run(appConfig)
|
||||
} finally {
|
||||
thread.setContextClassLoader(previousLoader)
|
||||
}
|
||||
|
||||
result match {
|
||||
case _: xsbti.Continue =>
|
||||
case e: xsbti.Exit =>
|
||||
sys.exit(e.code())
|
||||
case _: xsbti.Reboot =>
|
||||
sys.error("Not able to reboot yet")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Main extends AppOf[MainApp]
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.net.URL
|
||||
|
||||
object Repository {
|
||||
|
||||
final case class Maven(id: String, url: URL) extends xsbti.MavenRepository
|
||||
|
||||
final case class Ivy(
|
||||
id: String,
|
||||
url: URL,
|
||||
ivyPattern: String,
|
||||
artifactPattern: String,
|
||||
mavenCompatible: Boolean,
|
||||
skipConsistencyCheck: Boolean,
|
||||
descriptorOptional: Boolean
|
||||
) extends xsbti.IvyRepository
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import coursier.Dependency
|
||||
import coursier.util.Parse
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
final case class SbtConfig(
|
||||
organization: String,
|
||||
moduleName: String,
|
||||
version: String,
|
||||
scalaVersion: String,
|
||||
mainClass: String,
|
||||
dependencies: Seq[Dependency]
|
||||
)
|
||||
|
||||
object SbtConfig {
|
||||
|
||||
def defaultOrganization = "org.scala-sbt"
|
||||
def defaultModuleName = "sbt"
|
||||
def defaultMainClass = "sbt.xMain"
|
||||
|
||||
def fromConfig(config: Config): SbtConfig = {
|
||||
|
||||
val version = config.getString("sbt.version")
|
||||
|
||||
val scalaVersion =
|
||||
if (config.hasPath("scala.version"))
|
||||
config.getString("scala.version")
|
||||
else if (version.startsWith("0.13."))
|
||||
"2.10.6"
|
||||
else if (version.startsWith("1.0."))
|
||||
"2.12.1"
|
||||
else
|
||||
throw new Exception(s"Don't know what Scala version should be used for sbt version '$version'")
|
||||
|
||||
val org =
|
||||
if (config.hasPath("sbt.organization"))
|
||||
config.getString("sbt.organization")
|
||||
else
|
||||
defaultOrganization
|
||||
|
||||
val name =
|
||||
if (config.hasPath("sbt.module-name"))
|
||||
config.getString("sbt.module-name")
|
||||
else
|
||||
defaultModuleName
|
||||
|
||||
val mainClass =
|
||||
if (config.hasPath("sbt.main-class"))
|
||||
config.getString("sbt.main-class")
|
||||
else
|
||||
defaultMainClass
|
||||
|
||||
val scalaBinaryVersion = scalaVersion.split('.').take(2).mkString(".")
|
||||
val sbtBinaryVersion = version.split('.').take(2).mkString(".")
|
||||
|
||||
val rawPlugins =
|
||||
if (config.hasPath("plugins"))
|
||||
config.getStringList("plugins").asScala
|
||||
else
|
||||
Nil
|
||||
|
||||
val (pluginErrors, pluginsModuleVersions) = Parse.moduleVersions(rawPlugins, scalaVersion)
|
||||
|
||||
if (pluginErrors.nonEmpty) {
|
||||
???
|
||||
}
|
||||
|
||||
val pluginDependencies =
|
||||
pluginsModuleVersions.map {
|
||||
case (mod, ver) =>
|
||||
Dependency(
|
||||
mod.copy(
|
||||
attributes = mod.attributes ++ Seq(
|
||||
"scalaVersion" -> scalaBinaryVersion,
|
||||
"sbtVersion" -> sbtBinaryVersion
|
||||
)
|
||||
),
|
||||
ver
|
||||
)
|
||||
}
|
||||
|
||||
val rawDeps =
|
||||
if (config.hasPath("dependencies"))
|
||||
config.getStringList("dependencies").asScala
|
||||
else
|
||||
Nil
|
||||
|
||||
val (depsErrors, depsModuleVersions) = Parse.moduleVersions(rawDeps, scalaVersion)
|
||||
|
||||
if (depsErrors.nonEmpty) {
|
||||
???
|
||||
}
|
||||
|
||||
val dependencies =
|
||||
depsModuleVersions.map {
|
||||
case (mod, ver) =>
|
||||
Dependency(mod, ver)
|
||||
}
|
||||
|
||||
SbtConfig(
|
||||
org,
|
||||
name,
|
||||
version,
|
||||
scalaVersion,
|
||||
mainClass,
|
||||
pluginDependencies ++ dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package coursier.sbtlauncher
|
||||
|
||||
import java.io.File
|
||||
|
||||
final case class ScalaProvider(
|
||||
launcher: xsbti.Launcher,
|
||||
version: String,
|
||||
loader: ClassLoader,
|
||||
jars: Array[File],
|
||||
libraryJar: File,
|
||||
compilerJar: File,
|
||||
createApp: xsbti.ApplicationID => xsbti.AppProvider
|
||||
) extends xsbti.ScalaProvider {
|
||||
def app(id: xsbti.ApplicationID): xsbti.AppProvider =
|
||||
createApp(id)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
sbt.version=0.13.8
|
||||
|
||||
plugins = [
|
||||
"io.get-coursier:sbt-coursier:1.0.0-M15-1"
|
||||
"org.xerial.sbt:sbt-pack:0.8.2"
|
||||
"org.scala-js:sbt-scalajs:0.6.14"
|
||||
"com.jsuereth:sbt-pgp:1.0.0"
|
||||
"org.scoverage:sbt-scoverage:1.4.0"
|
||||
"org.tpolecat:tut-plugin:0.4.8"
|
||||
"com.typesafe.sbt:sbt-proguard:0.2.2"
|
||||
"com.typesafe:sbt-mima-plugin:0.1.13"
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"org.scala-sbt:scripted-plugin:"${sbt.version}
|
||||
]
|
||||
Loading…
Reference in New Issue