From d14f56a3fd881f809e58783c49866d1491a5f3fe Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sat, 20 Jan 2018 03:49:17 -0800 Subject: Swap over to simplified Mill module/source layout from SBT's Removes a lot of useless folders and gives us a chance to exercise this simplified layout. Support for the SBT layout is still verified by our integration tests --- scalalib/src/main/scala/mill/scalalib/Dep.scala | 44 --- .../src/main/scala/mill/scalalib/GenIdea.scala | 203 ----------- scalalib/src/main/scala/mill/scalalib/Lib.scala | 234 ------------ scalalib/src/main/scala/mill/scalalib/Main.scala | 7 - .../src/main/scala/mill/scalalib/MiscModule.scala | 72 ---- .../main/scala/mill/scalalib/PublishModule.scala | 70 ---- .../src/main/scala/mill/scalalib/ScalaModule.scala | 318 ---------------- .../src/main/scala/mill/scalalib/TestRunner.scala | 172 --------- .../src/main/scala/mill/scalalib/package.scala | 12 - .../src/main/scala/mill/scalalib/publish/Ivy.scala | 55 --- .../mill/scalalib/publish/JsonFormatters.scala | 11 - .../mill/scalalib/publish/LocalPublisher.scala | 33 -- .../src/main/scala/mill/scalalib/publish/Pom.scala | 90 ----- .../mill/scalalib/publish/SonatypeHttpApi.scala | 130 ------- .../mill/scalalib/publish/SonatypePublisher.scala | 148 -------- .../main/scala/mill/scalalib/publish/package.scala | 3 - .../scala/mill/scalalib/publish/settings.scala | 70 ---- scalalib/src/mill/scalalib/Dep.scala | 44 +++ scalalib/src/mill/scalalib/GenIdea.scala | 203 +++++++++++ scalalib/src/mill/scalalib/Lib.scala | 234 ++++++++++++ scalalib/src/mill/scalalib/Main.scala | 7 + scalalib/src/mill/scalalib/MiscModule.scala | 72 ++++ scalalib/src/mill/scalalib/PublishModule.scala | 70 ++++ scalalib/src/mill/scalalib/ScalaModule.scala | 318 ++++++++++++++++ scalalib/src/mill/scalalib/TestRunner.scala | 172 +++++++++ scalalib/src/mill/scalalib/package.scala | 12 + scalalib/src/mill/scalalib/publish/Ivy.scala | 55 +++ .../src/mill/scalalib/publish/JsonFormatters.scala | 11 + .../src/mill/scalalib/publish/LocalPublisher.scala | 33 ++ scalalib/src/mill/scalalib/publish/Pom.scala | 90 +++++ .../mill/scalalib/publish/SonatypeHttpApi.scala | 130 +++++++ .../mill/scalalib/publish/SonatypePublisher.scala | 148 ++++++++ scalalib/src/mill/scalalib/publish/package.scala | 3 + scalalib/src/mill/scalalib/publish/settings.scala | 70 ++++ ...2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml | 7 - ...ala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml | 7 - ...er_2.12.4_scala-compiler-2.12.4-sources.jar.xml | 7 - ...a-compiler_2.12.4_scala-compiler-2.12.4.jar.xml | 7 - ...ary_2.12.4_scala-library-2.12.4-sources.jar.xml | 7 - ...ala-library_2.12.4_scala-library-2.12.4.jar.xml | 7 - ...ect_2.12.4_scala-reflect-2.12.4-sources.jar.xml | 7 - ...ala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml | 7 - scalalib/src/test/resources/gen-idea/idea/misc.xml | 5 - .../src/test/resources/gen-idea/idea/modules.xml | 8 - .../src/test/resources/gen-idea/idea_modules/iml | 22 -- .../test/resources/gen-idea/idea_modules/root.iml | 9 - .../hello-world/src/main/scala/Main.scala | 12 - .../hello-world/src/main/scala/Result.scala | 7 - .../test/scala/mill/scalalib/GenIdeaTests.scala | 78 ---- .../test/scala/mill/scalalib/HelloWorldTests.scala | 400 --------------------- .../scala/mill/scalalib/ResolveDepsTests.scala | 40 --- ...2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml | 7 + ...ala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml | 7 + ...er_2.12.4_scala-compiler-2.12.4-sources.jar.xml | 7 + ...a-compiler_2.12.4_scala-compiler-2.12.4.jar.xml | 7 + ...ary_2.12.4_scala-library-2.12.4-sources.jar.xml | 7 + ...ala-library_2.12.4_scala-library-2.12.4.jar.xml | 7 + ...ect_2.12.4_scala-reflect-2.12.4-sources.jar.xml | 7 + ...ala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml | 7 + scalalib/test/resources/gen-idea/idea/misc.xml | 5 + scalalib/test/resources/gen-idea/idea/modules.xml | 8 + scalalib/test/resources/gen-idea/idea_modules/iml | 22 ++ .../test/resources/gen-idea/idea_modules/root.iml | 9 + scalalib/test/resources/hello-world/src/Main.scala | 12 + .../test/resources/hello-world/src/Result.scala | 7 + scalalib/test/src/mill/scalalib/GenIdeaTests.scala | 78 ++++ .../test/src/mill/scalalib/HelloWorldTests.scala | 400 +++++++++++++++++++++ .../test/src/mill/scalalib/ResolveDepsTests.scala | 40 +++ 68 files changed, 2309 insertions(+), 2309 deletions(-) delete mode 100644 scalalib/src/main/scala/mill/scalalib/Dep.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/GenIdea.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/Lib.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/Main.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/MiscModule.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/PublishModule.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/ScalaModule.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/TestRunner.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/package.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/Ivy.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/JsonFormatters.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/LocalPublisher.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/Pom.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/SonatypeHttpApi.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/SonatypePublisher.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/package.scala delete mode 100644 scalalib/src/main/scala/mill/scalalib/publish/settings.scala create mode 100644 scalalib/src/mill/scalalib/Dep.scala create mode 100644 scalalib/src/mill/scalalib/GenIdea.scala create mode 100644 scalalib/src/mill/scalalib/Lib.scala create mode 100644 scalalib/src/mill/scalalib/Main.scala create mode 100644 scalalib/src/mill/scalalib/MiscModule.scala create mode 100644 scalalib/src/mill/scalalib/PublishModule.scala create mode 100644 scalalib/src/mill/scalalib/ScalaModule.scala create mode 100644 scalalib/src/mill/scalalib/TestRunner.scala create mode 100644 scalalib/src/mill/scalalib/package.scala create mode 100644 scalalib/src/mill/scalalib/publish/Ivy.scala create mode 100644 scalalib/src/mill/scalalib/publish/JsonFormatters.scala create mode 100644 scalalib/src/mill/scalalib/publish/LocalPublisher.scala create mode 100644 scalalib/src/mill/scalalib/publish/Pom.scala create mode 100644 scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala create mode 100644 scalalib/src/mill/scalalib/publish/SonatypePublisher.scala create mode 100644 scalalib/src/mill/scalalib/publish/package.scala create mode 100644 scalalib/src/mill/scalalib/publish/settings.scala delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/misc.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea/modules.xml delete mode 100644 scalalib/src/test/resources/gen-idea/idea_modules/iml delete mode 100644 scalalib/src/test/resources/gen-idea/idea_modules/root.iml delete mode 100644 scalalib/src/test/resources/hello-world/src/main/scala/Main.scala delete mode 100644 scalalib/src/test/resources/hello-world/src/main/scala/Result.scala delete mode 100644 scalalib/src/test/scala/mill/scalalib/GenIdeaTests.scala delete mode 100644 scalalib/src/test/scala/mill/scalalib/HelloWorldTests.scala delete mode 100644 scalalib/src/test/scala/mill/scalalib/ResolveDepsTests.scala create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml create mode 100644 scalalib/test/resources/gen-idea/idea/misc.xml create mode 100644 scalalib/test/resources/gen-idea/idea/modules.xml create mode 100644 scalalib/test/resources/gen-idea/idea_modules/iml create mode 100644 scalalib/test/resources/gen-idea/idea_modules/root.iml create mode 100644 scalalib/test/resources/hello-world/src/Main.scala create mode 100644 scalalib/test/resources/hello-world/src/Result.scala create mode 100644 scalalib/test/src/mill/scalalib/GenIdeaTests.scala create mode 100644 scalalib/test/src/mill/scalalib/HelloWorldTests.scala create mode 100644 scalalib/test/src/mill/scalalib/ResolveDepsTests.scala (limited to 'scalalib') diff --git a/scalalib/src/main/scala/mill/scalalib/Dep.scala b/scalalib/src/main/scala/mill/scalalib/Dep.scala deleted file mode 100644 index aa301fcb..00000000 --- a/scalalib/src/main/scala/mill/scalalib/Dep.scala +++ /dev/null @@ -1,44 +0,0 @@ -package mill.scalalib -import mill.util.JsonFormatters._ -import upickle.default.{macroRW, ReadWriter => RW} -sealed trait Dep -object Dep{ - - implicit def parse(signature: String) = { - signature.split(':') match{ - case Array(a, b, c) => Dep.Java(a, b, c) - case Array(a, "", b, c) => Dep.Scala(a, b, c) - case Array(a, "", "", b, c) => Dep.Point(a, b, c) - case _ => throw new Exception(s"Unable to parse signature: [$signature]") - } - } - def apply(org: String, name: String, version: String): Dep = { - this(coursier.Dependency(coursier.Module(org, name), version)) - } - case class Java(dep: coursier.Dependency) extends Dep - object Java{ - implicit def rw: RW[Java] = macroRW - def apply(org: String, name: String, version: String): Dep = { - Java(coursier.Dependency(coursier.Module(org, name), version)) - } - } - implicit def default(dep: coursier.Dependency): Dep = new Java(dep) - def apply(dep: coursier.Dependency) = Scala(dep) - case class Scala(dep: coursier.Dependency) extends Dep - object Scala{ - implicit def rw: RW[Scala] = macroRW - def apply(org: String, name: String, version: String): Dep = { - Scala(coursier.Dependency(coursier.Module(org, name), version)) - } - } - case class Point(dep: coursier.Dependency) extends Dep - object Point{ - implicit def rw: RW[Point] = macroRW - def apply(org: String, name: String, version: String): Dep = { - Point(coursier.Dependency(coursier.Module(org, name), version)) - } - } - implicit def rw = RW.merge[Dep]( - Java.rw, Scala.rw, Point.rw - ) -} \ No newline at end of file diff --git a/scalalib/src/main/scala/mill/scalalib/GenIdea.scala b/scalalib/src/main/scala/mill/scalalib/GenIdea.scala deleted file mode 100644 index 4496c8c6..00000000 --- a/scalalib/src/main/scala/mill/scalalib/GenIdea.scala +++ /dev/null @@ -1,203 +0,0 @@ -package mill.scalalib - -import ammonite.ops._ -import mill.define.{Segment, Segments, Target} -import mill.eval.{Evaluator, PathRef, RootModuleLoader} -import mill.scalalib -import mill.util.Ctx.{LoaderCtx, LogCtx} -import mill.util.{Loose, PrintLogger, Strict} -import mill.util.Strict.Agg - -object GenIdea { - - def apply()(implicit ctx: LoaderCtx with LogCtx): Unit = { - val rootModule = ctx.load(RootModuleLoader) - val pp = new scala.xml.PrettyPrinter(999, 4) - rm! pwd/".idea" - rm! pwd/".idea_modules" - - - val evaluator = new Evaluator(pwd / 'out, pwd, rootModule , ctx.log) - - for((relPath, xml) <- xmlFileLayout(evaluator, rootModule)){ - write.over(pwd/relPath, pp.format(xml)) - } - } - - def xmlFileLayout[T](evaluator: Evaluator[T], rootModule: mill.Module): Seq[(RelPath, scala.xml.Node)] = { - - - val modules = rootModule.millInternal.segmentsToModules.values.collect{case x: scalalib.ScalaModule => (x.millModuleSegments, x)}.toSeq - - val resolved = for((path, mod) <- modules) yield { - val Seq(resolvedCp: Loose.Agg[PathRef], resolvedSrcs: Loose.Agg[PathRef]) = - evaluator.evaluate(Agg(mod.externalCompileDepClasspath, mod.externalCompileDepSources)) - .values - - (path, resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path), mod) - } - val moduleLabels = modules.map(_.swap).toMap - - val fixedFiles = Seq( - Tuple2(".idea"/"misc.xml", miscXmlTemplate()), - Tuple2( - ".idea"/"modules.xml", - allModulesXmlTemplate( - for((path, mod) <- modules) - yield moduleName(path) - ) - ), - Tuple2(".idea_modules"/"root.iml", rootXmlTemplate()) - ) - - val allResolved = resolved.flatMap(_._2).distinct - val minResolvedLength = allResolved.map(_.segments.length).min - val commonPrefix = allResolved.map(_.segments.take(minResolvedLength)) - .transpose - .takeWhile(_.distinct.length == 1) - .length - - val pathToLibName = allResolved - .map{p => (p, p.segments.drop(commonPrefix).mkString("_"))} - .toMap - - val libraries = allResolved.map{path => - val url = "jar://" + path + "!/" - val name = pathToLibName(path) - Tuple2(".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, url)) - } - - val moduleFiles = resolved.map{ case (path, resolvedDeps, mod) => - val Seq( - resoucesPathRefs: Loose.Agg[PathRef], - sourcesPathRef: Loose.Agg[PathRef], - generatedSourcePathRefs: Loose.Agg[PathRef], - allSourcesPathRefs: Loose.Agg[PathRef] - ) = evaluator.evaluate(Agg(mod.resources, mod.sources, mod.generatedSources, mod.allSources)).values - - val generatedSourcePaths = generatedSourcePathRefs.map(_.path) - val normalSourcePaths = (allSourcesPathRefs.map(_.path).toSet -- generatedSourcePaths.toSet).toSeq - - val paths = Evaluator.resolveDestPaths( - evaluator.workspacePath, - mod.compile.ctx.segments - ) - - val elem = moduleXmlTemplate( - Strict.Agg.from(resoucesPathRefs.map(_.path)), - Strict.Agg.from(normalSourcePaths), - Strict.Agg.from(generatedSourcePaths), - paths.out, - Strict.Agg.from(resolvedDeps.map(pathToLibName)), - Strict.Agg.from(mod.moduleDeps.map{ m => moduleName(moduleLabels(m))}.distinct) - ) - Tuple2(".idea_modules"/s"${moduleName(path)}.iml", elem) - } - fixedFiles ++ libraries ++ moduleFiles - } - - - def relify(p: Path) = { - val r = p.relativeTo(pwd/".idea_modules") - (Seq.fill(r.ups)("..") ++ r.segments).mkString("/") - } - - def moduleName(p: Segments) = p.value.foldLeft(StringBuilder.newBuilder) { - case (sb, Segment.Label(s)) if sb.isEmpty => sb.append(s) - case (sb, Segment.Cross(s)) if sb.isEmpty => sb.append(s.mkString("-")) - case (sb, Segment.Label(s)) => sb.append(".").append(s) - case (sb, Segment.Cross(s)) => sb.append("-").append(s.mkString("-")) - }.mkString.toLowerCase() - - def miscXmlTemplate() = { - - - - - - } - - def allModulesXmlTemplate(selectors: Seq[String]) = { - - - - - { - for(selector <- selectors) - yield { - val filepath = "$PROJECT_DIR$/.idea_modules/" + selector + ".iml" - val fileurl = "file://" + filepath - - } - } - - - - } - def rootXmlTemplate() = { - - - - - - - - - - } - def libraryXmlTemplate(name: String, url: String) = { - - - - - - - - } - def moduleXmlTemplate(resourcePaths: Strict.Agg[Path], - normalSourcePaths: Strict.Agg[Path], - generatedSourcePaths: Strict.Agg[Path], - outputPath: Path, - libNames: Strict.Agg[String], - depNames: Strict.Agg[String]) = { - - - - - { - for (normalSourcePath <- normalSourcePaths.toSeq.sorted) - yield - - - - } - { - for (generatedSourcePath <- generatedSourcePaths.toSeq.sorted) - yield - - - - } - { - for (resourcePath <- resourcePaths.toSeq.sorted) - yield - - - - } - - - - { - for(name <- libNames.toSeq.sorted) - yield - - } - { - for(depName <- depNames.toSeq.sorted) - yield - } - - - } -} diff --git a/scalalib/src/main/scala/mill/scalalib/Lib.scala b/scalalib/src/main/scala/mill/scalalib/Lib.scala deleted file mode 100644 index a038a59b..00000000 --- a/scalalib/src/main/scala/mill/scalalib/Lib.scala +++ /dev/null @@ -1,234 +0,0 @@ -package mill -package scalalib - -import java.io.File -import java.net.URLClassLoader -import java.util.Optional - -import ammonite.ops._ -import coursier.{Cache, Fetch, MavenRepository, Repository, Resolution, Module => CoursierModule} -import mill.define.Worker -import mill.eval.{PathRef, Result} -import mill.util.{Ctx} -import mill.util.Loose.Agg -import sbt.internal.inc._ -import sbt.internal.util.{ConsoleOut, MainAppender} -import sbt.util.LogExchange -import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _} - -object CompilationResult { - implicit val jsonFormatter: upickle.default.ReadWriter[CompilationResult] = upickle.default.macroRW -} - -// analysisFile is represented by Path, so we won't break caches after file changes -case class CompilationResult(analysisFile: Path, classes: PathRef) - -object ZincWorker extends Worker[ZincWorker]{ - def make() = new ZincWorker -} -class ZincWorker{ - @volatile var scalaClassloaderCache = Option.empty[(Long, ClassLoader)] - @volatile var scalaInstanceCache = Option.empty[(Long, ScalaInstance)] -} -object Lib{ - case class MockedLookup(am: File => Optional[CompileAnalysis]) extends PerClasspathEntryLookup { - override def analysis(classpathEntry: File): Optional[CompileAnalysis] = - am(classpathEntry) - - override def definesClass(classpathEntry: File): DefinesClass = - Locate.definesClass(classpathEntry) - } - - def grepJar(classPath: Agg[Path], s: String) = { - classPath - .find(_.toString.endsWith(s)) - .getOrElse(throw new Exception("Cannot find " + s)) - .toIO - } - - def compileScala(zincWorker: ZincWorker, - scalaVersion: String, - sources: Agg[Path], - compileClasspath: Agg[Path], - compilerClasspath: Agg[Path], - pluginClasspath: Agg[Path], - compilerBridge: Path, - scalacOptions: Seq[String], - scalacPluginClasspath: Agg[Path], - javacOptions: Seq[String], - upstreamCompileOutput: Seq[CompilationResult]) - (implicit ctx: Ctx): CompilationResult = { - val compileClasspathFiles = compileClasspath.map(_.toIO).toArray - - val compilerJars = compilerClasspath.toArray.map(_.toIO) - val pluginJars = pluginClasspath.toArray.map(_.toIO) - - val compilerClassloaderSig = compilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum - val scalaInstanceSig = - compilerClassloaderSig + pluginClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum - - val compilerClassLoader = zincWorker.scalaClassloaderCache match{ - case Some((k, v)) if k == compilerClassloaderSig => v - case _ => - val classloader = new URLClassLoader(compilerJars.map(_.toURI.toURL), null) - zincWorker.scalaClassloaderCache = Some((compilerClassloaderSig, classloader)) - classloader - } - - val scalaInstance = zincWorker.scalaInstanceCache match{ - case Some((k, v)) if k == scalaInstanceSig => v - case _ => - val scalaInstance = new ScalaInstance( - version = scalaVersion, - loader = new URLClassLoader(pluginJars.map(_.toURI.toURL), compilerClassLoader), - libraryJar = grepJar(compilerClasspath, s"scala-library-$scalaVersion.jar"), - compilerJar = grepJar(compilerClasspath, s"scala-compiler-$scalaVersion.jar"), - allJars = compilerJars ++ pluginJars, - explicitActual = None - ) - zincWorker.scalaInstanceCache = Some((scalaInstanceSig, scalaInstance)) - scalaInstance - } - - mkdir(ctx.dest) - - val ic = new sbt.internal.inc.IncrementalCompilerImpl() - - val logger = { - val consoleAppender = MainAppender.defaultScreen(ConsoleOut.printStreamOut( - ctx.log.outputStream - )) - val l = LogExchange.logger("Hello") - LogExchange.unbindLoggerAppenders("Hello") - LogExchange.bindLoggerAppenders("Hello", (consoleAppender -> sbt.util.Level.Info) :: Nil) - l - } - - def analysisMap(f: File): Optional[CompileAnalysis] = { - if (f.isFile) { - Optional.empty[CompileAnalysis] - } else { - upstreamCompileOutput.collectFirst { - case CompilationResult(zincPath, classFiles) if classFiles.path.toNIO == f.toPath => - FileAnalysisStore.binary(zincPath.toIO).get().map[CompileAnalysis](_.getAnalysis) - }.getOrElse(Optional.empty[CompileAnalysis]) - } - } - - val lookup = MockedLookup(analysisMap) - - val zincFile = ctx.dest / 'zinc - val classesDir = ctx.dest / 'classes - - val zincIOFile = zincFile.toIO - val classesIODir = classesDir.toIO - - val store = FileAnalysisStore.binary(zincIOFile) - - val newResult = ic.compile( - ic.inputs( - classpath = classesIODir +: compileClasspathFiles, - sources = for{ - root <- sources.toArray - if exists(root) - path <- ls.rec(root) - if path.isFile && (path.ext == "scala" || path.ext == "java") - } yield path.toIO, - classesDirectory = classesIODir, - scalacOptions = (scalacPluginClasspath.map(jar => s"-Xplugin:${jar}") ++ scalacOptions).toArray, - javacOptions = javacOptions.toArray, - maxErrors = 10, - sourcePositionMappers = Array(), - order = CompileOrder.Mixed, - compilers = ic.compilers( - scalaInstance, - ClasspathOptionsUtil.boot, - None, - ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO) - ), - setup = ic.setup( - lookup, - skip = false, - zincIOFile, - new FreshCompilerCache, - IncOptions.of(), - new ManagedLoggedReporter(10, logger), - None, - Array() - ), - pr = { - val prev = store.get() - PreviousResult.of(prev.map(_.getAnalysis), prev.map(_.getMiniSetup)) - } - ), - logger = logger - ) - - store.set( - AnalysisContents.create( - newResult.analysis(), - newResult.setup() - ) - ) - - CompilationResult(zincFile, PathRef(classesDir)) - } - - def resolveDependencies(repositories: Seq[Repository], - scalaVersion: String, - scalaBinaryVersion: String, - deps: TraversableOnce[Dep], - sources: Boolean = false): Result[Agg[PathRef]] = { - val flattened = deps.map{ - case Dep.Java(dep) => dep - case Dep.Scala(dep) => - dep.copy(module = dep.module.copy(name = dep.module.name + "_" + scalaBinaryVersion)) - case Dep.Point(dep) => - dep.copy(module = dep.module.copy(name = dep.module.name + "_" + scalaVersion)) - }.toSet - val start = Resolution(flattened) - - val fetch = Fetch.from(repositories, Cache.fetch()) - val resolution = start.process.run(fetch).unsafePerformSync - val errs = resolution.metadataErrors - if(errs.nonEmpty) { - val header = - s"""| - |Resolution failed for ${errs.length} modules: - |-------------------------------------------- - |""".stripMargin - - val errLines = errs.map { - case ((module, vsn), errMsgs) => s" ${module.trim}:$vsn \n\t" + errMsgs.mkString("\n\t") - }.mkString("\n") - val msg = header + errLines + "\n" - Result.Failure(msg) - } else { - val sourceOrJar = - if (sources) resolution.classifiersArtifacts(Seq("sources")) - else resolution.artifacts - val localArtifacts: Seq[File] = scalaz.concurrent.Task - .gatherUnordered(sourceOrJar.map(Cache.file(_).run)) - .unsafePerformSync - .flatMap(_.toOption) - - Agg.from( - localArtifacts.map(p => PathRef(Path(p), quick = true)).filter(_.path.ext == "jar") - ) - } - } - def scalaCompilerIvyDeps(scalaVersion: String) = Agg[Dep]( - Dep.Java("org.scala-lang", "scala-compiler", scalaVersion), - Dep.Java("org.scala-lang", "scala-reflect", scalaVersion) - ) - def scalaRuntimeIvyDeps(scalaVersion: String) = Agg[Dep]( - Dep.Java("org.scala-lang", "scala-library", scalaVersion) - ) - def compilerBridgeIvyDep(scalaVersion: String) = - Dep.Point(coursier.Dependency(coursier.Module("com.lihaoyi", "mill-bridge"), "0.1", transitive = false)) - - val DefaultShellScript: Seq[String] = Seq( - "#!/usr/bin/env sh", - "exec java -jar \"$0\" \"$@\"" - ) -} diff --git a/scalalib/src/main/scala/mill/scalalib/Main.scala b/scalalib/src/main/scala/mill/scalalib/Main.scala deleted file mode 100644 index 584fe9d1..00000000 --- a/scalalib/src/main/scala/mill/scalalib/Main.scala +++ /dev/null @@ -1,7 +0,0 @@ -package mill.scalalib - -object Main { - def main(args: Array[String]): Unit = { - mill.Main.main(args) - } -} diff --git a/scalalib/src/main/scala/mill/scalalib/MiscModule.scala b/scalalib/src/main/scala/mill/scalalib/MiscModule.scala deleted file mode 100644 index 502ba461..00000000 --- a/scalalib/src/main/scala/mill/scalalib/MiscModule.scala +++ /dev/null @@ -1,72 +0,0 @@ -package mill -package scalalib - -import mill.define.Cross.Resolver -import mill.define.{Cross, Task} -import mill.eval.{PathRef, Result} -import mill.util.Loose.Agg - - -trait SbtModule extends ScalaModule { outer => - override def sources = T.input{ - Agg( - PathRef(basePath / 'src / 'main / 'scala), - PathRef(basePath / 'src / 'main / 'java) - ) - } - override def resources = T.input{ Agg(PathRef(basePath / 'src / 'main / 'resources)) } - trait Tests extends super.Tests { - override def basePath = outer.basePath - override def sources = T.input{ - Agg( - PathRef(basePath / 'src / 'test / 'scala), - PathRef(basePath / 'src / 'test / 'java) - ) - } - override def resources = T.input{ Agg(PathRef(basePath / 'src / 'test / 'resources)) } - } -} - -trait CrossSbtModule extends SbtModule { outer => - override def basePath = super.basePath / ammonite.ops.up - implicit def crossSbtModuleResolver: Resolver[CrossSbtModule] = new Resolver[CrossSbtModule]{ - def resolve[V <: CrossSbtModule](c: Cross[V]): V = { - crossScalaVersion.split('.') - .inits - .takeWhile(_.length > 1) - .flatMap( prefix => - c.items.map(_._2).find(_.crossScalaVersion.split('.').startsWith(prefix)) - ) - .collectFirst{case x => x} - .getOrElse( - throw new Exception( - s"Unable to find compatible cross version between $crossScalaVersion and "+ - c.items.map(_._2.crossScalaVersion).mkString(",") - ) - ) - } - } - - def crossScalaVersion: String - def scalaVersion = crossScalaVersion - override def sources = T.input{ - super.sources() ++ - crossScalaVersion.split('.').inits.filter(_.nonEmpty).map(_.mkString(".")).map{ - s => PathRef{ basePath / 'src / 'main / s"scala-$s" } - } - - } - override def resources = T.input{ Agg(PathRef(basePath / 'src / 'main / 'resources)) } - trait Tests extends super.Tests { - override def basePath = outer.basePath - override def sources = T.input{ - super.sources() ++ - crossScalaVersion.split('.').inits.filter(_.nonEmpty).map(_.mkString(".")).map{ - s => PathRef{ basePath / 'src / 'test / s"scala-$s" } - } - } - override def resources = T.input{ Agg(PathRef(basePath / 'src / 'test / 'resources)) } - } -} - - diff --git a/scalalib/src/main/scala/mill/scalalib/PublishModule.scala b/scalalib/src/main/scala/mill/scalalib/PublishModule.scala deleted file mode 100644 index 64efce77..00000000 --- a/scalalib/src/main/scala/mill/scalalib/PublishModule.scala +++ /dev/null @@ -1,70 +0,0 @@ -package mill -package scalalib - -import ammonite.ops._ -import mill.eval.{PathRef, Result} -import mill.util.Loose.Agg -/** - * Configuration necessary for publishing a Scala module to Maven Central or similar - */ -trait PublishModule extends ScalaModule { outer => - import mill.scalalib.publish._ - - def pomSettings: T[PomSettings] - def publishVersion: T[String] = "0.0.1-SNAPSHOT" - - def pom = T { - val dependencies = - ivyDeps().map(Artifact.fromDep(_, scalaVersion(), scalaBinaryVersion())) - val pom = Pom(artifact(), dependencies, artifactName(), pomSettings()) - - val pomPath = T.ctx().dest / s"${artifactId()}-${publishVersion()}.pom" - write.over(pomPath, pom) - PathRef(pomPath) - } - - def ivy = T { - val dependencies = - ivyDeps().map(Artifact.fromDep(_, scalaVersion(), scalaBinaryVersion())) - val ivy = Ivy(artifact(), dependencies) - val ivyPath = T.ctx().dest / "ivy.xml" - write.over(ivyPath, ivy) - PathRef(ivyPath) - } - - def artifact: T[Artifact] = T { - Artifact(pomSettings().organization, artifactId(), publishVersion()) - } - - def publishLocal(): define.Command[Unit] = T.command { - LocalPublisher.publish( - jar = jar().path, - sourcesJar = sourcesJar().path, - docsJar = docsJar().path, - pom = pom().path, - ivy = ivy().path, - artifact = artifact() - ) - } - - def sonatypeUri: String = "https://oss.sonatype.org/service/local" - - def sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots" - - def publish(credentials: String, gpgPassphrase: String): define.Command[Unit] = T.command { - val baseName = s"${artifactId()}-${publishVersion()}" - val artifacts = Seq( - jar().path -> s"${baseName}.jar", - sourcesJar().path -> s"${baseName}-sources.jar", - docsJar().path -> s"${baseName}-javadoc.jar", - pom().path -> s"${baseName}.pom" - ) - new SonatypePublisher( - sonatypeUri, - sonatypeSnapshotUri, - credentials, - gpgPassphrase, - T.ctx().log - ).publish(artifacts, artifact()) - } -} diff --git a/scalalib/src/main/scala/mill/scalalib/ScalaModule.scala b/scalalib/src/main/scala/mill/scalalib/ScalaModule.scala deleted file mode 100644 index 1b2bd28d..00000000 --- a/scalalib/src/main/scala/mill/scalalib/ScalaModule.scala +++ /dev/null @@ -1,318 +0,0 @@ -package mill -package scalalib - -import ammonite.ops._ -import coursier.{Cache, MavenRepository, Repository} -import mill.define.{Cross, Task} -import mill.define.TaskModule -import mill.eval.{PathRef, Result} -import mill.modules.Jvm -import mill.modules.Jvm.{createAssembly, createJar, interactiveSubprocess, subprocess, inprocess} -import Lib._ -import mill.define.Cross.Resolver -import mill.util.Loose.Agg -import sbt.testing.Status - -/** - * Core configuration required to compile a single Scala compilation target - */ -trait ScalaModule extends mill.Module with TaskModule { outer => - def defaultCommandName() = "run" - trait Tests extends TestModule{ - def scalaVersion = outer.scalaVersion() - override def moduleDeps = Seq(outer) - } - def scalaVersion: T[String] - def mainClass: T[Option[String]] = None - - def scalaBinaryVersion = T{ scalaVersion().split('.').dropRight(1).mkString(".") } - def ivyDeps = T{ Agg.empty[Dep] } - def compileIvyDeps = T{ Agg.empty[Dep] } - def scalacPluginIvyDeps = T{ Agg.empty[Dep] } - def runIvyDeps = T{ Agg.empty[Dep] } - - def scalacOptions = T{ Seq.empty[String] } - def javacOptions = T{ Seq.empty[String] } - - def repositories: Seq[Repository] = Seq( - Cache.ivy2Local, - MavenRepository("https://repo1.maven.org/maven2") - ) - - def moduleDeps = Seq.empty[ScalaModule] - def depClasspath = T{ Agg.empty[PathRef] } - - - def upstreamRunClasspath = T{ - Task.traverse(moduleDeps)(p => - T.task(p.runDepClasspath() ++ p.runClasspath()) - ) - } - - def upstreamCompileOutput = T{ - Task.traverse(moduleDeps)(_.compile) - } - def upstreamCompileClasspath = T{ - externalCompileDepClasspath() ++ - upstreamCompileOutput().map(_.classes) ++ - Task.traverse(moduleDeps)(_.compileDepClasspath)().flatten - } - - def resolveDeps(deps: Task[Agg[Dep]], sources: Boolean = false) = T.task{ - resolveDependencies( - repositories, - scalaVersion(), - scalaBinaryVersion(), - deps(), - sources - ) - } - - def externalCompileDepClasspath: T[Agg[PathRef]] = T{ - Agg.from(Task.traverse(moduleDeps)(_.externalCompileDepClasspath)().flatten) ++ - resolveDeps( - T.task{ivyDeps() ++ compileIvyDeps() ++ scalaCompilerIvyDeps(scalaVersion())} - )() - } - - def externalCompileDepSources: T[Agg[PathRef]] = T{ - Agg.from(Task.traverse(moduleDeps)(_.externalCompileDepSources)().flatten) ++ - resolveDeps( - T.task{ivyDeps() ++ compileIvyDeps() ++ scalaCompilerIvyDeps(scalaVersion())}, - sources = true - )() - } - - /** - * Things that need to be on the classpath in order for this code to compile; - * might be less than the runtime classpath - */ - def compileDepClasspath: T[Agg[PathRef]] = T{ - upstreamCompileClasspath() ++ - depClasspath() - } - - /** - * Strange compiler-bridge jar that the Zinc incremental compile needs - */ - def compilerBridge: T[PathRef] = T{ - val compilerBridgeKey = "MILL_COMPILER_BRIDGE_" + scalaVersion().replace('.', '_') - val compilerBridgePath = sys.props(compilerBridgeKey) - if (compilerBridgePath != null) PathRef(Path(compilerBridgePath), quick = true) - else { - val dep = compilerBridgeIvyDep(scalaVersion()) - val classpath = resolveDependencies( - repositories, - scalaVersion(), - scalaBinaryVersion(), - Seq(dep) - ) - classpath match { - case Result.Success(resolved) => - resolved.filter(_.path.ext != "pom").toSeq match { - case Seq(single) => PathRef(single.path, quick = true) - case Seq() => throw new Exception(dep + " resolution failed") // TODO: find out, is it possible? - case _ => throw new Exception(dep + " resolution resulted in more than one file") - } - case f: Result.Failure => throw new Exception(dep + s" resolution failed.\n + ${f.msg}") // TODO: remove, resolveDependencies will take care of this. - } - } - } - - def scalacPluginClasspath: T[Agg[PathRef]] = - resolveDeps( - T.task{scalacPluginIvyDeps()} - )() - - /** - * Classpath of the Scala Compiler & any compiler plugins - */ - def scalaCompilerClasspath: T[Agg[PathRef]] = T{ - resolveDeps( - T.task{scalaCompilerIvyDeps(scalaVersion()) ++ scalaRuntimeIvyDeps(scalaVersion())} - )() - } - - /** - * Things that need to be on the classpath in order for this code to run - */ - def runDepClasspath: T[Agg[PathRef]] = T{ - Agg.from(upstreamRunClasspath().flatten) ++ - depClasspath() ++ - resolveDeps( - T.task{ivyDeps() ++ runIvyDeps() ++ scalaRuntimeIvyDeps(scalaVersion())} - )() - } - - def prependShellScript: T[String] = T{ "" } - - def sources = T.input{ Agg(PathRef(basePath / 'src)) } - def resources = T.input{ Agg(PathRef(basePath / 'resources)) } - def generatedSources = T { Agg.empty[PathRef] } - def allSources = T{ sources() ++ generatedSources() } - def compile: T[CompilationResult] = T.persistent{ - compileScala( - ZincWorker(), - scalaVersion(), - allSources().map(_.path), - compileDepClasspath().map(_.path), - scalaCompilerClasspath().map(_.path), - scalacPluginClasspath().map(_.path), - compilerBridge().path, - scalacOptions(), - scalacPluginClasspath().map(_.path), - javacOptions(), - upstreamCompileOutput() - ) - } - def runClasspath = T{ - runDepClasspath() ++ resources() ++ Seq(compile().classes) - } - - def assembly = T{ - createAssembly( - runClasspath().map(_.path).filter(exists), - mainClass(), - prependShellScript = prependShellScript() - ) - } - - def localClasspath = T{ resources() ++ Seq(compile().classes) } - - def jar = T{ - createJar( - localClasspath().map(_.path).filter(exists), - mainClass() - ) - } - - def docsJar = T { - val outDir = T.ctx().dest - - val javadocDir = outDir / 'javadoc - mkdir(javadocDir) - - val options = { - - val files = for{ - ref <- sources() - p <- ls.rec(ref.path) - if p.isFile - } yield p.toNIO.toString - files ++ Seq("-d", javadocDir.toNIO.toString, "-usejavacp") - } - - subprocess( - "scala.tools.nsc.ScalaDoc", - compileDepClasspath().filter(_.path.ext != "pom").map(_.path), - options = options.toSeq - ) - - createJar(Agg(javadocDir))(outDir / "javadoc.jar") - } - - def sourcesJar = T { - createJar((sources() ++ resources()).map(_.path).filter(exists))(T.ctx().dest / "sources.jar") - } - - def forkArgs = T{ Seq.empty[String] } - - - def run(args: String*) = T.command { - inprocess( - mainClass().getOrElse(throw new RuntimeException("No mainClass provided!")), - runClasspath().map(_.path), - args) - } - - def forkRun(args: String*) = T.command{ - subprocess( - mainClass().getOrElse(throw new RuntimeException("No mainClass provided!")), - runClasspath().map(_.path), - forkArgs(), - args, - workingDir = ammonite.ops.pwd) - } - - def runMain(mainClass: String, args: String*) = T.command{ - subprocess( - mainClass, - runClasspath().map(_.path), - forkArgs(), - args, - workingDir = ammonite.ops.pwd - ) - } - - def console() = T.command{ - interactiveSubprocess( - mainClass = "scala.tools.nsc.MainGenericRunner", - classPath = runClasspath().map(_.path), - options = Seq("-usejavacp") - ) - } - - // publish artifact with name "mill_2.12.4" instead of "mill_2.12" - def crossFullScalaVersion: T[Boolean] = false - - def artifactName: T[String] = basePath.last.toString - def artifactScalaVersion: T[String] = T { - if (crossFullScalaVersion()) scalaVersion() - else scalaBinaryVersion() - } - - def artifactId: T[String] = T { s"${artifactName()}_${artifactScalaVersion()}" } - -} - - -object TestModule{ - def handleResults(doneMsg: String, results: Seq[TestRunner.Result]) = { - if (results.count(Set(Status.Error, Status.Failure)) == 0) Result.Success((doneMsg, results)) - else { - val grouped = results.map(_.status).groupBy(x => x).mapValues(_.length).filter(_._2 != 0).toList.sorted - - Result.Failure(grouped.map{case (k, v) => k + ": " + v}.mkString(",")) - } - } -} -trait TestModule extends ScalaModule with TaskModule { - override def defaultCommandName() = "test" - def testFramework: T[String] - - def forkWorkingDir = ammonite.ops.pwd - - def forkTest(args: String*) = T.command{ - mkdir(T.ctx().dest) - val outputPath = T.ctx().dest/"out.json" - - Jvm.subprocess( - mainClass = "mill.scalalib.TestRunner", - classPath = Jvm.gatherClassloaderJars(), - jvmOptions = forkArgs(), - options = Seq( - testFramework(), - runClasspath().map(_.path).mkString(" "), - Seq(compile().classes.path).mkString(" "), - args.mkString(" "), - outputPath.toString, - T.ctx().log.colored.toString - ), - workingDir = forkWorkingDir - ) - - val jsonOutput = upickle.json.read(outputPath.toIO) - val (doneMsg, results) = upickle.default.readJs[(String, Seq[TestRunner.Result])](jsonOutput) - TestModule.handleResults(doneMsg, results) - - } - def test(args: String*) = T.command{ - val (doneMsg, results) = TestRunner( - testFramework(), - runClasspath().map(_.path), - Agg(compile().classes.path), - args - ) - TestModule.handleResults(doneMsg, results) - } -} \ No newline at end of file diff --git a/scalalib/src/main/scala/mill/scalalib/TestRunner.scala b/scalalib/src/main/scala/mill/scalalib/TestRunner.scala deleted file mode 100644 index 01726022..00000000 --- a/scalalib/src/main/scala/mill/scalalib/TestRunner.scala +++ /dev/null @@ -1,172 +0,0 @@ -package mill.scalalib - -import java.io.FileInputStream -import java.lang.annotation.Annotation -import java.net.URLClassLoader -import java.util.zip.ZipInputStream - -import ammonite.ops.{Path, ls, pwd} -import ammonite.util.Colors -import mill.modules.Jvm -import mill.util.Ctx.LogCtx -import mill.util.{PrintLogger} -import mill.util.Loose.Agg -import sbt.testing._ -import upickle.Js -import mill.util.JsonFormatters._ - -import scala.collection.mutable - -object TestRunner { - def listClassFiles(base: Path): Iterator[String] = { - if (base.isDir) ls.rec(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 runTests(cl: ClassLoader, framework: Framework, classpath: Agg[Path]) = { - - - val fingerprints = framework.fingerprints() - val testClasses = classpath.flatMap { base => - listClassFiles(base).flatMap { path => - val cls = cl.loadClass(path.stripSuffix(".class").replace('/', '.')) - fingerprints.find { - case f: SubclassFingerprint => - - (f.isModule == cls.getName.endsWith("$")) && - cl.loadClass(f.superclassName()).isAssignableFrom(cls) - case f: AnnotatedFingerprint => - (f.isModule == cls.getName.endsWith("$")) && - cls.isAnnotationPresent( - cl.loadClass(f.annotationName()).asInstanceOf[Class[Annotation]] - ) - }.map { f => (cls, f) } - } - } - testClasses - } - def main(args: Array[String]): Unit = { - try{ - val result = apply( - frameworkName = args(0), - entireClasspath = Agg.from(args(1).split(" ").map(Path(_))), - testClassfilePath = Agg.from(args(2).split(" ").map(Path(_))), - args = args(3) match{ case "" => Nil case x => x.split(" ").toList } - )(new PrintLogger( - args(5) == "true", - if(args(5) == "true") Colors.Default - else Colors.BlackWhite, - System.out, - System.err, - System.err - )) - val outputPath = args(4) - - ammonite.ops.write(Path(outputPath), upickle.default.write(result)) - }catch{case e: Throwable => - println(e) - e.printStackTrace() - } - // Tests are over, kill the JVM whether or not anyone's threads are still running - // Always return 0, even if tests fail. The caller can pick up the detailed test - // results from the outputPath - System.exit(0) - } - def apply(frameworkName: String, - entireClasspath: Agg[Path], - testClassfilePath: Agg[Path], - args: Seq[String]) - (implicit ctx: LogCtx): (String, Seq[Result]) = { - Jvm.inprocess(entireClasspath, classLoaderOverrideSbtTesting = true, cl => { - val framework = cl.loadClass(frameworkName) - .newInstance() - .asInstanceOf[sbt.testing.Framework] - - val testClasses = runTests(cl, framework, testClassfilePath) - - val runner = framework.runner(args.toArray, args.toArray, cl) - - val tasks = runner.tasks( - for ((cls, fingerprint) <- testClasses.toArray) - yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) - ) - val events = mutable.Buffer.empty[Event] - for (t <- tasks) { - t.execute( - new EventHandler { - def handle(event: Event) = events.append(event) - }, - Array( - new Logger { - def debug(msg: String) = ctx.log.info(msg) - - def error(msg: String) = ctx.log.error(msg) - - def ansiCodesSupported() = true - - def warn(msg: String) = ctx.log.info(msg) - - def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) - - def info(msg: String) = ctx.log.info(msg) - }) - ) - } - val doneMsg = runner.done() - val results = for(e <- events) yield { - val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None - Result( - e.fullyQualifiedName(), - e.selector() match{ - case s: NestedSuiteSelector => s.suiteId() - case s: NestedTestSelector => s.suiteId() + "." + s.testName() - case s: SuiteSelector => s.toString - case s: TestSelector => s.testName() - case s: TestWildcardSelector => s.testWildcard() - }, - e.duration(), - e.status(), - ex.map(_.getClass.getName), - ex.map(_.getMessage), - ex.map(_.getStackTrace) - ) - } - (doneMsg, results) - }) - } - - case class Result(fullyQualifiedName: String, - selector: String, - duration: Long, - status: Status, - exceptionName: Option[String], - exceptionMsg: Option[String], - exceptionTrace: Option[Seq[StackTraceElement]]) - - object Result{ - implicit def resultRW: upickle.default.ReadWriter[Result] = upickle.default.macroRW[Result] - implicit def statusRW: upickle.default.ReadWriter[Status] = upickle.default.ReadWriter[Status]( - { - case Status.Success => Js.Str("Success") - case Status.Error => Js.Str("Error") - case Status.Failure => Js.Str("Failure") - case Status.Skipped => Js.Str("Skipped") - case Status.Ignored => Js.Str("Ignored") - case Status.Canceled => Js.Str("Canceled") - case Status.Pending => Js.Str("Pending") - }, - { - case Js.Str("Success") => Status.Success - case Js.Str("Error") => Status.Error - case Js.Str("Failure") => Status.Failure - case Js.Str("Skipped") => Status.Skipped - case Js.Str("Ignored") => Status.Ignored - case Js.Str("Canceled") => Status.Canceled - case Js.Str("Pending") => Status.Pending - } - ) - } - -} diff --git a/scalalib/src/main/scala/mill/scalalib/package.scala b/scalalib/src/main/scala/mill/scalalib/package.scala deleted file mode 100644 index 5a282e82..00000000 --- a/scalalib/src/main/scala/mill/scalalib/package.scala +++ /dev/null @@ -1,12 +0,0 @@ -package mill - -package object scalalib { - implicit class DepSyntax(ctx: StringContext){ - def ivy(args: Any*) = Dep.parse{ - ( - ctx.parts.take(args.length).zip(args).flatMap{case (p, a) => Seq(p, a)} ++ - ctx.parts.drop(args.length) - ).mkString - } - } -} diff --git a/scalalib/src/main/scala/mill/scalalib/publish/Ivy.scala b/scalalib/src/main/scala/mill/scalalib/publish/Ivy.scala deleted file mode 100644 index b0b6443e..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/Ivy.scala +++ /dev/null @@ -1,55 +0,0 @@ -package mill.scalalib.publish - -import mill.util.Loose.Agg - -import scala.xml.PrettyPrinter - -object Ivy { - - val head = "\n" - - def apply( - artifact: Artifact, - dependencies: Agg[Dependency] - ): String = { - val xml = - - - - - - - - - - - - - - - - - - - - {dependencies.map(renderDependency)} - - - val pp = new PrettyPrinter(120, 4) - head + pp.format(xml).replaceAll(">", ">") - } - - private def renderDependency(dep: Dependency) = { - val scope = scopeToConf(dep.scope) - default(compile)"}> - } - - private def scopeToConf(s: Scope): String = s match { - case Scope.Compile => "compile" - case Scope.Provided => "provided" - case Scope.Test => "test" - case Scope.Runtime => "runtime" - } - -} diff --git a/scalalib/src/main/scala/mill/scalalib/publish/JsonFormatters.scala b/scalalib/src/main/scala/mill/scalalib/publish/JsonFormatters.scala deleted file mode 100644 index cf1af557..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/JsonFormatters.scala +++ /dev/null @@ -1,11 +0,0 @@ -package mill.scalalib.publish - -import upickle.default.{ReadWriter => RW} - -trait JsonFormatters { - implicit lazy val artifactFormat: RW[Artifact] = upickle.default.macroRW - implicit lazy val developerFormat: RW[Developer] = upickle.default.macroRW - implicit lazy val licenseFormat: RW[License] = upickle.default.macroRW - implicit lazy val scmFormat: RW[SCM] = upickle.default.macroRW - implicit lazy val pomSettingsFormat: RW[PomSettings] = upickle.default.macroRW -} diff --git a/scalalib/src/main/scala/mill/scalalib/publish/LocalPublisher.scala b/scalalib/src/main/scala/mill/scalalib/publish/LocalPublisher.scala deleted file mode 100644 index a9957e5c..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/LocalPublisher.scala +++ /dev/null @@ -1,33 +0,0 @@ -package mill.scalalib.publish - -import ammonite.ops._ - -object LocalPublisher { - - private val root: Path = home / ".ivy2" / "local" - - def publish(jar: Path, - sourcesJar: Path, - docsJar: Path, - pom: Path, - ivy: Path, - artifact: Artifact): Unit = { - val releaseDir = root / artifact.group / artifact.id / artifact.version - writeFiles( - jar -> releaseDir / "jars" / s"${artifact.id}.jar", - sourcesJar -> releaseDir / "srcs" / s"${artifact.id}-sources.jar", - docsJar -> releaseDir / "docs" / s"${artifact.id}-javadoc.jar", - pom -> releaseDir / "poms" / s"${artifact.id}.pom", - ivy -> releaseDir / "ivys" / "ivy.xml" - ) - } - - private def writeFiles(fromTo: (Path, Path)*): Unit = { - fromTo.foreach { - case (from, to) => - mkdir(to / up) - cp.over(from, to) - } - } - -} diff --git a/scalalib/src/main/scala/mill/scalalib/publish/Pom.scala b/scalalib/src/main/scala/mill/scalalib/publish/Pom.scala deleted file mode 100644 index 74dc6e8f..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/Pom.scala +++ /dev/null @@ -1,90 +0,0 @@ -package mill.scalalib.publish - -import mill.util.Loose.Agg - -import scala.xml.{Elem, NodeSeq, PrettyPrinter} - -object Pom { - - val head = "\n" - - //TODO - not only jar packaging support? - def apply(artifact: Artifact, - dependencies: Agg[Dependency], - name: String, - pomSettings: PomSettings): String = { - val xml = - - - 4.0.0 - {name} - {artifact.group} - {artifact.id} - jar - {pomSettings.description} - - {artifact.version} - {pomSettings.url} - - {pomSettings.licenses.map(renderLicense)} - - - {pomSettings.scm.url} - {pomSettings.scm.connection} - - - {pomSettings.developers.map(renderDeveloper)} - - - {dependencies.map(renderDependency)} - - - - val pp = new PrettyPrinter(120, 4) - head + pp.format(xml) - } - - private def renderLicense(l: License): Elem = { - - {l.name} - {l.url} - {l.distribution} - - } - - private def renderDeveloper(d: Developer): Elem = { - - {d.id} - {d.name} - { - d.organization.map { org => - {org} - }.getOrElse(NodeSeq.Empty) - } - { - d.organizationUrl.map { orgUrl => - {orgUrl} - }.getOrElse(NodeSeq.Empty) - } - - } - - private def renderDependency(d: Dependency): Elem = { - val scope = d.scope match { - case Scope.Compile => NodeSeq.Empty - case Scope.Provided => provided - case Scope.Test => test - case Scope.Runtime => runtime - } - - {d.artifact.group} - {d.artifact.id} - {d.artifact.version} - {scope} - - } - -} diff --git a/scalalib/src/main/scala/mill/scalalib/publish/SonatypeHttpApi.scala b/scalalib/src/main/scala/mill/scalalib/publish/SonatypeHttpApi.scala deleted file mode 100644 index 8ccdf3ea..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/SonatypeHttpApi.scala +++ /dev/null @@ -1,130 +0,0 @@ -package mill.scalalib.publish - -import java.util.Base64 - -import upickle.json - -import scala.concurrent.duration._ -import scalaj.http.{BaseHttp, HttpOptions, HttpRequest, HttpResponse} - -object PatientHttp - extends BaseHttp( - options = Seq( - HttpOptions.connTimeout(5.seconds.toMillis.toInt), - HttpOptions.readTimeout(1.minute.toMillis.toInt), - HttpOptions.followRedirects(false) - ) - ) - -class SonatypeHttpApi(uri: String, credentials: String) { - - private val base64Creds = base64(credentials) - - private val commonHeaders = Seq( - "Authorization" -> s"Basic ${base64Creds}", - "Accept" -> "application/json", - "Content-Type" -> "application/json" - ) - - // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles.html - def getStagingProfileUri(groupId: String): String = { - val response = withRetry( - PatientHttp(s"${uri}/staging/profiles").headers(commonHeaders)) - - val resourceUri = - json - .read(response.body)("data") - .arr - .find(profile => profile("name").str == groupId) - .map(_("resourceURI").str.toString) - - resourceUri.getOrElse( - throw new RuntimeException( - s"Could not find staging profile for groupId: ${groupId}") - ) - } - - def getStagingRepoState(stagingRepoId: String): String = { - val response = PatientHttp(s"${uri}/staging/repository/${stagingRepoId}") - .option(HttpOptions.readTimeout(60000)) - .headers(commonHeaders) - .asString - - json.read(response.body)("type").str.toString - } - - // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_start.html - def createStagingRepo(profileUri: String, groupId: String): String = { - val response = withRetry(PatientHttp(s"${profileUri}/start") - .headers(commonHeaders) - .postData( - s"""{"data": {"description": "fresh staging profile for ${groupId}"}}""")) - - json.read(response.body)("data")("stagedRepositoryId").str.toString - } - - // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_finish.html - def closeStagingRepo(profileUri: String, repositoryId: String): Boolean = { - val response = withRetry( - PatientHttp(s"${profileUri}/finish") - .headers(commonHeaders) - .postData( - s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "closing staging repository"}}""" - )) - - response.code == 201 - } - - // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_promote.html - def promoteStagingRepo(profileUri: String, repositoryId: String): Boolean = { - val response = withRetry( - PatientHttp(s"${profileUri}/promote") - .headers(commonHeaders) - .postData( - s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "promote staging repository"}}""" - )) - - response.code == 201 - } - - // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_drop.html - def dropStagingRepo(profileUri: String, repositoryId: String): Boolean = { - val response = withRetry( - PatientHttp(s"${profileUri}/drop") - .headers(commonHeaders) - .postData( - s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "drop staging repository"}}""" - )) - - response.code == 201 - } - - private val uploadTimeout = 5.minutes.toMillis.toInt - - def upload(uri: String, data: Array[Byte]): HttpResponse[String] = { - PatientHttp(uri) - .option(HttpOptions.readTimeout(uploadTimeout)) - .method("PUT") - .headers( - "Content-Type" -> "application/binary", - "Authorization" -> s"Basic ${base64Creds}" - ) - .put(data) - .asString - } - - private def withRetry(request: HttpRequest, - retries: Int = 10): HttpResponse[String] = { - val resp = request.asString - if (resp.is5xx && retries > 0) { - Thread.sleep(500) - withRetry(request, retries - 1) - } else { - resp - } - } - - private def base64(s: String) = - new String(Base64.getEncoder.encode(s.getBytes)) - -} diff --git a/scalalib/src/main/scala/mill/scalalib/publish/SonatypePublisher.scala b/scalalib/src/main/scala/mill/scalalib/publish/SonatypePublisher.scala deleted file mode 100644 index 0749b0c5..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/SonatypePublisher.scala +++ /dev/null @@ -1,148 +0,0 @@ -package mill.scalalib.publish - -import java.math.BigInteger -import java.security.MessageDigest - -import ammonite.ops._ -import mill.util.Logger - -import scalaj.http.HttpResponse - -class SonatypePublisher(uri: String, - snapshotUri: String, - credentials: String, - gpgPassphrase: String, - log: Logger) { - - private val api = new SonatypeHttpApi(uri, credentials) - - def publish(artifacts: Seq[(Path, String)], artifact: Artifact): Unit = { - val signedArtifacts = artifacts ++ artifacts.map { - case (file, name) => - poorMansSign(file, gpgPassphrase) -> s"${name}.asc" - } - - val signedArtifactsWithDigest = signedArtifacts.flatMap { - case (file, name) => - val content = read.bytes(file) - - Seq( - name -> content, - (name + ".md5") -> md5hex(content), - (name + ".sha1") -> sha1hex(content) - ) - } - - val publishPath = Seq( - artifact.group.replace(".", "/"), - artifact.id, - artifact.version - ).mkString("/") - - if (artifact.isSnapshot) - publishSnapshot(publishPath, signedArtifactsWithDigest, artifact) - else - publishRelease(publishPath, signedArtifactsWithDigest, artifact) - } - - private def publishSnapshot(publishPath: String, - payloads: Seq[(String, Array[Byte])], - artifact: Artifact): Unit = { - val baseUri: String = snapshotUri + "/" + publishPath - - val publishResults = payloads.map { - case (fileName, data) => - log.info(s"Uploading ${fileName}") - val resp = api.upload(s"${baseUri}/${fileName}", data) - resp - } - reportPublishResults(publishResults, artifact) - } - - private def publishRelease(publishPath: String, - payloads: Seq[(String, Array[Byte])], - artifact: Artifact): Unit = { - val profileUri = api.getStagingProfileUri(artifact.group) - val stagingRepoId = - api.createStagingRepo(profileUri, artifact.group) - val baseUri = - s"${uri}/staging/deployByRepositoryId/${stagingRepoId}/${publishPath}" - - val publishResults = payloads.map { - case (fileName, data) => - log.info(s"Uploading ${fileName}") - api.upload(s"${baseUri}/${fileName}", data) - } - reportPublishResults(publishResults, artifact) - - log.info("Closing staging repository") - api.closeStagingRepo(profileUri, stagingRepoId) - - log.info("Waiting for staging repository to close") - awaitRepoStatus("closed", stagingRepoId) - - log.info("Promoting staging repository") - api.promoteStagingRepo(profileUri, stagingRepoId) - - log.info("Waiting for staging repository to release") - awaitRepoStatus("released", stagingRepoId) - - log.info("Dropping staging repository") - api.dropStagingRepo(profileUri, stagingRepoId) - - log.info(s"Published ${artifact.id} successfully") - } - - private def reportPublishResults(publishResults: Seq[HttpResponse[String]], - artifact: Artifact) = { - if (publishResults.forall(_.is2xx)) { - log.info(s"Published ${artifact.id} to Sonatype") - } else { - val errors = publishResults.filterNot(_.is2xx).map { response => - s"Code: ${response.code}, message: ${response.body}" - } - throw new RuntimeException( - s"Failed to publish ${artifact.id} to Sonatype. Errors: \n${errors.mkString("\n")}" - ) - } - } - - private def awaitRepoStatus(status: String, - stagingRepoId: String, - attempts: Int = 20): Unit = { - def isRightStatus = - api.getStagingRepoState(stagingRepoId).equalsIgnoreCase(status) - var attemptsLeft = attempts - - while (attemptsLeft > 0 && !isRightStatus) { - Thread.sleep(3000) - attemptsLeft -= 1 - if (attemptsLeft == 0) { - throw new RuntimeException( - s"Couldn't wait for staging repository to be ${status}. Failing") - } - } - } - - // http://central.sonatype.org/pages/working-with-pgp-signatures.html#signing-a-file - private def poorMansSign(file: Path, passphrase: String): Path = { - val fileName = file.toString - import ammonite.ops.ImplicitWd._ - %("gpg", "--yes", "-a", "-b", "--passphrase", passphrase, fileName) - Path(fileName + ".asc") - } - - private def md5hex(bytes: Array[Byte]): Array[Byte] = - hexArray(md5.digest(bytes)).getBytes - - private def sha1hex(bytes: Array[Byte]): Array[Byte] = - hexArray(sha1.digest(bytes)).getBytes - - private def md5 = MessageDigest.getInstance("md5") - - private def sha1 = MessageDigest.getInstance("sha1") - - private def hexArray(arr: Array[Byte]) = - String.format("%0" + (arr.length << 1) + "x", new BigInteger(1, arr)) - -} diff --git a/scalalib/src/main/scala/mill/scalalib/publish/package.scala b/scalalib/src/main/scala/mill/scalalib/publish/package.scala deleted file mode 100644 index 99eeec14..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/package.scala +++ /dev/null @@ -1,3 +0,0 @@ -package mill.scalalib - -package object publish extends JsonFormatters diff --git a/scalalib/src/main/scala/mill/scalalib/publish/settings.scala b/scalalib/src/main/scala/mill/scalalib/publish/settings.scala deleted file mode 100644 index eb0a44b6..00000000 --- a/scalalib/src/main/scala/mill/scalalib/publish/settings.scala +++ /dev/null @@ -1,70 +0,0 @@ -package mill.scalalib.publish - -import mill.scalalib.Dep - -case class Artifact(group: String, id: String, version: String) { - def isSnapshot: Boolean = version.endsWith("-SNAPSHOT") -} - -object Artifact { - - def fromDep(dep: Dep, scalaFull: String, scalaBin: String): Dependency = { - dep match { - case Dep.Java(dep) => - Dependency( - Artifact(dep.module.organization, dep.module.name, dep.version), - Scope.Compile) - case Dep.Scala(dep) => - Dependency(Artifact(dep.module.organization, - s"${dep.module.name}_${scalaBin}", - dep.version), - Scope.Compile) - case Dep.Point(dep) => - Dependency(Artifact(dep.module.organization, - s"${dep.module.name}_${scalaFull}", - dep.version), - Scope.Compile) - } - } -} - -sealed trait Scope -object Scope { - case object Compile extends Scope - case object Provided extends Scope - case object Runtime extends Scope - case object Test extends Scope -} - -case class Dependency( - artifact: Artifact, - scope: Scope -) - -case class License( - name: String, - url: String, - distribution: String = "repo" -) - -case class SCM( - url: String, - connection: String -) - -case class Developer( - id: String, - name: String, - url: String, - organization: Option[String] = None, - organizationUrl: Option[String] = None -) - -case class PomSettings( - description: String, - organization: String, - url: String, - licenses: Seq[License], - scm: SCM, - developers: Seq[Developer] -) diff --git a/scalalib/src/mill/scalalib/Dep.scala b/scalalib/src/mill/scalalib/Dep.scala new file mode 100644 index 00000000..aa301fcb --- /dev/null +++ b/scalalib/src/mill/scalalib/Dep.scala @@ -0,0 +1,44 @@ +package mill.scalalib +import mill.util.JsonFormatters._ +import upickle.default.{macroRW, ReadWriter => RW} +sealed trait Dep +object Dep{ + + implicit def parse(signature: String) = { + signature.split(':') match{ + case Array(a, b, c) => Dep.Java(a, b, c) + case Array(a, "", b, c) => Dep.Scala(a, b, c) + case Array(a, "", "", b, c) => Dep.Point(a, b, c) + case _ => throw new Exception(s"Unable to parse signature: [$signature]") + } + } + def apply(org: String, name: String, version: String): Dep = { + this(coursier.Dependency(coursier.Module(org, name), version)) + } + case class Java(dep: coursier.Dependency) extends Dep + object Java{ + implicit def rw: RW[Java] = macroRW + def apply(org: String, name: String, version: String): Dep = { + Java(coursier.Dependency(coursier.Module(org, name), version)) + } + } + implicit def default(dep: coursier.Dependency): Dep = new Java(dep) + def apply(dep: coursier.Dependency) = Scala(dep) + case class Scala(dep: coursier.Dependency) extends Dep + object Scala{ + implicit def rw: RW[Scala] = macroRW + def apply(org: String, name: String, version: String): Dep = { + Scala(coursier.Dependency(coursier.Module(org, name), version)) + } + } + case class Point(dep: coursier.Dependency) extends Dep + object Point{ + implicit def rw: RW[Point] = macroRW + def apply(org: String, name: String, version: String): Dep = { + Point(coursier.Dependency(coursier.Module(org, name), version)) + } + } + implicit def rw = RW.merge[Dep]( + Java.rw, Scala.rw, Point.rw + ) +} \ No newline at end of file diff --git a/scalalib/src/mill/scalalib/GenIdea.scala b/scalalib/src/mill/scalalib/GenIdea.scala new file mode 100644 index 00000000..4496c8c6 --- /dev/null +++ b/scalalib/src/mill/scalalib/GenIdea.scala @@ -0,0 +1,203 @@ +package mill.scalalib + +import ammonite.ops._ +import mill.define.{Segment, Segments, Target} +import mill.eval.{Evaluator, PathRef, RootModuleLoader} +import mill.scalalib +import mill.util.Ctx.{LoaderCtx, LogCtx} +import mill.util.{Loose, PrintLogger, Strict} +import mill.util.Strict.Agg + +object GenIdea { + + def apply()(implicit ctx: LoaderCtx with LogCtx): Unit = { + val rootModule = ctx.load(RootModuleLoader) + val pp = new scala.xml.PrettyPrinter(999, 4) + rm! pwd/".idea" + rm! pwd/".idea_modules" + + + val evaluator = new Evaluator(pwd / 'out, pwd, rootModule , ctx.log) + + for((relPath, xml) <- xmlFileLayout(evaluator, rootModule)){ + write.over(pwd/relPath, pp.format(xml)) + } + } + + def xmlFileLayout[T](evaluator: Evaluator[T], rootModule: mill.Module): Seq[(RelPath, scala.xml.Node)] = { + + + val modules = rootModule.millInternal.segmentsToModules.values.collect{case x: scalalib.ScalaModule => (x.millModuleSegments, x)}.toSeq + + val resolved = for((path, mod) <- modules) yield { + val Seq(resolvedCp: Loose.Agg[PathRef], resolvedSrcs: Loose.Agg[PathRef]) = + evaluator.evaluate(Agg(mod.externalCompileDepClasspath, mod.externalCompileDepSources)) + .values + + (path, resolvedCp.map(_.path).filter(_.ext == "jar") ++ resolvedSrcs.map(_.path), mod) + } + val moduleLabels = modules.map(_.swap).toMap + + val fixedFiles = Seq( + Tuple2(".idea"/"misc.xml", miscXmlTemplate()), + Tuple2( + ".idea"/"modules.xml", + allModulesXmlTemplate( + for((path, mod) <- modules) + yield moduleName(path) + ) + ), + Tuple2(".idea_modules"/"root.iml", rootXmlTemplate()) + ) + + val allResolved = resolved.flatMap(_._2).distinct + val minResolvedLength = allResolved.map(_.segments.length).min + val commonPrefix = allResolved.map(_.segments.take(minResolvedLength)) + .transpose + .takeWhile(_.distinct.length == 1) + .length + + val pathToLibName = allResolved + .map{p => (p, p.segments.drop(commonPrefix).mkString("_"))} + .toMap + + val libraries = allResolved.map{path => + val url = "jar://" + path + "!/" + val name = pathToLibName(path) + Tuple2(".idea"/'libraries/s"$name.xml", libraryXmlTemplate(name, url)) + } + + val moduleFiles = resolved.map{ case (path, resolvedDeps, mod) => + val Seq( + resoucesPathRefs: Loose.Agg[PathRef], + sourcesPathRef: Loose.Agg[PathRef], + generatedSourcePathRefs: Loose.Agg[PathRef], + allSourcesPathRefs: Loose.Agg[PathRef] + ) = evaluator.evaluate(Agg(mod.resources, mod.sources, mod.generatedSources, mod.allSources)).values + + val generatedSourcePaths = generatedSourcePathRefs.map(_.path) + val normalSourcePaths = (allSourcesPathRefs.map(_.path).toSet -- generatedSourcePaths.toSet).toSeq + + val paths = Evaluator.resolveDestPaths( + evaluator.workspacePath, + mod.compile.ctx.segments + ) + + val elem = moduleXmlTemplate( + Strict.Agg.from(resoucesPathRefs.map(_.path)), + Strict.Agg.from(normalSourcePaths), + Strict.Agg.from(generatedSourcePaths), + paths.out, + Strict.Agg.from(resolvedDeps.map(pathToLibName)), + Strict.Agg.from(mod.moduleDeps.map{ m => moduleName(moduleLabels(m))}.distinct) + ) + Tuple2(".idea_modules"/s"${moduleName(path)}.iml", elem) + } + fixedFiles ++ libraries ++ moduleFiles + } + + + def relify(p: Path) = { + val r = p.relativeTo(pwd/".idea_modules") + (Seq.fill(r.ups)("..") ++ r.segments).mkString("/") + } + + def moduleName(p: Segments) = p.value.foldLeft(StringBuilder.newBuilder) { + case (sb, Segment.Label(s)) if sb.isEmpty => sb.append(s) + case (sb, Segment.Cross(s)) if sb.isEmpty => sb.append(s.mkString("-")) + case (sb, Segment.Label(s)) => sb.append(".").append(s) + case (sb, Segment.Cross(s)) => sb.append("-").append(s.mkString("-")) + }.mkString.toLowerCase() + + def miscXmlTemplate() = { + + + + + + } + + def allModulesXmlTemplate(selectors: Seq[String]) = { + + + + + { + for(selector <- selectors) + yield { + val filepath = "$PROJECT_DIR$/.idea_modules/" + selector + ".iml" + val fileurl = "file://" + filepath + + } + } + + + + } + def rootXmlTemplate() = { + + + + + + + + + + } + def libraryXmlTemplate(name: String, url: String) = { + + + + + + + + } + def moduleXmlTemplate(resourcePaths: Strict.Agg[Path], + normalSourcePaths: Strict.Agg[Path], + generatedSourcePaths: Strict.Agg[Path], + outputPath: Path, + libNames: Strict.Agg[String], + depNames: Strict.Agg[String]) = { + + + + + { + for (normalSourcePath <- normalSourcePaths.toSeq.sorted) + yield + + + + } + { + for (generatedSourcePath <- generatedSourcePaths.toSeq.sorted) + yield + + + + } + { + for (resourcePath <- resourcePaths.toSeq.sorted) + yield + + + + } + + + + { + for(name <- libNames.toSeq.sorted) + yield + + } + { + for(depName <- depNames.toSeq.sorted) + yield + } + + + } +} diff --git a/scalalib/src/mill/scalalib/Lib.scala b/scalalib/src/mill/scalalib/Lib.scala new file mode 100644 index 00000000..a038a59b --- /dev/null +++ b/scalalib/src/mill/scalalib/Lib.scala @@ -0,0 +1,234 @@ +package mill +package scalalib + +import java.io.File +import java.net.URLClassLoader +import java.util.Optional + +import ammonite.ops._ +import coursier.{Cache, Fetch, MavenRepository, Repository, Resolution, Module => CoursierModule} +import mill.define.Worker +import mill.eval.{PathRef, Result} +import mill.util.{Ctx} +import mill.util.Loose.Agg +import sbt.internal.inc._ +import sbt.internal.util.{ConsoleOut, MainAppender} +import sbt.util.LogExchange +import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _} + +object CompilationResult { + implicit val jsonFormatter: upickle.default.ReadWriter[CompilationResult] = upickle.default.macroRW +} + +// analysisFile is represented by Path, so we won't break caches after file changes +case class CompilationResult(analysisFile: Path, classes: PathRef) + +object ZincWorker extends Worker[ZincWorker]{ + def make() = new ZincWorker +} +class ZincWorker{ + @volatile var scalaClassloaderCache = Option.empty[(Long, ClassLoader)] + @volatile var scalaInstanceCache = Option.empty[(Long, ScalaInstance)] +} +object Lib{ + case class MockedLookup(am: File => Optional[CompileAnalysis]) extends PerClasspathEntryLookup { + override def analysis(classpathEntry: File): Optional[CompileAnalysis] = + am(classpathEntry) + + override def definesClass(classpathEntry: File): DefinesClass = + Locate.definesClass(classpathEntry) + } + + def grepJar(classPath: Agg[Path], s: String) = { + classPath + .find(_.toString.endsWith(s)) + .getOrElse(throw new Exception("Cannot find " + s)) + .toIO + } + + def compileScala(zincWorker: ZincWorker, + scalaVersion: String, + sources: Agg[Path], + compileClasspath: Agg[Path], + compilerClasspath: Agg[Path], + pluginClasspath: Agg[Path], + compilerBridge: Path, + scalacOptions: Seq[String], + scalacPluginClasspath: Agg[Path], + javacOptions: Seq[String], + upstreamCompileOutput: Seq[CompilationResult]) + (implicit ctx: Ctx): CompilationResult = { + val compileClasspathFiles = compileClasspath.map(_.toIO).toArray + + val compilerJars = compilerClasspath.toArray.map(_.toIO) + val pluginJars = pluginClasspath.toArray.map(_.toIO) + + val compilerClassloaderSig = compilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum + val scalaInstanceSig = + compilerClassloaderSig + pluginClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum + + val compilerClassLoader = zincWorker.scalaClassloaderCache match{ + case Some((k, v)) if k == compilerClassloaderSig => v + case _ => + val classloader = new URLClassLoader(compilerJars.map(_.toURI.toURL), null) + zincWorker.scalaClassloaderCache = Some((compilerClassloaderSig, classloader)) + classloader + } + + val scalaInstance = zincWorker.scalaInstanceCache match{ + case Some((k, v)) if k == scalaInstanceSig => v + case _ => + val scalaInstance = new ScalaInstance( + version = scalaVersion, + loader = new URLClassLoader(pluginJars.map(_.toURI.toURL), compilerClassLoader), + libraryJar = grepJar(compilerClasspath, s"scala-library-$scalaVersion.jar"), + compilerJar = grepJar(compilerClasspath, s"scala-compiler-$scalaVersion.jar"), + allJars = compilerJars ++ pluginJars, + explicitActual = None + ) + zincWorker.scalaInstanceCache = Some((scalaInstanceSig, scalaInstance)) + scalaInstance + } + + mkdir(ctx.dest) + + val ic = new sbt.internal.inc.IncrementalCompilerImpl() + + val logger = { + val consoleAppender = MainAppender.defaultScreen(ConsoleOut.printStreamOut( + ctx.log.outputStream + )) + val l = LogExchange.logger("Hello") + LogExchange.unbindLoggerAppenders("Hello") + LogExchange.bindLoggerAppenders("Hello", (consoleAppender -> sbt.util.Level.Info) :: Nil) + l + } + + def analysisMap(f: File): Optional[CompileAnalysis] = { + if (f.isFile) { + Optional.empty[CompileAnalysis] + } else { + upstreamCompileOutput.collectFirst { + case CompilationResult(zincPath, classFiles) if classFiles.path.toNIO == f.toPath => + FileAnalysisStore.binary(zincPath.toIO).get().map[CompileAnalysis](_.getAnalysis) + }.getOrElse(Optional.empty[CompileAnalysis]) + } + } + + val lookup = MockedLookup(analysisMap) + + val zincFile = ctx.dest / 'zinc + val classesDir = ctx.dest / 'classes + + val zincIOFile = zincFile.toIO + val classesIODir = classesDir.toIO + + val store = FileAnalysisStore.binary(zincIOFile) + + val newResult = ic.compile( + ic.inputs( + classpath = classesIODir +: compileClasspathFiles, + sources = for{ + root <- sources.toArray + if exists(root) + path <- ls.rec(root) + if path.isFile && (path.ext == "scala" || path.ext == "java") + } yield path.toIO, + classesDirectory = classesIODir, + scalacOptions = (scalacPluginClasspath.map(jar => s"-Xplugin:${jar}") ++ scalacOptions).toArray, + javacOptions = javacOptions.toArray, + maxErrors = 10, + sourcePositionMappers = Array(), + order = CompileOrder.Mixed, + compilers = ic.compilers( + scalaInstance, + ClasspathOptionsUtil.boot, + None, + ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO) + ), + setup = ic.setup( + lookup, + skip = false, + zincIOFile, + new FreshCompilerCache, + IncOptions.of(), + new ManagedLoggedReporter(10, logger), + None, + Array() + ), + pr = { + val prev = store.get() + PreviousResult.of(prev.map(_.getAnalysis), prev.map(_.getMiniSetup)) + } + ), + logger = logger + ) + + store.set( + AnalysisContents.create( + newResult.analysis(), + newResult.setup() + ) + ) + + CompilationResult(zincFile, PathRef(classesDir)) + } + + def resolveDependencies(repositories: Seq[Repository], + scalaVersion: String, + scalaBinaryVersion: String, + deps: TraversableOnce[Dep], + sources: Boolean = false): Result[Agg[PathRef]] = { + val flattened = deps.map{ + case Dep.Java(dep) => dep + case Dep.Scala(dep) => + dep.copy(module = dep.module.copy(name = dep.module.name + "_" + scalaBinaryVersion)) + case Dep.Point(dep) => + dep.copy(module = dep.module.copy(name = dep.module.name + "_" + scalaVersion)) + }.toSet + val start = Resolution(flattened) + + val fetch = Fetch.from(repositories, Cache.fetch()) + val resolution = start.process.run(fetch).unsafePerformSync + val errs = resolution.metadataErrors + if(errs.nonEmpty) { + val header = + s"""| + |Resolution failed for ${errs.length} modules: + |-------------------------------------------- + |""".stripMargin + + val errLines = errs.map { + case ((module, vsn), errMsgs) => s" ${module.trim}:$vsn \n\t" + errMsgs.mkString("\n\t") + }.mkString("\n") + val msg = header + errLines + "\n" + Result.Failure(msg) + } else { + val sourceOrJar = + if (sources) resolution.classifiersArtifacts(Seq("sources")) + else resolution.artifacts + val localArtifacts: Seq[File] = scalaz.concurrent.Task + .gatherUnordered(sourceOrJar.map(Cache.file(_).run)) + .unsafePerformSync + .flatMap(_.toOption) + + Agg.from( + localArtifacts.map(p => PathRef(Path(p), quick = true)).filter(_.path.ext == "jar") + ) + } + } + def scalaCompilerIvyDeps(scalaVersion: String) = Agg[Dep]( + Dep.Java("org.scala-lang", "scala-compiler", scalaVersion), + Dep.Java("org.scala-lang", "scala-reflect", scalaVersion) + ) + def scalaRuntimeIvyDeps(scalaVersion: String) = Agg[Dep]( + Dep.Java("org.scala-lang", "scala-library", scalaVersion) + ) + def compilerBridgeIvyDep(scalaVersion: String) = + Dep.Point(coursier.Dependency(coursier.Module("com.lihaoyi", "mill-bridge"), "0.1", transitive = false)) + + val DefaultShellScript: Seq[String] = Seq( + "#!/usr/bin/env sh", + "exec java -jar \"$0\" \"$@\"" + ) +} diff --git a/scalalib/src/mill/scalalib/Main.scala b/scalalib/src/mill/scalalib/Main.scala new file mode 100644 index 00000000..584fe9d1 --- /dev/null +++ b/scalalib/src/mill/scalalib/Main.scala @@ -0,0 +1,7 @@ +package mill.scalalib + +object Main { + def main(args: Array[String]): Unit = { + mill.Main.main(args) + } +} diff --git a/scalalib/src/mill/scalalib/MiscModule.scala b/scalalib/src/mill/scalalib/MiscModule.scala new file mode 100644 index 00000000..502ba461 --- /dev/null +++ b/scalalib/src/mill/scalalib/MiscModule.scala @@ -0,0 +1,72 @@ +package mill +package scalalib + +import mill.define.Cross.Resolver +import mill.define.{Cross, Task} +import mill.eval.{PathRef, Result} +import mill.util.Loose.Agg + + +trait SbtModule extends ScalaModule { outer => + override def sources = T.input{ + Agg( + PathRef(basePath / 'src / 'main / 'scala), + PathRef(basePath / 'src / 'main / 'java) + ) + } + override def resources = T.input{ Agg(PathRef(basePath / 'src / 'main / 'resources)) } + trait Tests extends super.Tests { + override def basePath = outer.basePath + override def sources = T.input{ + Agg( + PathRef(basePath / 'src / 'test / 'scala), + PathRef(basePath / 'src / 'test / 'java) + ) + } + override def resources = T.input{ Agg(PathRef(basePath / 'src / 'test / 'resources)) } + } +} + +trait CrossSbtModule extends SbtModule { outer => + override def basePath = super.basePath / ammonite.ops.up + implicit def crossSbtModuleResolver: Resolver[CrossSbtModule] = new Resolver[CrossSbtModule]{ + def resolve[V <: CrossSbtModule](c: Cross[V]): V = { + crossScalaVersion.split('.') + .inits + .takeWhile(_.length > 1) + .flatMap( prefix => + c.items.map(_._2).find(_.crossScalaVersion.split('.').startsWith(prefix)) + ) + .collectFirst{case x => x} + .getOrElse( + throw new Exception( + s"Unable to find compatible cross version between $crossScalaVersion and "+ + c.items.map(_._2.crossScalaVersion).mkString(",") + ) + ) + } + } + + def crossScalaVersion: String + def scalaVersion = crossScalaVersion + override def sources = T.input{ + super.sources() ++ + crossScalaVersion.split('.').inits.filter(_.nonEmpty).map(_.mkString(".")).map{ + s => PathRef{ basePath / 'src / 'main / s"scala-$s" } + } + + } + override def resources = T.input{ Agg(PathRef(basePath / 'src / 'main / 'resources)) } + trait Tests extends super.Tests { + override def basePath = outer.basePath + override def sources = T.input{ + super.sources() ++ + crossScalaVersion.split('.').inits.filter(_.nonEmpty).map(_.mkString(".")).map{ + s => PathRef{ basePath / 'src / 'test / s"scala-$s" } + } + } + override def resources = T.input{ Agg(PathRef(basePath / 'src / 'test / 'resources)) } + } +} + + diff --git a/scalalib/src/mill/scalalib/PublishModule.scala b/scalalib/src/mill/scalalib/PublishModule.scala new file mode 100644 index 00000000..64efce77 --- /dev/null +++ b/scalalib/src/mill/scalalib/PublishModule.scala @@ -0,0 +1,70 @@ +package mill +package scalalib + +import ammonite.ops._ +import mill.eval.{PathRef, Result} +import mill.util.Loose.Agg +/** + * Configuration necessary for publishing a Scala module to Maven Central or similar + */ +trait PublishModule extends ScalaModule { outer => + import mill.scalalib.publish._ + + def pomSettings: T[PomSettings] + def publishVersion: T[String] = "0.0.1-SNAPSHOT" + + def pom = T { + val dependencies = + ivyDeps().map(Artifact.fromDep(_, scalaVersion(), scalaBinaryVersion())) + val pom = Pom(artifact(), dependencies, artifactName(), pomSettings()) + + val pomPath = T.ctx().dest / s"${artifactId()}-${publishVersion()}.pom" + write.over(pomPath, pom) + PathRef(pomPath) + } + + def ivy = T { + val dependencies = + ivyDeps().map(Artifact.fromDep(_, scalaVersion(), scalaBinaryVersion())) + val ivy = Ivy(artifact(), dependencies) + val ivyPath = T.ctx().dest / "ivy.xml" + write.over(ivyPath, ivy) + PathRef(ivyPath) + } + + def artifact: T[Artifact] = T { + Artifact(pomSettings().organization, artifactId(), publishVersion()) + } + + def publishLocal(): define.Command[Unit] = T.command { + LocalPublisher.publish( + jar = jar().path, + sourcesJar = sourcesJar().path, + docsJar = docsJar().path, + pom = pom().path, + ivy = ivy().path, + artifact = artifact() + ) + } + + def sonatypeUri: String = "https://oss.sonatype.org/service/local" + + def sonatypeSnapshotUri: String = "https://oss.sonatype.org/content/repositories/snapshots" + + def publish(credentials: String, gpgPassphrase: String): define.Command[Unit] = T.command { + val baseName = s"${artifactId()}-${publishVersion()}" + val artifacts = Seq( + jar().path -> s"${baseName}.jar", + sourcesJar().path -> s"${baseName}-sources.jar", + docsJar().path -> s"${baseName}-javadoc.jar", + pom().path -> s"${baseName}.pom" + ) + new SonatypePublisher( + sonatypeUri, + sonatypeSnapshotUri, + credentials, + gpgPassphrase, + T.ctx().log + ).publish(artifacts, artifact()) + } +} diff --git a/scalalib/src/mill/scalalib/ScalaModule.scala b/scalalib/src/mill/scalalib/ScalaModule.scala new file mode 100644 index 00000000..1b2bd28d --- /dev/null +++ b/scalalib/src/mill/scalalib/ScalaModule.scala @@ -0,0 +1,318 @@ +package mill +package scalalib + +import ammonite.ops._ +import coursier.{Cache, MavenRepository, Repository} +import mill.define.{Cross, Task} +import mill.define.TaskModule +import mill.eval.{PathRef, Result} +import mill.modules.Jvm +import mill.modules.Jvm.{createAssembly, createJar, interactiveSubprocess, subprocess, inprocess} +import Lib._ +import mill.define.Cross.Resolver +import mill.util.Loose.Agg +import sbt.testing.Status + +/** + * Core configuration required to compile a single Scala compilation target + */ +trait ScalaModule extends mill.Module with TaskModule { outer => + def defaultCommandName() = "run" + trait Tests extends TestModule{ + def scalaVersion = outer.scalaVersion() + override def moduleDeps = Seq(outer) + } + def scalaVersion: T[String] + def mainClass: T[Option[String]] = None + + def scalaBinaryVersion = T{ scalaVersion().split('.').dropRight(1).mkString(".") } + def ivyDeps = T{ Agg.empty[Dep] } + def compileIvyDeps = T{ Agg.empty[Dep] } + def scalacPluginIvyDeps = T{ Agg.empty[Dep] } + def runIvyDeps = T{ Agg.empty[Dep] } + + def scalacOptions = T{ Seq.empty[String] } + def javacOptions = T{ Seq.empty[String] } + + def repositories: Seq[Repository] = Seq( + Cache.ivy2Local, + MavenRepository("https://repo1.maven.org/maven2") + ) + + def moduleDeps = Seq.empty[ScalaModule] + def depClasspath = T{ Agg.empty[PathRef] } + + + def upstreamRunClasspath = T{ + Task.traverse(moduleDeps)(p => + T.task(p.runDepClasspath() ++ p.runClasspath()) + ) + } + + def upstreamCompileOutput = T{ + Task.traverse(moduleDeps)(_.compile) + } + def upstreamCompileClasspath = T{ + externalCompileDepClasspath() ++ + upstreamCompileOutput().map(_.classes) ++ + Task.traverse(moduleDeps)(_.compileDepClasspath)().flatten + } + + def resolveDeps(deps: Task[Agg[Dep]], sources: Boolean = false) = T.task{ + resolveDependencies( + repositories, + scalaVersion(), + scalaBinaryVersion(), + deps(), + sources + ) + } + + def externalCompileDepClasspath: T[Agg[PathRef]] = T{ + Agg.from(Task.traverse(moduleDeps)(_.externalCompileDepClasspath)().flatten) ++ + resolveDeps( + T.task{ivyDeps() ++ compileIvyDeps() ++ scalaCompilerIvyDeps(scalaVersion())} + )() + } + + def externalCompileDepSources: T[Agg[PathRef]] = T{ + Agg.from(Task.traverse(moduleDeps)(_.externalCompileDepSources)().flatten) ++ + resolveDeps( + T.task{ivyDeps() ++ compileIvyDeps() ++ scalaCompilerIvyDeps(scalaVersion())}, + sources = true + )() + } + + /** + * Things that need to be on the classpath in order for this code to compile; + * might be less than the runtime classpath + */ + def compileDepClasspath: T[Agg[PathRef]] = T{ + upstreamCompileClasspath() ++ + depClasspath() + } + + /** + * Strange compiler-bridge jar that the Zinc incremental compile needs + */ + def compilerBridge: T[PathRef] = T{ + val compilerBridgeKey = "MILL_COMPILER_BRIDGE_" + scalaVersion().replace('.', '_') + val compilerBridgePath = sys.props(compilerBridgeKey) + if (compilerBridgePath != null) PathRef(Path(compilerBridgePath), quick = true) + else { + val dep = compilerBridgeIvyDep(scalaVersion()) + val classpath = resolveDependencies( + repositories, + scalaVersion(), + scalaBinaryVersion(), + Seq(dep) + ) + classpath match { + case Result.Success(resolved) => + resolved.filter(_.path.ext != "pom").toSeq match { + case Seq(single) => PathRef(single.path, quick = true) + case Seq() => throw new Exception(dep + " resolution failed") // TODO: find out, is it possible? + case _ => throw new Exception(dep + " resolution resulted in more than one file") + } + case f: Result.Failure => throw new Exception(dep + s" resolution failed.\n + ${f.msg}") // TODO: remove, resolveDependencies will take care of this. + } + } + } + + def scalacPluginClasspath: T[Agg[PathRef]] = + resolveDeps( + T.task{scalacPluginIvyDeps()} + )() + + /** + * Classpath of the Scala Compiler & any compiler plugins + */ + def scalaCompilerClasspath: T[Agg[PathRef]] = T{ + resolveDeps( + T.task{scalaCompilerIvyDeps(scalaVersion()) ++ scalaRuntimeIvyDeps(scalaVersion())} + )() + } + + /** + * Things that need to be on the classpath in order for this code to run + */ + def runDepClasspath: T[Agg[PathRef]] = T{ + Agg.from(upstreamRunClasspath().flatten) ++ + depClasspath() ++ + resolveDeps( + T.task{ivyDeps() ++ runIvyDeps() ++ scalaRuntimeIvyDeps(scalaVersion())} + )() + } + + def prependShellScript: T[String] = T{ "" } + + def sources = T.input{ Agg(PathRef(basePath / 'src)) } + def resources = T.input{ Agg(PathRef(basePath / 'resources)) } + def generatedSources = T { Agg.empty[PathRef] } + def allSources = T{ sources() ++ generatedSources() } + def compile: T[CompilationResult] = T.persistent{ + compileScala( + ZincWorker(), + scalaVersion(), + allSources().map(_.path), + compileDepClasspath().map(_.path), + scalaCompilerClasspath().map(_.path), + scalacPluginClasspath().map(_.path), + compilerBridge().path, + scalacOptions(), + scalacPluginClasspath().map(_.path), + javacOptions(), + upstreamCompileOutput() + ) + } + def runClasspath = T{ + runDepClasspath() ++ resources() ++ Seq(compile().classes) + } + + def assembly = T{ + createAssembly( + runClasspath().map(_.path).filter(exists), + mainClass(), + prependShellScript = prependShellScript() + ) + } + + def localClasspath = T{ resources() ++ Seq(compile().classes) } + + def jar = T{ + createJar( + localClasspath().map(_.path).filter(exists), + mainClass() + ) + } + + def docsJar = T { + val outDir = T.ctx().dest + + val javadocDir = outDir / 'javadoc + mkdir(javadocDir) + + val options = { + + val files = for{ + ref <- sources() + p <- ls.rec(ref.path) + if p.isFile + } yield p.toNIO.toString + files ++ Seq("-d", javadocDir.toNIO.toString, "-usejavacp") + } + + subprocess( + "scala.tools.nsc.ScalaDoc", + compileDepClasspath().filter(_.path.ext != "pom").map(_.path), + options = options.toSeq + ) + + createJar(Agg(javadocDir))(outDir / "javadoc.jar") + } + + def sourcesJar = T { + createJar((sources() ++ resources()).map(_.path).filter(exists))(T.ctx().dest / "sources.jar") + } + + def forkArgs = T{ Seq.empty[String] } + + + def run(args: String*) = T.command { + inprocess( + mainClass().getOrElse(throw new RuntimeException("No mainClass provided!")), + runClasspath().map(_.path), + args) + } + + def forkRun(args: String*) = T.command{ + subprocess( + mainClass().getOrElse(throw new RuntimeException("No mainClass provided!")), + runClasspath().map(_.path), + forkArgs(), + args, + workingDir = ammonite.ops.pwd) + } + + def runMain(mainClass: String, args: String*) = T.command{ + subprocess( + mainClass, + runClasspath().map(_.path), + forkArgs(), + args, + workingDir = ammonite.ops.pwd + ) + } + + def console() = T.command{ + interactiveSubprocess( + mainClass = "scala.tools.nsc.MainGenericRunner", + classPath = runClasspath().map(_.path), + options = Seq("-usejavacp") + ) + } + + // publish artifact with name "mill_2.12.4" instead of "mill_2.12" + def crossFullScalaVersion: T[Boolean] = false + + def artifactName: T[String] = basePath.last.toString + def artifactScalaVersion: T[String] = T { + if (crossFullScalaVersion()) scalaVersion() + else scalaBinaryVersion() + } + + def artifactId: T[String] = T { s"${artifactName()}_${artifactScalaVersion()}" } + +} + + +object TestModule{ + def handleResults(doneMsg: String, results: Seq[TestRunner.Result]) = { + if (results.count(Set(Status.Error, Status.Failure)) == 0) Result.Success((doneMsg, results)) + else { + val grouped = results.map(_.status).groupBy(x => x).mapValues(_.length).filter(_._2 != 0).toList.sorted + + Result.Failure(grouped.map{case (k, v) => k + ": " + v}.mkString(",")) + } + } +} +trait TestModule extends ScalaModule with TaskModule { + override def defaultCommandName() = "test" + def testFramework: T[String] + + def forkWorkingDir = ammonite.ops.pwd + + def forkTest(args: String*) = T.command{ + mkdir(T.ctx().dest) + val outputPath = T.ctx().dest/"out.json" + + Jvm.subprocess( + mainClass = "mill.scalalib.TestRunner", + classPath = Jvm.gatherClassloaderJars(), + jvmOptions = forkArgs(), + options = Seq( + testFramework(), + runClasspath().map(_.path).mkString(" "), + Seq(compile().classes.path).mkString(" "), + args.mkString(" "), + outputPath.toString, + T.ctx().log.colored.toString + ), + workingDir = forkWorkingDir + ) + + val jsonOutput = upickle.json.read(outputPath.toIO) + val (doneMsg, results) = upickle.default.readJs[(String, Seq[TestRunner.Result])](jsonOutput) + TestModule.handleResults(doneMsg, results) + + } + def test(args: String*) = T.command{ + val (doneMsg, results) = TestRunner( + testFramework(), + runClasspath().map(_.path), + Agg(compile().classes.path), + args + ) + TestModule.handleResults(doneMsg, results) + } +} \ No newline at end of file diff --git a/scalalib/src/mill/scalalib/TestRunner.scala b/scalalib/src/mill/scalalib/TestRunner.scala new file mode 100644 index 00000000..01726022 --- /dev/null +++ b/scalalib/src/mill/scalalib/TestRunner.scala @@ -0,0 +1,172 @@ +package mill.scalalib + +import java.io.FileInputStream +import java.lang.annotation.Annotation +import java.net.URLClassLoader +import java.util.zip.ZipInputStream + +import ammonite.ops.{Path, ls, pwd} +import ammonite.util.Colors +import mill.modules.Jvm +import mill.util.Ctx.LogCtx +import mill.util.{PrintLogger} +import mill.util.Loose.Agg +import sbt.testing._ +import upickle.Js +import mill.util.JsonFormatters._ + +import scala.collection.mutable + +object TestRunner { + def listClassFiles(base: Path): Iterator[String] = { + if (base.isDir) ls.rec(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 runTests(cl: ClassLoader, framework: Framework, classpath: Agg[Path]) = { + + + val fingerprints = framework.fingerprints() + val testClasses = classpath.flatMap { base => + listClassFiles(base).flatMap { path => + val cls = cl.loadClass(path.stripSuffix(".class").replace('/', '.')) + fingerprints.find { + case f: SubclassFingerprint => + + (f.isModule == cls.getName.endsWith("$")) && + cl.loadClass(f.superclassName()).isAssignableFrom(cls) + case f: AnnotatedFingerprint => + (f.isModule == cls.getName.endsWith("$")) && + cls.isAnnotationPresent( + cl.loadClass(f.annotationName()).asInstanceOf[Class[Annotation]] + ) + }.map { f => (cls, f) } + } + } + testClasses + } + def main(args: Array[String]): Unit = { + try{ + val result = apply( + frameworkName = args(0), + entireClasspath = Agg.from(args(1).split(" ").map(Path(_))), + testClassfilePath = Agg.from(args(2).split(" ").map(Path(_))), + args = args(3) match{ case "" => Nil case x => x.split(" ").toList } + )(new PrintLogger( + args(5) == "true", + if(args(5) == "true") Colors.Default + else Colors.BlackWhite, + System.out, + System.err, + System.err + )) + val outputPath = args(4) + + ammonite.ops.write(Path(outputPath), upickle.default.write(result)) + }catch{case e: Throwable => + println(e) + e.printStackTrace() + } + // Tests are over, kill the JVM whether or not anyone's threads are still running + // Always return 0, even if tests fail. The caller can pick up the detailed test + // results from the outputPath + System.exit(0) + } + def apply(frameworkName: String, + entireClasspath: Agg[Path], + testClassfilePath: Agg[Path], + args: Seq[String]) + (implicit ctx: LogCtx): (String, Seq[Result]) = { + Jvm.inprocess(entireClasspath, classLoaderOverrideSbtTesting = true, cl => { + val framework = cl.loadClass(frameworkName) + .newInstance() + .asInstanceOf[sbt.testing.Framework] + + val testClasses = runTests(cl, framework, testClassfilePath) + + val runner = framework.runner(args.toArray, args.toArray, cl) + + val tasks = runner.tasks( + for ((cls, fingerprint) <- testClasses.toArray) + yield new TaskDef(cls.getName.stripSuffix("$"), fingerprint, true, Array(new SuiteSelector)) + ) + val events = mutable.Buffer.empty[Event] + for (t <- tasks) { + t.execute( + new EventHandler { + def handle(event: Event) = events.append(event) + }, + Array( + new Logger { + def debug(msg: String) = ctx.log.info(msg) + + def error(msg: String) = ctx.log.error(msg) + + def ansiCodesSupported() = true + + def warn(msg: String) = ctx.log.info(msg) + + def trace(t: Throwable) = t.printStackTrace(ctx.log.outputStream) + + def info(msg: String) = ctx.log.info(msg) + }) + ) + } + val doneMsg = runner.done() + val results = for(e <- events) yield { + val ex = if (e.throwable().isDefined) Some(e.throwable().get) else None + Result( + e.fullyQualifiedName(), + e.selector() match{ + case s: NestedSuiteSelector => s.suiteId() + case s: NestedTestSelector => s.suiteId() + "." + s.testName() + case s: SuiteSelector => s.toString + case s: TestSelector => s.testName() + case s: TestWildcardSelector => s.testWildcard() + }, + e.duration(), + e.status(), + ex.map(_.getClass.getName), + ex.map(_.getMessage), + ex.map(_.getStackTrace) + ) + } + (doneMsg, results) + }) + } + + case class Result(fullyQualifiedName: String, + selector: String, + duration: Long, + status: Status, + exceptionName: Option[String], + exceptionMsg: Option[String], + exceptionTrace: Option[Seq[StackTraceElement]]) + + object Result{ + implicit def resultRW: upickle.default.ReadWriter[Result] = upickle.default.macroRW[Result] + implicit def statusRW: upickle.default.ReadWriter[Status] = upickle.default.ReadWriter[Status]( + { + case Status.Success => Js.Str("Success") + case Status.Error => Js.Str("Error") + case Status.Failure => Js.Str("Failure") + case Status.Skipped => Js.Str("Skipped") + case Status.Ignored => Js.Str("Ignored") + case Status.Canceled => Js.Str("Canceled") + case Status.Pending => Js.Str("Pending") + }, + { + case Js.Str("Success") => Status.Success + case Js.Str("Error") => Status.Error + case Js.Str("Failure") => Status.Failure + case Js.Str("Skipped") => Status.Skipped + case Js.Str("Ignored") => Status.Ignored + case Js.Str("Canceled") => Status.Canceled + case Js.Str("Pending") => Status.Pending + } + ) + } + +} diff --git a/scalalib/src/mill/scalalib/package.scala b/scalalib/src/mill/scalalib/package.scala new file mode 100644 index 00000000..5a282e82 --- /dev/null +++ b/scalalib/src/mill/scalalib/package.scala @@ -0,0 +1,12 @@ +package mill + +package object scalalib { + implicit class DepSyntax(ctx: StringContext){ + def ivy(args: Any*) = Dep.parse{ + ( + ctx.parts.take(args.length).zip(args).flatMap{case (p, a) => Seq(p, a)} ++ + ctx.parts.drop(args.length) + ).mkString + } + } +} diff --git a/scalalib/src/mill/scalalib/publish/Ivy.scala b/scalalib/src/mill/scalalib/publish/Ivy.scala new file mode 100644 index 00000000..b0b6443e --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/Ivy.scala @@ -0,0 +1,55 @@ +package mill.scalalib.publish + +import mill.util.Loose.Agg + +import scala.xml.PrettyPrinter + +object Ivy { + + val head = "\n" + + def apply( + artifact: Artifact, + dependencies: Agg[Dependency] + ): String = { + val xml = + + + + + + + + + + + + + + + + + + + + {dependencies.map(renderDependency)} + + + val pp = new PrettyPrinter(120, 4) + head + pp.format(xml).replaceAll(">", ">") + } + + private def renderDependency(dep: Dependency) = { + val scope = scopeToConf(dep.scope) + default(compile)"}> + } + + private def scopeToConf(s: Scope): String = s match { + case Scope.Compile => "compile" + case Scope.Provided => "provided" + case Scope.Test => "test" + case Scope.Runtime => "runtime" + } + +} diff --git a/scalalib/src/mill/scalalib/publish/JsonFormatters.scala b/scalalib/src/mill/scalalib/publish/JsonFormatters.scala new file mode 100644 index 00000000..cf1af557 --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/JsonFormatters.scala @@ -0,0 +1,11 @@ +package mill.scalalib.publish + +import upickle.default.{ReadWriter => RW} + +trait JsonFormatters { + implicit lazy val artifactFormat: RW[Artifact] = upickle.default.macroRW + implicit lazy val developerFormat: RW[Developer] = upickle.default.macroRW + implicit lazy val licenseFormat: RW[License] = upickle.default.macroRW + implicit lazy val scmFormat: RW[SCM] = upickle.default.macroRW + implicit lazy val pomSettingsFormat: RW[PomSettings] = upickle.default.macroRW +} diff --git a/scalalib/src/mill/scalalib/publish/LocalPublisher.scala b/scalalib/src/mill/scalalib/publish/LocalPublisher.scala new file mode 100644 index 00000000..a9957e5c --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/LocalPublisher.scala @@ -0,0 +1,33 @@ +package mill.scalalib.publish + +import ammonite.ops._ + +object LocalPublisher { + + private val root: Path = home / ".ivy2" / "local" + + def publish(jar: Path, + sourcesJar: Path, + docsJar: Path, + pom: Path, + ivy: Path, + artifact: Artifact): Unit = { + val releaseDir = root / artifact.group / artifact.id / artifact.version + writeFiles( + jar -> releaseDir / "jars" / s"${artifact.id}.jar", + sourcesJar -> releaseDir / "srcs" / s"${artifact.id}-sources.jar", + docsJar -> releaseDir / "docs" / s"${artifact.id}-javadoc.jar", + pom -> releaseDir / "poms" / s"${artifact.id}.pom", + ivy -> releaseDir / "ivys" / "ivy.xml" + ) + } + + private def writeFiles(fromTo: (Path, Path)*): Unit = { + fromTo.foreach { + case (from, to) => + mkdir(to / up) + cp.over(from, to) + } + } + +} diff --git a/scalalib/src/mill/scalalib/publish/Pom.scala b/scalalib/src/mill/scalalib/publish/Pom.scala new file mode 100644 index 00000000..74dc6e8f --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/Pom.scala @@ -0,0 +1,90 @@ +package mill.scalalib.publish + +import mill.util.Loose.Agg + +import scala.xml.{Elem, NodeSeq, PrettyPrinter} + +object Pom { + + val head = "\n" + + //TODO - not only jar packaging support? + def apply(artifact: Artifact, + dependencies: Agg[Dependency], + name: String, + pomSettings: PomSettings): String = { + val xml = + + + 4.0.0 + {name} + {artifact.group} + {artifact.id} + jar + {pomSettings.description} + + {artifact.version} + {pomSettings.url} + + {pomSettings.licenses.map(renderLicense)} + + + {pomSettings.scm.url} + {pomSettings.scm.connection} + + + {pomSettings.developers.map(renderDeveloper)} + + + {dependencies.map(renderDependency)} + + + + val pp = new PrettyPrinter(120, 4) + head + pp.format(xml) + } + + private def renderLicense(l: License): Elem = { + + {l.name} + {l.url} + {l.distribution} + + } + + private def renderDeveloper(d: Developer): Elem = { + + {d.id} + {d.name} + { + d.organization.map { org => + {org} + }.getOrElse(NodeSeq.Empty) + } + { + d.organizationUrl.map { orgUrl => + {orgUrl} + }.getOrElse(NodeSeq.Empty) + } + + } + + private def renderDependency(d: Dependency): Elem = { + val scope = d.scope match { + case Scope.Compile => NodeSeq.Empty + case Scope.Provided => provided + case Scope.Test => test + case Scope.Runtime => runtime + } + + {d.artifact.group} + {d.artifact.id} + {d.artifact.version} + {scope} + + } + +} diff --git a/scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala b/scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala new file mode 100644 index 00000000..8ccdf3ea --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/SonatypeHttpApi.scala @@ -0,0 +1,130 @@ +package mill.scalalib.publish + +import java.util.Base64 + +import upickle.json + +import scala.concurrent.duration._ +import scalaj.http.{BaseHttp, HttpOptions, HttpRequest, HttpResponse} + +object PatientHttp + extends BaseHttp( + options = Seq( + HttpOptions.connTimeout(5.seconds.toMillis.toInt), + HttpOptions.readTimeout(1.minute.toMillis.toInt), + HttpOptions.followRedirects(false) + ) + ) + +class SonatypeHttpApi(uri: String, credentials: String) { + + private val base64Creds = base64(credentials) + + private val commonHeaders = Seq( + "Authorization" -> s"Basic ${base64Creds}", + "Accept" -> "application/json", + "Content-Type" -> "application/json" + ) + + // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles.html + def getStagingProfileUri(groupId: String): String = { + val response = withRetry( + PatientHttp(s"${uri}/staging/profiles").headers(commonHeaders)) + + val resourceUri = + json + .read(response.body)("data") + .arr + .find(profile => profile("name").str == groupId) + .map(_("resourceURI").str.toString) + + resourceUri.getOrElse( + throw new RuntimeException( + s"Could not find staging profile for groupId: ${groupId}") + ) + } + + def getStagingRepoState(stagingRepoId: String): String = { + val response = PatientHttp(s"${uri}/staging/repository/${stagingRepoId}") + .option(HttpOptions.readTimeout(60000)) + .headers(commonHeaders) + .asString + + json.read(response.body)("type").str.toString + } + + // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_start.html + def createStagingRepo(profileUri: String, groupId: String): String = { + val response = withRetry(PatientHttp(s"${profileUri}/start") + .headers(commonHeaders) + .postData( + s"""{"data": {"description": "fresh staging profile for ${groupId}"}}""")) + + json.read(response.body)("data")("stagedRepositoryId").str.toString + } + + // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_finish.html + def closeStagingRepo(profileUri: String, repositoryId: String): Boolean = { + val response = withRetry( + PatientHttp(s"${profileUri}/finish") + .headers(commonHeaders) + .postData( + s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "closing staging repository"}}""" + )) + + response.code == 201 + } + + // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_promote.html + def promoteStagingRepo(profileUri: String, repositoryId: String): Boolean = { + val response = withRetry( + PatientHttp(s"${profileUri}/promote") + .headers(commonHeaders) + .postData( + s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "promote staging repository"}}""" + )) + + response.code == 201 + } + + // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_drop.html + def dropStagingRepo(profileUri: String, repositoryId: String): Boolean = { + val response = withRetry( + PatientHttp(s"${profileUri}/drop") + .headers(commonHeaders) + .postData( + s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "drop staging repository"}}""" + )) + + response.code == 201 + } + + private val uploadTimeout = 5.minutes.toMillis.toInt + + def upload(uri: String, data: Array[Byte]): HttpResponse[String] = { + PatientHttp(uri) + .option(HttpOptions.readTimeout(uploadTimeout)) + .method("PUT") + .headers( + "Content-Type" -> "application/binary", + "Authorization" -> s"Basic ${base64Creds}" + ) + .put(data) + .asString + } + + private def withRetry(request: HttpRequest, + retries: Int = 10): HttpResponse[String] = { + val resp = request.asString + if (resp.is5xx && retries > 0) { + Thread.sleep(500) + withRetry(request, retries - 1) + } else { + resp + } + } + + private def base64(s: String) = + new String(Base64.getEncoder.encode(s.getBytes)) + +} diff --git a/scalalib/src/mill/scalalib/publish/SonatypePublisher.scala b/scalalib/src/mill/scalalib/publish/SonatypePublisher.scala new file mode 100644 index 00000000..0749b0c5 --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/SonatypePublisher.scala @@ -0,0 +1,148 @@ +package mill.scalalib.publish + +import java.math.BigInteger +import java.security.MessageDigest + +import ammonite.ops._ +import mill.util.Logger + +import scalaj.http.HttpResponse + +class SonatypePublisher(uri: String, + snapshotUri: String, + credentials: String, + gpgPassphrase: String, + log: Logger) { + + private val api = new SonatypeHttpApi(uri, credentials) + + def publish(artifacts: Seq[(Path, String)], artifact: Artifact): Unit = { + val signedArtifacts = artifacts ++ artifacts.map { + case (file, name) => + poorMansSign(file, gpgPassphrase) -> s"${name}.asc" + } + + val signedArtifactsWithDigest = signedArtifacts.flatMap { + case (file, name) => + val content = read.bytes(file) + + Seq( + name -> content, + (name + ".md5") -> md5hex(content), + (name + ".sha1") -> sha1hex(content) + ) + } + + val publishPath = Seq( + artifact.group.replace(".", "/"), + artifact.id, + artifact.version + ).mkString("/") + + if (artifact.isSnapshot) + publishSnapshot(publishPath, signedArtifactsWithDigest, artifact) + else + publishRelease(publishPath, signedArtifactsWithDigest, artifact) + } + + private def publishSnapshot(publishPath: String, + payloads: Seq[(String, Array[Byte])], + artifact: Artifact): Unit = { + val baseUri: String = snapshotUri + "/" + publishPath + + val publishResults = payloads.map { + case (fileName, data) => + log.info(s"Uploading ${fileName}") + val resp = api.upload(s"${baseUri}/${fileName}", data) + resp + } + reportPublishResults(publishResults, artifact) + } + + private def publishRelease(publishPath: String, + payloads: Seq[(String, Array[Byte])], + artifact: Artifact): Unit = { + val profileUri = api.getStagingProfileUri(artifact.group) + val stagingRepoId = + api.createStagingRepo(profileUri, artifact.group) + val baseUri = + s"${uri}/staging/deployByRepositoryId/${stagingRepoId}/${publishPath}" + + val publishResults = payloads.map { + case (fileName, data) => + log.info(s"Uploading ${fileName}") + api.upload(s"${baseUri}/${fileName}", data) + } + reportPublishResults(publishResults, artifact) + + log.info("Closing staging repository") + api.closeStagingRepo(profileUri, stagingRepoId) + + log.info("Waiting for staging repository to close") + awaitRepoStatus("closed", stagingRepoId) + + log.info("Promoting staging repository") + api.promoteStagingRepo(profileUri, stagingRepoId) + + log.info("Waiting for staging repository to release") + awaitRepoStatus("released", stagingRepoId) + + log.info("Dropping staging repository") + api.dropStagingRepo(profileUri, stagingRepoId) + + log.info(s"Published ${artifact.id} successfully") + } + + private def reportPublishResults(publishResults: Seq[HttpResponse[String]], + artifact: Artifact) = { + if (publishResults.forall(_.is2xx)) { + log.info(s"Published ${artifact.id} to Sonatype") + } else { + val errors = publishResults.filterNot(_.is2xx).map { response => + s"Code: ${response.code}, message: ${response.body}" + } + throw new RuntimeException( + s"Failed to publish ${artifact.id} to Sonatype. Errors: \n${errors.mkString("\n")}" + ) + } + } + + private def awaitRepoStatus(status: String, + stagingRepoId: String, + attempts: Int = 20): Unit = { + def isRightStatus = + api.getStagingRepoState(stagingRepoId).equalsIgnoreCase(status) + var attemptsLeft = attempts + + while (attemptsLeft > 0 && !isRightStatus) { + Thread.sleep(3000) + attemptsLeft -= 1 + if (attemptsLeft == 0) { + throw new RuntimeException( + s"Couldn't wait for staging repository to be ${status}. Failing") + } + } + } + + // http://central.sonatype.org/pages/working-with-pgp-signatures.html#signing-a-file + private def poorMansSign(file: Path, passphrase: String): Path = { + val fileName = file.toString + import ammonite.ops.ImplicitWd._ + %("gpg", "--yes", "-a", "-b", "--passphrase", passphrase, fileName) + Path(fileName + ".asc") + } + + private def md5hex(bytes: Array[Byte]): Array[Byte] = + hexArray(md5.digest(bytes)).getBytes + + private def sha1hex(bytes: Array[Byte]): Array[Byte] = + hexArray(sha1.digest(bytes)).getBytes + + private def md5 = MessageDigest.getInstance("md5") + + private def sha1 = MessageDigest.getInstance("sha1") + + private def hexArray(arr: Array[Byte]) = + String.format("%0" + (arr.length << 1) + "x", new BigInteger(1, arr)) + +} diff --git a/scalalib/src/mill/scalalib/publish/package.scala b/scalalib/src/mill/scalalib/publish/package.scala new file mode 100644 index 00000000..99eeec14 --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/package.scala @@ -0,0 +1,3 @@ +package mill.scalalib + +package object publish extends JsonFormatters diff --git a/scalalib/src/mill/scalalib/publish/settings.scala b/scalalib/src/mill/scalalib/publish/settings.scala new file mode 100644 index 00000000..eb0a44b6 --- /dev/null +++ b/scalalib/src/mill/scalalib/publish/settings.scala @@ -0,0 +1,70 @@ +package mill.scalalib.publish + +import mill.scalalib.Dep + +case class Artifact(group: String, id: String, version: String) { + def isSnapshot: Boolean = version.endsWith("-SNAPSHOT") +} + +object Artifact { + + def fromDep(dep: Dep, scalaFull: String, scalaBin: String): Dependency = { + dep match { + case Dep.Java(dep) => + Dependency( + Artifact(dep.module.organization, dep.module.name, dep.version), + Scope.Compile) + case Dep.Scala(dep) => + Dependency(Artifact(dep.module.organization, + s"${dep.module.name}_${scalaBin}", + dep.version), + Scope.Compile) + case Dep.Point(dep) => + Dependency(Artifact(dep.module.organization, + s"${dep.module.name}_${scalaFull}", + dep.version), + Scope.Compile) + } + } +} + +sealed trait Scope +object Scope { + case object Compile extends Scope + case object Provided extends Scope + case object Runtime extends Scope + case object Test extends Scope +} + +case class Dependency( + artifact: Artifact, + scope: Scope +) + +case class License( + name: String, + url: String, + distribution: String = "repo" +) + +case class SCM( + url: String, + connection: String +) + +case class Developer( + id: String, + name: String, + url: String, + organization: Option[String] = None, + organizationUrl: Option[String] = None +) + +case class PomSettings( + description: String, + organization: String, + url: String, + licenses: Seq[License], + scm: SCM, + developers: Seq[Developer] +) diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml deleted file mode 100644 index a6398d46..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml deleted file mode 100644 index 5e21f3f3..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml deleted file mode 100644 index fe9d533a..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml deleted file mode 100644 index ec0460d7..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml deleted file mode 100644 index a1278e29..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml deleted file mode 100644 index bbd957f0..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml deleted file mode 100644 index 09db75bb..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml b/scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml deleted file mode 100644 index f23b80f6..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/misc.xml b/scalalib/src/test/resources/gen-idea/idea/misc.xml deleted file mode 100644 index 2726692f..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/misc.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea/modules.xml b/scalalib/src/test/resources/gen-idea/idea/modules.xml deleted file mode 100644 index 374b9a1f..00000000 --- a/scalalib/src/test/resources/gen-idea/idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea_modules/iml b/scalalib/src/test/resources/gen-idea/idea_modules/iml deleted file mode 100644 index 548865f1..00000000 --- a/scalalib/src/test/resources/gen-idea/idea_modules/iml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/gen-idea/idea_modules/root.iml b/scalalib/src/test/resources/gen-idea/idea_modules/root.iml deleted file mode 100644 index dcf5f7fc..00000000 --- a/scalalib/src/test/resources/gen-idea/idea_modules/root.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/scalalib/src/test/resources/hello-world/src/main/scala/Main.scala b/scalalib/src/test/resources/hello-world/src/main/scala/Main.scala deleted file mode 100644 index 14139d6f..00000000 --- a/scalalib/src/test/resources/hello-world/src/main/scala/Main.scala +++ /dev/null @@ -1,12 +0,0 @@ -import scala.collection._ // unused import to check unused imports warning -import java.nio.file.{Files, Paths} - -object Main extends App { - - val person = Person.fromString("rockjam:25") - val greeting = s"hello ${person.name}, your age is: ${person.age}" - println(greeting) - val resultPath = Paths.get(args(0)) - Files.createDirectories(resultPath.getParent) - Files.write(resultPath, greeting.getBytes) -} diff --git a/scalalib/src/test/resources/hello-world/src/main/scala/Result.scala b/scalalib/src/test/resources/hello-world/src/main/scala/Result.scala deleted file mode 100644 index d7d29a51..00000000 --- a/scalalib/src/test/resources/hello-world/src/main/scala/Result.scala +++ /dev/null @@ -1,7 +0,0 @@ -object Person { - def fromString(s: String): Person = { - val Array(name, age) = s.split(":") - Person(name, age.toInt) - } -} -case class Person(name: String, age: Int) diff --git a/scalalib/src/test/scala/mill/scalalib/GenIdeaTests.scala b/scalalib/src/test/scala/mill/scalalib/GenIdeaTests.scala deleted file mode 100644 index 36945f65..00000000 --- a/scalalib/src/test/scala/mill/scalalib/GenIdeaTests.scala +++ /dev/null @@ -1,78 +0,0 @@ -package mill.scalalib - -import ammonite.ops._ -import mill._ - -import mill.util.{TestEvaluator, TestUtil} -import utest._ - -object GenIdeaTests extends TestSuite { - - val basePath = pwd / 'target / 'workspace / "gen-idea" - val outPath = basePath / 'out - val workingSrcPath = basePath / 'src - - trait HelloWorldModule extends scalalib.ScalaModule { - def scalaVersion = "2.12.4" - def basePath = HelloWorldTests.workingSrcPath - } - - object HelloWorld extends TestUtil.BaseModule with HelloWorldModule - - val helloWorldEvaluator = new TestEvaluator( - HelloWorld, - outPath, - workingSrcPath - ) - - def tests: Tests = Tests { - 'genIdeaTests - { - helloWorldEvaluator(HelloWorld.scalaVersion) - val x = GenIdea.xmlFileLayout(helloWorldEvaluator.evaluator, HelloWorld) - val pp = new scala.xml.PrettyPrinter(999, 4) - - for((relPath, xml) <- GenIdea.xmlFileLayout(helloWorldEvaluator.evaluator, HelloWorld)){ - write.over(basePath/ "generated"/ relPath, pp.format(xml)) - } - - Seq( - "gen-idea/idea_modules/iml" -> - basePath / "generated" / ".idea_modules" /".iml", - "gen-idea/idea_modules/root.iml" -> - basePath / "generated" / ".idea_modules" /"root.iml", - "gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml", - "gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml", - "gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "scala-library_2.12.4_scala-library-2.12.4.jar.xml", - "gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml", - "gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml", - "gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml", - "gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml", - "gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml" -> - basePath / "generated" / ".idea" / "libraries" / "scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml", - "gen-idea/idea/modules.xml" -> - basePath / "generated" / ".idea" / "modules.xml", - "gen-idea/idea/misc.xml" -> - basePath / "generated" / ".idea" / "misc.xml", - ).foreach { case (resource, generated) => - println("checking "+resource) - val resourceString = scala.io.Source.fromResource(resource).getLines().mkString("\n") - val generatedString = normaliseLibraryPaths(read! generated) - - assert(resourceString == generatedString) - } - } - } - - - private val libPathRegex = """([\w/]+)/.coursier""".r - private def normaliseLibraryPaths(in: String): String = { - libPathRegex.replaceAllIn(in, "COURSIER_HOME") - } -} diff --git a/scalalib/src/test/scala/mill/scalalib/HelloWorldTests.scala b/scalalib/src/test/scala/mill/scalalib/HelloWorldTests.scala deleted file mode 100644 index 7444604b..00000000 --- a/scalalib/src/test/scala/mill/scalalib/HelloWorldTests.scala +++ /dev/null @@ -1,400 +0,0 @@ -package mill.scalalib - -import java.util.jar.JarFile - -import ammonite.ops._ -import ammonite.ops.ImplicitWd._ -import mill._ -import mill.define.Target -import mill.eval.{Evaluator, Result} -import mill.scalalib.publish._ -import mill.util.{TestEvaluator, TestUtil} -import sbt.internal.inc.CompileFailed -import utest._ - -import scala.collection.JavaConverters._ - - -object HelloWorldTests extends TestSuite { - trait HelloWorldModule extends scalalib.ScalaModule { - def scalaVersion = "2.12.4" - def basePath = HelloWorldTests.workingSrcPath - } - - object HelloWorld extends TestUtil.BaseModule with HelloWorldModule - object CrossHelloWorld extends TestUtil.BaseModule { - object cross extends Cross[HelloWorldCross]("2.10.6", "2.11.11", "2.12.3", "2.12.4") - class HelloWorldCross(v: String) extends HelloWorldModule { - def scalaVersion = v - } - } - - object HelloWorldWithMain extends TestUtil.BaseModule with HelloWorldModule { - def mainClass = Some("Main") - } - - object HelloWorldWithMainAssembly extends TestUtil.BaseModule with HelloWorldModule { - def mainClass = Some("Main") - def assembly = T{ - mill.modules.Jvm.createAssembly( - runClasspath().map(_.path).filter(exists), - prependShellScript = prependShellScript(), - mainClass = mainClass() - ) - } - } - - object HelloWorldWarnUnused extends TestUtil.BaseModule with HelloWorldModule { - def scalacOptions = T(Seq("-Ywarn-unused")) - } - - object HelloWorldFatalWarnings extends TestUtil.BaseModule with HelloWorldModule { - def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) - } - - object HelloWorldWithPublish extends TestUtil.BaseModule with HelloWorldModule with PublishModule { - def artifactName = "hello-world" - def publishVersion = "0.0.1" - - def pomSettings = PomSettings( - organization = "com.lihaoyi", - description = "hello world ready for real world publishing", - url = "https://github.com/lihaoyi/hello-world-publish", - licenses = Seq( - License("Apache License, Version 2.0", - "http://www.apache.org/licenses/LICENSE-2.0")), - scm = SCM( - "https://github.com/lihaoyi/hello-world-publish", - "scm:git:https://github.com/lihaoyi/hello-world-publish" - ), - developers = - Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi")) - ) - } - object HelloWorldScalaOverride extends TestUtil.BaseModule with HelloWorldModule { - override def scalaVersion: Target[String] = "2.11.11" - } - val srcPath = pwd / 'scalalib / 'src / 'test / 'resources / "hello-world" - val basePath = pwd / 'target / 'workspace / "hello-world" - val workingSrcPath = basePath / 'src - val outPath = basePath / 'out - val mainObject = workingSrcPath / 'src / 'main / 'scala / "Main.scala" - - - - - val helloWorldEvaluator = new TestEvaluator( - HelloWorld, - outPath, - workingSrcPath - ) - val helloWorldWithMainEvaluator = new TestEvaluator( - HelloWorldWithMain, - outPath, - workingSrcPath - ) - val helloWorldWithMainAssemblyEvaluator = new TestEvaluator( - HelloWorldWithMainAssembly, - outPath, - workingSrcPath - ) - val helloWorldFatalEvaluator = new TestEvaluator( - HelloWorldFatalWarnings, - outPath, - workingSrcPath - ) - val helloWorldOverrideEvaluator = new TestEvaluator( - HelloWorldScalaOverride, - outPath, - workingSrcPath - ) - val helloWorldCrossEvaluator = new TestEvaluator( - CrossHelloWorld, - outPath, - workingSrcPath - ) - - - def tests: Tests = Tests { - prepareWorkspace() - 'scalaVersion - { - 'fromBuild - { - val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.scalaVersion) - - assert( - result == "2.12.4", - evalCount > 0 - ) - } - 'override - { - val Right((result, evalCount)) = helloWorldOverrideEvaluator(HelloWorldScalaOverride.scalaVersion) - - assert( - result == "2.11.11", - evalCount > 0 - ) - } - } - 'scalacOptions - { - 'emptyByDefault - { - val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.scalacOptions) - - assert( - result.isEmpty, - evalCount > 0 - ) - } - 'override - { - val Right((result, evalCount)) = helloWorldFatalEvaluator(HelloWorldFatalWarnings.scalacOptions) - - assert( - result == Seq("-Ywarn-unused", "-Xfatal-warnings"), - evalCount > 0 - ) - } - } - 'compile - { - 'fromScratch - { - val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.compile) - - val analysisFile = result.analysisFile - val outputFiles = ls.rec(result.classes.path) - val expectedClassfiles = compileClassfiles.map(outPath / 'compile / 'dest / 'classes / _) - assert( - result.classes.path == outPath / 'compile / 'dest / 'classes, - exists(analysisFile), - outputFiles.nonEmpty, - outputFiles.forall(expectedClassfiles.contains), - evalCount > 0 - ) - - // don't recompile if nothing changed - val Right((_, unchangedEvalCount)) = helloWorldEvaluator(HelloWorld.compile) - assert(unchangedEvalCount == 0) - } - 'recompileOnChange - { - val Right((_, freshCount)) = helloWorldEvaluator(HelloWorld.compile) - assert(freshCount > 0) - - write.append(mainObject, "\n") - - val Right((_, incCompileCount)) = helloWorldEvaluator(HelloWorld.compile) - assert(incCompileCount > 0, incCompileCount < freshCount) - } - 'failOnError - { - write.append(mainObject, "val x: ") - - val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.compile) - - assert(err.isInstanceOf[CompileFailed]) - - val paths = Evaluator.resolveDestPaths( - outPath, - HelloWorld.compile.ctx.segments - ) - - assert( - ls.rec(paths.dest / 'classes).isEmpty, - !exists(paths.meta) - ) - // Works when fixed - write.over(mainObject, read(mainObject).dropRight("val x: ".length)) - - val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.compile) - } - 'passScalacOptions - { - // compilation fails because of "-Xfatal-warnings" flag - val Left(Result.Exception(err, _)) = helloWorldFatalEvaluator(HelloWorldFatalWarnings.compile) - - assert(err.isInstanceOf[CompileFailed]) - } - } - 'runMain - { - 'runMainObject - { - val runResult = basePath / 'out / 'runMain / 'dest / "hello-mill" - - val Right((_, evalCount)) = helloWorldEvaluator(HelloWorld.runMain("Main", runResult.toString)) - assert(evalCount > 0) - - assert( - exists(runResult), - read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'runCross{ - def cross(v: String) { - - val runResult = basePath / 'out / 'cross / v / 'runMain / 'dest / "hello-mill" - - val Right((_, evalCount)) = helloWorldCrossEvaluator( - CrossHelloWorld.cross(v).runMain("Main", runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - exists(runResult), - read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'v210 - cross("2.10.6") - 'v211 - cross("2.11.11") - 'v2123 - cross("2.12.3") - 'v2124 - cross("2.12.4") - } - - - 'notRunInvalidMainObject - { - val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.runMain("Invalid")) - - assert( - err.isInstanceOf[InteractiveShelloutException] - ) - } - 'notRunWhenComplileFailed - { - write.append(mainObject, "val x: ") - - val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.runMain("Main")) - - assert( - err.isInstanceOf[CompileFailed] - ) - } - } - - 'forkRun - { - 'runIfMainClassProvided - { - val runResult = basePath / 'out / 'run / 'dest / "hello-mill" - val Right((_, evalCount)) = helloWorldWithMainEvaluator( - HelloWorldWithMain.forkRun(runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - exists(runResult), - read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'notRunWithoutMainClass - { - val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.forkRun()) - - assert( - err.isInstanceOf[RuntimeException] - ) - } - } - 'run - { - 'runIfMainClassProvided - { - val runResult = basePath / 'out / 'run / 'dest / "hello-mill" - val Right((_, evalCount)) = helloWorldWithMainEvaluator( - HelloWorldWithMain.run(runResult.toString) - ) - - assert(evalCount > 0) - - - assert( - exists(runResult), - read(runResult) == "hello rockjam, your age is: 25" - ) - } - 'notRunWithoutMainClass - { - val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.run()) - - assert( - err.isInstanceOf[RuntimeException] - ) - } - } - 'jar - { - 'nonEmpty - { - val Right((result, evalCount)) = helloWorldWithMainEvaluator(HelloWorldWithMain.jar) - - assert( - exists(result.path), - evalCount > 0 - ) - - val jarFile = new JarFile(result.path.toIO) - val entries = jarFile.entries().asScala.map(_.getName).toSet - - val manifestFiles = Seq[RelPath]( - "META-INF" / "MANIFEST.MF" - ) - val expectedFiles = compileClassfiles ++ manifestFiles - - assert( - entries.nonEmpty, - entries == expectedFiles.map(_.toString()).toSet - ) - - val mainClass = jarMainClass(jarFile) - assert(mainClass.contains("Main")) - } - 'logOutputToFile { - helloWorldEvaluator(HelloWorld.compile) - - val logFile = outPath / 'compile / 'log - assert(exists(logFile)) - } - } - 'assembly - { - 'assembly - { - val Right((result, evalCount)) = helloWorldWithMainAssemblyEvaluator(HelloWorldWithMainAssembly.assembly) - assert( - exists(result.path), - evalCount > 0 - ) - val jarFile = new JarFile(result.path.toIO) - val entries = jarFile.entries().asScala.map(_.getName).toSet - - assert(entries.contains("Main.class")) - assert(entries.exists(s => s.contains("scala/Predef.class"))) - - val mainClass = jarMainClass(jarFile) - assert(mainClass.contains("Main")) - } - 'run - { - val Right((result, evalCount)) = helloWorldWithMainAssemblyEvaluator(HelloWorldWithMainAssembly.assembly) - - assert( - exists(result.path), - evalCount > 0 - ) - val runResult = basePath / "hello-mill" - - %%("java", "-jar", result.path, runResult)(wd = basePath) - - assert( - exists(runResult), - read(runResult) == "hello rockjam, your age is: 25" - ) - } - } - } - - def jarMainClass(jar: JarFile): Option[String] = { - import java.util.jar.Attributes._ - val attrs = jar.getManifest.getMainAttributes.asScala - attrs.get(Name.MAIN_CLASS).map(_.asInstanceOf[String]) - } - - def compileClassfiles = Seq[RelPath]( - "Main.class", - "Main$.class", - "Main$delayedInit$body.class", - "Person.class", - "Person$.class" - ) - - def prepareWorkspace(): Unit = { - rm(outPath) - rm(workingSrcPath) - mkdir(outPath) - cp(srcPath, workingSrcPath) - } - -} diff --git a/scalalib/src/test/scala/mill/scalalib/ResolveDepsTests.scala b/scalalib/src/test/scala/mill/scalalib/ResolveDepsTests.scala deleted file mode 100644 index d1ddde20..00000000 --- a/scalalib/src/test/scala/mill/scalalib/ResolveDepsTests.scala +++ /dev/null @@ -1,40 +0,0 @@ -package mill.scalalib - -import coursier.Cache -import coursier.maven.MavenRepository -import mill.eval.Result.{Failure, Success} -import mill.eval.{PathRef, Result} -import mill.util.Loose.Agg -import utest._ - -object ResolveDepsTests extends TestSuite { - val repos = Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")) - - def evalDeps(deps: Agg[Dep]): Result[Agg[PathRef]] = Lib.resolveDependencies(repos, "2.12.4", "2.12", deps) - - val tests = Tests { - 'resolveValidDeps - { - val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3") - val Success(paths) = evalDeps(deps) - assert(paths.nonEmpty) - } - - 'errOnInvalidOrgDeps - { - val deps = Agg(ivy"xxx.yyy.invalid::pprint:0.5.3") - val Failure(errMsg) = evalDeps(deps) - assert(errMsg.contains("xxx.yyy.invalid")) - } - - 'errOnInvalidVersionDeps - { - val deps = Agg(ivy"com.lihaoyi::pprint:invalid.version.num") - val Failure(errMsg) = evalDeps(deps) - assert(errMsg.contains("invalid.version.num")) - } - - 'errOnPartialSuccess - { - val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3", ivy"fake::fake:fake") - val Failure(errMsg) = evalDeps(deps) - assert(errMsg.contains("fake")) - } - } -} diff --git a/scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml new file mode 100644 index 00000000..a6398d46 --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml new file mode 100644 index 00000000..5e21f3f3 --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml new file mode 100644 index 00000000..fe9d533a --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml new file mode 100644 index 00000000..ec0460d7 --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml new file mode 100644 index 00000000..a1278e29 --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml new file mode 100644 index 00000000..bbd957f0 --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml new file mode 100644 index 00000000..09db75bb --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml b/scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml new file mode 100644 index 00000000..f23b80f6 --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/misc.xml b/scalalib/test/resources/gen-idea/idea/misc.xml new file mode 100644 index 00000000..2726692f --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/misc.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea/modules.xml b/scalalib/test/resources/gen-idea/idea/modules.xml new file mode 100644 index 00000000..374b9a1f --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea_modules/iml b/scalalib/test/resources/gen-idea/idea_modules/iml new file mode 100644 index 00000000..548865f1 --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea_modules/iml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/gen-idea/idea_modules/root.iml b/scalalib/test/resources/gen-idea/idea_modules/root.iml new file mode 100644 index 00000000..dcf5f7fc --- /dev/null +++ b/scalalib/test/resources/gen-idea/idea_modules/root.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/scalalib/test/resources/hello-world/src/Main.scala b/scalalib/test/resources/hello-world/src/Main.scala new file mode 100644 index 00000000..14139d6f --- /dev/null +++ b/scalalib/test/resources/hello-world/src/Main.scala @@ -0,0 +1,12 @@ +import scala.collection._ // unused import to check unused imports warning +import java.nio.file.{Files, Paths} + +object Main extends App { + + val person = Person.fromString("rockjam:25") + val greeting = s"hello ${person.name}, your age is: ${person.age}" + println(greeting) + val resultPath = Paths.get(args(0)) + Files.createDirectories(resultPath.getParent) + Files.write(resultPath, greeting.getBytes) +} diff --git a/scalalib/test/resources/hello-world/src/Result.scala b/scalalib/test/resources/hello-world/src/Result.scala new file mode 100644 index 00000000..d7d29a51 --- /dev/null +++ b/scalalib/test/resources/hello-world/src/Result.scala @@ -0,0 +1,7 @@ +object Person { + def fromString(s: String): Person = { + val Array(name, age) = s.split(":") + Person(name, age.toInt) + } +} +case class Person(name: String, age: Int) diff --git a/scalalib/test/src/mill/scalalib/GenIdeaTests.scala b/scalalib/test/src/mill/scalalib/GenIdeaTests.scala new file mode 100644 index 00000000..36945f65 --- /dev/null +++ b/scalalib/test/src/mill/scalalib/GenIdeaTests.scala @@ -0,0 +1,78 @@ +package mill.scalalib + +import ammonite.ops._ +import mill._ + +import mill.util.{TestEvaluator, TestUtil} +import utest._ + +object GenIdeaTests extends TestSuite { + + val basePath = pwd / 'target / 'workspace / "gen-idea" + val outPath = basePath / 'out + val workingSrcPath = basePath / 'src + + trait HelloWorldModule extends scalalib.ScalaModule { + def scalaVersion = "2.12.4" + def basePath = HelloWorldTests.workingSrcPath + } + + object HelloWorld extends TestUtil.BaseModule with HelloWorldModule + + val helloWorldEvaluator = new TestEvaluator( + HelloWorld, + outPath, + workingSrcPath + ) + + def tests: Tests = Tests { + 'genIdeaTests - { + helloWorldEvaluator(HelloWorld.scalaVersion) + val x = GenIdea.xmlFileLayout(helloWorldEvaluator.evaluator, HelloWorld) + val pp = new scala.xml.PrettyPrinter(999, 4) + + for((relPath, xml) <- GenIdea.xmlFileLayout(helloWorldEvaluator.evaluator, HelloWorld)){ + write.over(basePath/ "generated"/ relPath, pp.format(xml)) + } + + Seq( + "gen-idea/idea_modules/iml" -> + basePath / "generated" / ".idea_modules" /".iml", + "gen-idea/idea_modules/root.iml" -> + basePath / "generated" / ".idea_modules" /"root.iml", + "gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "scala-reflect_2.12.4_scala-reflect-2.12.4-sources.jar.xml", + "gen-idea/idea/libraries/scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "scala-reflect_2.12.4_scala-reflect-2.12.4.jar.xml", + "gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "scala-library_2.12.4_scala-library-2.12.4.jar.xml", + "gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6.jar.xml", + "gen-idea/idea/libraries/modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "modules_scala-xml_2.12_1.0.6_scala-xml_2.12-1.0.6-sources.jar.xml", + "gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "scala-compiler_2.12.4_scala-compiler-2.12.4-sources.jar.xml", + "gen-idea/idea/libraries/scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "scala-library_2.12.4_scala-library-2.12.4-sources.jar.xml", + "gen-idea/idea/libraries/scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml" -> + basePath / "generated" / ".idea" / "libraries" / "scala-compiler_2.12.4_scala-compiler-2.12.4.jar.xml", + "gen-idea/idea/modules.xml" -> + basePath / "generated" / ".idea" / "modules.xml", + "gen-idea/idea/misc.xml" -> + basePath / "generated" / ".idea" / "misc.xml", + ).foreach { case (resource, generated) => + println("checking "+resource) + val resourceString = scala.io.Source.fromResource(resource).getLines().mkString("\n") + val generatedString = normaliseLibraryPaths(read! generated) + + assert(resourceString == generatedString) + } + } + } + + + private val libPathRegex = """([\w/]+)/.coursier""".r + private def normaliseLibraryPaths(in: String): String = { + libPathRegex.replaceAllIn(in, "COURSIER_HOME") + } +} diff --git a/scalalib/test/src/mill/scalalib/HelloWorldTests.scala b/scalalib/test/src/mill/scalalib/HelloWorldTests.scala new file mode 100644 index 00000000..8ac45bf3 --- /dev/null +++ b/scalalib/test/src/mill/scalalib/HelloWorldTests.scala @@ -0,0 +1,400 @@ +package mill.scalalib + +import java.util.jar.JarFile + +import ammonite.ops._ +import ammonite.ops.ImplicitWd._ +import mill._ +import mill.define.Target +import mill.eval.{Evaluator, Result} +import mill.scalalib.publish._ +import mill.util.{TestEvaluator, TestUtil} +import sbt.internal.inc.CompileFailed +import utest._ + +import scala.collection.JavaConverters._ + + +object HelloWorldTests extends TestSuite { + trait HelloWorldModule extends scalalib.ScalaModule { + def scalaVersion = "2.12.4" + def basePath = HelloWorldTests.workingSrcPath + } + + object HelloWorld extends TestUtil.BaseModule with HelloWorldModule + object CrossHelloWorld extends TestUtil.BaseModule { + object cross extends Cross[HelloWorldCross]("2.10.6", "2.11.11", "2.12.3", "2.12.4") + class HelloWorldCross(v: String) extends HelloWorldModule { + def scalaVersion = v + } + } + + object HelloWorldWithMain extends TestUtil.BaseModule with HelloWorldModule { + def mainClass = Some("Main") + } + + object HelloWorldWithMainAssembly extends TestUtil.BaseModule with HelloWorldModule { + def mainClass = Some("Main") + def assembly = T{ + mill.modules.Jvm.createAssembly( + runClasspath().map(_.path).filter(exists), + prependShellScript = prependShellScript(), + mainClass = mainClass() + ) + } + } + + object HelloWorldWarnUnused extends TestUtil.BaseModule with HelloWorldModule { + def scalacOptions = T(Seq("-Ywarn-unused")) + } + + object HelloWorldFatalWarnings extends TestUtil.BaseModule with HelloWorldModule { + def scalacOptions = T(Seq("-Ywarn-unused", "-Xfatal-warnings")) + } + + object HelloWorldWithPublish extends TestUtil.BaseModule with HelloWorldModule with PublishModule { + def artifactName = "hello-world" + def publishVersion = "0.0.1" + + def pomSettings = PomSettings( + organization = "com.lihaoyi", + description = "hello world ready for real world publishing", + url = "https://github.com/lihaoyi/hello-world-publish", + licenses = Seq( + License("Apache License, Version 2.0", + "http://www.apache.org/licenses/LICENSE-2.0")), + scm = SCM( + "https://github.com/lihaoyi/hello-world-publish", + "scm:git:https://github.com/lihaoyi/hello-world-publish" + ), + developers = + Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi")) + ) + } + object HelloWorldScalaOverride extends TestUtil.BaseModule with HelloWorldModule { + override def scalaVersion: Target[String] = "2.11.11" + } + val srcPath = pwd / 'scalalib / 'test / 'resources / "hello-world" + val basePath = pwd / 'target / 'workspace / "hello-world" + val workingSrcPath = basePath / 'src + val outPath = basePath / 'out + val mainObject = workingSrcPath / 'src / "Main.scala" + + + + + val helloWorldEvaluator = new TestEvaluator( + HelloWorld, + outPath, + workingSrcPath + ) + val helloWorldWithMainEvaluator = new TestEvaluator( + HelloWorldWithMain, + outPath, + workingSrcPath + ) + val helloWorldWithMainAssemblyEvaluator = new TestEvaluator( + HelloWorldWithMainAssembly, + outPath, + workingSrcPath + ) + val helloWorldFatalEvaluator = new TestEvaluator( + HelloWorldFatalWarnings, + outPath, + workingSrcPath + ) + val helloWorldOverrideEvaluator = new TestEvaluator( + HelloWorldScalaOverride, + outPath, + workingSrcPath + ) + val helloWorldCrossEvaluator = new TestEvaluator( + CrossHelloWorld, + outPath, + workingSrcPath + ) + + + def tests: Tests = Tests { + prepareWorkspace() + 'scalaVersion - { + 'fromBuild - { + val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.scalaVersion) + + assert( + result == "2.12.4", + evalCount > 0 + ) + } + 'override - { + val Right((result, evalCount)) = helloWorldOverrideEvaluator(HelloWorldScalaOverride.scalaVersion) + + assert( + result == "2.11.11", + evalCount > 0 + ) + } + } + 'scalacOptions - { + 'emptyByDefault - { + val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.scalacOptions) + + assert( + result.isEmpty, + evalCount > 0 + ) + } + 'override - { + val Right((result, evalCount)) = helloWorldFatalEvaluator(HelloWorldFatalWarnings.scalacOptions) + + assert( + result == Seq("-Ywarn-unused", "-Xfatal-warnings"), + evalCount > 0 + ) + } + } + 'compile - { + 'fromScratch - { + val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.compile) + + val analysisFile = result.analysisFile + val outputFiles = ls.rec(result.classes.path) + val expectedClassfiles = compileClassfiles.map(outPath / 'compile / 'dest / 'classes / _) + assert( + result.classes.path == outPath / 'compile / 'dest / 'classes, + exists(analysisFile), + outputFiles.nonEmpty, + outputFiles.forall(expectedClassfiles.contains), + evalCount > 0 + ) + + // don't recompile if nothing changed + val Right((_, unchangedEvalCount)) = helloWorldEvaluator(HelloWorld.compile) + assert(unchangedEvalCount == 0) + } + 'recompileOnChange - { + val Right((_, freshCount)) = helloWorldEvaluator(HelloWorld.compile) + assert(freshCount > 0) + + write.append(mainObject, "\n") + + val Right((_, incCompileCount)) = helloWorldEvaluator(HelloWorld.compile) + assert(incCompileCount > 0, incCompileCount < freshCount) + } + 'failOnError - { + write.append(mainObject, "val x: ") + + val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.compile) + + assert(err.isInstanceOf[CompileFailed]) + + val paths = Evaluator.resolveDestPaths( + outPath, + HelloWorld.compile.ctx.segments + ) + + assert( + ls.rec(paths.dest / 'classes).isEmpty, + !exists(paths.meta) + ) + // Works when fixed + write.over(mainObject, read(mainObject).dropRight("val x: ".length)) + + val Right((result, evalCount)) = helloWorldEvaluator(HelloWorld.compile) + } + 'passScalacOptions - { + // compilation fails because of "-Xfatal-warnings" flag + val Left(Result.Exception(err, _)) = helloWorldFatalEvaluator(HelloWorldFatalWarnings.compile) + + assert(err.isInstanceOf[CompileFailed]) + } + } + 'runMain - { + 'runMainObject - { + val runResult = basePath / 'out / 'runMain / 'dest / "hello-mill" + + val Right((_, evalCount)) = helloWorldEvaluator(HelloWorld.runMain("Main", runResult.toString)) + assert(evalCount > 0) + + assert( + exists(runResult), + read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'runCross{ + def cross(v: String) { + + val runResult = basePath / 'out / 'cross / v / 'runMain / 'dest / "hello-mill" + + val Right((_, evalCount)) = helloWorldCrossEvaluator( + CrossHelloWorld.cross(v).runMain("Main", runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + exists(runResult), + read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'v210 - cross("2.10.6") + 'v211 - cross("2.11.11") + 'v2123 - cross("2.12.3") + 'v2124 - cross("2.12.4") + } + + + 'notRunInvalidMainObject - { + val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.runMain("Invalid")) + + assert( + err.isInstanceOf[InteractiveShelloutException] + ) + } + 'notRunWhenComplileFailed - { + write.append(mainObject, "val x: ") + + val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.runMain("Main")) + + assert( + err.isInstanceOf[CompileFailed] + ) + } + } + + 'forkRun - { + 'runIfMainClassProvided - { + val runResult = basePath / 'out / 'run / 'dest / "hello-mill" + val Right((_, evalCount)) = helloWorldWithMainEvaluator( + HelloWorldWithMain.forkRun(runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + exists(runResult), + read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'notRunWithoutMainClass - { + val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.forkRun()) + + assert( + err.isInstanceOf[RuntimeException] + ) + } + } + 'run - { + 'runIfMainClassProvided - { + val runResult = basePath / 'out / 'run / 'dest / "hello-mill" + val Right((_, evalCount)) = helloWorldWithMainEvaluator( + HelloWorldWithMain.run(runResult.toString) + ) + + assert(evalCount > 0) + + + assert( + exists(runResult), + read(runResult) == "hello rockjam, your age is: 25" + ) + } + 'notRunWithoutMainClass - { + val Left(Result.Exception(err, _)) = helloWorldEvaluator(HelloWorld.run()) + + assert( + err.isInstanceOf[RuntimeException] + ) + } + } + 'jar - { + 'nonEmpty - { + val Right((result, evalCount)) = helloWorldWithMainEvaluator(HelloWorldWithMain.jar) + + assert( + exists(result.path), + evalCount > 0 + ) + + val jarFile = new JarFile(result.path.toIO) + val entries = jarFile.entries().asScala.map(_.getName).toSet + + val manifestFiles = Seq[RelPath]( + "META-INF" / "MANIFEST.MF" + ) + val expectedFiles = compileClassfiles ++ manifestFiles + + assert( + entries.nonEmpty, + entries == expectedFiles.map(_.toString()).toSet + ) + + val mainClass = jarMainClass(jarFile) + assert(mainClass.contains("Main")) + } + 'logOutputToFile { + helloWorldEvaluator(HelloWorld.compile) + + val logFile = outPath / 'compile / 'log + assert(exists(logFile)) + } + } + 'assembly - { + 'assembly - { + val Right((result, evalCount)) = helloWorldWithMainAssemblyEvaluator(HelloWorldWithMainAssembly.assembly) + assert( + exists(result.path), + evalCount > 0 + ) + val jarFile = new JarFile(result.path.toIO) + val entries = jarFile.entries().asScala.map(_.getName).toSet + + assert(entries.contains("Main.class")) + assert(entries.exists(s => s.contains("scala/Predef.class"))) + + val mainClass = jarMainClass(jarFile) + assert(mainClass.contains("Main")) + } + 'run - { + val Right((result, evalCount)) = helloWorldWithMainAssemblyEvaluator(HelloWorldWithMainAssembly.assembly) + + assert( + exists(result.path), + evalCount > 0 + ) + val runResult = basePath / "hello-mill" + + %%("java", "-jar", result.path, runResult)(wd = basePath) + + assert( + exists(runResult), + read(runResult) == "hello rockjam, your age is: 25" + ) + } + } + } + + def jarMainClass(jar: JarFile): Option[String] = { + import java.util.jar.Attributes._ + val attrs = jar.getManifest.getMainAttributes.asScala + attrs.get(Name.MAIN_CLASS).map(_.asInstanceOf[String]) + } + + def compileClassfiles = Seq[RelPath]( + "Main.class", + "Main$.class", + "Main$delayedInit$body.class", + "Person.class", + "Person$.class" + ) + + def prepareWorkspace(): Unit = { + rm(outPath) + rm(workingSrcPath) + mkdir(outPath) + cp(srcPath, workingSrcPath) + } + +} diff --git a/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala new file mode 100644 index 00000000..d1ddde20 --- /dev/null +++ b/scalalib/test/src/mill/scalalib/ResolveDepsTests.scala @@ -0,0 +1,40 @@ +package mill.scalalib + +import coursier.Cache +import coursier.maven.MavenRepository +import mill.eval.Result.{Failure, Success} +import mill.eval.{PathRef, Result} +import mill.util.Loose.Agg +import utest._ + +object ResolveDepsTests extends TestSuite { + val repos = Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")) + + def evalDeps(deps: Agg[Dep]): Result[Agg[PathRef]] = Lib.resolveDependencies(repos, "2.12.4", "2.12", deps) + + val tests = Tests { + 'resolveValidDeps - { + val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3") + val Success(paths) = evalDeps(deps) + assert(paths.nonEmpty) + } + + 'errOnInvalidOrgDeps - { + val deps = Agg(ivy"xxx.yyy.invalid::pprint:0.5.3") + val Failure(errMsg) = evalDeps(deps) + assert(errMsg.contains("xxx.yyy.invalid")) + } + + 'errOnInvalidVersionDeps - { + val deps = Agg(ivy"com.lihaoyi::pprint:invalid.version.num") + val Failure(errMsg) = evalDeps(deps) + assert(errMsg.contains("invalid.version.num")) + } + + 'errOnPartialSuccess - { + val deps = Agg(ivy"com.lihaoyi::pprint:0.5.3", ivy"fake::fake:fake") + val Failure(errMsg) = evalDeps(deps) + assert(errMsg.contains("fake")) + } + } +} -- cgit v1.2.3