package mill
package scalalib
import java.io.{File, FileInputStream}
import java.lang.annotation.Annotation
import java.lang.reflect.Modifier
import java.util.zip.ZipInputStream
import javax.tools.ToolProvider
import ammonite.util.Util
import coursier.{Cache, Dependency, Fetch, Repository, Resolution}
import mill.scalalib.api.Util.isDotty
import mill.Agg
import mill.eval.{PathRef, Result}
import mill.modules.Jvm
import mill.api.Ctx
import sbt.testing._
import scala.collection.mutable
object Lib{
def depToDependencyJava(dep: Dep, platformSuffix: String = ""): Dependency = {
assert(dep.cross.isConstant, s"Not a Java dependency: $dep")
depToDependency(dep, "", platformSuffix)
}
def depToDependency(dep: Dep, scalaVersion: String, platformSuffix: String = ""): Dependency =
dep.toDependency(
binaryVersion = mill.scalalib.api.Util.scalaBinaryVersion(scalaVersion),
fullVersion = scalaVersion,
platformSuffix = platformSuffix
)
def resolveDependenciesMetadata(repositories: Seq[Repository],
depToDependency: Dep => coursier.Dependency,
deps: TraversableOnce[Dep],
mapDependencies: Option[Dependency => Dependency] = None,
ctx: Option[mill.util.Ctx.Log] = None) = {
val depSeq = deps.toSeq
mill.modules.Jvm.resolveDependenciesMetadata(
repositories,
depSeq.map(depToDependency),
depSeq.filter(_.force).map(depToDependency),
mapDependencies,
ctx
)
}
/**
* Resolve dependencies using Coursier.
*
* We do not bother breaking this out into the separate ZincWorker classpath,
* because Coursier is already bundled with mill/Ammonite to support the
* `import $ivy` syntax.
*/
def resolveDependencies(repositories: Seq[Repository],
depToDependency: Dep => coursier.Dependency,
deps: TraversableOnce[Dep],
sources: Boolean = false,
mapDependencies: Option[Dependency => Dependency] = None,
ctx: Option[mill.util.Ctx.Log] = None): Result[Agg[PathRef]] = {
val depSeq = deps.toSeq
mill.modules.Jvm.resolveDependencies(
repositories,
depSeq.map(depToDependency),
depSeq.filter(_.force).map(depToDependency),
sources,
mapDependencies,
ctx
)
}
def scalaCompilerIvyDeps(scalaOrganization: String, scalaVersion: String) =
if (mill.scalalib.api.Util.isDotty(scalaVersion))
Agg(ivy"$scalaOrganization::dotty-compiler:$scalaVersion".forceVersion())
else
Agg(
ivy"$scalaOrganization:scala-compiler:$scalaVersion".forceVersion(),
ivy"$scalaOrganization:scala-reflect:$scalaVersion".forceVersion()
)
def scalaRuntimeIvyDeps(scalaOrganization: String, scalaVersion: String) = Agg[Dep](
ivy"$scalaOrganization:scala-library:$scalaVersion".forceVersion()
)
def listClassFiles(base: os.Path): Iterator[String] = {
if (os.isDir(base)) os.walk(base).toIterator.filter(_.ext == "class").map(_.relativeTo(base).toString)
else {
val zip = new ZipInputStream(new FileInputStream(base.toIO))
Iterator.continually(zip.getNextEntry).takeWhile(_ != null).map(_.getName).filter(_.endsWith(".class"))
}
}
def discoverTests(cl: ClassLoader, framework: Framework, classpath: Agg[os.Path]) = {
val fingerprints = framework.fingerprints()
val testClasses = classpath.flatMap { base =>
// Don't blow up if there are no classfiles representing
// the tests to run Instead just don't run anything
if (!os.exists(base)) Nil
else listClassFiles(base).flatMap { path =>
val cls = cl.loadClass(path.stripSuffix(".class").replace('/', '.'))
val publicConstructorCount =
cls.getConstructors.count(c => c.getParameterCount == 0 && Modifier.isPublic(c.getModifiers))
if (Modifier.isAbstract(cls.getModifiers) || cls.isInterface || publicConstructorCount > 1) {
None
} else {
(cls.getName.endsWith("$"), publicConstructorCount == 0) match{
case (true, true) => matchFingerprints(cl, cls, fingerprints, isModule = true)
case (false, false) => matchFingerprints(cl, cls, fingerprints, isModule = false)
case _ => None
}
}
}
}
testClasses
}
def matchFingerprints(cl: ClassLoader, cls: Class[_], fingerprints: Array[Fingerprint], isModule: Boolean) = {
fingerprints.find {
case f: SubclassFingerprint =>
f.isModule == isModule &&
cl.loadClass(f.superclassName()).isAssignableFrom(cls)
case f: AnnotatedFingerprint =>
val annotationCls = cl.loadClass(f.annotationName()).asInstanceOf[Class[Annotation]]
f.isModule == isModule &&
(
cls.isAnnotationPresent(annotationCls) ||
cls.getDeclaredMethods.exists(_.isAnnotationPresent(annotationCls)) ||
cls.getMethods.exists(m => m.isAnnotationPresent(annotationCls) && Modifier.isPublic(m.getModifiers()))
)
}.map { f => (cls, f) }
}
}